@pixelbyte-software/pixcode 1.41.5 → 1.42.0
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/assets/{index-BmK4VcVP.js → index-CkOamyD3.js} +4 -4
- package/dist/index.html +1 -1
- package/dist-server/server/modules/orchestration/workflows/handoff-artifact.js +123 -0
- package/dist-server/server/modules/orchestration/workflows/handoff-artifact.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +68 -18
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-trace.js +4 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-trace.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -0
- package/server/modules/orchestration/a2a/types.ts +1 -0
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +78 -18
- package/server/modules/orchestration/workflows/workflow-trace.ts +2 -0
- package/server/modules/orchestration/workflows/workflow.types.ts +3 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const root = process.cwd();
|
|
8
|
+
|
|
9
|
+
function read(relativePath) {
|
|
10
|
+
return fs.readFileSync(path.join(root, relativePath), 'utf8');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const handoffSource = read('server/modules/orchestration/workflows/handoff-artifact.ts');
|
|
14
|
+
assert.match(handoffSource, /PIXCODE_HANDOFF_PROTOCOL/, 'Handoff artifacts should declare a stable protocol id.');
|
|
15
|
+
assert.match(handoffSource, /pixcode\.handoff\.v1/, 'Handoff artifacts should use the v1 protocol id.');
|
|
16
|
+
assert.match(handoffSource, /taskStatus/, 'Handoff schema should require task status.');
|
|
17
|
+
assert.match(handoffSource, /contextSummary/, 'Handoff schema should require compacted context summary.');
|
|
18
|
+
assert.match(handoffSource, /changedFiles/, 'Handoff schema should require changed files.');
|
|
19
|
+
assert.match(handoffSource, /blockers/, 'Handoff schema should require blockers.');
|
|
20
|
+
assert.match(handoffSource, /nextAction/, 'Handoff schema should require the requested next action.');
|
|
21
|
+
assert.match(handoffSource, /parseHandoffArtifact/, 'Handoff artifacts should have parser/validation logic.');
|
|
22
|
+
assert.match(handoffSource, /formatHandoffArtifactForContext/, 'Handoff artifacts should be formatted for downstream agent context.');
|
|
23
|
+
assert.match(handoffSource, /handoffArtifactToWorkflowArtifact/, 'Handoff artifacts should persist as workflow artifacts.');
|
|
24
|
+
|
|
25
|
+
const runnerSource = read('server/modules/orchestration/workflows/workflow-runner.ts');
|
|
26
|
+
assert.match(runnerSource, /requiresHandoffArtifact/, 'Workflow runner should detect handoff nodes that require artifacts.');
|
|
27
|
+
assert.match(runnerSource, /parseHandoffArtifact/, 'Workflow runner should validate handoff node output.');
|
|
28
|
+
assert.match(runnerSource, /handoffArtifactToWorkflowArtifact/, 'Workflow runner should persist structured handoff artifacts.');
|
|
29
|
+
assert.match(runnerSource, /formatHandoffArtifactForContext/, 'Workflow runner should pass structured handoff context downstream.');
|
|
30
|
+
assert.match(runnerSource, /Invalid handoff artifact/, 'Invalid handoff artifacts should fail visibly.');
|
|
31
|
+
assert.match(runnerSource, /"protocol": "pixcode\.handoff\.v1"/, 'Handoff prompts should request the protocol JSON shape.');
|
|
32
|
+
|
|
33
|
+
const workflowTypes = read('server/modules/orchestration/workflows/workflow.types.ts');
|
|
34
|
+
assert.match(workflowTypes, /WorkflowHandoffArtifact/, 'Workflow run types should expose handoff artifacts.');
|
|
35
|
+
assert.match(workflowTypes, /handoffArtifact\?: WorkflowHandoffArtifact/, 'Node runs should persist the validated handoff artifact.');
|
|
36
|
+
|
|
37
|
+
const traceSource = read('server/modules/orchestration/workflows/workflow-trace.ts');
|
|
38
|
+
assert.match(traceSource, /handoff-artifact/, 'Trace timeline should identify handoff artifacts.');
|
|
39
|
+
assert.match(traceSource, /workflow\.trace\.handoffArtifact/, 'Trace timeline should label handoff artifacts.');
|
|
40
|
+
|
|
41
|
+
const nodeStream = read('src/components/orchestration/workflows/WorkflowNodeStream.tsx');
|
|
42
|
+
assert.match(nodeStream, /handoff-artifact/, 'Workflow UI should render handoff artifacts.');
|
|
43
|
+
assert.match(nodeStream, /orchestration\.artifact\.handoff/, 'Workflow UI should label handoff artifacts.');
|
|
44
|
+
|
|
45
|
+
const en = read('src/i18n/locales/en/common.json');
|
|
46
|
+
const tr = read('src/i18n/locales/tr/common.json');
|
|
47
|
+
assert.match(en, /"handoff": "Handoff artifact"/, 'English UI should label handoff artifacts.');
|
|
48
|
+
assert.match(tr, /"handoff": "Handoff artifact"/, 'Turkish UI should label handoff artifacts.');
|
|
49
|
+
|
|
50
|
+
console.log('handoff artifact protocol smoke passed');
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export const PIXCODE_HANDOFF_PROTOCOL = 'pixcode.handoff.v1' as const;
|
|
2
|
+
|
|
3
|
+
export type WorkflowHandoffTaskStatus =
|
|
4
|
+
| 'ready'
|
|
5
|
+
| 'completed'
|
|
6
|
+
| 'blocked'
|
|
7
|
+
| 'failed'
|
|
8
|
+
| 'needs-review';
|
|
9
|
+
|
|
10
|
+
export interface WorkflowHandoffArtifact {
|
|
11
|
+
protocol: typeof PIXCODE_HANDOFF_PROTOCOL;
|
|
12
|
+
taskStatus: WorkflowHandoffTaskStatus;
|
|
13
|
+
contextSummary: string;
|
|
14
|
+
taskResult: string;
|
|
15
|
+
changedFiles: string[];
|
|
16
|
+
blockers: string[];
|
|
17
|
+
risks: string[];
|
|
18
|
+
nextAction: string;
|
|
19
|
+
nextInstructions: string;
|
|
20
|
+
producedBy?: {
|
|
21
|
+
workflowRunId?: string;
|
|
22
|
+
nodeId?: string;
|
|
23
|
+
agentLabel?: string;
|
|
24
|
+
stage?: string;
|
|
25
|
+
};
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type HandoffArtifactParseResult =
|
|
30
|
+
| { ok: true; artifact: WorkflowHandoffArtifact }
|
|
31
|
+
| { ok: false; error: string };
|
|
32
|
+
|
|
33
|
+
type HandoffArtifactMetadata = {
|
|
34
|
+
workflowRunId?: string;
|
|
35
|
+
nodeId?: string;
|
|
36
|
+
agentLabel?: string;
|
|
37
|
+
stage?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const VALID_TASK_STATUSES = new Set<WorkflowHandoffTaskStatus>([
|
|
41
|
+
'ready',
|
|
42
|
+
'completed',
|
|
43
|
+
'blocked',
|
|
44
|
+
'failed',
|
|
45
|
+
'needs-review',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
function extractJsonCandidate(text: string): string | null {
|
|
49
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]?.trim();
|
|
50
|
+
if (fenced) return fenced;
|
|
51
|
+
|
|
52
|
+
const start = text.indexOf('{');
|
|
53
|
+
const end = text.lastIndexOf('}');
|
|
54
|
+
if (start === -1 || end === -1 || end <= start) return null;
|
|
55
|
+
return text.slice(start, end + 1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readRequiredString(record: Record<string, unknown>, key: string): string | null {
|
|
59
|
+
const value = record[key];
|
|
60
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
61
|
+
return value.trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sanitizeChangedFile(filePath: string): string {
|
|
65
|
+
const normalized = filePath.trim().replaceAll('\\', '/');
|
|
66
|
+
if (!normalized) return '';
|
|
67
|
+
if (!normalized.startsWith('/') && !/^[a-zA-Z]:\//.test(normalized)) return normalized;
|
|
68
|
+
return normalized.split('/').filter(Boolean).slice(-4).join('/');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readStringArray(record: Record<string, unknown>, key: string): string[] | null {
|
|
72
|
+
const value = record[key];
|
|
73
|
+
if (!Array.isArray(value)) return null;
|
|
74
|
+
return value
|
|
75
|
+
.filter((item): item is string => typeof item === 'string')
|
|
76
|
+
.map((item) => key === 'changedFiles' ? sanitizeChangedFile(item) : item.trim())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.slice(0, 40);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function parseHandoffArtifact(
|
|
82
|
+
text: string,
|
|
83
|
+
metadata: HandoffArtifactMetadata = {},
|
|
84
|
+
): HandoffArtifactParseResult {
|
|
85
|
+
const candidate = extractJsonCandidate(text);
|
|
86
|
+
if (!candidate) {
|
|
87
|
+
return { ok: false, error: 'Invalid handoff artifact: expected one JSON object.' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let parsed: unknown;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(candidate);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: `Invalid handoff artifact: JSON parse failed (${error instanceof Error ? error.message : String(error)}).`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
101
|
+
return { ok: false, error: 'Invalid handoff artifact: payload must be an object.' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const record = parsed as Record<string, unknown>;
|
|
105
|
+
if (record.protocol !== PIXCODE_HANDOFF_PROTOCOL) {
|
|
106
|
+
return { ok: false, error: `Invalid handoff artifact: protocol must be ${PIXCODE_HANDOFF_PROTOCOL}.` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const taskStatus = record.taskStatus;
|
|
110
|
+
if (typeof taskStatus !== 'string' || !VALID_TASK_STATUSES.has(taskStatus as WorkflowHandoffTaskStatus)) {
|
|
111
|
+
return { ok: false, error: 'Invalid handoff artifact: taskStatus is missing or unsupported.' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const contextSummary = readRequiredString(record, 'contextSummary');
|
|
115
|
+
const taskResult = readRequiredString(record, 'taskResult');
|
|
116
|
+
const changedFiles = readStringArray(record, 'changedFiles');
|
|
117
|
+
const blockers = readStringArray(record, 'blockers');
|
|
118
|
+
const risks = readStringArray(record, 'risks');
|
|
119
|
+
const nextAction = readRequiredString(record, 'nextAction');
|
|
120
|
+
const nextInstructions = readRequiredString(record, 'nextInstructions');
|
|
121
|
+
|
|
122
|
+
if (!contextSummary || !taskResult || !changedFiles || !blockers || !risks || !nextAction || !nextInstructions) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: 'Invalid handoff artifact: required fields are protocol, taskStatus, contextSummary, taskResult, changedFiles, blockers, risks, nextAction, and nextInstructions.',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
artifact: {
|
|
132
|
+
protocol: PIXCODE_HANDOFF_PROTOCOL,
|
|
133
|
+
taskStatus: taskStatus as WorkflowHandoffTaskStatus,
|
|
134
|
+
contextSummary,
|
|
135
|
+
taskResult,
|
|
136
|
+
changedFiles,
|
|
137
|
+
blockers,
|
|
138
|
+
risks,
|
|
139
|
+
nextAction,
|
|
140
|
+
nextInstructions,
|
|
141
|
+
producedBy: {
|
|
142
|
+
workflowRunId: metadata.workflowRunId,
|
|
143
|
+
nodeId: metadata.nodeId,
|
|
144
|
+
agentLabel: metadata.agentLabel,
|
|
145
|
+
stage: metadata.stage,
|
|
146
|
+
},
|
|
147
|
+
createdAt: new Date().toISOString(),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function formatHandoffArtifactForContext(artifact: WorkflowHandoffArtifact): string {
|
|
153
|
+
return [
|
|
154
|
+
`Pixcode handoff artifact (${PIXCODE_HANDOFF_PROTOCOL})`,
|
|
155
|
+
JSON.stringify(artifact, null, 2),
|
|
156
|
+
].join('\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function handoffArtifactToWorkflowArtifact(artifact: WorkflowHandoffArtifact): {
|
|
160
|
+
type: 'handoff-artifact';
|
|
161
|
+
data: Record<string, unknown>;
|
|
162
|
+
metadata: Record<string, unknown>;
|
|
163
|
+
} {
|
|
164
|
+
return {
|
|
165
|
+
type: 'handoff-artifact',
|
|
166
|
+
data: artifact as unknown as Record<string, unknown>,
|
|
167
|
+
metadata: {
|
|
168
|
+
protocol: artifact.protocol,
|
|
169
|
+
taskStatus: artifact.taskStatus,
|
|
170
|
+
changedFileCount: artifact.changedFiles.length,
|
|
171
|
+
blockerCount: artifact.blockers.length,
|
|
172
|
+
nextAction: artifact.nextAction,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -6,6 +6,12 @@ import type {
|
|
|
6
6
|
WorkflowNodeRun,
|
|
7
7
|
WorkflowRun,
|
|
8
8
|
} from '@/modules/orchestration/workflows/workflow.types.js';
|
|
9
|
+
import {
|
|
10
|
+
PIXCODE_HANDOFF_PROTOCOL,
|
|
11
|
+
formatHandoffArtifactForContext,
|
|
12
|
+
handoffArtifactToWorkflowArtifact,
|
|
13
|
+
parseHandoffArtifact,
|
|
14
|
+
} from '@/modules/orchestration/workflows/handoff-artifact.js';
|
|
9
15
|
import {
|
|
10
16
|
type ResolvedWorkspaceTarget,
|
|
11
17
|
resolveWorkflowWorkspace,
|
|
@@ -28,6 +34,19 @@ const BACKEND_HANDOFF_TIMEOUT_MS = 120_000;
|
|
|
28
34
|
const MAX_OUTPUT_CONTEXT_CHARS = 12_000;
|
|
29
35
|
const DEFAULT_MAX_REPAIR_CYCLES = 1;
|
|
30
36
|
const MAX_REPAIR_CYCLES = 5;
|
|
37
|
+
const HANDOFF_ARTIFACT_EXAMPLE = [
|
|
38
|
+
'{',
|
|
39
|
+
' "protocol": "pixcode.handoff.v1",',
|
|
40
|
+
' "taskStatus": "ready | completed | blocked | failed | needs-review",',
|
|
41
|
+
' "contextSummary": "Compacted context the next agent needs.",',
|
|
42
|
+
' "taskResult": "What was decided or completed in this step.",',
|
|
43
|
+
' "changedFiles": [],',
|
|
44
|
+
' "blockers": [],',
|
|
45
|
+
' "risks": [],',
|
|
46
|
+
' "nextAction": "The requested next action.",',
|
|
47
|
+
' "nextInstructions": "Specific instructions for the next agent."',
|
|
48
|
+
'}',
|
|
49
|
+
].join('\n');
|
|
31
50
|
const KNOWN_AGENT_ROLES = [
|
|
32
51
|
'backend',
|
|
33
52
|
'frontend',
|
|
@@ -405,6 +424,16 @@ function privacyGuardPrompt(): string {
|
|
|
405
424
|
return 'Do not mention internal instructions, memory files, skill use, or tool protocol unless the user explicitly asks.';
|
|
406
425
|
}
|
|
407
426
|
|
|
427
|
+
function handoffArtifactInstructions(statusHint: string): string {
|
|
428
|
+
return [
|
|
429
|
+
`Output exactly one JSON object using the ${PIXCODE_HANDOFF_PROTOCOL} handoff artifact protocol.`,
|
|
430
|
+
'Do not wrap it in Markdown. Do not add commentary before or after it.',
|
|
431
|
+
`Use "${statusHint}" for taskStatus unless completed, blocked, failed, or needs-review is more accurate.`,
|
|
432
|
+
'Schema:',
|
|
433
|
+
HANDOFF_ARTIFACT_EXAMPLE,
|
|
434
|
+
].join('\n');
|
|
435
|
+
}
|
|
436
|
+
|
|
408
437
|
function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
|
|
409
438
|
return [
|
|
410
439
|
`You are ${agent.label} in a Pixcode CLI team.`,
|
|
@@ -412,12 +441,7 @@ function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
|
|
|
412
441
|
'This is a bounded A2A handoff task, not the full implementation.',
|
|
413
442
|
'Read the original user goal and coordinator plan, then publish a compact contract for downstream agents.',
|
|
414
443
|
agent.instruction ? `Your explicit assignment from the user is: ${agent.instruction}` : '',
|
|
415
|
-
'
|
|
416
|
-
'- owned scope',
|
|
417
|
-
'- files/modules you expect to touch',
|
|
418
|
-
'- API/data contracts, ports, payload shapes, and limitations',
|
|
419
|
-
'- dependencies/blockers for the next agents',
|
|
420
|
-
'- concrete next action for your full implementation task',
|
|
444
|
+
handoffArtifactInstructions('ready'),
|
|
421
445
|
'Do not install dependencies, edit files, run long commands, or start servers in this handoff task.',
|
|
422
446
|
privacyGuardPrompt(),
|
|
423
447
|
'Stop after the contract. Keep it concise and respond in the same language as the user request.',
|
|
@@ -431,11 +455,7 @@ function handoffInitPrompt(agent: AgentAssignment, index: number): string {
|
|
|
431
455
|
'Create a compact init packet for the next visible work step.',
|
|
432
456
|
'Use the original user goal and any prior compact handoff packet included above.',
|
|
433
457
|
agent.instruction ? `The explicit assignment for this agent is: ${agent.instruction}` : '',
|
|
434
|
-
'
|
|
435
|
-
'- user goal in one sentence',
|
|
436
|
-
'- prior agent handoff summary, if present',
|
|
437
|
-
'- this agent responsibility',
|
|
438
|
-
'- exact constraints and blockers this agent must respect',
|
|
458
|
+
handoffArtifactInstructions('ready'),
|
|
439
459
|
privacyGuardPrompt(),
|
|
440
460
|
'Do not perform the task yet. Do not mention that this is hidden from the user.',
|
|
441
461
|
'Respond in the same language as the user request.',
|
|
@@ -462,12 +482,7 @@ function handoffCompactPrompt(agent: AgentAssignment, index: number): string {
|
|
|
462
482
|
`You are compacting ${agent.label}'s strict handoff output for the next Pixcode agent.`,
|
|
463
483
|
`This is internal compact step ${index + 1}.`,
|
|
464
484
|
'Read the prior visible work output included above and create a compact handoff packet.',
|
|
465
|
-
'
|
|
466
|
-
'- Ben ne yaptım / What I did',
|
|
467
|
-
'- Dokunduğum alanlar / Touched areas',
|
|
468
|
-
'- Kanıt, komut veya çıktı / Evidence, commands, outputs',
|
|
469
|
-
'- Sonraki ajan şunu bilsin / What the next agent must know',
|
|
470
|
-
'- Bloker veya risk / Blockers or risks',
|
|
485
|
+
handoffArtifactInstructions('completed'),
|
|
471
486
|
privacyGuardPrompt(),
|
|
472
487
|
'Do not include raw logs unless they are essential. Keep it concise and actionable.',
|
|
473
488
|
'Respond in the same language as the user request.',
|
|
@@ -487,6 +502,18 @@ function compactOutputForContext(text: string): string {
|
|
|
487
502
|
].join('');
|
|
488
503
|
}
|
|
489
504
|
|
|
505
|
+
function requiresHandoffArtifact(node: WorkflowNode): boolean {
|
|
506
|
+
return node.stage === 'handoff' || node.stage === 'handoff_init' || node.stage === 'handoff_compact';
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function handoffArtifactSource(result: TaskResult): string {
|
|
510
|
+
const structured = result.artifacts.find((artifact) => artifact.type === 'handoff-artifact' && artifact.data);
|
|
511
|
+
if (structured?.data) {
|
|
512
|
+
return JSON.stringify(structured.data);
|
|
513
|
+
}
|
|
514
|
+
return result.text;
|
|
515
|
+
}
|
|
516
|
+
|
|
490
517
|
function isExternalDirectoryPermissionError(value: unknown): boolean {
|
|
491
518
|
const text = String(value ?? '').toLocaleLowerCase('en');
|
|
492
519
|
return (
|
|
@@ -1688,7 +1715,40 @@ class WorkflowRunner {
|
|
|
1688
1715
|
throw new WorkflowCanceledError();
|
|
1689
1716
|
}
|
|
1690
1717
|
if (result.state === 'completed') {
|
|
1691
|
-
|
|
1718
|
+
let outputForContext = result.text;
|
|
1719
|
+
if (requiresHandoffArtifact(node)) {
|
|
1720
|
+
const handoffParse = parseHandoffArtifact(handoffArtifactSource(result), {
|
|
1721
|
+
workflowRunId: run.id,
|
|
1722
|
+
nodeId: node.id,
|
|
1723
|
+
agentLabel: node.agentLabel,
|
|
1724
|
+
stage: node.stage,
|
|
1725
|
+
});
|
|
1726
|
+
if (!handoffParse.ok) {
|
|
1727
|
+
const visibleHandoffError = handoffParse.error.startsWith('Invalid handoff artifact')
|
|
1728
|
+
? handoffParse.error
|
|
1729
|
+
: `Invalid handoff artifact: ${handoffParse.error}`;
|
|
1730
|
+
nodeRun.status = 'failed';
|
|
1731
|
+
nodeRun.error = visibleHandoffError;
|
|
1732
|
+
workflowStore.setRun(run);
|
|
1733
|
+
if (await this.runFallbackAfterFailure(node, workflow, run, outputs, started, completed, visibleHandoffError)) {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (node.onFail === 'continue') {
|
|
1737
|
+
completed.add(node.id);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
throw new Error(visibleHandoffError);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
nodeRun.handoffArtifact = handoffParse.artifact;
|
|
1744
|
+
nodeRun.artifacts = [
|
|
1745
|
+
...(nodeRun.artifacts ?? []).filter((artifact) => artifact.type !== 'handoff-artifact'),
|
|
1746
|
+
handoffArtifactToWorkflowArtifact(handoffParse.artifact),
|
|
1747
|
+
];
|
|
1748
|
+
outputForContext = formatHandoffArtifactForContext(handoffParse.artifact);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
outputs.set(node.id, compactOutputForContext(outputForContext));
|
|
1692
1752
|
completed.add(node.id);
|
|
1693
1753
|
nodeRun.status = 'completed';
|
|
1694
1754
|
workflowStore.setRun(run);
|
|
@@ -87,6 +87,7 @@ function artifactTitleKey(type: string): string {
|
|
|
87
87
|
if (type === 'file-diff') return 'workflow.trace.fileChanged';
|
|
88
88
|
if (type === 'preview-url') return 'workflow.trace.previewReady';
|
|
89
89
|
if (type === 'command-output') return 'workflow.trace.commandOutput';
|
|
90
|
+
if (type === 'handoff-artifact') return 'workflow.trace.handoffArtifact';
|
|
90
91
|
return 'workflow.trace.artifact';
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -98,6 +99,7 @@ function artifactTitle(type: string): string {
|
|
|
98
99
|
if (type === 'file-diff') return 'File changes captured';
|
|
99
100
|
if (type === 'preview-url') return 'Preview output captured';
|
|
100
101
|
if (type === 'command-output') return 'Command output captured';
|
|
102
|
+
if (type === 'handoff-artifact') return 'Handoff artifact captured';
|
|
101
103
|
return 'Artifact captured';
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { WorkflowHandoffArtifact } from '@/modules/orchestration/workflows/handoff-artifact.js';
|
|
2
|
+
|
|
1
3
|
export type WorkflowRunStatus = 'queued' | 'running' | 'completed' | 'failed' | 'canceled';
|
|
2
4
|
export type WorkflowNodeStatus = WorkflowRunStatus | 'skipped';
|
|
3
5
|
|
|
@@ -47,6 +49,7 @@ export interface WorkflowNodeRun {
|
|
|
47
49
|
finishedAt?: number;
|
|
48
50
|
error?: string;
|
|
49
51
|
outputText?: string;
|
|
52
|
+
handoffArtifact?: WorkflowHandoffArtifact;
|
|
50
53
|
messages?: Array<{
|
|
51
54
|
role: string;
|
|
52
55
|
text: string;
|