@pixelbyte-software/pixcode 1.42.0 → 1.42.2

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.
Files changed (24) hide show
  1. package/dist/assets/{index-CkOamyD3.js → index-CMeiCqQf.js} +182 -182
  2. package/dist/index.html +1 -1
  3. package/dist-server/server/modules/orchestration/workflows/context-packet.js +89 -0
  4. package/dist-server/server/modules/orchestration/workflows/context-packet.js.map +1 -0
  5. package/dist-server/server/modules/orchestration/workflows/workflow-fallback-policy.js +114 -0
  6. package/dist-server/server/modules/orchestration/workflows/workflow-fallback-policy.js.map +1 -0
  7. package/dist-server/server/modules/orchestration/workflows/workflow-replay.js +177 -0
  8. package/dist-server/server/modules/orchestration/workflows/workflow-replay.js.map +1 -0
  9. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +58 -7
  10. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
  11. package/dist-server/server/modules/orchestration/workflows/workflow-trace.js +95 -0
  12. package/dist-server/server/modules/orchestration/workflows/workflow-trace.js.map +1 -1
  13. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +88 -0
  14. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -1
  15. package/package.json +1 -1
  16. package/scripts/smoke/context-packet.mjs +43 -0
  17. package/scripts/smoke/workflow-fallback-replay.mjs +56 -0
  18. package/server/modules/orchestration/workflows/context-packet.ts +186 -0
  19. package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -0
  20. package/server/modules/orchestration/workflows/workflow-replay.ts +254 -0
  21. package/server/modules/orchestration/workflows/workflow-runner.ts +119 -6
  22. package/server/modules/orchestration/workflows/workflow-trace.ts +98 -0
  23. package/server/modules/orchestration/workflows/workflow.routes.ts +107 -0
  24. package/server/modules/orchestration/workflows/workflow.types.ts +7 -0
@@ -0,0 +1,43 @@
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 contextSource = read('server/modules/orchestration/workflows/context-packet.ts');
14
+ assert.match(contextSource, /PIXCODE_CONTEXT_PROTOCOL/, 'Context packets should declare a stable protocol id.');
15
+ assert.match(contextSource, /pixcode\.context\.v1/, 'Context packets should use the v1 protocol id.');
16
+ assert.match(contextSource, /originalUserRequest/, 'Context packets should preserve the original user request.');
17
+ assert.match(contextSource, /project/, 'Context packets should include project metadata.');
18
+ assert.match(contextSource, /task/, 'Context packets should include task metadata.');
19
+ assert.match(contextSource, /constraints/, 'Context packets should include execution constraints.');
20
+ assert.match(contextSource, /upstreamArtifacts/, 'Context packets should include upstream artifact context.');
21
+ assert.match(contextSource, /runState/, 'Context packets should include current run state.');
22
+ assert.match(contextSource, /compaction/, 'Context packets should expose compaction metadata.');
23
+ assert.match(contextSource, /formatContextPacketForPrompt/, 'Context packets should be formatted for prompts.');
24
+
25
+ const types = read('server/modules/orchestration/workflows/workflow.types.ts');
26
+ assert.match(types, /WorkflowContextPacket/, 'Workflow types should expose context packets.');
27
+ assert.match(types, /contextPacket\?: WorkflowContextPacket/, 'Node runs should persist context packets.');
28
+
29
+ const runner = read('server/modules/orchestration/workflows/workflow-runner.ts');
30
+ assert.match(runner, /buildWorkflowContextPacket/, 'Workflow runner should build context packets.');
31
+ assert.match(runner, /formatContextPacketForPrompt/, 'Workflow runner should inject context packets into prompts.');
32
+ assert.ok(
33
+ runner.indexOf('Original user request') < runner.indexOf('formatContextPacketForPrompt(contextPacket)')
34
+ && runner.indexOf('formatContextPacketForPrompt(contextPacket)') < runner.indexOf('workspaceContextPrompt(workspaceTarget)'),
35
+ 'Original request must stay before structured context, and workspace context must remain derived context.',
36
+ );
37
+
38
+ const trace = read('server/modules/orchestration/workflows/workflow-trace.ts');
39
+ assert.match(trace, /contextPacket/, 'Workflow trace should surface context packet metadata.');
40
+ assert.match(trace, /workflow\.trace\.contextPacket/, 'Workflow trace should label context packet events.');
41
+ assert.match(trace, /compaction/, 'Workflow trace should include context compaction metadata.');
42
+
43
+ console.log('context packet smoke passed');
@@ -0,0 +1,56 @@
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 fallbackPolicy = read('server/modules/orchestration/workflows/workflow-fallback-policy.ts');
14
+ assert.match(fallbackPolicy, /PIXCODE_FALLBACK_POLICY_PROTOCOL/, 'Fallback policy should declare a stable protocol id.');
15
+ assert.match(fallbackPolicy, /pixcode\.fallback-policy\.v1/, 'Fallback policy should use the v1 protocol id.');
16
+ assert.match(fallbackPolicy, /provider_failure/, 'Fallback policy should classify provider failures.');
17
+ assert.match(fallbackPolicy, /timeout/, 'Fallback policy should classify timeouts.');
18
+ assert.match(fallbackPolicy, /tool_failure/, 'Fallback policy should classify tool failures.');
19
+ assert.match(fallbackPolicy, /invalid_output/, 'Fallback policy should classify invalid output.');
20
+ assert.match(fallbackPolicy, /resolveWorkflowFallbackDecision/, 'Fallback policy should expose a decision helper.');
21
+
22
+ const replay = read('server/modules/orchestration/workflows/workflow-replay.ts');
23
+ assert.match(replay, /PIXCODE_REPLAY_PROTOCOL/, 'Replay support should declare a stable protocol id.');
24
+ assert.match(replay, /pixcode\.workflow-replay\.v1/, 'Replay support should use the v1 protocol id.');
25
+ assert.match(replay, /buildWorkflowReplayPlan/, 'Replay support should build a replay plan from stored run data.');
26
+ assert.match(replay, /requiresApproval/, 'Replay plans should expose approval requirements.');
27
+ assert.match(replay, /file-write/, 'Replay safety should detect file-write actions.');
28
+ assert.match(replay, /shell/, 'Replay safety should detect shell actions.');
29
+ assert.match(replay, /network/, 'Replay safety should detect network actions.');
30
+
31
+ const runner = read('server/modules/orchestration/workflows/workflow-runner.ts');
32
+ assert.match(runner, /resolveWorkflowFallbackDecision/, 'Workflow runner should use policy-driven fallback decisions.');
33
+ assert.match(runner, /fallbackTrigger/, 'Fallback nodes should record the trigger that launched them.');
34
+ assert.match(runner, /fallbackSkippedEvents/, 'Skipped fallback decisions should be recorded.');
35
+
36
+ const trace = read('server/modules/orchestration/workflows/workflow-trace.ts');
37
+ assert.match(trace, /workflow\.trace\.fallback/, 'Trace timeline should surface fallback events.');
38
+ assert.match(trace, /workflow\.trace\.replay/, 'Trace timeline should surface replay metadata.');
39
+
40
+ const routes = read('server/modules/orchestration/workflows/workflow.routes.ts');
41
+ assert.match(routes, /replay-plan/, 'Workflow routes should expose a replay plan endpoint.');
42
+ assert.match(routes, /REPLAY_APPROVAL_REQUIRED/, 'Replay route should require approval before unsafe replay.');
43
+ assert.match(routes, /workflowRunner\.start\(\s*replayPlan\.workflow/s, 'Replay route should start from the generated replay workflow.');
44
+
45
+ const panel = read('src/components/orchestration/workflows/WorkflowRunPanel.tsx');
46
+ assert.match(panel, /loadReplayPlan/, 'Workflow run panel should load replay plans.');
47
+ assert.match(panel, /replayRun/, 'Workflow run panel should expose a replay action.');
48
+ assert.match(panel, /approveReplay/, 'Workflow run panel should expose explicit approval for guarded replay.');
49
+ assert.match(panel, /orchestration\.replayRun/, 'Workflow UI should render replay labels.');
50
+
51
+ const en = read('src/i18n/locales/en/common.json');
52
+ const tr = read('src/i18n/locales/tr/common.json');
53
+ assert.match(en, /"replayRun"/, 'English replay translation is missing.');
54
+ assert.match(tr, /"replayRun"/, 'Turkish replay translation is missing.');
55
+
56
+ console.log('workflow fallback replay smoke passed');
@@ -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,161 @@
1
+ import type { WorkflowNode, WorkflowRun } from '@/modules/orchestration/workflows/workflow.types.js';
2
+
3
+ export const PIXCODE_FALLBACK_POLICY_PROTOCOL = 'pixcode.fallback-policy.v1';
4
+
5
+ export type WorkflowFallbackTrigger = 'provider_failure' | 'timeout' | 'tool_failure' | 'invalid_output';
6
+
7
+ export interface WorkflowFallbackPolicy {
8
+ protocol: typeof PIXCODE_FALLBACK_POLICY_PROTOCOL;
9
+ enabled: boolean;
10
+ triggers: WorkflowFallbackTrigger[];
11
+ maxFallbacksPerRun: number;
12
+ requireDifferentAgent: boolean;
13
+ }
14
+
15
+ export interface WorkflowFallbackDecision {
16
+ shouldFallback: boolean;
17
+ trigger: WorkflowFallbackTrigger;
18
+ reason: string;
19
+ policy: WorkflowFallbackPolicy;
20
+ skippedReason?: string;
21
+ }
22
+
23
+ const DEFAULT_TRIGGERS: WorkflowFallbackTrigger[] = [
24
+ 'provider_failure',
25
+ 'timeout',
26
+ 'tool_failure',
27
+ 'invalid_output',
28
+ ];
29
+
30
+ export const DEFAULT_WORKFLOW_FALLBACK_POLICY: WorkflowFallbackPolicy = {
31
+ protocol: PIXCODE_FALLBACK_POLICY_PROTOCOL,
32
+ enabled: true,
33
+ triggers: DEFAULT_TRIGGERS,
34
+ maxFallbacksPerRun: 3,
35
+ requireDifferentAgent: true,
36
+ };
37
+
38
+ function readRecord(value: unknown): Record<string, unknown> | undefined {
39
+ return value && typeof value === 'object' ? value as Record<string, unknown> : undefined;
40
+ }
41
+
42
+ function readBoolean(value: unknown): boolean | undefined {
43
+ return typeof value === 'boolean' ? value : undefined;
44
+ }
45
+
46
+ function readNumber(value: unknown): number | undefined {
47
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
48
+ }
49
+
50
+ function isWorkflowFallbackTrigger(value: unknown): value is WorkflowFallbackTrigger {
51
+ return value === 'provider_failure'
52
+ || value === 'timeout'
53
+ || value === 'tool_failure'
54
+ || value === 'invalid_output';
55
+ }
56
+
57
+ function readFallbackTriggers(value: unknown): WorkflowFallbackTrigger[] | undefined {
58
+ if (!Array.isArray(value)) return undefined;
59
+ const triggers = value.filter(isWorkflowFallbackTrigger);
60
+ return triggers.length > 0 ? [...new Set(triggers)] : undefined;
61
+ }
62
+
63
+ export function readWorkflowFallbackPolicy(metadata?: Record<string, unknown>): WorkflowFallbackPolicy {
64
+ const settings = readRecord(metadata?.settings) ?? {};
65
+ const configured = readRecord(settings.fallbackPolicy) ?? {};
66
+ const maxFallbacksPerRun = readNumber(configured.maxFallbacksPerRun);
67
+
68
+ return {
69
+ protocol: PIXCODE_FALLBACK_POLICY_PROTOCOL,
70
+ enabled: readBoolean(configured.enabled) ?? DEFAULT_WORKFLOW_FALLBACK_POLICY.enabled,
71
+ triggers: readFallbackTriggers(configured.triggers) ?? DEFAULT_WORKFLOW_FALLBACK_POLICY.triggers,
72
+ maxFallbacksPerRun: maxFallbacksPerRun === undefined
73
+ ? DEFAULT_WORKFLOW_FALLBACK_POLICY.maxFallbacksPerRun
74
+ : Math.max(0, Math.min(8, Math.round(maxFallbacksPerRun))),
75
+ requireDifferentAgent: readBoolean(configured.requireDifferentAgent)
76
+ ?? DEFAULT_WORKFLOW_FALLBACK_POLICY.requireDifferentAgent,
77
+ };
78
+ }
79
+
80
+ export function classifyWorkflowFailure(
81
+ reason: string,
82
+ explicitTrigger?: WorkflowFallbackTrigger,
83
+ ): WorkflowFallbackTrigger {
84
+ if (explicitTrigger) return explicitTrigger;
85
+
86
+ const text = reason.toLocaleLowerCase('en');
87
+ if (/timed out|timeout|deadline/u.test(text)) return 'timeout';
88
+ if (/invalid (handoff|output|artifact|json|schema)|parse|protocol/u.test(text)) return 'invalid_output';
89
+ if (
90
+ /tool|command|shell|exit code|permission|file write|write failed|network|fetch|curl|wget|gh |npm |git /u
91
+ .test(text)
92
+ ) {
93
+ return 'tool_failure';
94
+ }
95
+ return 'provider_failure';
96
+ }
97
+
98
+ function fallbackEventCount(run: WorkflowRun): number {
99
+ return Array.isArray(run.metadata?.fallbackEvents) ? run.metadata.fallbackEvents.length : 0;
100
+ }
101
+
102
+ export function resolveWorkflowFallbackDecision({
103
+ run,
104
+ node,
105
+ reason,
106
+ trigger,
107
+ fallbackAgentInstanceId,
108
+ }: {
109
+ run: WorkflowRun;
110
+ node: WorkflowNode;
111
+ reason: string;
112
+ trigger?: WorkflowFallbackTrigger;
113
+ fallbackAgentInstanceId?: string;
114
+ }): WorkflowFallbackDecision {
115
+ const fallbackTrigger = classifyWorkflowFailure(reason, trigger);
116
+ const policy = readWorkflowFallbackPolicy(run.metadata);
117
+
118
+ if (!policy.enabled) {
119
+ return {
120
+ shouldFallback: false,
121
+ trigger: fallbackTrigger,
122
+ reason,
123
+ policy,
124
+ skippedReason: 'Fallback policy is disabled.',
125
+ };
126
+ }
127
+ if (!policy.triggers.includes(fallbackTrigger)) {
128
+ return {
129
+ shouldFallback: false,
130
+ trigger: fallbackTrigger,
131
+ reason,
132
+ policy,
133
+ skippedReason: `Fallback trigger ${fallbackTrigger} is not enabled.`,
134
+ };
135
+ }
136
+ if (fallbackEventCount(run) >= policy.maxFallbacksPerRun) {
137
+ return {
138
+ shouldFallback: false,
139
+ trigger: fallbackTrigger,
140
+ reason,
141
+ policy,
142
+ skippedReason: `Fallback limit ${policy.maxFallbacksPerRun} reached.`,
143
+ };
144
+ }
145
+ if (policy.requireDifferentAgent && fallbackAgentInstanceId && fallbackAgentInstanceId === node.agentInstanceId) {
146
+ return {
147
+ shouldFallback: false,
148
+ trigger: fallbackTrigger,
149
+ reason,
150
+ policy,
151
+ skippedReason: 'Fallback agent must be different from the failed agent.',
152
+ };
153
+ }
154
+
155
+ return {
156
+ shouldFallback: true,
157
+ trigger: fallbackTrigger,
158
+ reason,
159
+ policy,
160
+ };
161
+ }