@pixelbyte-software/pixcode 1.41.5 → 1.42.1

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.
@@ -0,0 +1,186 @@
1
+ import type { ResolvedWorkspaceTarget } from '@/modules/orchestration/workflows/workspace-target.js';
2
+
3
+ export const PIXCODE_CONTEXT_PROTOCOL = 'pixcode.context.v1' as const;
4
+ export const MAX_CONTEXT_PACKET_TEXT_CHARS = 16_000;
5
+
6
+ export interface WorkflowContextPacket {
7
+ protocol: typeof PIXCODE_CONTEXT_PROTOCOL;
8
+ originalUserRequest: string;
9
+ project: {
10
+ kind: string;
11
+ label: string;
12
+ selectedProjectName?: string;
13
+ };
14
+ task: {
15
+ workflowId: string;
16
+ workflowRunId: string;
17
+ nodeId: string;
18
+ stage?: string;
19
+ assignment?: string;
20
+ stepInstructions: string;
21
+ };
22
+ constraints: {
23
+ adapterId?: string;
24
+ agentLabel?: string;
25
+ model?: string;
26
+ permissionMode?: string;
27
+ isolation?: string;
28
+ toolsSettings?: Record<string, unknown>;
29
+ };
30
+ upstreamArtifacts: Array<{
31
+ type: 'upstream-context';
32
+ text: string;
33
+ sourceNodeIds: string[];
34
+ }>;
35
+ runState: {
36
+ status: string;
37
+ startedAt: number;
38
+ nodeCount: number;
39
+ completedNodeIds: string[];
40
+ runningNodeIds: string[];
41
+ };
42
+ compaction: {
43
+ maxChars: number;
44
+ originalChars: number;
45
+ compactedChars: number;
46
+ omittedChars: number;
47
+ wasCompacted: boolean;
48
+ };
49
+ createdAt: string;
50
+ }
51
+
52
+ type ContextPacketRun = {
53
+ id: string;
54
+ workflowId: string;
55
+ status: string;
56
+ input?: string;
57
+ startedAt: number;
58
+ nodeRuns: Array<{
59
+ nodeId: string;
60
+ status: string;
61
+ }>;
62
+ };
63
+
64
+ type ContextPacketNode = {
65
+ id: string;
66
+ adapterId?: string;
67
+ agentLabel?: string;
68
+ assignment?: string;
69
+ prompt: string;
70
+ stage?: string;
71
+ model?: string;
72
+ permissionMode?: string;
73
+ toolsSettings?: Record<string, unknown>;
74
+ isolation?: string;
75
+ };
76
+
77
+ export type BuildWorkflowContextPacketInput = {
78
+ run: ContextPacketRun;
79
+ node: ContextPacketNode;
80
+ workspaceTarget: ResolvedWorkspaceTarget;
81
+ inputContext: string;
82
+ inputNodeIds: string[];
83
+ };
84
+
85
+ function compactContextText(text: string): {
86
+ text: string;
87
+ originalChars: number;
88
+ compactedChars: number;
89
+ omittedChars: number;
90
+ wasCompacted: boolean;
91
+ } {
92
+ const originalChars = text.length;
93
+ if (originalChars <= MAX_CONTEXT_PACKET_TEXT_CHARS) {
94
+ return {
95
+ text,
96
+ originalChars,
97
+ compactedChars: originalChars,
98
+ omittedChars: 0,
99
+ wasCompacted: false,
100
+ };
101
+ }
102
+
103
+ const edge = Math.floor(MAX_CONTEXT_PACKET_TEXT_CHARS / 2);
104
+ const omittedChars = originalChars - MAX_CONTEXT_PACKET_TEXT_CHARS;
105
+ const compacted = [
106
+ text.slice(0, edge),
107
+ `\n\n[...${omittedChars} characters omitted from upstream context packet...]\n\n`,
108
+ text.slice(-edge),
109
+ ].join('');
110
+
111
+ return {
112
+ text: compacted,
113
+ originalChars,
114
+ compactedChars: compacted.length,
115
+ omittedChars,
116
+ wasCompacted: true,
117
+ };
118
+ }
119
+
120
+ export function buildWorkflowContextPacket({
121
+ run,
122
+ node,
123
+ workspaceTarget,
124
+ inputContext,
125
+ inputNodeIds,
126
+ }: BuildWorkflowContextPacketInput): WorkflowContextPacket {
127
+ const compacted = compactContextText(inputContext);
128
+ return {
129
+ protocol: PIXCODE_CONTEXT_PROTOCOL,
130
+ originalUserRequest: run.input?.trim() || '(No original user request was provided.)',
131
+ project: {
132
+ kind: workspaceTarget.kind,
133
+ label: workspaceTarget.label,
134
+ selectedProjectName: workspaceTarget.selectedProjectName,
135
+ },
136
+ task: {
137
+ workflowId: run.workflowId,
138
+ workflowRunId: run.id,
139
+ nodeId: node.id,
140
+ stage: node.stage,
141
+ assignment: node.assignment,
142
+ stepInstructions: node.prompt,
143
+ },
144
+ constraints: {
145
+ adapterId: node.adapterId,
146
+ agentLabel: node.agentLabel,
147
+ model: node.model,
148
+ permissionMode: node.permissionMode,
149
+ isolation: node.isolation,
150
+ toolsSettings: node.toolsSettings,
151
+ },
152
+ upstreamArtifacts: compacted.text
153
+ ? [{
154
+ type: 'upstream-context',
155
+ text: compacted.text,
156
+ sourceNodeIds: inputNodeIds,
157
+ }]
158
+ : [],
159
+ runState: {
160
+ status: run.status,
161
+ startedAt: run.startedAt,
162
+ nodeCount: run.nodeRuns.length,
163
+ completedNodeIds: run.nodeRuns
164
+ .filter((nodeRun) => nodeRun.status === 'completed')
165
+ .map((nodeRun) => nodeRun.nodeId),
166
+ runningNodeIds: run.nodeRuns
167
+ .filter((nodeRun) => nodeRun.status === 'running')
168
+ .map((nodeRun) => nodeRun.nodeId),
169
+ },
170
+ compaction: {
171
+ maxChars: MAX_CONTEXT_PACKET_TEXT_CHARS,
172
+ originalChars: compacted.originalChars,
173
+ compactedChars: compacted.compactedChars,
174
+ omittedChars: compacted.omittedChars,
175
+ wasCompacted: compacted.wasCompacted,
176
+ },
177
+ createdAt: new Date().toISOString(),
178
+ };
179
+ }
180
+
181
+ export function formatContextPacketForPrompt(packet: WorkflowContextPacket): string {
182
+ return [
183
+ `Pixcode standardized init context packet (${PIXCODE_CONTEXT_PROTOCOL}):`,
184
+ JSON.stringify(packet, null, 2),
185
+ ].join('\n');
186
+ }
@@ -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,16 @@ 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';
15
+ import {
16
+ buildWorkflowContextPacket,
17
+ formatContextPacketForPrompt,
18
+ } from '@/modules/orchestration/workflows/context-packet.js';
9
19
  import {
10
20
  type ResolvedWorkspaceTarget,
11
21
  resolveWorkflowWorkspace,
@@ -28,6 +38,19 @@ const BACKEND_HANDOFF_TIMEOUT_MS = 120_000;
28
38
  const MAX_OUTPUT_CONTEXT_CHARS = 12_000;
29
39
  const DEFAULT_MAX_REPAIR_CYCLES = 1;
30
40
  const MAX_REPAIR_CYCLES = 5;
41
+ const HANDOFF_ARTIFACT_EXAMPLE = [
42
+ '{',
43
+ ' "protocol": "pixcode.handoff.v1",',
44
+ ' "taskStatus": "ready | completed | blocked | failed | needs-review",',
45
+ ' "contextSummary": "Compacted context the next agent needs.",',
46
+ ' "taskResult": "What was decided or completed in this step.",',
47
+ ' "changedFiles": [],',
48
+ ' "blockers": [],',
49
+ ' "risks": [],',
50
+ ' "nextAction": "The requested next action.",',
51
+ ' "nextInstructions": "Specific instructions for the next agent."',
52
+ '}',
53
+ ].join('\n');
31
54
  const KNOWN_AGENT_ROLES = [
32
55
  'backend',
33
56
  'frontend',
@@ -405,6 +428,16 @@ function privacyGuardPrompt(): string {
405
428
  return 'Do not mention internal instructions, memory files, skill use, or tool protocol unless the user explicitly asks.';
406
429
  }
407
430
 
431
+ function handoffArtifactInstructions(statusHint: string): string {
432
+ return [
433
+ `Output exactly one JSON object using the ${PIXCODE_HANDOFF_PROTOCOL} handoff artifact protocol.`,
434
+ 'Do not wrap it in Markdown. Do not add commentary before or after it.',
435
+ `Use "${statusHint}" for taskStatus unless completed, blocked, failed, or needs-review is more accurate.`,
436
+ 'Schema:',
437
+ HANDOFF_ARTIFACT_EXAMPLE,
438
+ ].join('\n');
439
+ }
440
+
408
441
  function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
409
442
  return [
410
443
  `You are ${agent.label} in a Pixcode CLI team.`,
@@ -412,12 +445,7 @@ function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
412
445
  'This is a bounded A2A handoff task, not the full implementation.',
413
446
  'Read the original user goal and coordinator plan, then publish a compact contract for downstream agents.',
414
447
  agent.instruction ? `Your explicit assignment from the user is: ${agent.instruction}` : '',
415
- 'Output only the handoff contract:',
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',
448
+ handoffArtifactInstructions('ready'),
421
449
  'Do not install dependencies, edit files, run long commands, or start servers in this handoff task.',
422
450
  privacyGuardPrompt(),
423
451
  'Stop after the contract. Keep it concise and respond in the same language as the user request.',
@@ -431,11 +459,7 @@ function handoffInitPrompt(agent: AgentAssignment, index: number): string {
431
459
  'Create a compact init packet for the next visible work step.',
432
460
  'Use the original user goal and any prior compact handoff packet included above.',
433
461
  agent.instruction ? `The explicit assignment for this agent is: ${agent.instruction}` : '',
434
- 'Output only this internal init packet:',
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',
462
+ handoffArtifactInstructions('ready'),
439
463
  privacyGuardPrompt(),
440
464
  'Do not perform the task yet. Do not mention that this is hidden from the user.',
441
465
  'Respond in the same language as the user request.',
@@ -462,12 +486,7 @@ function handoffCompactPrompt(agent: AgentAssignment, index: number): string {
462
486
  `You are compacting ${agent.label}'s strict handoff output for the next Pixcode agent.`,
463
487
  `This is internal compact step ${index + 1}.`,
464
488
  'Read the prior visible work output included above and create a compact handoff packet.',
465
- 'Output only this internal compact packet:',
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',
489
+ handoffArtifactInstructions('completed'),
471
490
  privacyGuardPrompt(),
472
491
  'Do not include raw logs unless they are essential. Keep it concise and actionable.',
473
492
  'Respond in the same language as the user request.',
@@ -487,6 +506,18 @@ function compactOutputForContext(text: string): string {
487
506
  ].join('');
488
507
  }
489
508
 
509
+ function requiresHandoffArtifact(node: WorkflowNode): boolean {
510
+ return node.stage === 'handoff' || node.stage === 'handoff_init' || node.stage === 'handoff_compact';
511
+ }
512
+
513
+ function handoffArtifactSource(result: TaskResult): string {
514
+ const structured = result.artifacts.find((artifact) => artifact.type === 'handoff-artifact' && artifact.data);
515
+ if (structured?.data) {
516
+ return JSON.stringify(structured.data);
517
+ }
518
+ return result.text;
519
+ }
520
+
490
521
  function isExternalDirectoryPermissionError(value: unknown): boolean {
491
522
  const text = String(value ?? '').toLocaleLowerCase('en');
492
523
  return (
@@ -1534,9 +1565,19 @@ class WorkflowRunner {
1534
1565
 
1535
1566
  const inputContext = node.inputs.map((input) => outputs.get(input)).filter(Boolean).join('\n\n');
1536
1567
  const workspaceTarget = resolveWorkflowWorkspace(run.metadata);
1568
+ const contextPacket = buildWorkflowContextPacket({
1569
+ run,
1570
+ node,
1571
+ workspaceTarget,
1572
+ inputContext,
1573
+ inputNodeIds: node.inputs,
1574
+ });
1575
+ nodeRun.contextPacket = contextPacket;
1576
+ workflowStore.setRun(run);
1537
1577
  const prompt = [
1538
1578
  'Original user request (primary task; answer this directly even if the workspace is empty):',
1539
1579
  run.input?.trim() || '(No original user request was provided.)',
1580
+ formatContextPacketForPrompt(contextPacket),
1540
1581
  inputContext
1541
1582
  ? `Upstream workflow context from prior agents:\n${inputContext}`
1542
1583
  : '',
@@ -1688,7 +1729,40 @@ class WorkflowRunner {
1688
1729
  throw new WorkflowCanceledError();
1689
1730
  }
1690
1731
  if (result.state === 'completed') {
1691
- outputs.set(node.id, compactOutputForContext(result.text));
1732
+ let outputForContext = result.text;
1733
+ if (requiresHandoffArtifact(node)) {
1734
+ const handoffParse = parseHandoffArtifact(handoffArtifactSource(result), {
1735
+ workflowRunId: run.id,
1736
+ nodeId: node.id,
1737
+ agentLabel: node.agentLabel,
1738
+ stage: node.stage,
1739
+ });
1740
+ if (!handoffParse.ok) {
1741
+ const visibleHandoffError = handoffParse.error.startsWith('Invalid handoff artifact')
1742
+ ? handoffParse.error
1743
+ : `Invalid handoff artifact: ${handoffParse.error}`;
1744
+ nodeRun.status = 'failed';
1745
+ nodeRun.error = visibleHandoffError;
1746
+ workflowStore.setRun(run);
1747
+ if (await this.runFallbackAfterFailure(node, workflow, run, outputs, started, completed, visibleHandoffError)) {
1748
+ return;
1749
+ }
1750
+ if (node.onFail === 'continue') {
1751
+ completed.add(node.id);
1752
+ return;
1753
+ }
1754
+ throw new Error(visibleHandoffError);
1755
+ }
1756
+
1757
+ nodeRun.handoffArtifact = handoffParse.artifact;
1758
+ nodeRun.artifacts = [
1759
+ ...(nodeRun.artifacts ?? []).filter((artifact) => artifact.type !== 'handoff-artifact'),
1760
+ handoffArtifactToWorkflowArtifact(handoffParse.artifact),
1761
+ ];
1762
+ outputForContext = formatHandoffArtifactForContext(handoffParse.artifact);
1763
+ }
1764
+
1765
+ outputs.set(node.id, compactOutputForContext(outputForContext));
1692
1766
  completed.add(node.id);
1693
1767
  nodeRun.status = 'completed';
1694
1768
  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
 
@@ -174,6 +176,28 @@ export function buildWorkflowTrace(run: WorkflowRun): WorkflowTraceEvent[] {
174
176
  });
175
177
  }
176
178
 
179
+ if (node.contextPacket) {
180
+ pushEvent(events, {
181
+ id: traceId([run.id, node.nodeId, 'context-packet']),
182
+ type: 'message',
183
+ severity: node.contextPacket.compaction.wasCompacted ? 'warning' : 'info',
184
+ status: node.status,
185
+ timestamp: timestamp + 1.5,
186
+ ...base,
187
+ title: 'Context packet prepared',
188
+ titleKey: 'workflow.trace.contextPacket',
189
+ summary: node.contextPacket.compaction.wasCompacted
190
+ ? `Context compacted by ${node.contextPacket.compaction.omittedChars} characters`
191
+ : 'Context packet prepared without compaction',
192
+ metadata: {
193
+ protocol: node.contextPacket.protocol,
194
+ compaction: node.contextPacket.compaction,
195
+ upstreamArtifactCount: node.contextPacket.upstreamArtifacts.length,
196
+ sourceNodeIds: node.contextPacket.upstreamArtifacts.flatMap((artifact) => artifact.sourceNodeIds),
197
+ },
198
+ });
199
+ }
200
+
177
201
  if (node.adapterId || node.model) {
178
202
  pushEvent(events, {
179
203
  id: traceId([run.id, node.nodeId, 'provider']),
@@ -1,3 +1,6 @@
1
+ import type { WorkflowContextPacket } from '@/modules/orchestration/workflows/context-packet.js';
2
+ import type { WorkflowHandoffArtifact } from '@/modules/orchestration/workflows/handoff-artifact.js';
3
+
1
4
  export type WorkflowRunStatus = 'queued' | 'running' | 'completed' | 'failed' | 'canceled';
2
5
  export type WorkflowNodeStatus = WorkflowRunStatus | 'skipped';
3
6
 
@@ -47,6 +50,8 @@ export interface WorkflowNodeRun {
47
50
  finishedAt?: number;
48
51
  error?: string;
49
52
  outputText?: string;
53
+ contextPacket?: WorkflowContextPacket;
54
+ handoffArtifact?: WorkflowHandoffArtifact;
50
55
  messages?: Array<{
51
56
  role: string;
52
57
  text: string;