agent-relay 4.0.19 → 4.0.21
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/dist/index.cjs +1407 -537
- package/dist/src/cli/commands/messaging.d.ts +44 -0
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js +195 -8
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/commands/setup.d.ts +30 -1
- package/dist/src/cli/commands/setup.d.ts.map +1 -1
- package/dist/src/cli/commands/setup.js +102 -85
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/node_modules/@agent-relay/cloud/dist/auth.d.ts +2 -2
- package/node_modules/@agent-relay/cloud/dist/auth.d.ts.map +1 -1
- package/node_modules/@agent-relay/cloud/dist/auth.js +108 -62
- package/node_modules/@agent-relay/cloud/dist/auth.js.map +1 -1
- package/node_modules/@agent-relay/cloud/package.json +2 -2
- package/node_modules/@agent-relay/config/package.json +1 -1
- package/node_modules/@agent-relay/hooks/package.json +4 -4
- package/node_modules/@agent-relay/sdk/dist/workflows/trajectory.d.ts +5 -35
- package/node_modules/@agent-relay/sdk/dist/workflows/trajectory.d.ts.map +1 -1
- package/node_modules/@agent-relay/sdk/dist/workflows/trajectory.js +158 -292
- package/node_modules/@agent-relay/sdk/dist/workflows/trajectory.js.map +1 -1
- package/node_modules/@agent-relay/sdk/package.json +3 -2
- package/node_modules/@agent-relay/telemetry/package.json +1 -1
- package/node_modules/@agent-relay/trajectory/package.json +2 -2
- package/node_modules/@agent-relay/user-directory/package.json +2 -2
- package/node_modules/@agent-relay/utils/package.json +2 -2
- package/node_modules/@clack/core/CHANGELOG.md +200 -0
- package/node_modules/@clack/core/LICENSE +9 -0
- package/node_modules/@clack/core/README.md +22 -0
- package/node_modules/@clack/core/dist/index.cjs +15 -0
- package/node_modules/@clack/core/dist/index.cjs.map +1 -0
- package/node_modules/@clack/core/dist/index.d.cts +151 -0
- package/node_modules/@clack/core/dist/index.d.mts +151 -0
- package/node_modules/@clack/core/dist/index.d.ts +151 -0
- package/node_modules/@clack/core/dist/index.mjs +15 -0
- package/node_modules/@clack/core/dist/index.mjs.map +1 -0
- package/node_modules/@clack/core/package.json +62 -0
- package/node_modules/@clack/prompts/CHANGELOG.md +256 -0
- package/node_modules/@clack/prompts/LICENSE +23 -0
- package/node_modules/@clack/prompts/README.md +158 -0
- package/node_modules/@clack/prompts/dist/index.cjs +77 -0
- package/node_modules/@clack/prompts/dist/index.d.ts +106 -0
- package/node_modules/@clack/prompts/dist/index.mjs +77 -0
- package/node_modules/@clack/prompts/node_modules/is-unicode-supported/index.d.ts +12 -0
- package/node_modules/@clack/prompts/node_modules/is-unicode-supported/index.js +17 -0
- package/node_modules/@clack/prompts/node_modules/is-unicode-supported/license +9 -0
- package/node_modules/@clack/prompts/node_modules/is-unicode-supported/package.json +43 -0
- package/node_modules/@clack/prompts/node_modules/is-unicode-supported/readme.md +35 -0
- package/node_modules/@clack/prompts/package.json +65 -0
- package/node_modules/@smithy/config-resolver/package.json +2 -2
- package/node_modules/@smithy/util-defaults-mode-node/package.json +2 -2
- package/node_modules/@smithy/util-endpoints/dist-cjs/index.js +154 -61
- package/node_modules/@smithy/util-endpoints/dist-es/bdd/BinaryDecisionDiagram.js +15 -0
- package/node_modules/@smithy/util-endpoints/dist-es/decideEndpoint.js +41 -0
- package/node_modules/@smithy/util-endpoints/dist-es/index.js +2 -0
- package/node_modules/@smithy/util-endpoints/dist-es/lib/coalesce.js +8 -0
- package/node_modules/@smithy/util-endpoints/dist-es/lib/index.js +3 -0
- package/node_modules/@smithy/util-endpoints/dist-es/lib/ite.js +3 -0
- package/node_modules/@smithy/util-endpoints/dist-es/lib/split.js +13 -0
- package/node_modules/@smithy/util-endpoints/dist-es/lib/substring.js +1 -1
- package/node_modules/@smithy/util-endpoints/dist-es/utils/endpointFunctions.js +4 -1
- package/node_modules/@smithy/util-endpoints/dist-es/utils/evaluateExpression.js +20 -5
- package/node_modules/@smithy/util-endpoints/dist-es/utils/evaluateTemplate.js +3 -6
- package/node_modules/@smithy/util-endpoints/dist-es/utils/getReferenceValue.js +1 -5
- package/node_modules/@smithy/util-endpoints/dist-types/bdd/BinaryDecisionDiagram.d.ts +22 -0
- package/node_modules/@smithy/util-endpoints/dist-types/decideEndpoint.d.ts +7 -0
- package/node_modules/@smithy/util-endpoints/dist-types/index.d.ts +2 -0
- package/node_modules/@smithy/util-endpoints/dist-types/lib/coalesce.d.ts +7 -0
- package/node_modules/@smithy/util-endpoints/dist-types/lib/index.d.ts +3 -0
- package/node_modules/@smithy/util-endpoints/dist-types/lib/ite.d.ts +6 -0
- package/node_modules/@smithy/util-endpoints/dist-types/lib/split.d.ts +11 -0
- package/node_modules/@smithy/util-endpoints/dist-types/utils/endpointFunctions.d.ts +4 -0
- package/node_modules/@smithy/util-endpoints/dist-types/utils/getReferenceValue.d.ts +3 -1
- package/node_modules/@smithy/util-endpoints/package.json +1 -1
- package/node_modules/agent-trajectories/README.md +562 -0
- package/node_modules/agent-trajectories/dist/chunk-W222QB6V.js +2036 -0
- package/node_modules/agent-trajectories/dist/chunk-W222QB6V.js.map +1 -0
- package/node_modules/agent-trajectories/dist/cli/index.d.ts +2 -0
- package/node_modules/agent-trajectories/dist/cli/index.js +4592 -0
- package/node_modules/agent-trajectories/dist/cli/index.js.map +1 -0
- package/node_modules/agent-trajectories/dist/index-7tzw_CMS.d.ts +1777 -0
- package/node_modules/agent-trajectories/dist/index.d.ts +90 -0
- package/node_modules/agent-trajectories/dist/index.js +73 -0
- package/node_modules/agent-trajectories/dist/index.js.map +1 -0
- package/node_modules/agent-trajectories/dist/sdk/index.d.ts +2 -0
- package/node_modules/agent-trajectories/dist/sdk/index.js +39 -0
- package/node_modules/agent-trajectories/dist/sdk/index.js.map +1 -0
- package/node_modules/agent-trajectories/package.json +72 -0
- package/node_modules/commander/LICENSE +22 -0
- package/node_modules/commander/Readme.md +1157 -0
- package/node_modules/commander/esm.mjs +16 -0
- package/node_modules/commander/index.js +24 -0
- package/node_modules/commander/lib/argument.js +149 -0
- package/node_modules/commander/lib/command.js +2509 -0
- package/node_modules/commander/lib/error.js +39 -0
- package/node_modules/commander/lib/help.js +520 -0
- package/node_modules/commander/lib/option.js +330 -0
- package/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/node_modules/commander/package-support.json +16 -0
- package/node_modules/commander/package.json +84 -0
- package/node_modules/commander/typings/esm.d.mts +3 -0
- package/node_modules/commander/typings/index.d.ts +969 -0
- package/node_modules/picocolors/LICENSE +15 -0
- package/node_modules/picocolors/README.md +21 -0
- package/node_modules/picocolors/package.json +25 -0
- package/node_modules/picocolors/picocolors.browser.js +4 -0
- package/node_modules/picocolors/picocolors.d.ts +5 -0
- package/node_modules/picocolors/picocolors.js +75 -0
- package/node_modules/picocolors/types.d.ts +51 -0
- package/node_modules/sisteransi/license +21 -0
- package/node_modules/sisteransi/package.json +34 -0
- package/node_modules/sisteransi/readme.md +113 -0
- package/node_modules/sisteransi/src/index.js +58 -0
- package/node_modules/sisteransi/src/sisteransi.d.ts +35 -0
- package/package.json +10 -10
- package/packages/cloud/dist/auth.d.ts +2 -2
- package/packages/cloud/dist/auth.d.ts.map +1 -1
- package/packages/cloud/dist/auth.js +108 -62
- package/packages/cloud/dist/auth.js.map +1 -1
- package/packages/cloud/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/sdk/dist/workflows/trajectory.d.ts +5 -35
- package/packages/sdk/dist/workflows/trajectory.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/trajectory.js +158 -292
- package/packages/sdk/dist/workflows/trajectory.js.map +1 -1
- package/packages/sdk/package.json +3 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Writes trajectory JSON files directly to `.trajectories/active/` in a format
|
|
5
|
-
* compatible with `trail show`. No external CLI or package dependency required.
|
|
6
|
-
*
|
|
7
|
-
* Design principles:
|
|
8
|
-
* 1. One trajectory per workflow run
|
|
9
|
-
* 2. Chapters map to workflow phases, not individual steps
|
|
10
|
-
* 3. Non-blocking — trajectory recording never fails the workflow
|
|
11
|
-
* 4. Opt-in but default-on
|
|
12
|
-
*/
|
|
13
|
-
import { randomBytes } from 'node:crypto';
|
|
14
|
-
import { existsSync } from 'node:fs';
|
|
15
|
-
import { mkdir, writeFile, rename } from 'node:fs/promises';
|
|
16
|
-
import path from 'node:path';
|
|
1
|
+
/** WorkflowTrajectory records canonical workflow trajectories via agent-trajectories. */
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { FileStorage, abandonTrajectory, addChapter as appendChapter, addEvent as appendEvent, completeTrajectory, createTrajectory, } from 'agent-trajectories';
|
|
17
4
|
function classifyFailure(error) {
|
|
18
5
|
const e = error.toLowerCase();
|
|
19
6
|
if (e.includes('timed out') || e.includes('timeout'))
|
|
@@ -31,35 +18,94 @@ function classifyFailure(error) {
|
|
|
31
18
|
function diagnosisFor(cause, outcome) {
|
|
32
19
|
switch (cause) {
|
|
33
20
|
case 'timeout':
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
return (`Interactive agent timed out — it may have gone idle, failed to self-terminate, or the task scope was too broad. ` +
|
|
39
|
-
`Check if the agent was waiting for relay signals that never arrived.`);
|
|
21
|
+
return outcome.nonInteractive
|
|
22
|
+
? 'Non-interactive agent timed out — the task is likely too large or complex for a single subprocess call. Consider pre-reading large files in a deterministic step and injecting only the relevant excerpt via {{steps.X.output}}.'
|
|
23
|
+
: 'Interactive agent timed out — it may have gone idle, failed to self-terminate, or the task scope was too broad. Check if the agent was waiting for relay signals that never arrived.';
|
|
40
24
|
case 'verification_mismatch':
|
|
41
|
-
return
|
|
42
|
-
`The task prompt may not clearly specify the required output format, ` +
|
|
43
|
-
`or the agent produced correct work but did not emit the signal.`);
|
|
25
|
+
return `Agent completed but did not output the expected sentinel "${outcome.verificationValue ?? '(unknown)'}". The task prompt may not clearly specify the required output format, or the agent produced correct work but did not emit the signal.`;
|
|
44
26
|
case 'spawn_failed':
|
|
45
|
-
return
|
|
27
|
+
return 'The agent process could not be started — the CLI binary may be missing from PATH or the working directory is incorrect.';
|
|
46
28
|
case 'exit_nonzero':
|
|
47
|
-
return
|
|
29
|
+
return 'The agent process exited with a non-zero exit code. Check stderr for the root cause.';
|
|
48
30
|
case 'aborted':
|
|
49
|
-
return
|
|
31
|
+
return 'The step was cancelled (user interrupt or upstream abort).';
|
|
50
32
|
default:
|
|
51
|
-
return
|
|
33
|
+
return 'Unexpected failure. Review the error and step definition.';
|
|
52
34
|
}
|
|
53
35
|
}
|
|
54
|
-
|
|
36
|
+
function buildSynthesis(label, outcomes, unblocks) {
|
|
37
|
+
const completed = outcomes.filter((o) => o.status === 'completed');
|
|
38
|
+
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
39
|
+
const retried = outcomes.filter((o) => o.attempts > 1 && o.status !== 'failed');
|
|
40
|
+
const parts = [`${label} resolved.`, `${completed.length}/${outcomes.length} steps completed.`];
|
|
41
|
+
if (failed.length > 0)
|
|
42
|
+
parts.push(`${failed.length} step(s) failed: ${failed.map((s) => s.name).join(', ')}.`);
|
|
43
|
+
if (retried.length > 0)
|
|
44
|
+
parts.push(`${retried.length} step(s) required retries: ${retried.map((s) => s.name).join(', ')}.`);
|
|
45
|
+
else if (failed.length === 0)
|
|
46
|
+
parts.push('All steps completed on first attempt.');
|
|
47
|
+
if (unblocks?.length)
|
|
48
|
+
parts.push(`Unblocking: ${unblocks.join(', ')}.`);
|
|
49
|
+
return parts.join(' ');
|
|
50
|
+
}
|
|
51
|
+
function computeConfidence(outcomes) {
|
|
52
|
+
if (outcomes.length === 0)
|
|
53
|
+
return 0.7;
|
|
54
|
+
const total = outcomes.length;
|
|
55
|
+
const completed = outcomes.filter((o) => o.status === 'completed').length;
|
|
56
|
+
const firstAttempt = outcomes.filter((o) => o.attempts === 1 && o.status === 'completed').length;
|
|
57
|
+
const verified = outcomes.filter((o) => o.verificationPassed).length;
|
|
58
|
+
return Math.min(1, 0.5 * (completed / total) + 0.25 * (firstAttempt / total) + 0.25 * (verified / total));
|
|
59
|
+
}
|
|
60
|
+
function formatElapsed(elapsed, long) {
|
|
61
|
+
return elapsed > 60_000
|
|
62
|
+
? `${Math.round(elapsed / 60_000)}${long ? ' minutes' : 'min'}`
|
|
63
|
+
: `${Math.round(elapsed / 1_000)}${long ? ' seconds' : 's'}`;
|
|
64
|
+
}
|
|
65
|
+
function buildRunSummary(outcomes, startTime) {
|
|
66
|
+
const completed = outcomes.filter((o) => o.status === 'completed');
|
|
67
|
+
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
68
|
+
const skipped = outcomes.filter((o) => o.status === 'skipped');
|
|
69
|
+
const elapsedStr = formatElapsed(Date.now() - startTime, false);
|
|
70
|
+
if (failed.length === 0) {
|
|
71
|
+
const retried = completed.filter((o) => o.attempts > 1);
|
|
72
|
+
const base = `All ${completed.length} steps completed in ${elapsedStr}.`;
|
|
73
|
+
return retried.length > 0
|
|
74
|
+
? `${base} ${retried.length} step(s) needed retries: ${retried.map((o) => o.name).join(', ')}.`
|
|
75
|
+
: base;
|
|
76
|
+
}
|
|
77
|
+
const firstFailure = failed[0];
|
|
78
|
+
const cause = classifyFailure(firstFailure.error ?? '');
|
|
79
|
+
const cascaded = skipped.length > 0
|
|
80
|
+
? ` Caused ${skipped.length} downstream step(s) to be skipped: ${skipped.map((o) => o.name).join(', ')}.`
|
|
81
|
+
: '';
|
|
82
|
+
return `Failed at "${firstFailure.name}" [${cause}] after ${elapsedStr}.${cascaded} ${completed.length}/${outcomes.length} steps completed before failure.`;
|
|
83
|
+
}
|
|
84
|
+
function extractLearnings(outcomes) {
|
|
85
|
+
const learnings = [];
|
|
86
|
+
const timeouts = outcomes.filter((o) => o.status === 'failed' && classifyFailure(o.error ?? '') === 'timeout');
|
|
87
|
+
if (timeouts.some((o) => o.nonInteractive))
|
|
88
|
+
learnings.push(`Non-interactive agent timeouts detected (${timeouts.map((o) => o.name).join(', ')}). Use deterministic steps to pre-read files and inject content — non-interactive agents should not discover information via tools.`);
|
|
89
|
+
const verifyFails = outcomes.filter((o) => o.status === 'failed' && classifyFailure(o.error ?? '') === 'verification_mismatch');
|
|
90
|
+
if (verifyFails.length > 0)
|
|
91
|
+
learnings.push(`Verification mismatch on: ${verifyFails.map((o) => `"${o.name}" (expected "${o.verificationValue ?? '?'}")`).join(', ')}. Make the required output format more explicit in the task prompt.`);
|
|
92
|
+
const retried = outcomes.filter((o) => o.attempts > 1 && o.status === 'completed');
|
|
93
|
+
if (retried.length > 0)
|
|
94
|
+
learnings.push(`${retried.map((o) => `"${o.name}" (${o.attempts} attempts)`).join(', ')} succeeded after retries — consider adding clearer output instructions to reduce retries.`);
|
|
95
|
+
return learnings;
|
|
96
|
+
}
|
|
97
|
+
const extractChallenges = (outcomes) => outcomes
|
|
98
|
+
.filter((o) => o.status === 'failed')
|
|
99
|
+
.map((step) => diagnosisFor(classifyFailure(step.error ?? ''), step));
|
|
55
100
|
export class WorkflowTrajectory {
|
|
56
101
|
trajectory = null;
|
|
57
|
-
|
|
102
|
+
storage;
|
|
103
|
+
storageInit;
|
|
58
104
|
enabled;
|
|
59
105
|
reflectOnBarriers;
|
|
60
106
|
reflectOnConverge;
|
|
61
107
|
autoDecisions;
|
|
62
|
-
|
|
108
|
+
storageBaseDir;
|
|
63
109
|
runId;
|
|
64
110
|
startTime = 0;
|
|
65
111
|
swarmPattern = 'dag';
|
|
@@ -70,79 +116,56 @@ export class WorkflowTrajectory {
|
|
|
70
116
|
this.reflectOnConverge = cfg.reflectOnConverge !== false;
|
|
71
117
|
this.autoDecisions = cfg.autoDecisions !== false;
|
|
72
118
|
this.runId = runId;
|
|
73
|
-
|
|
119
|
+
const dataDir = process.env.TRAJECTORIES_DATA_DIR ?? join(cwd, '.trajectories');
|
|
120
|
+
this.storageBaseDir = process.env.TRAJECTORIES_DATA_DIR ? dirname(dataDir) : cwd;
|
|
74
121
|
}
|
|
75
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
76
|
-
/** Start the trajectory (called at run:started). */
|
|
77
122
|
async start(workflowName, stepCount, trackInfo, description, pattern) {
|
|
78
123
|
if (!this.enabled)
|
|
79
124
|
return;
|
|
80
125
|
this.startTime = Date.now();
|
|
81
126
|
this.swarmPattern = pattern ?? 'dag';
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
// Open Planning chapter — record intent, not just mechanics
|
|
127
|
+
this.trajectory = createTrajectory({
|
|
128
|
+
title: workflowName,
|
|
129
|
+
description,
|
|
130
|
+
source: { system: 'workflow-runner', id: this.runId },
|
|
131
|
+
});
|
|
132
|
+
const workflowId = process.env.TRAJECTORIES_WORKFLOW_ID?.trim();
|
|
133
|
+
if (workflowId)
|
|
134
|
+
this.trajectory = { ...this.trajectory, workflowId };
|
|
135
|
+
this.trajectory.agents.push({
|
|
136
|
+
name: 'orchestrator',
|
|
137
|
+
role: 'workflow-runner',
|
|
138
|
+
joinedAt: new Date().toISOString(),
|
|
139
|
+
});
|
|
96
140
|
this.openChapter('Planning', 'orchestrator');
|
|
97
|
-
if (description)
|
|
98
|
-
// Record why this workflow exists
|
|
141
|
+
if (description)
|
|
99
142
|
this.addEvent('note', `Purpose: ${description.trim()}`);
|
|
100
|
-
}
|
|
101
143
|
this.addEvent('note', `Approach: ${stepCount}-step ${this.swarmPattern} workflow${trackInfo ? ` — ${trackInfo}` : ''}`);
|
|
102
144
|
await this.flush();
|
|
103
145
|
}
|
|
104
|
-
// ── Chapters ───────────────────────────────────────────────────────────────
|
|
105
|
-
/** Begin a new parallel track chapter. */
|
|
106
146
|
async beginTrack(trackName) {
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
await this.flush();
|
|
147
|
+
if (this.enabled && this.trajectory) {
|
|
148
|
+
this.openChapter(`Execution: ${trackName}`, 'orchestrator');
|
|
149
|
+
await this.flush();
|
|
150
|
+
}
|
|
112
151
|
}
|
|
113
|
-
/** Begin a convergence chapter (after barrier/parallel completion). */
|
|
114
152
|
async beginConvergence(label) {
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
await this.flush();
|
|
120
|
-
}
|
|
121
|
-
/** Begin the retrospective chapter. */
|
|
122
|
-
openRetrospective() {
|
|
123
|
-
if (!this.trajectory)
|
|
124
|
-
return;
|
|
125
|
-
this.closeCurrentChapter();
|
|
126
|
-
this.openChapter('Retrospective', 'orchestrator');
|
|
153
|
+
if (this.enabled && this.trajectory) {
|
|
154
|
+
this.openChapter(`Convergence: ${label}`, 'orchestrator');
|
|
155
|
+
await this.flush();
|
|
156
|
+
}
|
|
127
157
|
}
|
|
128
|
-
// ── Step events ────────────────────────────────────────────────────────────
|
|
129
|
-
/** Record step started — captures intent, not just assignment. */
|
|
130
158
|
async stepStarted(step, agent, participants) {
|
|
131
159
|
if (!this.enabled || !this.trajectory)
|
|
132
160
|
return;
|
|
133
161
|
await this.registerAgent(agent, participants?.role ?? step.agent ?? 'deterministic');
|
|
134
|
-
if (participants?.owner && participants.owner !== agent)
|
|
162
|
+
if (participants?.owner && participants.owner !== agent)
|
|
135
163
|
await this.registerAgent(participants.owner, 'owner');
|
|
136
|
-
|
|
137
|
-
if (participants?.specialist) {
|
|
164
|
+
if (participants?.specialist)
|
|
138
165
|
await this.registerAgent(participants.specialist, 'specialist');
|
|
139
|
-
|
|
140
|
-
if (participants?.reviewer) {
|
|
166
|
+
if (participants?.reviewer)
|
|
141
167
|
await this.registerAgent(participants.reviewer, 'reviewer');
|
|
142
|
-
}
|
|
143
|
-
this.closeCurrentChapter();
|
|
144
168
|
this.openChapter(`Execution: ${step.name}`, agent);
|
|
145
|
-
// Capture the step's purpose: first non-empty sentence of the task
|
|
146
169
|
const intent = step.task
|
|
147
170
|
? step.task
|
|
148
171
|
.trim()
|
|
@@ -154,25 +177,18 @@ export class WorkflowTrajectory {
|
|
|
154
177
|
await this.flush();
|
|
155
178
|
}
|
|
156
179
|
async registerAgent(name, role) {
|
|
157
|
-
if (!this.enabled || !this.trajectory)
|
|
180
|
+
if (!this.enabled || !this.trajectory || this.trajectory.agents.some((agent) => agent.name === name))
|
|
158
181
|
return;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
name,
|
|
162
|
-
role,
|
|
163
|
-
joinedAt: new Date().toISOString(),
|
|
164
|
-
});
|
|
165
|
-
await this.flush();
|
|
166
|
-
}
|
|
182
|
+
this.trajectory.agents.push({ name, role, joinedAt: new Date().toISOString() });
|
|
183
|
+
await this.flush();
|
|
167
184
|
}
|
|
168
185
|
async stepSupervisionAssigned(step, supervised) {
|
|
169
186
|
if (!this.enabled || !this.trajectory)
|
|
170
187
|
return;
|
|
171
188
|
await this.registerAgent(supervised.owner.name, 'owner');
|
|
172
189
|
await this.registerAgent(supervised.specialist.name, 'specialist');
|
|
173
|
-
if (supervised.reviewer?.name)
|
|
190
|
+
if (supervised.reviewer?.name)
|
|
174
191
|
await this.registerAgent(supervised.reviewer.name, 'reviewer');
|
|
175
|
-
}
|
|
176
192
|
const reviewerNote = supervised.reviewer?.name ? `, reviewer=${supervised.reviewer.name}` : '';
|
|
177
193
|
this.addEvent('decision', `"${step.name}" supervision assigned → owner=${supervised.owner.name}, specialist=${supervised.specialist.name}${reviewerNote}`, 'medium', {
|
|
178
194
|
owner: supervised.owner.name,
|
|
@@ -204,38 +220,25 @@ export class WorkflowTrajectory {
|
|
|
204
220
|
const modeLabel = decision.mode === 'marker' ? 'marker-based' : `${decision.mode}-based`;
|
|
205
221
|
const reason = decision.reason ? ` — ${decision.reason}` : '';
|
|
206
222
|
const evidence = this.formatCompletionEvidenceSummary(decision.evidence);
|
|
207
|
-
|
|
208
|
-
this.addEvent(decision.mode === 'marker' ? 'completion-marker' : 'completion-evidence', `"${stepName}" ${modeLabel} completion${reason}${evidenceSuffix}`, 'medium', {
|
|
209
|
-
stepName,
|
|
210
|
-
completionMode: decision.mode,
|
|
211
|
-
reason: decision.reason,
|
|
212
|
-
evidence: decision.evidence,
|
|
213
|
-
});
|
|
223
|
+
this.addEvent(decision.mode === 'marker' ? 'completion-marker' : 'completion-evidence', `"${stepName}" ${modeLabel} completion${reason}${evidence ? ` (${evidence})` : ''}`, 'medium', { stepName, completionMode: decision.mode, reason: decision.reason, evidence: decision.evidence });
|
|
214
224
|
await this.flush();
|
|
215
225
|
}
|
|
216
|
-
/** Record step completed — captures what was accomplished. */
|
|
217
226
|
async stepCompleted(step, output, attempt, decision) {
|
|
218
227
|
if (!this.enabled || !this.trajectory)
|
|
219
228
|
return;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// agents conventionally output their sentinel last (e.g. "ANALYSIS_DONE")
|
|
229
|
+
if (decision)
|
|
230
|
+
await this.stepCompletionDecision(step.name, decision);
|
|
223
231
|
const lines = output
|
|
224
232
|
.split('\n')
|
|
225
|
-
.map((
|
|
233
|
+
.map((line) => line.trim())
|
|
226
234
|
.filter(Boolean);
|
|
227
235
|
const lastMeaningful = lines.at(-1) ?? '';
|
|
228
236
|
const completion = lastMeaningful.length > 0 && lastMeaningful.length < 100
|
|
229
237
|
? lastMeaningful
|
|
230
238
|
: output.trim().slice(0, 120) || '(no output)';
|
|
231
|
-
|
|
232
|
-
await this.stepCompletionDecision(step.name, decision);
|
|
233
|
-
}
|
|
234
|
-
const modeSuffix = decision ? ` [${decision.mode}]` : '';
|
|
235
|
-
this.addEvent('finding', `"${step.name}" completed${suffix}${modeSuffix} → ${completion}`, 'medium');
|
|
239
|
+
this.addEvent('finding', `"${step.name}" completed${attempt > 1 ? ` (after ${attempt} attempts)` : ''}${decision ? ` [${decision.mode}]` : ''} → ${completion}`, 'medium');
|
|
236
240
|
await this.flush();
|
|
237
241
|
}
|
|
238
|
-
/** Record step failed — categorizes root cause for actionable diagnosis. */
|
|
239
242
|
async stepFailed(step, error, attempt, maxRetries, outcome) {
|
|
240
243
|
if (!this.enabled || !this.trajectory)
|
|
241
244
|
return;
|
|
@@ -257,44 +260,30 @@ export class WorkflowTrajectory {
|
|
|
257
260
|
});
|
|
258
261
|
await this.flush();
|
|
259
262
|
}
|
|
260
|
-
/** Record step skipped — note the cascade impact. */
|
|
261
263
|
async stepSkipped(step, reason) {
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
if (this.enabled && this.trajectory) {
|
|
265
|
+
this.addEvent('note', `"${step.name}" skipped — ${reason}`);
|
|
266
|
+
await this.flush();
|
|
267
|
+
}
|
|
266
268
|
}
|
|
267
|
-
/** Record step retrying. */
|
|
268
269
|
async stepRetrying(step, attempt, maxRetries) {
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
if (this.enabled && this.trajectory) {
|
|
271
|
+
this.addEvent('note', `"${step.name}" retrying (attempt ${attempt}/${maxRetries + 1})`);
|
|
272
|
+
await this.flush();
|
|
273
|
+
}
|
|
273
274
|
}
|
|
274
|
-
// ── Reflections ────────────────────────────────────────────────────────────
|
|
275
|
-
/** Record a reflection at a convergence point. */
|
|
276
275
|
async reflect(synthesis, confidence, focalPoints) {
|
|
277
276
|
if (!this.enabled || !this.trajectory)
|
|
278
277
|
return;
|
|
279
|
-
|
|
280
|
-
if (focalPoints?.length) {
|
|
281
|
-
raw.focalPoints = focalPoints;
|
|
282
|
-
}
|
|
283
|
-
this.addEvent('reflection', synthesis, 'high', raw);
|
|
278
|
+
this.addEvent('reflection', synthesis, 'high', focalPoints?.length ? { confidence, focalPoints } : { confidence });
|
|
284
279
|
await this.flush();
|
|
285
280
|
}
|
|
286
|
-
/** Synthesize and reflect after a set of steps complete (barrier or parallel convergence). */
|
|
287
281
|
async synthesizeAndReflect(label, outcomes, unblocks) {
|
|
288
282
|
if (!this.enabled || !this.trajectory)
|
|
289
283
|
return;
|
|
290
|
-
const synthesis = this.buildSynthesis(label, outcomes, unblocks);
|
|
291
|
-
const confidence = this.computeConfidence(outcomes);
|
|
292
|
-
const focalPoints = outcomes.map((o) => `${o.name}: ${o.status}`);
|
|
293
284
|
await this.beginConvergence(label);
|
|
294
|
-
await this.reflect(
|
|
285
|
+
await this.reflect(buildSynthesis(label, outcomes, unblocks), computeConfidence(outcomes), outcomes.map((o) => `${o.name}: ${o.status}`));
|
|
295
286
|
}
|
|
296
|
-
// ── Decisions ──────────────────────────────────────────────────────────────
|
|
297
|
-
/** Record an orchestrator decision. */
|
|
298
287
|
async decide(question, chosen, reasoning) {
|
|
299
288
|
if (!this.enabled || !this.trajectory || !this.autoDecisions)
|
|
300
289
|
return;
|
|
@@ -305,52 +294,39 @@ export class WorkflowTrajectory {
|
|
|
305
294
|
});
|
|
306
295
|
await this.flush();
|
|
307
296
|
}
|
|
308
|
-
// ── Completion ─────────────────────────────────────────────────────────────
|
|
309
|
-
/** Complete the trajectory with a summary. */
|
|
310
297
|
async complete(summary, confidence, meta) {
|
|
311
298
|
if (!this.enabled || !this.trajectory)
|
|
312
299
|
return;
|
|
313
|
-
this.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
this.addEvent('reflection', `${summary} (completed in ${elapsedStr})`, 'high');
|
|
317
|
-
this.trajectory.status = 'completed';
|
|
318
|
-
this.trajectory.completedAt = new Date().toISOString();
|
|
319
|
-
this.trajectory.retrospective = {
|
|
300
|
+
this.openChapter('Retrospective', 'orchestrator');
|
|
301
|
+
this.addEvent('reflection', `${summary} (completed in ${formatElapsed(Date.now() - this.startTime, true)})`, 'high');
|
|
302
|
+
this.trajectory = completeTrajectory(this.trajectory, {
|
|
320
303
|
summary,
|
|
321
|
-
approach:
|
|
304
|
+
approach: this.buildApproach(),
|
|
322
305
|
confidence,
|
|
323
306
|
learnings: meta?.learnings,
|
|
324
307
|
challenges: meta?.challenges,
|
|
325
|
-
};
|
|
326
|
-
this.closeCurrentChapter();
|
|
308
|
+
});
|
|
327
309
|
await this.flush();
|
|
328
|
-
await this.moveToCompleted();
|
|
329
310
|
}
|
|
330
|
-
/** Abandon the trajectory. */
|
|
331
311
|
async abandon(reason, meta) {
|
|
332
312
|
if (!this.enabled || !this.trajectory)
|
|
333
313
|
return;
|
|
334
|
-
const elapsed = Date.now() - this.startTime;
|
|
335
|
-
const elapsedStr = elapsed > 60_000 ? `${Math.round(elapsed / 60_000)} minutes` : `${Math.round(elapsed / 1_000)} seconds`;
|
|
336
314
|
const summary = meta?.summary ?? `Workflow abandoned: ${reason}`;
|
|
337
|
-
this.
|
|
338
|
-
this.addEvent('reflection', `${summary} (abandoned after ${
|
|
315
|
+
this.openChapter('Retrospective', 'orchestrator');
|
|
316
|
+
this.addEvent('reflection', `${summary} (abandoned after ${formatElapsed(Date.now() - this.startTime, true)})`, 'high');
|
|
339
317
|
this.addEvent('error', `Workflow abandoned: ${reason}`, 'high');
|
|
340
|
-
this.trajectory
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
318
|
+
this.trajectory = {
|
|
319
|
+
...abandonTrajectory(this.trajectory),
|
|
320
|
+
retrospective: {
|
|
321
|
+
summary,
|
|
322
|
+
approach: this.buildApproach(),
|
|
323
|
+
confidence: meta?.confidence ?? 0,
|
|
324
|
+
learnings: meta?.learnings,
|
|
325
|
+
challenges: meta?.challenges,
|
|
326
|
+
},
|
|
348
327
|
};
|
|
349
|
-
this.closeCurrentChapter();
|
|
350
328
|
await this.flush();
|
|
351
|
-
await this.moveToCompleted();
|
|
352
329
|
}
|
|
353
|
-
// ── Getters ────────────────────────────────────────────────────────────────
|
|
354
330
|
isEnabled() {
|
|
355
331
|
return this.enabled;
|
|
356
332
|
}
|
|
@@ -363,129 +339,33 @@ export class WorkflowTrajectory {
|
|
|
363
339
|
getTrajectoryId() {
|
|
364
340
|
return this.trajectory?.id ?? null;
|
|
365
341
|
}
|
|
366
|
-
// ── Synthesis helpers ──────────────────────────────────────────────────────
|
|
367
342
|
buildSynthesis(label, outcomes, unblocks) {
|
|
368
|
-
|
|
369
|
-
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
370
|
-
const retried = outcomes.filter((o) => o.attempts > 1 && o.status !== 'failed');
|
|
371
|
-
const parts = [`${label} resolved.`, `${completed.length}/${outcomes.length} steps completed.`];
|
|
372
|
-
if (failed.length > 0) {
|
|
373
|
-
parts.push(`${failed.length} step(s) failed: ${failed.map((s) => s.name).join(', ')}.`);
|
|
374
|
-
}
|
|
375
|
-
if (retried.length > 0) {
|
|
376
|
-
parts.push(`${retried.length} step(s) required retries: ${retried.map((s) => s.name).join(', ')}.`);
|
|
377
|
-
}
|
|
378
|
-
else if (failed.length === 0) {
|
|
379
|
-
parts.push('All steps completed on first attempt.');
|
|
380
|
-
}
|
|
381
|
-
if (unblocks?.length) {
|
|
382
|
-
parts.push(`Unblocking: ${unblocks.join(', ')}.`);
|
|
383
|
-
}
|
|
384
|
-
return parts.join(' ');
|
|
343
|
+
return buildSynthesis(label, outcomes, unblocks);
|
|
385
344
|
}
|
|
386
345
|
computeConfidence(outcomes) {
|
|
387
|
-
|
|
388
|
-
return 0.7;
|
|
389
|
-
const total = outcomes.length;
|
|
390
|
-
const completed = outcomes.filter((o) => o.status === 'completed').length;
|
|
391
|
-
const firstAttempt = outcomes.filter((o) => o.attempts === 1 && o.status === 'completed').length;
|
|
392
|
-
const verified = outcomes.filter((o) => o.verificationPassed).length;
|
|
393
|
-
// Base: 0.5 scaled by completion rate, +0.25 for first-attempt, +0.25 for verified
|
|
394
|
-
const completionRate = completed / total;
|
|
395
|
-
return Math.min(1.0, 0.5 * completionRate + (firstAttempt / total) * 0.25 + (verified / total) * 0.25);
|
|
346
|
+
return computeConfidence(outcomes);
|
|
396
347
|
}
|
|
397
348
|
buildRunSummary(outcomes) {
|
|
398
|
-
|
|
399
|
-
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
400
|
-
const skipped = outcomes.filter((o) => o.status === 'skipped');
|
|
401
|
-
const elapsed = Date.now() - this.startTime;
|
|
402
|
-
const elapsedStr = elapsed > 60_000 ? `${Math.round(elapsed / 60_000)}min` : `${Math.round(elapsed / 1_000)}s`;
|
|
403
|
-
if (failed.length === 0) {
|
|
404
|
-
const retried = completed.filter((o) => o.attempts > 1);
|
|
405
|
-
const base = `All ${completed.length} steps completed in ${elapsedStr}.`;
|
|
406
|
-
return retried.length > 0
|
|
407
|
-
? `${base} ${retried.length} step(s) needed retries: ${retried.map((o) => o.name).join(', ')}.`
|
|
408
|
-
: base;
|
|
409
|
-
}
|
|
410
|
-
// Failure narrative — focus on root cause of the first failure
|
|
411
|
-
const firstFailure = failed[0];
|
|
412
|
-
const cause = classifyFailure(firstFailure.error ?? '');
|
|
413
|
-
const cascaded = skipped.length > 0
|
|
414
|
-
? ` Caused ${skipped.length} downstream step(s) to be skipped: ${skipped.map((o) => o.name).join(', ')}.`
|
|
415
|
-
: '';
|
|
416
|
-
return (`Failed at "${firstFailure.name}" [${cause}] after ${elapsedStr}.${cascaded} ` +
|
|
417
|
-
`${completed.length}/${outcomes.length} steps completed before failure.`);
|
|
349
|
+
return buildRunSummary(outcomes, this.startTime);
|
|
418
350
|
}
|
|
419
351
|
extractLearnings(outcomes) {
|
|
420
|
-
|
|
421
|
-
const timeouts = outcomes.filter((o) => o.status === 'failed' && classifyFailure(o.error ?? '') === 'timeout');
|
|
422
|
-
if (timeouts.some((o) => o.nonInteractive)) {
|
|
423
|
-
learnings.push(`Non-interactive agent timeouts detected (${timeouts.map((o) => o.name).join(', ')}). ` +
|
|
424
|
-
`Use deterministic steps to pre-read files and inject content — non-interactive agents should not discover information via tools.`);
|
|
425
|
-
}
|
|
426
|
-
const verifyFails = outcomes.filter((o) => o.status === 'failed' && classifyFailure(o.error ?? '') === 'verification_mismatch');
|
|
427
|
-
if (verifyFails.length > 0) {
|
|
428
|
-
learnings.push(`Verification mismatch on: ${verifyFails.map((o) => `"${o.name}" (expected "${o.verificationValue ?? '?'}")`).join(', ')}. ` +
|
|
429
|
-
`Make the required output format more explicit in the task prompt.`);
|
|
430
|
-
}
|
|
431
|
-
const retried = outcomes.filter((o) => o.attempts > 1 && o.status === 'completed');
|
|
432
|
-
if (retried.length > 0) {
|
|
433
|
-
learnings.push(`${retried.map((o) => `"${o.name}" (${o.attempts} attempts)`).join(', ')} succeeded after retries — ` +
|
|
434
|
-
`consider adding clearer output instructions to reduce retries.`);
|
|
435
|
-
}
|
|
436
|
-
return learnings;
|
|
352
|
+
return extractLearnings(outcomes);
|
|
437
353
|
}
|
|
438
354
|
extractChallenges(outcomes) {
|
|
439
|
-
|
|
440
|
-
const failed = outcomes.filter((o) => o.status === 'failed');
|
|
441
|
-
for (const step of failed) {
|
|
442
|
-
const cause = classifyFailure(step.error ?? '');
|
|
443
|
-
challenges.push(diagnosisFor(cause, step));
|
|
444
|
-
}
|
|
445
|
-
return challenges;
|
|
355
|
+
return extractChallenges(outcomes);
|
|
446
356
|
}
|
|
447
|
-
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
448
357
|
openChapter(title, agentName) {
|
|
449
358
|
if (!this.trajectory)
|
|
450
359
|
return;
|
|
451
|
-
|
|
452
|
-
id: `ch_${randomBytes(4).toString('hex')}`,
|
|
453
|
-
title,
|
|
454
|
-
agentName,
|
|
455
|
-
startedAt: new Date().toISOString(),
|
|
456
|
-
events: [],
|
|
457
|
-
};
|
|
458
|
-
this.trajectory.chapters.push(chapter);
|
|
459
|
-
this.currentChapterId = chapter.id;
|
|
460
|
-
}
|
|
461
|
-
closeCurrentChapter() {
|
|
462
|
-
if (!this.trajectory || !this.currentChapterId)
|
|
463
|
-
return;
|
|
464
|
-
const chapter = this.trajectory.chapters.find((c) => c.id === this.currentChapterId);
|
|
465
|
-
if (chapter && !chapter.endedAt) {
|
|
466
|
-
chapter.endedAt = new Date().toISOString();
|
|
467
|
-
}
|
|
468
|
-
this.currentChapterId = null;
|
|
360
|
+
this.trajectory = appendChapter(this.trajectory, { title, agentName });
|
|
469
361
|
}
|
|
470
362
|
addEvent(type, content, significance, raw) {
|
|
471
363
|
if (!this.trajectory)
|
|
472
364
|
return;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
chapter = this.trajectory.chapters[this.trajectory.chapters.length - 1];
|
|
478
|
-
}
|
|
479
|
-
const event = {
|
|
480
|
-
ts: Date.now(),
|
|
481
|
-
type,
|
|
482
|
-
content,
|
|
483
|
-
};
|
|
484
|
-
if (significance)
|
|
485
|
-
event.significance = significance;
|
|
486
|
-
if (raw)
|
|
487
|
-
event.raw = raw;
|
|
488
|
-
chapter.events.push(event);
|
|
365
|
+
this.trajectory = appendEvent(this.trajectory, { type, content, significance, raw });
|
|
366
|
+
}
|
|
367
|
+
buildApproach() {
|
|
368
|
+
return `${this.swarmPattern} workflow (${this.trajectory?.agents.filter((a) => a.role !== 'workflow-runner').length ?? 0} agents)`;
|
|
489
369
|
}
|
|
490
370
|
formatCompletionEvidenceSummary(evidence) {
|
|
491
371
|
if (!evidence)
|
|
@@ -503,34 +383,20 @@ export class WorkflowTrajectory {
|
|
|
503
383
|
parts.push(`exit=${evidence.exitCode}`);
|
|
504
384
|
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
505
385
|
}
|
|
506
|
-
async
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const activeDir = path.join(this.dataDir, 'active');
|
|
511
|
-
await mkdir(activeDir, { recursive: true });
|
|
512
|
-
const filePath = path.join(activeDir, `${this.trajectory.id}.json`);
|
|
513
|
-
await writeFile(filePath, JSON.stringify(this.trajectory, null, 2), 'utf-8');
|
|
514
|
-
}
|
|
515
|
-
catch {
|
|
516
|
-
// Non-blocking: trajectory recording failure should never break the workflow
|
|
517
|
-
}
|
|
386
|
+
async ensureStorage() {
|
|
387
|
+
this.storage ??= new FileStorage(this.storageBaseDir);
|
|
388
|
+
this.storageInit ??= this.storage.initialize();
|
|
389
|
+
await this.storageInit;
|
|
518
390
|
}
|
|
519
|
-
async
|
|
391
|
+
async flush() {
|
|
520
392
|
if (!this.trajectory)
|
|
521
393
|
return;
|
|
522
394
|
try {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
await mkdir(completedDir, { recursive: true });
|
|
526
|
-
const activePath = path.join(activeDir, `${this.trajectory.id}.json`);
|
|
527
|
-
const completedPath = path.join(completedDir, `${this.trajectory.id}.json`);
|
|
528
|
-
if (existsSync(activePath)) {
|
|
529
|
-
await rename(activePath, completedPath);
|
|
530
|
-
}
|
|
395
|
+
await this.ensureStorage();
|
|
396
|
+
await this.storage?.save(this.trajectory);
|
|
531
397
|
}
|
|
532
398
|
catch {
|
|
533
|
-
//
|
|
399
|
+
// non-blocking: flush failures must never break the workflow
|
|
534
400
|
}
|
|
535
401
|
}
|
|
536
402
|
}
|