Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions nodejs/src/cdp/services/hogflows/actions/hog_function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,24 @@ describe('HogFunctionHandler', () => {
)
})

it('links the AI task in the wait log, whatever the step stores', async () => {
// Serve the per-team template under the real id, because other test files share the template table.
const awaitedTemplateId = TASK_TEMPLATE_ID
const getTemplate =
mockHogFunctionTemplateManager.getHogFunctionTemplate.bind(mockHogFunctionTemplateManager)
jest.spyOn(mockHogFunctionTemplateManager, 'getHogFunctionTemplate').mockImplementation((id) =>
getTemplate(id === 'template-posthog-create-task' ? awaitedTemplateId : id)
)
TASK_TEMPLATE_ID = 'template-posthog-create-task'
buildTaskFlow()

const { invocationResult } = await execute()

expect(invocationResult.logs.map((l) => l.message)).toContainEqual(
expect.stringMatching(/Waiting for the task to finish \(up to .+\) \[Task:t1\|r1\]$/)
)
})

it.each([
['the template asks for no wait', { skip: { value: true } }, true],
['waiting is turned off', {}, false],
Expand Down
6 changes: 4 additions & 2 deletions nodejs/src/cdp/services/hogflows/actions/hog_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { RecipientPreferencesService } from '../../messaging/recipient-preferenc
import { CdpUsageReporterService } from '../../usage/cdp-usage-reporter.service'
import { trackHogFlowBillableInvocation } from '../billing-utils'
import { HogFlowFunctionsService } from '../hogflow-functions.service'
import { actionIdForLogging, findContinueAction } from '../hogflow-utils'
import { actionIdForLogging, aiTaskLogToken, findContinueAction } from '../hogflow-utils'
import { observeMissingVariableReferences } from '../hogflow-variable-usage'
import { ActionHandler, ActionHandlerOptions, ActionHandlerResult } from './action.interface'

Expand Down Expand Up @@ -353,10 +353,12 @@ export class HogFunctionHandler implements ActionHandler {
label: awaitRequest.label,
parkedAt: DateTime.now().toISO()!,
}
// The stored variable only links the task when it holds the whole result, so link it here too.
const taskToken = aiTaskLogToken(action, dispatch)
result.logs.push({
level: 'info',
timestamp: DateTime.now(),
message: `${actionIdForLogging(action)} Waiting for the ${awaitRequest.label} to finish (up to ${humanDuration(awaitRequest.maxWait)})`,
message: `${actionIdForLogging(action)} Waiting for the ${awaitRequest.label} to finish (up to ${humanDuration(awaitRequest.maxWait)})${taskToken ? ` ${taskToken}` : ''}`,
})
// Stored now so a step that later fails still leaves the ids for `on_error: continue`.
return { scheduledAt: deadline, result: dispatch }
Expand Down
17 changes: 2 additions & 15 deletions nodejs/src/cdp/services/hogflows/hogflow-executor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { hasSensitiveEmailStep } from './hogflow-sensitivity'
import {
WorkflowChangedError,
actionIdForLogging,
aiTaskLogToken,
ensureCurrentAction,
findContinueAction,
shouldSkipAction,
Expand Down Expand Up @@ -1096,22 +1097,8 @@ export class HogFlowExecutorService {
this.log(result, 'debug', `Stored action result in variable(s): ${storedSummary}`)
}

// The create-ai-task destination's result variable is what lets a workflow author jump from a
// run's logs to the task it kicked off. Everything else stays plain JSON.
private describeStoredVariable(action: HogFlowAction, value: unknown): string {
const isCreateAiTaskAction =
action.type === 'function' && action.config.template_id === 'template-posthog-create-task'
if (
isCreateAiTaskAction &&
value &&
typeof value === 'object' &&
typeof (value as Record<string, unknown>).id === 'string' &&
(value as Record<string, unknown>).id !== ''
) {
const runId = (value as Record<string, unknown>).run_id
return `[Task:${(value as Record<string, unknown>).id}|${typeof runId === 'string' ? runId : ''}]`
}
return JSON.stringify(value)
return aiTaskLogToken(action, value) ?? JSON.stringify(value)
}

private logExecutionTriggerInfo(invocation: CyclotronJobInvocationHogFlow): MinimalLogEntry {
Expand Down
15 changes: 15 additions & 0 deletions nodejs/src/cdp/services/hogflows/hogflow-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ export async function shouldSkipAction(
}

// Special format which the frontend understands and can render as a link
// The logs UI renders this token as a "View task" link, so a workflow author can jump from a run's
// logs to the task it kicked off.
export function aiTaskLogToken(action: HogFlowAction, value: unknown): string | null {
const isCreateAiTaskAction =
action.type === 'function' && action.config.template_id === 'template-posthog-create-task'
if (!isCreateAiTaskAction || !value || typeof value !== 'object') {
return null
}
const { id, run_id: runId } = value as Record<string, unknown>
if (typeof id !== 'string' || id === '') {
return null
}
return `[Task:${id}|${typeof runId === 'string' ? runId : ''}]`
}

export const actionIdForLogging = (action: Pick<HogFlowAction, 'id'>): string => {
return `[Action:${action.id}]`
}
Expand Down
Loading