@vellumai/assistant 0.9.0-dev.202606181513.fd39213 → 0.9.0-dev.202606181735.ae6e1d8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openapi.yaml +82 -0
- package/package.json +1 -1
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +38 -0
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/memory/db-init.ts +2 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +134 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +147 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +26 -0
- package/src/plugins/defaults/advisor/consult.ts +76 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +24 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +51 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +52 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +35 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +1 -1
- package/src/runtime/routes/workflow-routes.test.ts +225 -1
- package/src/runtime/routes/workflow-routes.ts +131 -1
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -3
- package/src/workflows/run-manager.ts +63 -0
package/openapi.yaml
CHANGED
|
@@ -27773,6 +27773,88 @@ paths:
|
|
|
27773
27773
|
additionalProperties: false
|
|
27774
27774
|
"404":
|
|
27775
27775
|
description: Run not found
|
|
27776
|
+
/v1/workflows/runs/{id}/journal:
|
|
27777
|
+
get:
|
|
27778
|
+
operationId: workflows_runs_by_id_journal_get
|
|
27779
|
+
summary: Get workflow run journal
|
|
27780
|
+
description: Return a workflow run's leaf journal as bounded per-leaf summaries (one entry per finished leaf).
|
|
27781
|
+
tags:
|
|
27782
|
+
- workflows
|
|
27783
|
+
parameters:
|
|
27784
|
+
- name: id
|
|
27785
|
+
in: path
|
|
27786
|
+
required: true
|
|
27787
|
+
schema:
|
|
27788
|
+
type: string
|
|
27789
|
+
responses:
|
|
27790
|
+
"200":
|
|
27791
|
+
description: Successful response
|
|
27792
|
+
content:
|
|
27793
|
+
application/json:
|
|
27794
|
+
schema:
|
|
27795
|
+
type: object
|
|
27796
|
+
properties:
|
|
27797
|
+
runId:
|
|
27798
|
+
type: string
|
|
27799
|
+
status:
|
|
27800
|
+
type: string
|
|
27801
|
+
enum:
|
|
27802
|
+
- running
|
|
27803
|
+
- completed
|
|
27804
|
+
- failed
|
|
27805
|
+
- aborted
|
|
27806
|
+
- cap_exceeded
|
|
27807
|
+
- interrupted
|
|
27808
|
+
agentsSpawned:
|
|
27809
|
+
type: number
|
|
27810
|
+
inputTokens:
|
|
27811
|
+
type: number
|
|
27812
|
+
outputTokens:
|
|
27813
|
+
type: number
|
|
27814
|
+
phase:
|
|
27815
|
+
type: string
|
|
27816
|
+
leaves:
|
|
27817
|
+
type: array
|
|
27818
|
+
items:
|
|
27819
|
+
type: object
|
|
27820
|
+
properties:
|
|
27821
|
+
seq:
|
|
27822
|
+
type: number
|
|
27823
|
+
kind:
|
|
27824
|
+
type: string
|
|
27825
|
+
enum:
|
|
27826
|
+
- agent
|
|
27827
|
+
- workflow
|
|
27828
|
+
label:
|
|
27829
|
+
type: string
|
|
27830
|
+
phase:
|
|
27831
|
+
type: string
|
|
27832
|
+
promptSummary:
|
|
27833
|
+
type: string
|
|
27834
|
+
status:
|
|
27835
|
+
type: string
|
|
27836
|
+
resultSummary:
|
|
27837
|
+
type: string
|
|
27838
|
+
inputTokens:
|
|
27839
|
+
type: number
|
|
27840
|
+
outputTokens:
|
|
27841
|
+
type: number
|
|
27842
|
+
createdAt:
|
|
27843
|
+
anyOf:
|
|
27844
|
+
- type: number
|
|
27845
|
+
- type: "null"
|
|
27846
|
+
required:
|
|
27847
|
+
- seq
|
|
27848
|
+
- kind
|
|
27849
|
+
- status
|
|
27850
|
+
- createdAt
|
|
27851
|
+
additionalProperties: false
|
|
27852
|
+
required:
|
|
27853
|
+
- runId
|
|
27854
|
+
- leaves
|
|
27855
|
+
additionalProperties: false
|
|
27856
|
+
"404":
|
|
27857
|
+
description: Run not found
|
|
27776
27858
|
/v1/workflows/runs/{id}/resume:
|
|
27777
27859
|
post:
|
|
27778
27860
|
operationId: workflows_runs_by_id_resume_post
|
package/package.json
CHANGED
|
@@ -190,13 +190,16 @@ describe("plugin tool contributions", () => {
|
|
|
190
190
|
registerPlugin(plugin);
|
|
191
191
|
|
|
192
192
|
await bootstrapPlugins();
|
|
193
|
-
//
|
|
194
|
-
|
|
193
|
+
// `bootstrapPlugins` also registers the first-party defaults (the advisor
|
|
194
|
+
// default contributes the `advisor` tool), so the global tool set is not
|
|
195
|
+
// empty. What matters here is that the no-tools plugin contributed nothing
|
|
196
|
+
// of its own — its tool refcount stays at zero.
|
|
197
|
+
expect(getPluginRefCount("no-tools")).toBe(0);
|
|
195
198
|
|
|
196
199
|
// Shutdown must also be safe — `unregisterPluginTools` is idempotent for
|
|
197
200
|
// plugins that never contributed any tools.
|
|
198
201
|
await runShutdownHooks("test-shutdown");
|
|
199
|
-
expect(
|
|
202
|
+
expect(getPluginRefCount("no-tools")).toBe(0);
|
|
200
203
|
});
|
|
201
204
|
|
|
202
205
|
test("tools declared before init() runs are only visible after bootstrap", async () => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workflow_completed` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client notification that a `run_workflow` run has reached a
|
|
5
|
+
* terminal state. Carries `runId`, the terminal `status`, cumulative
|
|
6
|
+
* `agentsSpawned`/`inputTokens`/`outputTokens` counters, and an optional
|
|
7
|
+
* human-readable `summary`.
|
|
8
|
+
*
|
|
9
|
+
* `conversationId` is present for a run launched from a conversation, so
|
|
10
|
+
* clients can route the event to that conversation's inline workflow card. It
|
|
11
|
+
* is omitted for a conversationless run (e.g. a scheduled workflow with no
|
|
12
|
+
* wake/origin conversation), which broadcasts unscoped for raw SSE listeners.
|
|
13
|
+
*
|
|
14
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
15
|
+
* directly from this file; external consumers import via
|
|
16
|
+
* `@vellumai/assistant-api`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Lifecycle status of a `run_workflow` run. `running` is the live
|
|
23
|
+
* state; the remainder are terminal. `cap_exceeded` is reached when a
|
|
24
|
+
* run hits its configured agent/token cap; `interrupted` when the run
|
|
25
|
+
* is halted by an external signal.
|
|
26
|
+
*/
|
|
27
|
+
export const WorkflowRunStatusSchema = z.enum([
|
|
28
|
+
"running",
|
|
29
|
+
"completed",
|
|
30
|
+
"failed",
|
|
31
|
+
"aborted",
|
|
32
|
+
"cap_exceeded",
|
|
33
|
+
"interrupted",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
export type WorkflowRunStatus = z.infer<typeof WorkflowRunStatusSchema>;
|
|
37
|
+
|
|
38
|
+
export const WorkflowCompletedEventSchema = z
|
|
39
|
+
.object({
|
|
40
|
+
type: z.literal("workflow_completed"),
|
|
41
|
+
runId: z.string(),
|
|
42
|
+
conversationId: z.string().optional(),
|
|
43
|
+
status: WorkflowRunStatusSchema,
|
|
44
|
+
agentsSpawned: z.number(),
|
|
45
|
+
inputTokens: z.number(),
|
|
46
|
+
outputTokens: z.number(),
|
|
47
|
+
summary: z.string().optional(),
|
|
48
|
+
})
|
|
49
|
+
.strict();
|
|
50
|
+
|
|
51
|
+
export type WorkflowCompletedEvent = z.infer<
|
|
52
|
+
typeof WorkflowCompletedEventSchema
|
|
53
|
+
>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workflow_leaf_finished` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client notification that a leaf agent within a
|
|
5
|
+
* `run_workflow` run has finished. Carries `runId`, the parent
|
|
6
|
+
* `conversationId`, the monotonic `seq` identifying the leaf within the
|
|
7
|
+
* run, the terminal `status`, optional `inputTokens`/`outputTokens`
|
|
8
|
+
* counters, and optional human-readable display fields (`label`,
|
|
9
|
+
* `resultSummary`).
|
|
10
|
+
*
|
|
11
|
+
* `conversationId` is present so clients can route the event to the
|
|
12
|
+
* originating conversation's inline workflow card and its live leaf
|
|
13
|
+
* tree.
|
|
14
|
+
*
|
|
15
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
16
|
+
* directly from this file; external consumers import via
|
|
17
|
+
* `@vellumai/assistant-api`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
export const WorkflowLeafFinishedEventSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
type: z.literal("workflow_leaf_finished"),
|
|
25
|
+
runId: z.string(),
|
|
26
|
+
conversationId: z.string(),
|
|
27
|
+
seq: z.number().int(),
|
|
28
|
+
status: z.enum(["completed", "failed"]),
|
|
29
|
+
label: z.string().optional(),
|
|
30
|
+
inputTokens: z.number().optional(),
|
|
31
|
+
outputTokens: z.number().optional(),
|
|
32
|
+
resultSummary: z.string().optional(),
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
|
|
36
|
+
export type WorkflowLeafFinishedEvent = z.infer<
|
|
37
|
+
typeof WorkflowLeafFinishedEventSchema
|
|
38
|
+
>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workflow_leaf_started` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client notification that a leaf agent within a
|
|
5
|
+
* `run_workflow` run has started. Carries `runId`, the parent
|
|
6
|
+
* `conversationId`, the monotonic `seq` identifying the leaf within the
|
|
7
|
+
* run, and optional human-readable display fields (`label`, `phase`,
|
|
8
|
+
* `promptSummary`).
|
|
9
|
+
*
|
|
10
|
+
* `conversationId` is present so clients can route the event to the
|
|
11
|
+
* originating conversation's inline workflow card and its live leaf
|
|
12
|
+
* tree.
|
|
13
|
+
*
|
|
14
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
15
|
+
* directly from this file; external consumers import via
|
|
16
|
+
* `@vellumai/assistant-api`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
export const WorkflowLeafStartedEventSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
type: z.literal("workflow_leaf_started"),
|
|
24
|
+
runId: z.string(),
|
|
25
|
+
conversationId: z.string(),
|
|
26
|
+
seq: z.number().int(),
|
|
27
|
+
label: z.string().optional(),
|
|
28
|
+
phase: z.string().optional(),
|
|
29
|
+
promptSummary: z.string().optional(),
|
|
30
|
+
})
|
|
31
|
+
.strict();
|
|
32
|
+
|
|
33
|
+
export type WorkflowLeafStartedEvent = z.infer<
|
|
34
|
+
typeof WorkflowLeafStartedEventSchema
|
|
35
|
+
>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workflow_progress` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client incremental progress update for an in-flight
|
|
5
|
+
* `run_workflow` run. Carries `runId`, the running `agentsSpawned` count,
|
|
6
|
+
* and optional human-readable display fields (`phase`, `label`, `message`).
|
|
7
|
+
*
|
|
8
|
+
* `conversationId` is present for a run launched from a conversation, so
|
|
9
|
+
* clients can route the event to that conversation's inline workflow card. It
|
|
10
|
+
* is omitted for a conversationless run (e.g. a scheduled workflow with no
|
|
11
|
+
* wake/origin conversation), which broadcasts unscoped for raw SSE listeners.
|
|
12
|
+
*
|
|
13
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
14
|
+
* directly from this file; external consumers import via
|
|
15
|
+
* `@vellumai/assistant-api`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
export const WorkflowProgressEventSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
type: z.literal("workflow_progress"),
|
|
23
|
+
runId: z.string(),
|
|
24
|
+
conversationId: z.string().optional(),
|
|
25
|
+
agentsSpawned: z.number(),
|
|
26
|
+
phase: z.string().optional(),
|
|
27
|
+
label: z.string().optional(),
|
|
28
|
+
message: z.string().optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
|
|
32
|
+
export type WorkflowProgressEvent = z.infer<typeof WorkflowProgressEventSchema>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workflow_started` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client notification that a `run_workflow` run has begun.
|
|
5
|
+
* Carries `runId`, the parent `conversationId`, an optional anchoring
|
|
6
|
+
* `toolUseId`, and an optional human-readable `label`.
|
|
7
|
+
*
|
|
8
|
+
* `conversationId` is present so clients can route the event to the
|
|
9
|
+
* originating conversation's inline workflow card. `toolUseId` (the
|
|
10
|
+
* `skill_execute` block id that launched the run) anchors that inline
|
|
11
|
+
* card to the exact spawn tool call, mirroring
|
|
12
|
+
* `subagent_spawned.parentToolUseId`.
|
|
13
|
+
*
|
|
14
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
15
|
+
* directly from this file; external consumers import via
|
|
16
|
+
* `@vellumai/assistant-api`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
export const WorkflowStartedEventSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
type: z.literal("workflow_started"),
|
|
24
|
+
runId: z.string(),
|
|
25
|
+
conversationId: z.string(),
|
|
26
|
+
toolUseId: z.string().optional(),
|
|
27
|
+
label: z.string().optional(),
|
|
28
|
+
})
|
|
29
|
+
.strict();
|
|
30
|
+
|
|
31
|
+
export type WorkflowStartedEvent = z.infer<typeof WorkflowStartedEventSchema>;
|
package/src/api/index.ts
CHANGED
|
@@ -52,6 +52,11 @@ import { UISurfaceUpdateEventSchema } from "./events/ui-surface-update.js";
|
|
|
52
52
|
import { UsageProgressEventSchema } from "./events/usage-progress.js";
|
|
53
53
|
import { UsageUpdateEventSchema } from "./events/usage-update.js";
|
|
54
54
|
import { UserMessageEchoEventSchema } from "./events/user-message-echo.js";
|
|
55
|
+
import { WorkflowCompletedEventSchema } from "./events/workflow-completed.js";
|
|
56
|
+
import { WorkflowLeafFinishedEventSchema } from "./events/workflow-leaf-finished.js";
|
|
57
|
+
import { WorkflowLeafStartedEventSchema } from "./events/workflow-leaf-started.js";
|
|
58
|
+
import { WorkflowProgressEventSchema } from "./events/workflow-progress.js";
|
|
59
|
+
import { WorkflowStartedEventSchema } from "./events/workflow-started.js";
|
|
55
60
|
|
|
56
61
|
export {
|
|
57
62
|
CALL_SITE_COMPACTION_AGENT,
|
|
@@ -326,6 +331,28 @@ export {
|
|
|
326
331
|
type UserMessageEchoEvent,
|
|
327
332
|
UserMessageEchoEventSchema,
|
|
328
333
|
} from "./events/user-message-echo.js";
|
|
334
|
+
export {
|
|
335
|
+
type WorkflowCompletedEvent,
|
|
336
|
+
WorkflowCompletedEventSchema,
|
|
337
|
+
type WorkflowRunStatus,
|
|
338
|
+
WorkflowRunStatusSchema,
|
|
339
|
+
} from "./events/workflow-completed.js";
|
|
340
|
+
export {
|
|
341
|
+
type WorkflowLeafFinishedEvent,
|
|
342
|
+
WorkflowLeafFinishedEventSchema,
|
|
343
|
+
} from "./events/workflow-leaf-finished.js";
|
|
344
|
+
export {
|
|
345
|
+
type WorkflowLeafStartedEvent,
|
|
346
|
+
WorkflowLeafStartedEventSchema,
|
|
347
|
+
} from "./events/workflow-leaf-started.js";
|
|
348
|
+
export {
|
|
349
|
+
type WorkflowProgressEvent,
|
|
350
|
+
WorkflowProgressEventSchema,
|
|
351
|
+
} from "./events/workflow-progress.js";
|
|
352
|
+
export {
|
|
353
|
+
type WorkflowStartedEvent,
|
|
354
|
+
WorkflowStartedEventSchema,
|
|
355
|
+
} from "./events/workflow-started.js";
|
|
329
356
|
export {
|
|
330
357
|
type DictationContext,
|
|
331
358
|
DictationContextSchema,
|
|
@@ -443,6 +470,12 @@ export {
|
|
|
443
470
|
type SubagentDetailResponse,
|
|
444
471
|
SubagentDetailResponseSchema,
|
|
445
472
|
} from "./responses/subagent-detail.js";
|
|
473
|
+
export {
|
|
474
|
+
type WorkflowJournalResponse,
|
|
475
|
+
WorkflowJournalResponseSchema,
|
|
476
|
+
type WorkflowLeaf,
|
|
477
|
+
WorkflowLeafSchema,
|
|
478
|
+
} from "./responses/workflow-journal.js";
|
|
446
479
|
|
|
447
480
|
/**
|
|
448
481
|
* Canonical SSE event schema for the assistant runtime.
|
|
@@ -510,6 +543,11 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
|
510
543
|
UsageProgressEventSchema,
|
|
511
544
|
UsageUpdateEventSchema,
|
|
512
545
|
UserMessageEchoEventSchema,
|
|
546
|
+
WorkflowCompletedEventSchema,
|
|
547
|
+
WorkflowLeafFinishedEventSchema,
|
|
548
|
+
WorkflowLeafStartedEventSchema,
|
|
549
|
+
WorkflowProgressEventSchema,
|
|
550
|
+
WorkflowStartedEventSchema,
|
|
513
551
|
]);
|
|
514
552
|
|
|
515
553
|
/**
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract for the workflow-journal REST endpoint. Returns a
|
|
3
|
+
* `run_workflow` run's live status, cumulative usage/agent counters,
|
|
4
|
+
* current phase, and the ordered list of leaf agents spawned by the
|
|
5
|
+
* run.
|
|
6
|
+
*
|
|
7
|
+
* Reuses the canonical `WorkflowRunStatusSchema` defined alongside the
|
|
8
|
+
* `workflow_completed` SSE event so the polled REST journal and the
|
|
9
|
+
* streamed lifecycle events share one status shape.
|
|
10
|
+
*
|
|
11
|
+
* Canonical wire-contract source. Assistant code imports the types
|
|
12
|
+
* directly from this file via relative paths; external consumers
|
|
13
|
+
* (web client, gateway, evals) import via `@vellumai/assistant-api`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
import { WorkflowRunStatusSchema } from "../events/workflow-completed.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A single leaf within a workflow run. `kind` distinguishes a leaf
|
|
22
|
+
* agent from a nested workflow; `status` is an open string rather than
|
|
23
|
+
* a closed enum since it covers both lifecycle states and terminal
|
|
24
|
+
* results.
|
|
25
|
+
*/
|
|
26
|
+
export const WorkflowLeafSchema = z.object({
|
|
27
|
+
seq: z.number().int(),
|
|
28
|
+
kind: z.enum(["agent", "workflow"]),
|
|
29
|
+
label: z.string().optional(),
|
|
30
|
+
phase: z.string().optional(),
|
|
31
|
+
promptSummary: z.string().optional(),
|
|
32
|
+
status: z.string(),
|
|
33
|
+
resultSummary: z.string().optional(),
|
|
34
|
+
inputTokens: z.number().optional(),
|
|
35
|
+
outputTokens: z.number().optional(),
|
|
36
|
+
createdAt: z.number().nullable(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export type WorkflowLeaf = z.infer<typeof WorkflowLeafSchema>;
|
|
40
|
+
|
|
41
|
+
export const WorkflowJournalResponseSchema = z.object({
|
|
42
|
+
runId: z.string(),
|
|
43
|
+
status: WorkflowRunStatusSchema.optional(),
|
|
44
|
+
agentsSpawned: z.number().optional(),
|
|
45
|
+
inputTokens: z.number().optional(),
|
|
46
|
+
outputTokens: z.number().optional(),
|
|
47
|
+
phase: z.string().optional(),
|
|
48
|
+
leaves: z.array(WorkflowLeafSchema),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export type WorkflowJournalResponse = z.infer<
|
|
52
|
+
typeof WorkflowJournalResponseSchema
|
|
53
|
+
>;
|
|
@@ -18,6 +18,13 @@ import type { WorkflowRunStatus } from "../../workflows/journal-store.js";
|
|
|
18
18
|
export interface WorkflowProgress {
|
|
19
19
|
type: "workflow_progress";
|
|
20
20
|
runId: string;
|
|
21
|
+
/**
|
|
22
|
+
* Originating conversation id, when launched from one; lets `broadcastMessage`
|
|
23
|
+
* auto-scope + seq-stamp the event to that conversation's SSE stream. Omitted
|
|
24
|
+
* for a conversationless run (e.g. a scheduled workflow), which broadcasts
|
|
25
|
+
* unscoped for raw SSE listeners and the DB record.
|
|
26
|
+
*/
|
|
27
|
+
conversationId?: string;
|
|
21
28
|
/** Latest phase title, when this emission came from a `phase(...)` call. */
|
|
22
29
|
phase?: string;
|
|
23
30
|
/** Run label (the workflow's `meta.name`), for client display. */
|
|
@@ -36,6 +43,13 @@ export interface WorkflowProgress {
|
|
|
36
43
|
export interface WorkflowCompleted {
|
|
37
44
|
type: "workflow_completed";
|
|
38
45
|
runId: string;
|
|
46
|
+
/**
|
|
47
|
+
* Originating conversation id, when launched from one; lets `broadcastMessage`
|
|
48
|
+
* auto-scope + seq-stamp the event to that conversation's SSE stream. Omitted
|
|
49
|
+
* for a conversationless run (e.g. a scheduled workflow), which broadcasts
|
|
50
|
+
* unscoped for raw SSE listeners and the DB record.
|
|
51
|
+
*/
|
|
52
|
+
conversationId?: string;
|
|
39
53
|
status: WorkflowRunStatus;
|
|
40
54
|
agentsSpawned: number;
|
|
41
55
|
inputTokens: number;
|
|
@@ -44,6 +58,74 @@ export interface WorkflowCompleted {
|
|
|
44
58
|
summary?: string;
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A workflow run has started. Emitted once at launch, before any leaf events.
|
|
63
|
+
*/
|
|
64
|
+
export interface WorkflowStarted {
|
|
65
|
+
type: "workflow_started";
|
|
66
|
+
runId: string;
|
|
67
|
+
/**
|
|
68
|
+
* Originating conversation id; lets `broadcastMessage` auto-scope +
|
|
69
|
+
* seq-stamp the event to the conversation's SSE stream.
|
|
70
|
+
*/
|
|
71
|
+
conversationId: string;
|
|
72
|
+
/**
|
|
73
|
+
* Tool-use id of the `skill_execute` block that launched this run, for
|
|
74
|
+
* anchoring the inline workflow card to the exact spawn tool call.
|
|
75
|
+
*/
|
|
76
|
+
toolUseId?: string;
|
|
77
|
+
/** Run label (the workflow's `meta.name`), for client display. */
|
|
78
|
+
label?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A leaf agent within a workflow run has started. `seq` orders leaves within
|
|
83
|
+
* the run for stable client-side tree placement.
|
|
84
|
+
*/
|
|
85
|
+
export interface WorkflowLeafStarted {
|
|
86
|
+
type: "workflow_leaf_started";
|
|
87
|
+
runId: string;
|
|
88
|
+
/**
|
|
89
|
+
* Originating conversation id; lets `broadcastMessage` auto-scope +
|
|
90
|
+
* seq-stamp the event to the conversation's SSE stream.
|
|
91
|
+
*/
|
|
92
|
+
conversationId: string;
|
|
93
|
+
seq: number;
|
|
94
|
+
/** Leaf label, for client display. */
|
|
95
|
+
label?: string;
|
|
96
|
+
/** Phase the leaf belongs to, when the workflow declares phases. */
|
|
97
|
+
phase?: string;
|
|
98
|
+
/** Short summary of the leaf's prompt, for client display. */
|
|
99
|
+
promptSummary?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A leaf agent within a workflow run has finished. `seq` matches the
|
|
104
|
+
* corresponding `workflow_leaf_started` event.
|
|
105
|
+
*/
|
|
106
|
+
export interface WorkflowLeafFinished {
|
|
107
|
+
type: "workflow_leaf_finished";
|
|
108
|
+
runId: string;
|
|
109
|
+
/**
|
|
110
|
+
* Originating conversation id; lets `broadcastMessage` auto-scope +
|
|
111
|
+
* seq-stamp the event to the conversation's SSE stream.
|
|
112
|
+
*/
|
|
113
|
+
conversationId: string;
|
|
114
|
+
seq: number;
|
|
115
|
+
status: "completed" | "failed";
|
|
116
|
+
/** Leaf label, for client display. */
|
|
117
|
+
label?: string;
|
|
118
|
+
inputTokens?: number;
|
|
119
|
+
outputTokens?: number;
|
|
120
|
+
/** Short summary of the leaf's result, for client display. */
|
|
121
|
+
resultSummary?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
47
124
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
48
125
|
|
|
49
|
-
export type _WorkflowsServerMessages =
|
|
126
|
+
export type _WorkflowsServerMessages =
|
|
127
|
+
| WorkflowProgress
|
|
128
|
+
| WorkflowCompleted
|
|
129
|
+
| WorkflowStarted
|
|
130
|
+
| WorkflowLeafStarted
|
|
131
|
+
| WorkflowLeafFinished;
|
package/src/memory/db-init.ts
CHANGED
|
@@ -224,6 +224,7 @@ import {
|
|
|
224
224
|
migrateUsageLlmCallCount,
|
|
225
225
|
migrateVoiceInviteColumns,
|
|
226
226
|
migrateVoiceInviteDisplayMetadata,
|
|
227
|
+
migrateWorkflowJournalLeafTokens,
|
|
227
228
|
migrateWorkflowRuns,
|
|
228
229
|
migrateWorkflowRunTrust,
|
|
229
230
|
recoverCrashedMigrations,
|
|
@@ -523,6 +524,7 @@ export function initializeDb(): void {
|
|
|
523
524
|
migrateScheduleCapabilities,
|
|
524
525
|
migrateContactChannelsRenormalizeAddresses,
|
|
525
526
|
migrateScheduleDefaultNoReuseConversation,
|
|
527
|
+
migrateWorkflowJournalLeafTokens,
|
|
526
528
|
];
|
|
527
529
|
|
|
528
530
|
// Run each migration step, catching and logging individual failures so one
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Add nullable `input_tokens` / `output_tokens` columns to `workflow_journal`.
|
|
6
|
+
*
|
|
7
|
+
* Persists per-leaf token usage so the journal route can attribute usage to each
|
|
8
|
+
* leaf. The client then computes run-level token metrics from the per-leaf sum
|
|
9
|
+
* (a single source of truth), counting each leaf exactly once regardless of
|
|
10
|
+
* whether its usage arrives via a live `leaf_finished` event or a journal
|
|
11
|
+
* backfill — which avoids the undercount that arose when a mid-run journal
|
|
12
|
+
* aggregate counted a leaf the journal could not itself attribute.
|
|
13
|
+
*
|
|
14
|
+
* Nullable — legacy rows and non-completed leaves (failures, nested
|
|
15
|
+
* `workflow`-kind entries) stay NULL and contribute zero to the sum.
|
|
16
|
+
*
|
|
17
|
+
* Idempotent — each ALTER is wrapped so a re-run (column already present) is a
|
|
18
|
+
* no-op.
|
|
19
|
+
*/
|
|
20
|
+
export function migrateWorkflowJournalLeafTokens(database: DrizzleDb): void {
|
|
21
|
+
const raw = getSqliteFrom(database);
|
|
22
|
+
try {
|
|
23
|
+
raw.exec(`ALTER TABLE workflow_journal ADD COLUMN input_tokens INTEGER`);
|
|
24
|
+
} catch {
|
|
25
|
+
/* Column already exists */
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
raw.exec(`ALTER TABLE workflow_journal ADD COLUMN output_tokens INTEGER`);
|
|
29
|
+
} catch {
|
|
30
|
+
/* Column already exists */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -284,6 +284,7 @@ export { migrateContactChannelsUniqueExtUser } from "./289-contact-channels-uniq
|
|
|
284
284
|
export { migrateScheduleCapabilities } from "./290-schedule-capabilities.js";
|
|
285
285
|
export { migrateContactChannelsRenormalizeAddresses } from "./291-contact-channels-renormalize-addresses.js";
|
|
286
286
|
export { migrateScheduleDefaultNoReuseConversation } from "./292-schedule-default-no-reuse-conversation.js";
|
|
287
|
+
export { migrateWorkflowJournalLeafTokens } from "./293-workflow-journal-leaf-tokens.js";
|
|
287
288
|
export {
|
|
288
289
|
MIGRATION_REGISTRY,
|
|
289
290
|
type MigrationRegistryEntry,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { Message } from "../../../../providers/types.js";
|
|
4
|
+
import {
|
|
5
|
+
getCapture,
|
|
6
|
+
recordMessages,
|
|
7
|
+
recordSystemPrompt,
|
|
8
|
+
resetAdvisorStateForTests,
|
|
9
|
+
seedCapture,
|
|
10
|
+
} from "../advisor-state-store.js";
|
|
11
|
+
|
|
12
|
+
const userMsg = (t: string): Message => ({
|
|
13
|
+
role: "user",
|
|
14
|
+
content: [{ type: "text", text: t }],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
resetAdvisorStateForTests();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("advisor state store", () => {
|
|
22
|
+
test("records system prompt and messages independently per conversation", () => {
|
|
23
|
+
recordSystemPrompt("c1", "system A");
|
|
24
|
+
recordMessages("c1", [userMsg("hello")]);
|
|
25
|
+
recordSystemPrompt("c2", "system B");
|
|
26
|
+
|
|
27
|
+
expect(getCapture("c1")?.systemPrompt).toBe("system A");
|
|
28
|
+
expect(getCapture("c1")?.messages).toEqual([userMsg("hello")]);
|
|
29
|
+
expect(getCapture("c2")?.systemPrompt).toBe("system B");
|
|
30
|
+
expect(getCapture("c2")?.messages).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("seedCapture snapshots (copies) the array", () => {
|
|
34
|
+
const live: Message[] = [userMsg("one")];
|
|
35
|
+
seedCapture("c1", live);
|
|
36
|
+
live.push(userMsg("two"));
|
|
37
|
+
expect(getCapture("c1")?.messages).toEqual([userMsg("one")]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("getCapture returns undefined for an unseen conversation", () => {
|
|
41
|
+
expect(getCapture("nope")).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
});
|