@pixelbyte-software/pixcode 1.36.3 → 1.37.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-CfHK8y_H.css +32 -0
- package/dist/assets/{index-Bp8mXdQd.js → index-D8uNxHf1.js} +165 -159
- package/dist/index.html +2 -2
- package/dist-server/server/daemon-manager.js +18 -12
- package/dist-server/server/daemon-manager.js.map +1 -1
- package/dist-server/server/database/db.js +53 -2
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +11 -4
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +10 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +7 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -1
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +143 -26
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
- package/dist-server/server/routes/taskmaster.js +194 -0
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/dist-server/server/routes/telegram.js +16 -2
- package/dist-server/server/routes/telegram.js.map +1 -1
- package/dist-server/server/services/install-jobs.js +1 -0
- package/dist-server/server/services/install-jobs.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +66 -9
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/telegram/bot.js +48 -6
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +903 -0
- package/dist-server/server/services/telegram/control-center.js.map +1 -0
- package/dist-server/server/services/telegram/telegram-http-client.js +26 -4
- package/dist-server/server/services/telegram/telegram-http-client.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +150 -2
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +3 -1
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -0
- package/scripts/smoke/daemon-entrypoint.mjs +20 -0
- package/scripts/smoke/multi-worker-slots.mjs +42 -0
- package/scripts/smoke/notification-center.mjs +63 -0
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -0
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -0
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -0
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -0
- package/scripts/smoke/static-root-routing.mjs +21 -0
- package/scripts/smoke/strict-handoff-compact.mjs +60 -0
- package/scripts/smoke/taskmaster-execution-telegram.mjs +52 -0
- package/scripts/smoke/taskmaster-onboarding.mjs +52 -0
- package/scripts/smoke/telegram-control.mjs +242 -0
- package/scripts/smoke/update-issue-progress.mjs +69 -0
- package/scripts/smoke/version-modal-autoshow.mjs +29 -0
- package/server/daemon-manager.js +17 -12
- package/server/database/db.js +56 -2
- package/server/index.js +12 -5
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +10 -1
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +7 -0
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +3 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +149 -26
- package/server/modules/orchestration/workflows/workflow.types.ts +2 -0
- package/server/routes/taskmaster.js +201 -0
- package/server/routes/telegram.js +17 -2
- package/server/services/install-jobs.js +1 -0
- package/server/services/notification-orchestrator.js +76 -8
- package/server/services/telegram/bot.js +58 -6
- package/server/services/telegram/control-center.js +965 -0
- package/server/services/telegram/telegram-http-client.js +25 -4
- package/server/services/telegram/translations.js +150 -2
- package/dist/assets/index-Dx7QyTSN.css +0 -32
package/server/database/db.js
CHANGED
|
@@ -217,6 +217,7 @@ function migrateSqliteIfPresent() {
|
|
|
217
217
|
verified_at: tl.verified_at || null,
|
|
218
218
|
notifications_enabled: tl.notifications_enabled !== 0,
|
|
219
219
|
bridge_enabled: tl.bridge_enabled !== 0,
|
|
220
|
+
telegram_control: null,
|
|
220
221
|
updated_at: tl.updated_at || nowIso(),
|
|
221
222
|
});
|
|
222
223
|
}
|
|
@@ -492,7 +493,7 @@ const credentialsDb = {
|
|
|
492
493
|
// Notification preferences
|
|
493
494
|
// ---------------------------------------------------------------------------
|
|
494
495
|
const DEFAULT_NOTIFICATION_PREFERENCES = {
|
|
495
|
-
channels: { inApp:
|
|
496
|
+
channels: { inApp: true, webPush: false, telegram: true, desktop: true },
|
|
496
497
|
events: { actionRequired: true, stop: true, error: true, updates: true },
|
|
497
498
|
};
|
|
498
499
|
|
|
@@ -500,8 +501,10 @@ const normalizeNotificationPreferences = (value) => {
|
|
|
500
501
|
const source = value && typeof value === 'object' ? value : {};
|
|
501
502
|
return {
|
|
502
503
|
channels: {
|
|
503
|
-
inApp: source.channels?.inApp
|
|
504
|
+
inApp: source.channels?.inApp !== false,
|
|
504
505
|
webPush: source.channels?.webPush === true,
|
|
506
|
+
telegram: source.channels?.telegram !== false,
|
|
507
|
+
desktop: source.channels?.desktop !== false,
|
|
505
508
|
},
|
|
506
509
|
events: {
|
|
507
510
|
actionRequired: source.events?.actionRequired !== false,
|
|
@@ -672,6 +675,40 @@ const appConfigDb = {
|
|
|
672
675
|
// ---------------------------------------------------------------------------
|
|
673
676
|
// Telegram — singleton config + per-user links
|
|
674
677
|
// ---------------------------------------------------------------------------
|
|
678
|
+
const DEFAULT_TELEGRAM_CONTROL_STATE = {
|
|
679
|
+
remoteControlEnabled: true,
|
|
680
|
+
progressMode: 'final',
|
|
681
|
+
selectedProjectName: null,
|
|
682
|
+
selectedProjectPath: null,
|
|
683
|
+
selectedProvider: 'opencode',
|
|
684
|
+
selectedModel: null,
|
|
685
|
+
selectedWorkflowId: null,
|
|
686
|
+
awaiting: null,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
function normalizeTelegramControlState(value = {}) {
|
|
690
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
691
|
+
const selectedProvider = ['claude', 'cursor', 'codex', 'gemini', 'qwen', 'opencode'].includes(raw.selectedProvider)
|
|
692
|
+
? raw.selectedProvider
|
|
693
|
+
: DEFAULT_TELEGRAM_CONTROL_STATE.selectedProvider;
|
|
694
|
+
const progressMode = ['final', 'steps', 'all'].includes(raw.progressMode)
|
|
695
|
+
? raw.progressMode
|
|
696
|
+
: DEFAULT_TELEGRAM_CONTROL_STATE.progressMode;
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
...DEFAULT_TELEGRAM_CONTROL_STATE,
|
|
700
|
+
...raw,
|
|
701
|
+
remoteControlEnabled: raw.remoteControlEnabled !== false,
|
|
702
|
+
progressMode,
|
|
703
|
+
selectedProvider,
|
|
704
|
+
selectedProjectName: typeof raw.selectedProjectName === 'string' ? raw.selectedProjectName : null,
|
|
705
|
+
selectedProjectPath: typeof raw.selectedProjectPath === 'string' ? raw.selectedProjectPath : null,
|
|
706
|
+
selectedModel: typeof raw.selectedModel === 'string' ? raw.selectedModel : null,
|
|
707
|
+
selectedWorkflowId: typeof raw.selectedWorkflowId === 'string' ? raw.selectedWorkflowId : null,
|
|
708
|
+
awaiting: raw.awaiting && typeof raw.awaiting === 'object' ? raw.awaiting : null,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
675
712
|
const telegramConfigDb = {
|
|
676
713
|
get: () => {
|
|
677
714
|
const row = store.raw.telegram_config[0];
|
|
@@ -705,6 +742,7 @@ const telegramLinksDb = {
|
|
|
705
742
|
verified_at: null,
|
|
706
743
|
notifications_enabled: true,
|
|
707
744
|
bridge_enabled: true,
|
|
745
|
+
telegram_control: normalizeTelegramControlState(),
|
|
708
746
|
updated_at: nowIso(),
|
|
709
747
|
});
|
|
710
748
|
},
|
|
@@ -742,6 +780,7 @@ const telegramLinksDb = {
|
|
|
742
780
|
language: row.language,
|
|
743
781
|
notifications_enabled: row.notifications_enabled,
|
|
744
782
|
bridge_enabled: row.bridge_enabled,
|
|
783
|
+
telegram_control: normalizeTelegramControlState(row.telegram_control),
|
|
745
784
|
};
|
|
746
785
|
},
|
|
747
786
|
listVerified: () =>
|
|
@@ -752,6 +791,7 @@ const telegramLinksDb = {
|
|
|
752
791
|
language: r.language,
|
|
753
792
|
notifications_enabled: r.notifications_enabled,
|
|
754
793
|
bridge_enabled: r.bridge_enabled,
|
|
794
|
+
telegram_control: normalizeTelegramControlState(r.telegram_control),
|
|
755
795
|
})),
|
|
756
796
|
updatePreferences: (userId, { language, notificationsEnabled, bridgeEnabled }) => {
|
|
757
797
|
const patch = { updated_at: nowIso() };
|
|
@@ -761,6 +801,20 @@ const telegramLinksDb = {
|
|
|
761
801
|
if (Object.keys(patch).length === 1) return; // only updated_at → no real change
|
|
762
802
|
store.updateWhere('telegram_links', (r) => r.user_id === userId, patch);
|
|
763
803
|
},
|
|
804
|
+
getControlState: (userId) => {
|
|
805
|
+
const row = store.findWhere('telegram_links', (r) => r.user_id === userId);
|
|
806
|
+
return normalizeTelegramControlState(row?.telegram_control);
|
|
807
|
+
},
|
|
808
|
+
updateControlState: (userId, patch) => {
|
|
809
|
+
const row = store.findWhere('telegram_links', (r) => r.user_id === userId);
|
|
810
|
+
const current = normalizeTelegramControlState(row?.telegram_control);
|
|
811
|
+
const next = normalizeTelegramControlState({ ...current, ...(patch || {}) });
|
|
812
|
+
store.updateWhere('telegram_links', (r) => r.user_id === userId, {
|
|
813
|
+
telegram_control: next,
|
|
814
|
+
updated_at: nowIso(),
|
|
815
|
+
});
|
|
816
|
+
return next;
|
|
817
|
+
},
|
|
764
818
|
unlink: (userId) => {
|
|
765
819
|
store.deleteWhere('telegram_links', (r) => r.user_id === userId);
|
|
766
820
|
},
|
package/server/index.js
CHANGED
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
import { primeCliBinPath } from './services/install-jobs.js';
|
|
100
100
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
101
101
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
|
102
|
+
import { setNotificationWebSocketServer } from './services/notification-orchestrator.js';
|
|
102
103
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
103
104
|
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
104
105
|
import { IS_PLATFORM } from './constants/config.js';
|
|
@@ -319,6 +320,7 @@ const wss = new WebSocketServer({
|
|
|
319
320
|
|
|
320
321
|
// Make WebSocket server available to routes
|
|
321
322
|
app.locals.wss = wss;
|
|
323
|
+
setNotificationWebSocketServer(wss);
|
|
322
324
|
|
|
323
325
|
app.use(cors({ exposedHeaders: ['X-Refreshed-Token'] }));
|
|
324
326
|
app.use(express.json({
|
|
@@ -413,11 +415,9 @@ app.use('/api/telegram', authenticateToken, telegramRoutes);
|
|
|
413
415
|
// Agent API Routes (uses API key authentication)
|
|
414
416
|
app.use('/api/agent', agentRoutes);
|
|
415
417
|
|
|
416
|
-
//
|
|
417
|
-
app
|
|
418
|
-
|
|
419
|
-
// Static files served after API routes
|
|
420
|
-
// Add cache control: HTML files should not be cached, but assets can be cached
|
|
418
|
+
// Static app files served after API routes. Keep dist before public so
|
|
419
|
+
// / and /index.html always resolve to the Pixcode app, not the GitHub Pages
|
|
420
|
+
// landing page that also lives in public/index.html.
|
|
421
421
|
app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
|
422
422
|
setHeaders: (res, filePath) => {
|
|
423
423
|
if (filePath.endsWith('.html')) {
|
|
@@ -432,6 +432,12 @@ app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
|
|
432
432
|
}
|
|
433
433
|
}));
|
|
434
434
|
|
|
435
|
+
// Serve extra public files (api-docs.html, llms.txt, landing pages) without
|
|
436
|
+
// letting public/index.html shadow the production app root.
|
|
437
|
+
app.use(express.static(path.join(APP_ROOT, 'public'), {
|
|
438
|
+
index: false,
|
|
439
|
+
}));
|
|
440
|
+
|
|
435
441
|
// API Routes (protected)
|
|
436
442
|
// /api/config endpoint removed - no longer needed
|
|
437
443
|
// Frontend now uses window.location for WebSocket URLs
|
|
@@ -1809,6 +1815,7 @@ function handleChatConnection(ws, request) {
|
|
|
1809
1815
|
console.log('[INFO] Chat WebSocket connected');
|
|
1810
1816
|
|
|
1811
1817
|
// Add to connected clients for project updates
|
|
1818
|
+
ws.userId = request?.user?.id ?? request?.user?.userId ?? null;
|
|
1812
1819
|
connectedClients.add(ws);
|
|
1813
1820
|
|
|
1814
1821
|
// Wrap WebSocket with writer for consistent interface with SSEStreamWriter
|
|
@@ -48,11 +48,20 @@ export function createOrchestrationTaskRouter(): Router {
|
|
|
48
48
|
try {
|
|
49
49
|
const adapterId = typeof req.body?.adapterId === 'string' ? req.body.adapterId : '';
|
|
50
50
|
const isolation = req.body?.isolation;
|
|
51
|
+
const projectPath = typeof req.body?.projectPath === 'string' ? req.body.projectPath : undefined;
|
|
52
|
+
const model = typeof req.body?.model === 'string' ? req.body.model : undefined;
|
|
53
|
+
const permissionMode = typeof req.body?.permissionMode === 'string' ? req.body.permissionMode : undefined;
|
|
51
54
|
if (!adapterId) {
|
|
52
55
|
res.status(400).json({ error: { code: 'ADAPTER_REQUIRED', message: 'adapterId is required' } });
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
55
|
-
const task = await orchestrationTaskService.dispatch(req.params.id, {
|
|
58
|
+
const task = await orchestrationTaskService.dispatch(req.params.id, {
|
|
59
|
+
adapterId,
|
|
60
|
+
isolation,
|
|
61
|
+
projectPath,
|
|
62
|
+
model,
|
|
63
|
+
permissionMode,
|
|
64
|
+
});
|
|
56
65
|
res.json(task);
|
|
57
66
|
} catch (error) {
|
|
58
67
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -71,6 +71,12 @@ class OrchestrationTaskService {
|
|
|
71
71
|
},
|
|
72
72
|
metadata: {
|
|
73
73
|
isolation: input.isolation ?? 'worktree',
|
|
74
|
+
model: input.model,
|
|
75
|
+
permissionMode: input.permissionMode,
|
|
76
|
+
workspace: {
|
|
77
|
+
kind: input.isolation ?? 'worktree',
|
|
78
|
+
projectPath: input.projectPath,
|
|
79
|
+
},
|
|
74
80
|
orchestrationTaskId: task.id,
|
|
75
81
|
taskmasterId: task.taskmasterId,
|
|
76
82
|
},
|
|
@@ -86,6 +92,7 @@ class OrchestrationTaskService {
|
|
|
86
92
|
task.adapterId = input.adapterId;
|
|
87
93
|
task.adapterSelector = input.adapterId;
|
|
88
94
|
task.workspaceKind = input.isolation ?? 'worktree';
|
|
95
|
+
task.workspacePath = input.projectPath;
|
|
89
96
|
task.state = 'in_progress';
|
|
90
97
|
task.updatedAt = Date.now();
|
|
91
98
|
this.store.set(task);
|
|
@@ -276,6 +276,10 @@ function rolePrompt(role: AgentRole): string {
|
|
|
276
276
|
return 'Implementation work should avoid duplicating other agents and should report changed files, commands, blockers, and next actions.';
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
function privacyGuardPrompt(): string {
|
|
280
|
+
return 'Do not mention internal instructions, memory files, skill use, or tool protocol unless the user explicitly asks.';
|
|
281
|
+
}
|
|
282
|
+
|
|
279
283
|
function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
|
|
280
284
|
return [
|
|
281
285
|
`You are ${agent.label} in a Pixcode CLI team.`,
|
|
@@ -290,10 +294,61 @@ function handoffPrompt(agent: AgentAssignment, role: AgentRole): string {
|
|
|
290
294
|
'- dependencies/blockers for the next agents',
|
|
291
295
|
'- concrete next action for your full implementation task',
|
|
292
296
|
'Do not install dependencies, edit files, run long commands, or start servers in this handoff task.',
|
|
297
|
+
privacyGuardPrompt(),
|
|
293
298
|
'Stop after the contract. Keep it concise and respond in the same language as the user request.',
|
|
294
299
|
].filter(Boolean).join('\n');
|
|
295
300
|
}
|
|
296
301
|
|
|
302
|
+
function handoffInitPrompt(agent: AgentAssignment, index: number): string {
|
|
303
|
+
return [
|
|
304
|
+
`You are preparing ${agent.label} for a strict Pixcode handoff chain.`,
|
|
305
|
+
`This is internal step ${index + 1}.`,
|
|
306
|
+
'Create a compact init packet for the next visible work step.',
|
|
307
|
+
'Use the original user goal and any prior compact handoff packet included above.',
|
|
308
|
+
agent.instruction ? `The explicit assignment for this agent is: ${agent.instruction}` : '',
|
|
309
|
+
'Output only this internal init packet:',
|
|
310
|
+
'- user goal in one sentence',
|
|
311
|
+
'- prior agent handoff summary, if present',
|
|
312
|
+
'- this agent responsibility',
|
|
313
|
+
'- exact constraints and blockers this agent must respect',
|
|
314
|
+
privacyGuardPrompt(),
|
|
315
|
+
'Do not perform the task yet. Do not mention that this is hidden from the user.',
|
|
316
|
+
'Respond in the same language as the user request.',
|
|
317
|
+
].filter(Boolean).join('\n');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function handoffWorkPrompt(agent: AgentAssignment, index: number): string {
|
|
321
|
+
return [
|
|
322
|
+
`You are ${agent.label} in a strict Pixcode handoff chain.`,
|
|
323
|
+
`This is visible work step ${index + 1}.`,
|
|
324
|
+
'The internal init packet above is your starting context. Do the assigned work now.',
|
|
325
|
+
agent.instruction
|
|
326
|
+
? `Your explicit assignment from the user is: ${agent.instruction}`
|
|
327
|
+
: 'Use the init packet and original user goal to choose the next useful work for this step.',
|
|
328
|
+
rolePrompt(agent.role ?? 'implementation'),
|
|
329
|
+
privacyGuardPrompt(),
|
|
330
|
+
'Report only user-facing progress, changed files, commands, verification, blockers, and next actions.',
|
|
331
|
+
'Respond in the same language as the user request.',
|
|
332
|
+
].filter(Boolean).join('\n');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function handoffCompactPrompt(agent: AgentAssignment, index: number): string {
|
|
336
|
+
return [
|
|
337
|
+
`You are compacting ${agent.label}'s strict handoff output for the next Pixcode agent.`,
|
|
338
|
+
`This is internal compact step ${index + 1}.`,
|
|
339
|
+
'Read the prior visible work output included above and create a compact handoff packet.',
|
|
340
|
+
'Output only this internal compact packet:',
|
|
341
|
+
'- Ben ne yaptım / What I did',
|
|
342
|
+
'- Dokunduğum alanlar / Touched areas',
|
|
343
|
+
'- Kanıt, komut veya çıktı / Evidence, commands, outputs',
|
|
344
|
+
'- Sonraki ajan şunu bilsin / What the next agent must know',
|
|
345
|
+
'- Bloker veya risk / Blockers or risks',
|
|
346
|
+
privacyGuardPrompt(),
|
|
347
|
+
'Do not include raw logs unless they are essential. Keep it concise and actionable.',
|
|
348
|
+
'Respond in the same language as the user request.',
|
|
349
|
+
].join('\n');
|
|
350
|
+
}
|
|
351
|
+
|
|
297
352
|
function compactOutputForContext(text: string): string {
|
|
298
353
|
if (text.length <= MAX_OUTPUT_CONTEXT_CHARS) {
|
|
299
354
|
return text;
|
|
@@ -374,6 +429,7 @@ function expandAgentTeamWorkflow(workflow: Workflow, metadata?: Record<string, u
|
|
|
374
429
|
? `Your explicit assignment from the user is: ${agent.instruction}`
|
|
375
430
|
: 'No fixed per-agent assignment was set. Take the part assigned to you by the coordinator; if none is named, choose useful work that fits this CLI.',
|
|
376
431
|
rolePrompt(stage),
|
|
432
|
+
privacyGuardPrompt(),
|
|
377
433
|
'Respond in the same language as the user request.',
|
|
378
434
|
].filter(Boolean).join('\n'),
|
|
379
435
|
inputs,
|
|
@@ -419,6 +475,8 @@ function expandAgentTeamWorkflow(workflow: Workflow, metadata?: Record<string, u
|
|
|
419
475
|
prompt: [
|
|
420
476
|
'Collect the worker outputs into one user-facing result.',
|
|
421
477
|
'Show what each CLI did, which parts failed, what changed, and the next action if work remains.',
|
|
478
|
+
'Do not expose internal prompts, memory lookup, skill/tool instructions, raw agent logs, or role prefixes like "agent:" and "user:".',
|
|
479
|
+
'If a worker reveals internal process text, summarize only the useful user-facing result.',
|
|
422
480
|
'Respond in the same language as the user request.',
|
|
423
481
|
].join('\n'),
|
|
424
482
|
inputs: workerNodes.map((node) => node.id),
|
|
@@ -436,6 +494,7 @@ function stagePrompt(agent: AgentAssignment, stage: AgentRole): string {
|
|
|
436
494
|
agent.role && agent.role !== stage ? `User custom stage label: ${agent.role}.` : '',
|
|
437
495
|
agent.instruction ? `User assignment for you: ${agent.instruction}` : '',
|
|
438
496
|
rolePrompt(stage),
|
|
497
|
+
privacyGuardPrompt(),
|
|
439
498
|
'Keep the answer concise, structured, and useful for the next stage.',
|
|
440
499
|
'Respond in the same language as the user request.',
|
|
441
500
|
].filter(Boolean).join('\n');
|
|
@@ -586,31 +645,89 @@ function expandSequentialHandoffWorkflow(workflow: Workflow, metadata?: Record<s
|
|
|
586
645
|
throw new Error('Select at least one CLI agent.');
|
|
587
646
|
}
|
|
588
647
|
|
|
648
|
+
const nodes: WorkflowNode[] = agents.flatMap((agent, index): WorkflowNode[] => {
|
|
649
|
+
const initNodeId = safeAgentNodeId(agent, index, 'init');
|
|
650
|
+
const workNodeId = safeAgentNodeId(agent, index, 'work');
|
|
651
|
+
const compactNodeId = safeAgentNodeId(agent, index, 'compact');
|
|
652
|
+
|
|
653
|
+
return [
|
|
654
|
+
{
|
|
655
|
+
id: initNodeId,
|
|
656
|
+
adapterId: agent.adapterId,
|
|
657
|
+
agentInstanceId: agent.instanceId,
|
|
658
|
+
agentLabel: `${agent.label} Init`,
|
|
659
|
+
assignment: agent.instruction,
|
|
660
|
+
stage: 'handoff_init',
|
|
661
|
+
model: agent.model,
|
|
662
|
+
permissionMode: agent.permissionMode,
|
|
663
|
+
toolsSettings: agent.toolsSettings,
|
|
664
|
+
prompt: handoffInitPrompt(agent, index),
|
|
665
|
+
inputs: index === 0 ? [] : [safeAgentNodeId(agents[index - 1], index - 1, 'compact')],
|
|
666
|
+
output: 'message',
|
|
667
|
+
onFail: 'abort',
|
|
668
|
+
internal: true,
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: workNodeId,
|
|
672
|
+
adapterId: agent.adapterId,
|
|
673
|
+
agentInstanceId: agent.instanceId,
|
|
674
|
+
agentLabel: agent.label,
|
|
675
|
+
assignment: agent.instruction,
|
|
676
|
+
stage: agent.role ?? 'implementation',
|
|
677
|
+
model: agent.model,
|
|
678
|
+
permissionMode: agent.permissionMode,
|
|
679
|
+
toolsSettings: agent.toolsSettings,
|
|
680
|
+
prompt: handoffWorkPrompt(agent, index),
|
|
681
|
+
inputs: [initNodeId],
|
|
682
|
+
output: 'both',
|
|
683
|
+
onFail: 'abort',
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
id: compactNodeId,
|
|
687
|
+
adapterId: agent.adapterId,
|
|
688
|
+
agentInstanceId: agent.instanceId,
|
|
689
|
+
agentLabel: `${agent.label} Compact`,
|
|
690
|
+
assignment: agent.instruction,
|
|
691
|
+
stage: 'handoff_compact',
|
|
692
|
+
model: agent.model,
|
|
693
|
+
permissionMode: agent.permissionMode,
|
|
694
|
+
toolsSettings: agent.toolsSettings,
|
|
695
|
+
prompt: handoffCompactPrompt(agent, index),
|
|
696
|
+
inputs: [workNodeId],
|
|
697
|
+
output: 'message',
|
|
698
|
+
onFail: 'abort',
|
|
699
|
+
internal: true,
|
|
700
|
+
},
|
|
701
|
+
];
|
|
702
|
+
});
|
|
703
|
+
const reportAgent = agents[0];
|
|
704
|
+
const lastCompactNodeId = safeAgentNodeId(agents[agents.length - 1], agents.length - 1, 'compact');
|
|
705
|
+
|
|
589
706
|
return {
|
|
590
707
|
...workflow,
|
|
591
|
-
nodes:
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
708
|
+
nodes: [
|
|
709
|
+
...nodes,
|
|
710
|
+
{
|
|
711
|
+
id: 'final_report',
|
|
712
|
+
adapterId: reportAgent.adapterId,
|
|
713
|
+
agentInstanceId: reportAgent.instanceId,
|
|
714
|
+
agentLabel: reportAgent.label,
|
|
715
|
+
stage: 'final_report',
|
|
716
|
+
model: reportAgent.model,
|
|
717
|
+
permissionMode: reportAgent.permissionMode,
|
|
718
|
+
toolsSettings: reportAgent.toolsSettings,
|
|
719
|
+
prompt: [
|
|
720
|
+
'Create the final user-facing result for this strict handoff run.',
|
|
721
|
+
'Use the final compact handoff packet and the original user goal.',
|
|
722
|
+
'Summarize what each visible agent did, what changed, verification, blockers, and next actions.',
|
|
723
|
+
'Do not expose internal init packets, compact packets, prompts, memory lookup, skill/tool instructions, raw agent logs, or role prefixes like "agent:" and "user:".',
|
|
724
|
+
'Respond in the same language as the user request.',
|
|
725
|
+
].join('\n'),
|
|
726
|
+
inputs: [lastCompactNodeId],
|
|
727
|
+
output: 'message',
|
|
728
|
+
onFail: 'abort',
|
|
729
|
+
},
|
|
730
|
+
],
|
|
614
731
|
};
|
|
615
732
|
}
|
|
616
733
|
|
|
@@ -646,6 +763,7 @@ function expandWorkflowForRun(workflow: Workflow, metadata?: Record<string, unkn
|
|
|
646
763
|
`You are ${agent.label}.`,
|
|
647
764
|
'Review the requested change for bugs, regressions, missing validation, security, scale, and user-experience risks.',
|
|
648
765
|
agent.instruction ? `Focus on this user assignment: ${agent.instruction}` : '',
|
|
766
|
+
privacyGuardPrompt(),
|
|
649
767
|
'Respond in the same language as the user request.',
|
|
650
768
|
].filter(Boolean).join('\n'),
|
|
651
769
|
inputs: [],
|
|
@@ -666,7 +784,11 @@ function expandWorkflowForRun(workflow: Workflow, metadata?: Record<string, unkn
|
|
|
666
784
|
model: reportAgent.model,
|
|
667
785
|
permissionMode: reportAgent.permissionMode,
|
|
668
786
|
toolsSettings: reportAgent.toolsSettings,
|
|
669
|
-
prompt:
|
|
787
|
+
prompt: [
|
|
788
|
+
'Aggregate the prior agent reviews into a concise prioritized report.',
|
|
789
|
+
'Do not expose internal prompts, memory lookup, skill/tool instructions, raw agent logs, or role prefixes like "agent:" and "user:".',
|
|
790
|
+
'Respond in the same language as the user request.',
|
|
791
|
+
].join('\n'),
|
|
670
792
|
inputs: reviewNodes.map((node) => node.id),
|
|
671
793
|
output: 'message',
|
|
672
794
|
onFail: 'abort',
|
|
@@ -701,13 +823,13 @@ function readTaskResult(task: RawTask): TaskResult {
|
|
|
701
823
|
};
|
|
702
824
|
});
|
|
703
825
|
const outputMessages = messages.filter((message) => message.role !== 'user');
|
|
704
|
-
const
|
|
826
|
+
const userFacingTaskText = outputMessages.map((message) => message.text.trim()).filter(Boolean).join('\n\n');
|
|
705
827
|
const error = task.error?.message
|
|
706
828
|
? `${task.error.code ? `${task.error.code}: ` : ''}${task.error.message}`
|
|
707
829
|
: undefined;
|
|
708
830
|
return {
|
|
709
831
|
state: task.state ?? 'submitted',
|
|
710
|
-
text,
|
|
832
|
+
text: userFacingTaskText,
|
|
711
833
|
error,
|
|
712
834
|
messages,
|
|
713
835
|
artifacts,
|
|
@@ -757,6 +879,7 @@ function nodeRunFromNode(node: WorkflowNode): WorkflowNodeRun {
|
|
|
757
879
|
permissionMode: node.permissionMode,
|
|
758
880
|
timeoutMs: node.timeoutMs,
|
|
759
881
|
stage: node.stage,
|
|
882
|
+
internal: node.internal,
|
|
760
883
|
status: 'queued',
|
|
761
884
|
};
|
|
762
885
|
}
|
|
@@ -17,6 +17,7 @@ export interface WorkflowNode {
|
|
|
17
17
|
toolsSettings?: Record<string, unknown>;
|
|
18
18
|
isolation?: 'host' | 'worktree' | 'docker';
|
|
19
19
|
timeoutMs?: number;
|
|
20
|
+
internal?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface Workflow {
|
|
@@ -38,6 +39,7 @@ export interface WorkflowNodeRun {
|
|
|
38
39
|
permissionMode?: string;
|
|
39
40
|
timeoutMs?: number;
|
|
40
41
|
stage?: string;
|
|
42
|
+
internal?: boolean;
|
|
41
43
|
status: WorkflowNodeStatus;
|
|
42
44
|
a2aTaskId?: string;
|
|
43
45
|
startedAt?: number;
|