@undefineds.co/linx 0.3.4 → 0.3.7
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/README.md +58 -23
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +334 -162
- package/dist/index.js.map +1 -1
- package/dist/lib/account-session.js +4 -8
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +228 -178
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/auto-mode/archive.js +38 -7
- package/dist/lib/auto-mode/archive.js.map +1 -1
- package/dist/lib/auto-mode/auth.js.map +1 -1
- package/dist/lib/auto-mode/display.js +71 -45
- package/dist/lib/auto-mode/display.js.map +1 -1
- package/dist/lib/auto-mode/format.js +9 -7
- package/dist/lib/auto-mode/format.js.map +1 -1
- package/dist/lib/auto-mode/hooks/claude.js +12 -2
- package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
- package/dist/lib/auto-mode/hooks/codex.js +17 -7
- package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
- package/dist/lib/auto-mode/hooks/index.js +28 -8
- package/dist/lib/auto-mode/hooks/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-ai.js +20 -37
- package/dist/lib/auto-mode/pod-ai.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +124 -195
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/auto-mode/pod-persistence.js +169 -90
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
- package/dist/lib/auto-mode/runner.js +683 -81
- package/dist/lib/auto-mode/runner.js.map +1 -1
- package/dist/lib/auto-mode/secretary.js +186 -41
- package/dist/lib/auto-mode/secretary.js.map +1 -1
- package/dist/lib/auto-mode-command.js +32 -32
- package/dist/lib/auto-mode-command.js.map +1 -1
- package/dist/lib/chat-api.js +242 -50
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js +164 -17
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/credentials-store.js +33 -42
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/linx-cloud-errors.js +61 -0
- package/dist/lib/linx-cloud-errors.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +8 -5
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/login-command.js +9 -2
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +3 -20
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +143 -17
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js +2 -6
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
- package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
- package/dist/lib/pi-adapter/backend-command.js +2 -0
- package/dist/lib/pi-adapter/backend-command.js.map +1 -0
- package/dist/lib/pi-adapter/backend-credentials.js +80 -0
- package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
- package/dist/lib/pi-adapter/branding.js +246 -108
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/control-state.js +72 -0
- package/dist/lib/pi-adapter/control-state.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +2634 -30
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +382 -210
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +531 -64
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +81 -85
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/pod-status-output.js +54 -0
- package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +458 -228
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session-control.js +509 -0
- package/dist/lib/pi-adapter/session-control.js.map +1 -0
- package/dist/lib/pi-adapter/session.js +35 -22
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +89 -32
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/sync-recovery.js +89 -0
- package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
- package/dist/lib/pi-adapter/web-fetch.js +13 -14
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
- package/dist/lib/pod-chat-store.js +254 -78
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +156 -35
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/solid-auth-store.js +27 -0
- package/dist/lib/solid-auth-store.js.map +1 -0
- package/dist/lib/solid-auth.js +2 -4
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/solid-client-credentials-login.js +100 -0
- package/dist/lib/solid-client-credentials-login.js.map +1 -0
- package/dist/lib/solid-local-store.js +31 -0
- package/dist/lib/solid-local-store.js.map +1 -0
- package/dist/lib/symphony/archive.js +328 -18
- package/dist/lib/symphony/archive.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +2222 -0
- package/dist/lib/symphony/pod-projection.js.map +1 -0
- package/dist/lib/symphony-command.js +602 -178
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/lib/sync-checkpoint-store.js +74 -0
- package/dist/lib/sync-checkpoint-store.js.map +1 -0
- package/dist/skills/symphony/SKILL.md +665 -0
- package/package.json +15 -9
- package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
- package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
- package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
- package/vendor/agent-runtime/dist/auto-mode.js +288 -31
- package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
- package/vendor/agent-runtime/dist/control-plane.js +79 -0
- package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
- package/vendor/agent-runtime/dist/file-sync.js +314 -0
- package/vendor/agent-runtime/dist/index.d.ts +7 -0
- package/vendor/agent-runtime/dist/index.js +7 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
- package/vendor/agent-runtime/dist/reconciler.js +361 -0
- package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
- package/vendor/agent-runtime/dist/symphony.js +362 -57
- package/vendor/agent-runtime/dist/sync.d.ts +271 -0
- package/vendor/agent-runtime/dist/sync.js +550 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
- package/vendor/agent-runtime/dist/turn-controller.js +2 -2
- package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
- package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
- package/vendor/agent-runtime/package.json +8 -1
- package/vendor/pi-web-access/CHANGELOG.md +387 -0
- package/vendor/pi-web-access/LICENSE +21 -0
- package/vendor/pi-web-access/README.md +352 -0
- package/vendor/pi-web-access/activity.ts +101 -0
- package/vendor/pi-web-access/banner.png +0 -0
- package/vendor/pi-web-access/chrome-cookies.ts +322 -0
- package/vendor/pi-web-access/code-search.ts +107 -0
- package/vendor/pi-web-access/curator-page.ts +3359 -0
- package/vendor/pi-web-access/curator-server.ts +605 -0
- package/vendor/pi-web-access/exa.ts +520 -0
- package/vendor/pi-web-access/extract.ts +641 -0
- package/vendor/pi-web-access/gemini-api.ts +112 -0
- package/vendor/pi-web-access/gemini-search.ts +361 -0
- package/vendor/pi-web-access/gemini-url-context.ts +126 -0
- package/vendor/pi-web-access/gemini-web-config.ts +52 -0
- package/vendor/pi-web-access/gemini-web.ts +396 -0
- package/vendor/pi-web-access/github-api.ts +196 -0
- package/vendor/pi-web-access/github-extract.ts +634 -0
- package/vendor/pi-web-access/index.ts +2346 -0
- package/vendor/pi-web-access/package.json +45 -0
- package/vendor/pi-web-access/pdf-extract.ts +192 -0
- package/vendor/pi-web-access/perplexity.ts +195 -0
- package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
- package/vendor/pi-web-access/rsc-extract.ts +338 -0
- package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
- package/vendor/pi-web-access/storage.ts +72 -0
- package/vendor/pi-web-access/summary-review.ts +276 -0
- package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
- package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
- package/vendor/pi-web-access/utils.ts +44 -0
- package/vendor/pi-web-access/video-extract.ts +378 -0
- package/vendor/pi-web-access/youtube-extract.ts +310 -0
- package/dist/lib/pi-adapter/auth.js +0 -68
- package/dist/lib/pi-adapter/auth.js.map +0 -1
- package/dist/lib/pi-adapter/pod-tools.js +0 -140
- package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
- package/dist/skills/drizzle-solid/SKILL.md +0 -340
- package/dist/skills/pod-storage/SKILL.md +0 -100
- package/dist/skills/solid-modeling/SKILL.md +0 -274
- package/dist/skills/xpod-componentsjs/SKILL.md +0 -284
|
@@ -1,218 +1,503 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
+
import { runThreadReconcilerCycle, summarizeWakeJobExecutionRecord, } from '../../vendor/agent-runtime/dist/index.js';
|
|
4
|
+
import { appendSymphonyReconcilerDecision, createRunPlan, renderSymphonyRuntimePrompt, } from '../../vendor/agent-runtime/dist/symphony.js';
|
|
3
5
|
import { runAutoMode, listArchivedAutoModeSessions } from './auto-mode/index.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
+
import { formatSymphonyRecordSummary, getSymphonyHome, attachSymphonyRunPlanToIssue, createSymphonyRunPlanDraft, triageSymphonyIssue, withSymphonyIssueStatus, withSymphonyDeliveryStatus, withSymphonySessionStatus, withSymphonyTaskStatus, writeSymphonyRunPlan, } from './symphony/archive.js';
|
|
7
|
+
import { listOpenSymphonyIssuesFromPod, mirrorSymphonyProjectionJsonLdFromPod, persistSymphonyProjectionToPod } from './symphony/pod-projection.js';
|
|
6
8
|
const defaultRuntime = {
|
|
7
9
|
runAutoMode,
|
|
8
10
|
listAutoModeSessions: listArchivedAutoModeSessions,
|
|
11
|
+
persistSymphonyProjectionToPod,
|
|
12
|
+
listOpenSymphonyIssuesFromPod,
|
|
13
|
+
mirrorSymphonyProjectionJsonLdFromPod,
|
|
9
14
|
};
|
|
10
|
-
export function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
export async function runSymphony(argv, runtime = defaultRuntime) {
|
|
16
|
+
throwIfAborted(argv.signal);
|
|
17
|
+
const objective = normalizeObjective(argv.objective);
|
|
18
|
+
const cwd = resolve(argv.cwd || process.cwd());
|
|
19
|
+
const workspace = resolveWorkspaceMetadata(cwd, argv);
|
|
20
|
+
const backend = argv.backend ?? 'codex';
|
|
21
|
+
const mode = 'off';
|
|
22
|
+
const secretaryAutoEnabled = Boolean(argv.auto);
|
|
23
|
+
const workers = normalizeWorkers(argv.worker, backend, argv.target);
|
|
24
|
+
const agentRuntime = resolveSymphonyAgentRuntime(argv);
|
|
25
|
+
const requestedWorkerModel = normalizeOptional(argv.workerModel) ?? normalizeOptional(argv.model);
|
|
26
|
+
assertSymphonyWorkerModelCompatibility(backend, requestedWorkerModel, workers);
|
|
27
|
+
const planInput = {
|
|
28
|
+
objective,
|
|
29
|
+
title: normalizeOptional(argv.title),
|
|
30
|
+
acceptanceCriteria: normalizeAcceptanceCriteria(argv.acceptance),
|
|
31
|
+
workspacePath: cwd,
|
|
32
|
+
workspaceKind: workspace.kind,
|
|
33
|
+
repository: workspace.repository,
|
|
34
|
+
branch: workspace.branch,
|
|
35
|
+
worktree: workspace.worktree,
|
|
36
|
+
baseRevision: workspace.baseRevision,
|
|
37
|
+
environment: {
|
|
38
|
+
kind: 'local-shell',
|
|
39
|
+
label: 'linx-cli',
|
|
40
|
+
runtime: backend,
|
|
17
41
|
},
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
backend,
|
|
43
|
+
mode,
|
|
44
|
+
secretaryAutoEnabled,
|
|
45
|
+
model: normalizeOptional(argv.model),
|
|
46
|
+
workerModel: normalizeOptional(argv.workerModel),
|
|
47
|
+
workerSupervisorIntervalMs: normalizePositiveInteger(argv.workerSupervisorIntervalMs),
|
|
48
|
+
chat: normalizeOptional(argv.chat),
|
|
49
|
+
thread: normalizeOptional(argv.thread),
|
|
50
|
+
messages: normalizeMessages(argv.messages),
|
|
51
|
+
issuer: {
|
|
52
|
+
source: 'user',
|
|
53
|
+
chat: normalizeOptional(argv.chat),
|
|
54
|
+
thread: normalizeOptional(argv.thread),
|
|
55
|
+
messages: normalizeMessages(argv.messages),
|
|
20
56
|
},
|
|
57
|
+
target: argv.target,
|
|
58
|
+
workers,
|
|
21
59
|
};
|
|
60
|
+
const plan = await createSymphonyRunPlanForRuntime(planInput, runtime, argv.signal);
|
|
61
|
+
throwIfAborted(argv.signal);
|
|
62
|
+
const quietProjectionErrors = argv.quietProjectionErrors === true;
|
|
63
|
+
let currentPlan = await persistSymphonyProjectionBestEffort(plan, 'planned', runtime, {
|
|
64
|
+
quiet: quietProjectionErrors,
|
|
65
|
+
signal: argv.signal,
|
|
66
|
+
});
|
|
67
|
+
if (argv.dryRun) {
|
|
68
|
+
throwIfAborted(argv.signal);
|
|
69
|
+
currentPlan = withUpdatedIssue({
|
|
70
|
+
...currentPlan,
|
|
71
|
+
workers: currentPlan.workers.map((worker) => ({
|
|
72
|
+
...worker,
|
|
73
|
+
session: withSymphonySessionStatus(worker.session, 'planned', { dryRun: true }),
|
|
74
|
+
})),
|
|
75
|
+
});
|
|
76
|
+
currentPlan = await persistSymphonyProjectionBestEffort(currentPlan, 'planned', runtime, {
|
|
77
|
+
quiet: quietProjectionErrors,
|
|
78
|
+
signal: argv.signal,
|
|
79
|
+
});
|
|
80
|
+
if (argv.print !== false) {
|
|
81
|
+
printSymphonyRunPlan(currentPlan, { dryRun: true });
|
|
82
|
+
}
|
|
83
|
+
return currentPlan;
|
|
84
|
+
}
|
|
85
|
+
let issue = withSymphonyIssueStatus(currentPlan.issue, 'in_progress');
|
|
86
|
+
currentPlan = { ...currentPlan, issue };
|
|
87
|
+
currentPlan = await persistSymphonyProjectionBestEffort(currentPlan, 'running', runtime, {
|
|
88
|
+
quiet: quietProjectionErrors,
|
|
89
|
+
signal: argv.signal,
|
|
90
|
+
});
|
|
91
|
+
issue = currentPlan.issue;
|
|
92
|
+
try {
|
|
93
|
+
const dispatchedWorkers = [];
|
|
94
|
+
let firstFailure = null;
|
|
95
|
+
for (const [index, worker] of currentPlan.workers.entries()) {
|
|
96
|
+
throwIfAborted(argv.signal);
|
|
97
|
+
const dispatched = await runSymphonyWorker({
|
|
98
|
+
worker,
|
|
99
|
+
issue: currentPlan.issue,
|
|
100
|
+
workerIndex: index + 1,
|
|
101
|
+
workerCount: currentPlan.workers.length,
|
|
102
|
+
secretaryAutoEnabled,
|
|
103
|
+
cwd,
|
|
104
|
+
plain: Boolean(argv.plain),
|
|
105
|
+
agentRuntime,
|
|
106
|
+
model: normalizeOptional(argv.workerModel) ?? normalizeOptional(argv.model),
|
|
107
|
+
credentialSource: normalizeCredentialSource(argv.credentialSource),
|
|
108
|
+
goalMode: argv.workerGoalMode !== false,
|
|
109
|
+
quiet: argv.quietWorkers === true,
|
|
110
|
+
passthroughArgs: (argv['--'] ?? []).map(String),
|
|
111
|
+
commandOverride: normalizeOptional(argv.commandOverride),
|
|
112
|
+
commandEnv: normalizeCommandEnv(argv.commandEnv),
|
|
113
|
+
runtime,
|
|
114
|
+
signal: argv.signal,
|
|
115
|
+
onWorkerDispatched: async (runningWorker) => {
|
|
116
|
+
currentPlan = withUpdatedWorker(currentPlan, runningWorker);
|
|
117
|
+
currentPlan = await persistSymphonyProjectionBestEffort(currentPlan, 'running', runtime, {
|
|
118
|
+
quiet: quietProjectionErrors,
|
|
119
|
+
signal: argv.signal,
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
dispatchedWorkers.push(dispatched.worker);
|
|
124
|
+
currentPlan = withUpdatedWorker(currentPlan, dispatched.worker);
|
|
125
|
+
if (dispatched.exitCode !== 0 && !firstFailure) {
|
|
126
|
+
firstFailure = {
|
|
127
|
+
exitCode: dispatched.exitCode,
|
|
128
|
+
error: `Backend ${dispatched.worker.session.backend} exited with code ${dispatched.exitCode}`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const status = firstFailure ? 'failed' : 'completed';
|
|
133
|
+
issue = withSymphonyIssueStatus(issue, firstFailure ? 'blocked' : 'resolved', firstFailure ? { error: firstFailure.error } : {});
|
|
134
|
+
currentPlan = withUpdatedIssue({
|
|
135
|
+
...currentPlan,
|
|
136
|
+
issue,
|
|
137
|
+
workers: dispatchedWorkers,
|
|
138
|
+
});
|
|
139
|
+
currentPlan = await persistSymphonyProjectionBestEffort(currentPlan, status, runtime, {
|
|
140
|
+
quiet: quietProjectionErrors,
|
|
141
|
+
signal: argv.signal,
|
|
142
|
+
});
|
|
143
|
+
if (argv.print !== false) {
|
|
144
|
+
printSymphonyRunPlan(currentPlan, { dryRun: false });
|
|
145
|
+
}
|
|
146
|
+
if (firstFailure) {
|
|
147
|
+
process.exitCode = firstFailure.exitCode;
|
|
148
|
+
}
|
|
149
|
+
return currentPlan;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (isAbortError(error)) {
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
156
|
+
issue = withSymphonyIssueStatus(issue, 'blocked', { error: message });
|
|
157
|
+
const failedWorker = currentPlan.workers[0];
|
|
158
|
+
if (failedWorker) {
|
|
159
|
+
const worker = {
|
|
160
|
+
task: failedWorker.task,
|
|
161
|
+
taskRecord: withSymphonyTaskStatus(failedWorker.taskRecord, 'failed', { error: message }),
|
|
162
|
+
delivery: withSymphonyDeliveryStatus(failedWorker.delivery, 'failed', { error: message }),
|
|
163
|
+
session: withSymphonySessionStatus(failedWorker.session, 'failed', { error: message, exitCode: 1 }),
|
|
164
|
+
};
|
|
165
|
+
currentPlan = withUpdatedWorker({ ...currentPlan, issue }, worker);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
currentPlan = { ...currentPlan, issue };
|
|
169
|
+
}
|
|
170
|
+
await persistSymphonyProjectionBestEffort(currentPlan, 'failed', runtime, {
|
|
171
|
+
quiet: quietProjectionErrors,
|
|
172
|
+
signal: argv.signal,
|
|
173
|
+
});
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
22
176
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
177
|
+
async function createSymphonyRunPlanForRuntime(input, runtime, signal) {
|
|
178
|
+
throwIfAborted(signal);
|
|
179
|
+
const listIssues = runtime.listOpenSymphonyIssuesFromPod;
|
|
180
|
+
if (!listIssues) {
|
|
181
|
+
return createSymphonyRunPlanDraft(input);
|
|
182
|
+
}
|
|
183
|
+
let podIssues;
|
|
184
|
+
try {
|
|
185
|
+
podIssues = await withAbortSignal(listIssues(), signal);
|
|
186
|
+
throwIfAborted(signal);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (isAbortError(error)) {
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
podIssues = null;
|
|
193
|
+
}
|
|
194
|
+
if (!podIssues) {
|
|
195
|
+
return createSymphonyRunPlanDraft(input);
|
|
196
|
+
}
|
|
197
|
+
const plan = createRunPlan(input);
|
|
198
|
+
const triage = triageSymphonyIssue({
|
|
199
|
+
objective: input.objective,
|
|
200
|
+
chat: input.chat,
|
|
201
|
+
thread: input.thread,
|
|
202
|
+
workspacePath: input.workspacePath,
|
|
203
|
+
issues: podIssues,
|
|
204
|
+
});
|
|
205
|
+
return triage.action === 'update' && triage.issue
|
|
206
|
+
? attachSymphonyRunPlanToIssue(plan, triage.issue)
|
|
207
|
+
: plan;
|
|
31
208
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
209
|
+
async function runSymphonyWorker(input) {
|
|
210
|
+
throwIfAborted(input.signal);
|
|
211
|
+
const beforeAutoModeIds = new Set(input.runtime.listAutoModeSessions().map((record) => record.id));
|
|
212
|
+
const cwd = input.worker.session.cwd || input.cwd;
|
|
213
|
+
const workspace = input.worker.session.workspace ?? {
|
|
214
|
+
path: cwd,
|
|
215
|
+
kind: 'folder',
|
|
216
|
+
};
|
|
217
|
+
const prompt = renderSymphonyRuntimePrompt({
|
|
218
|
+
issue: input.issue,
|
|
219
|
+
task: input.worker.task,
|
|
220
|
+
objective: input.worker.taskRecord.objective,
|
|
221
|
+
acceptanceCriteria: input.worker.taskRecord.acceptanceCriteria,
|
|
222
|
+
workspace,
|
|
223
|
+
backend: input.worker.session.backend,
|
|
224
|
+
mode: input.worker.session.mode,
|
|
225
|
+
secretaryAutoEnabled: input.worker.session.secretaryAutoEnabled ?? input.secretaryAutoEnabled,
|
|
226
|
+
session: input.worker.session.uri,
|
|
227
|
+
target: input.worker.session.target,
|
|
228
|
+
issuer: input.issue.issuer,
|
|
229
|
+
...(input.workerCount > 1 ? {
|
|
230
|
+
workerIndex: input.workerIndex,
|
|
231
|
+
workerCount: input.workerCount,
|
|
232
|
+
} : {}),
|
|
233
|
+
});
|
|
234
|
+
let exitCode = null;
|
|
235
|
+
let wakeError;
|
|
236
|
+
let runningDelivery = input.worker.delivery;
|
|
237
|
+
let runningSession = input.worker.session;
|
|
238
|
+
let runningTask = input.worker.taskRecord;
|
|
239
|
+
const cycle = await runThreadReconcilerCycle({
|
|
240
|
+
policy: {
|
|
241
|
+
kind: 'symphony',
|
|
242
|
+
assignedWorkerAgent: input.worker.delivery.targetAgent,
|
|
243
|
+
secretaryAgent: '__secretary__',
|
|
244
|
+
},
|
|
245
|
+
handleWakeJob: async ({ decisionSummary, record }) => {
|
|
246
|
+
try {
|
|
247
|
+
throwIfAborted(input.signal);
|
|
248
|
+
exitCode = await input.runtime.runAutoMode({
|
|
249
|
+
backend: input.worker.session.backend,
|
|
250
|
+
autoEnabled: input.secretaryAutoEnabled,
|
|
251
|
+
mode: 'off',
|
|
252
|
+
cwd,
|
|
253
|
+
plain: input.plain,
|
|
254
|
+
model: normalizeOptional(input.worker.session.model) ?? input.model,
|
|
255
|
+
credentialSource: input.credentialSource,
|
|
256
|
+
prompt,
|
|
257
|
+
goalMode: input.goalMode,
|
|
258
|
+
quiet: input.quiet,
|
|
259
|
+
passthroughArgs: input.passthroughArgs,
|
|
260
|
+
...(input.commandOverride ? { commandOverride: input.commandOverride } : {}),
|
|
261
|
+
...(input.commandEnv ? { commandEnv: input.commandEnv } : {}),
|
|
262
|
+
metadata: {
|
|
263
|
+
symphony: {
|
|
264
|
+
issue: input.issue.uri,
|
|
265
|
+
task: input.worker.task,
|
|
266
|
+
delivery: input.worker.delivery.uri,
|
|
267
|
+
session: input.worker.session.uri,
|
|
268
|
+
...(input.agentRuntime ? { agentRuntime: input.agentRuntime } : {}),
|
|
269
|
+
...(input.worker.session.model ? { workerModel: input.worker.session.model } : {}),
|
|
270
|
+
...(input.worker.session.supervisor ? { supervisor: input.worker.session.supervisor } : {}),
|
|
271
|
+
},
|
|
272
|
+
reconciler: decisionSummary,
|
|
273
|
+
scheduler: {
|
|
274
|
+
wakeRecord: summarizeWakeJobExecutionRecord(record),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
278
|
+
});
|
|
279
|
+
throwIfAborted(input.signal);
|
|
280
|
+
return { exitCode };
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
wakeError = error;
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
event: createSymphonyWorkerDispatchEvent(input.worker),
|
|
288
|
+
dispatchOptions: {
|
|
289
|
+
randomId: `${input.worker.delivery.uri}-dispatch`,
|
|
290
|
+
},
|
|
291
|
+
onDispatched: (dispatch) => {
|
|
292
|
+
throwIfAborted(input.signal);
|
|
293
|
+
runningDelivery = appendSymphonyReconcilerDecision(withSymphonyDeliveryStatus(input.worker.delivery, 'dispatched'), dispatch.summary);
|
|
294
|
+
runningSession = appendSymphonyReconcilerDecision(withSymphonySessionStatus(input.worker.session, 'running'), dispatch.summary);
|
|
295
|
+
runningTask = appendSymphonyReconcilerDecision(withSymphonyTaskStatus(input.worker.taskRecord, 'running'), dispatch.summary);
|
|
296
|
+
return input.onWorkerDispatched?.({
|
|
297
|
+
task: input.worker.task,
|
|
298
|
+
taskRecord: runningTask,
|
|
299
|
+
delivery: runningDelivery,
|
|
300
|
+
session: runningSession,
|
|
98
301
|
});
|
|
99
302
|
},
|
|
100
|
-
|
|
101
|
-
|
|
303
|
+
});
|
|
304
|
+
const dispatchScheduler = cycle.schedulerSummary;
|
|
305
|
+
if (dispatchScheduler.failed.length > 0) {
|
|
306
|
+
throw wakeError ?? new Error(String(dispatchScheduler.failed[0]?.error ?? 'Symphony worker wake job failed'));
|
|
307
|
+
}
|
|
308
|
+
if (exitCode === null) {
|
|
309
|
+
throw new Error('Symphony worker was not awakened by the Thread Reconciler.');
|
|
310
|
+
}
|
|
311
|
+
throwIfAborted(input.signal);
|
|
312
|
+
const autoModeSessionId = resolveCreatedAutoModeSessionId(beforeAutoModeIds, input.runtime);
|
|
313
|
+
const status = exitCode === 0 ? 'completed' : 'failed';
|
|
314
|
+
const statusDecision = await dispatchSymphonyWorkerStatusDecision({
|
|
315
|
+
worker: {
|
|
316
|
+
task: input.worker.task,
|
|
317
|
+
taskRecord: runningTask,
|
|
318
|
+
delivery: runningDelivery,
|
|
319
|
+
session: runningSession,
|
|
320
|
+
},
|
|
321
|
+
status,
|
|
322
|
+
exitCode,
|
|
323
|
+
autoModeSessionId,
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
status,
|
|
327
|
+
exitCode,
|
|
328
|
+
worker: {
|
|
329
|
+
task: input.worker.task,
|
|
330
|
+
taskRecord: appendSymphonyReconcilerDecision(withSymphonyTaskStatus(runningTask, status, {
|
|
331
|
+
...(exitCode === 0 ? {} : { error: `Backend exited with code ${exitCode}` }),
|
|
332
|
+
}), statusDecision.decision),
|
|
333
|
+
delivery: appendSymphonyReconcilerDecision(withSymphonyDeliveryStatus(runningDelivery, status, {
|
|
334
|
+
autoModeSessionId,
|
|
335
|
+
...(exitCode === 0 ? {} : { error: `Backend exited with code ${exitCode}` }),
|
|
336
|
+
}), statusDecision.decision),
|
|
337
|
+
session: appendSymphonyReconcilerDecision(withSymphonySessionStatus(runningSession, status, {
|
|
338
|
+
autoModeSessionId,
|
|
339
|
+
exitCode,
|
|
340
|
+
...(exitCode === 0 ? {} : { error: `Backend exited with code ${exitCode}` }),
|
|
341
|
+
}), statusDecision.decision),
|
|
102
342
|
},
|
|
103
343
|
};
|
|
104
344
|
}
|
|
105
|
-
function
|
|
345
|
+
function createSymphonyWorkerDispatchEvent(worker) {
|
|
106
346
|
return {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
347
|
+
type: 'delivery.submitted',
|
|
348
|
+
...(worker.delivery.chat ? { chat: worker.delivery.chat } : {}),
|
|
349
|
+
...(worker.delivery.thread ? { thread: worker.delivery.thread } : {}),
|
|
350
|
+
resource: worker.delivery.uri,
|
|
351
|
+
actor: {
|
|
352
|
+
id: '__secretary__',
|
|
353
|
+
role: 'secretary',
|
|
354
|
+
},
|
|
355
|
+
data: {
|
|
356
|
+
deliveryType: worker.delivery.type,
|
|
357
|
+
issue: worker.delivery.issue,
|
|
358
|
+
task: worker.delivery.task,
|
|
359
|
+
delivery: worker.delivery.uri,
|
|
360
|
+
session: worker.session.uri,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async function dispatchSymphonyWorkerStatusDecision(input) {
|
|
365
|
+
let wakeError;
|
|
366
|
+
const cycle = await runThreadReconcilerCycle({
|
|
367
|
+
policy: {
|
|
368
|
+
kind: 'symphony',
|
|
369
|
+
secretaryAgent: '__secretary__',
|
|
111
370
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
371
|
+
handleWakeJob: ({ job }) => {
|
|
372
|
+
try {
|
|
373
|
+
return {
|
|
374
|
+
targetAgent: job.targetAgent,
|
|
375
|
+
targetRole: job.targetRole,
|
|
376
|
+
status: input.status,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
wakeError = error;
|
|
381
|
+
throw error;
|
|
117
382
|
}
|
|
118
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
119
383
|
},
|
|
384
|
+
event: createSymphonyWorkerStatusEvent(input),
|
|
385
|
+
dispatchOptions: {
|
|
386
|
+
randomId: `${input.worker.delivery.uri}-${input.status}`,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
const scheduler = cycle.schedulerSummary;
|
|
390
|
+
if (scheduler.failed.length > 0) {
|
|
391
|
+
throw wakeError ?? new Error(String(scheduler.failed[0]?.error ?? 'Symphony status wake job failed'));
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
decision: cycle.summary,
|
|
395
|
+
scheduler,
|
|
120
396
|
};
|
|
121
397
|
}
|
|
122
|
-
function
|
|
398
|
+
function createSymphonyWorkerStatusEvent(input) {
|
|
123
399
|
return {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
400
|
+
type: input.status === 'completed' ? 'delivery.completed' : 'delivery.failed',
|
|
401
|
+
...(input.worker.delivery.chat ? { chat: input.worker.delivery.chat } : {}),
|
|
402
|
+
...(input.worker.delivery.thread ? { thread: input.worker.delivery.thread } : {}),
|
|
403
|
+
resource: input.worker.delivery.uri,
|
|
404
|
+
actor: {
|
|
405
|
+
id: input.worker.delivery.targetAgent,
|
|
406
|
+
role: 'worker',
|
|
131
407
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
process.stdout.write(`${JSON.stringify(resolved.record, null, 2)}\n`);
|
|
408
|
+
data: {
|
|
409
|
+
issue: input.worker.delivery.issue,
|
|
410
|
+
task: input.worker.delivery.task,
|
|
411
|
+
delivery: input.worker.delivery.uri,
|
|
412
|
+
session: input.worker.session.uri,
|
|
413
|
+
autoModeSessionId: input.autoModeSessionId,
|
|
414
|
+
exitCode: input.exitCode,
|
|
140
415
|
},
|
|
141
416
|
};
|
|
142
417
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const mode = argv.auto ? 'auto' : 'manual';
|
|
149
|
-
const plan = createArchivedSymphonyRunPlan({
|
|
150
|
-
objective,
|
|
151
|
-
title: normalizeOptional(argv.title),
|
|
152
|
-
acceptanceCriteria: normalizeAcceptanceCriteria(argv.acceptance),
|
|
153
|
-
workspacePath: cwd,
|
|
154
|
-
workspaceKind: workspace.kind,
|
|
155
|
-
repository: workspace.repository,
|
|
156
|
-
branch: workspace.branch,
|
|
157
|
-
worktree: workspace.worktree,
|
|
158
|
-
backend,
|
|
159
|
-
mode,
|
|
160
|
-
model: normalizeOptional(argv.model),
|
|
161
|
-
});
|
|
162
|
-
if (argv.dryRun) {
|
|
163
|
-
updateSymphonySessionStatus(plan.session, 'planned', { dryRun: true });
|
|
164
|
-
printSymphonyRunPlan(plan, { dryRun: true });
|
|
418
|
+
async function persistSymphonyProjectionBestEffort(plan, stage, runtime, options = {}) {
|
|
419
|
+
throwIfAborted(options.signal);
|
|
420
|
+
const persist = runtime.persistSymphonyProjectionToPod;
|
|
421
|
+
if (!persist) {
|
|
422
|
+
writeSymphonyRunPlan(plan);
|
|
165
423
|
return plan;
|
|
166
424
|
}
|
|
167
|
-
let issue = updateSymphonyIssueStatus(plan.issue, 'in_progress');
|
|
168
|
-
let delivery = updateSymphonyDeliveryStatus(plan.delivery, 'dispatched');
|
|
169
|
-
let session = updateSymphonySessionStatus(plan.session, 'running');
|
|
170
|
-
const beforeAutoModeIds = new Set(runtime.listAutoModeSessions().map((record) => record.id));
|
|
171
425
|
try {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
plain: Boolean(argv.plain),
|
|
178
|
-
model: normalizeOptional(argv.model),
|
|
179
|
-
prompt: plan.delivery.projection.prompt,
|
|
180
|
-
goalMode: true,
|
|
181
|
-
passthroughArgs: (argv['--'] ?? []).map(String),
|
|
182
|
-
});
|
|
183
|
-
const autoModeSessionId = resolveCreatedAutoModeSessionId(beforeAutoModeIds, runtime);
|
|
184
|
-
const status = exitCode === 0 ? 'completed' : 'failed';
|
|
185
|
-
issue = updateSymphonyIssueStatus(issue, exitCode === 0 ? 'resolved' : 'blocked', exitCode === 0 ? {} : { error: `Backend exited with code ${exitCode}` });
|
|
186
|
-
delivery = updateSymphonyDeliveryStatus(delivery, status, {
|
|
187
|
-
autoModeSessionId,
|
|
188
|
-
...(exitCode === 0 ? {} : { error: `Backend exited with code ${exitCode}` }),
|
|
189
|
-
});
|
|
190
|
-
session = updateSymphonySessionStatus(session, status, {
|
|
191
|
-
autoModeSessionId,
|
|
192
|
-
exitCode,
|
|
193
|
-
...(exitCode === 0 ? {} : { error: `Backend exited with code ${exitCode}` }),
|
|
194
|
-
});
|
|
195
|
-
printSymphonyRunPlan({ issue, task: plan.task, delivery, session }, { dryRun: false });
|
|
196
|
-
if (exitCode !== 0) {
|
|
197
|
-
process.exitCode = exitCode;
|
|
426
|
+
const result = await withAbortSignal(persist(plan, { stage }), options.signal);
|
|
427
|
+
throwIfAborted(options.signal);
|
|
428
|
+
if (!result) {
|
|
429
|
+
writeSymphonyRunPlan(plan);
|
|
430
|
+
return plan;
|
|
198
431
|
}
|
|
199
|
-
|
|
432
|
+
await mirrorSymphonyProjectionBestEffort(result, runtime, options);
|
|
433
|
+
return result.plan;
|
|
200
434
|
}
|
|
201
435
|
catch (error) {
|
|
436
|
+
if (isAbortError(error)) {
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
202
439
|
const message = error instanceof Error ? error.message : String(error);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
440
|
+
if (!options.quiet) {
|
|
441
|
+
process.stderr.write(`[symphony] Pod projection skipped: ${message}\n`);
|
|
442
|
+
}
|
|
443
|
+
writeSymphonyRunPlan(plan);
|
|
444
|
+
return plan;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function mirrorSymphonyProjectionBestEffort(result, runtime, options = {}) {
|
|
448
|
+
throwIfAborted(options.signal);
|
|
449
|
+
if (!result) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const mirror = runtime.mirrorSymphonyProjectionJsonLdFromPod;
|
|
453
|
+
if (!mirror) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
await withAbortSignal(mirror(result), options.signal);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
if (isAbortError(error)) {
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
464
|
+
if (!options.quiet) {
|
|
465
|
+
process.stderr.write(`[symphony] Pod JSON-LD mirror skipped: ${message}\n`);
|
|
466
|
+
}
|
|
207
467
|
}
|
|
208
468
|
}
|
|
469
|
+
function withUpdatedIssue(plan) {
|
|
470
|
+
const primary = plan.workers[0];
|
|
471
|
+
if (!primary) {
|
|
472
|
+
return plan;
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
...plan,
|
|
476
|
+
task: primary.task,
|
|
477
|
+
delivery: primary.delivery,
|
|
478
|
+
session: primary.session,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function withUpdatedWorker(plan, worker) {
|
|
482
|
+
const workers = plan.workers.map((candidate) => (candidate.session.uri === worker.session.uri ? worker : candidate));
|
|
483
|
+
return withUpdatedIssue({
|
|
484
|
+
...plan,
|
|
485
|
+
workers,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
209
488
|
function printSymphonyRunPlan(plan, options) {
|
|
210
|
-
process.stdout.write(options.dryRun ? 'LinX Symphony dry-run\n' : 'LinX Symphony
|
|
211
|
-
process.stdout.write(`
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
489
|
+
process.stdout.write(options.dryRun ? 'LinX Secretary Symphony dry-run\n' : 'LinX Secretary Symphony dispatch\n');
|
|
490
|
+
process.stdout.write(`work: ${formatSymphonyRecordSummary('issue', plan.issue)}\n`);
|
|
491
|
+
for (const [index, worker] of plan.workers.entries()) {
|
|
492
|
+
const prefix = plan.workers.length > 1 ? `worker ${index + 1}/${plan.workers.length}` : 'worker';
|
|
493
|
+
if (worker.taskRecord) {
|
|
494
|
+
process.stdout.write(`${prefix}: ${formatSymphonyRecordSummary('task', worker.taskRecord)}\n`);
|
|
495
|
+
}
|
|
496
|
+
process.stdout.write(`task: ${worker.task}\n`);
|
|
497
|
+
process.stdout.write(`dispatch: ${formatSymphonyRecordSummary('delivery', worker.delivery)}\n`);
|
|
498
|
+
process.stdout.write(`session: ${formatSymphonyRecordSummary('session', worker.session)}\n`);
|
|
499
|
+
}
|
|
500
|
+
process.stdout.write(`local mirror/cache: ${getSymphonyHome()}\n`);
|
|
216
501
|
if (options.dryRun) {
|
|
217
502
|
process.stdout.write('\nProjected runtime prompt:\n');
|
|
218
503
|
process.stdout.write(`${plan.delivery.projection.prompt}\n`);
|
|
@@ -224,6 +509,52 @@ function resolveCreatedAutoModeSessionId(beforeIds, runtime) {
|
|
|
224
509
|
.sort((left, right) => right.startedAt.localeCompare(left.startedAt));
|
|
225
510
|
return created[0]?.id;
|
|
226
511
|
}
|
|
512
|
+
function assertSymphonyWorkerModelCompatibility(fallbackBackend, workerModel, workers) {
|
|
513
|
+
if (!workerModel || !isNonCodexProviderModel(workerModel)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const targets = workers && workers.length > 0 ? workers : [{ backend: fallbackBackend }];
|
|
517
|
+
const codexTarget = targets.find((target) => (target.backend ?? fallbackBackend) === 'codex');
|
|
518
|
+
if (codexTarget) {
|
|
519
|
+
throw new Error(`codex backend cannot run worker model ${workerModel}. Use backend claude/cc or linx for provider-routed models.`);
|
|
520
|
+
}
|
|
521
|
+
const claudeTarget = targets.find((target) => (target.backend ?? fallbackBackend) === 'claude');
|
|
522
|
+
if (claudeTarget && isProviderRoutedClaudeModel(workerModel)) {
|
|
523
|
+
throw new Error(`claude backend cannot set provider-routed worker model ${workerModel}. Configure it behind a Claude Code alias such as opus, or omit the worker model.`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function isNonCodexProviderModel(model) {
|
|
527
|
+
return /(?:deepseek|claude|qwen|gemini|kimi|moonshot|mistral|grok|glm|minimax)/iu.test(model);
|
|
528
|
+
}
|
|
529
|
+
function isProviderRoutedClaudeModel(model) {
|
|
530
|
+
return /(?:deepseek|qwen|gemini|kimi|moonshot|mistral|grok|glm|minimax)/iu.test(model);
|
|
531
|
+
}
|
|
532
|
+
function normalizeWorkers(values, fallbackBackend, explicitTarget) {
|
|
533
|
+
if (!values || values.length === 0) {
|
|
534
|
+
return explicitTarget ? [explicitTarget] : undefined;
|
|
535
|
+
}
|
|
536
|
+
return values.map((value) => {
|
|
537
|
+
const [backendPart, agentPart] = value.split(':', 2);
|
|
538
|
+
const backend = normalizeBackend(backendPart) ?? fallbackBackend;
|
|
539
|
+
const agent = normalizeOptional(agentPart) ?? `${backend}-worker`;
|
|
540
|
+
return {
|
|
541
|
+
source: 'explicit-backend',
|
|
542
|
+
backend,
|
|
543
|
+
agent,
|
|
544
|
+
label: agent,
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
function normalizeBackend(value) {
|
|
549
|
+
const normalized = normalizeOptional(value);
|
|
550
|
+
if (normalized === 'cc') {
|
|
551
|
+
return 'claude';
|
|
552
|
+
}
|
|
553
|
+
if (normalized === 'linx' || normalized === 'codex' || normalized === 'claude' || normalized === 'codebuddy') {
|
|
554
|
+
return normalized;
|
|
555
|
+
}
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
227
558
|
function normalizeObjective(parts) {
|
|
228
559
|
const objective = (parts ?? [])
|
|
229
560
|
.filter((item) => typeof item === 'string')
|
|
@@ -231,7 +562,7 @@ function normalizeObjective(parts) {
|
|
|
231
562
|
.replace(/\s+/g, ' ')
|
|
232
563
|
.trim();
|
|
233
564
|
if (!objective) {
|
|
234
|
-
throw new Error('
|
|
565
|
+
throw new Error('Symphony objective is required');
|
|
235
566
|
}
|
|
236
567
|
return objective;
|
|
237
568
|
}
|
|
@@ -242,10 +573,102 @@ function normalizeAcceptanceCriteria(value) {
|
|
|
242
573
|
.map((item) => item.trim())
|
|
243
574
|
.filter(Boolean);
|
|
244
575
|
}
|
|
576
|
+
function normalizeMessages(value) {
|
|
577
|
+
const messages = (value ?? [])
|
|
578
|
+
.map((item) => normalizeOptional(item))
|
|
579
|
+
.filter((item) => Boolean(item));
|
|
580
|
+
return messages.length > 0 ? messages : undefined;
|
|
581
|
+
}
|
|
582
|
+
function resolveSymphonyAgentRuntime(argv) {
|
|
583
|
+
const explicit = normalizeAgentRuntimeConfig(argv.agentRuntime);
|
|
584
|
+
const model = explicit?.model
|
|
585
|
+
?? normalizeOptional(argv.secretaryModel)
|
|
586
|
+
?? normalizeOptional(argv.model);
|
|
587
|
+
if (!explicit && !model) {
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
backend: explicit?.backend ?? 'linx',
|
|
592
|
+
credentialSource: explicit?.credentialSource ?? 'cloud',
|
|
593
|
+
...explicit,
|
|
594
|
+
...(model ? { model } : {}),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function normalizeAgentRuntimeConfig(value) {
|
|
598
|
+
if (!value) {
|
|
599
|
+
return undefined;
|
|
600
|
+
}
|
|
601
|
+
const metadata = isRecord(value.metadata) ? { ...value.metadata } : undefined;
|
|
602
|
+
const resolved = {
|
|
603
|
+
...(normalizeOptional(value.backend) ? { backend: normalizeOptional(value.backend) } : {}),
|
|
604
|
+
...(normalizeOptional(value.model) ? { model: normalizeOptional(value.model) } : {}),
|
|
605
|
+
...(normalizeOptional(value.credentialSource) ? { credentialSource: normalizeOptional(value.credentialSource) } : {}),
|
|
606
|
+
...(normalizeOptional(value.runtime) ? { runtime: normalizeOptional(value.runtime) } : {}),
|
|
607
|
+
...(normalizeOptional(value.transport) ? { transport: normalizeOptional(value.transport) } : {}),
|
|
608
|
+
...(normalizeOptional(value.endpoint) ? { endpoint: normalizeOptional(value.endpoint) } : {}),
|
|
609
|
+
...(metadata ? { metadata } : {}),
|
|
610
|
+
};
|
|
611
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
612
|
+
}
|
|
245
613
|
function normalizeOptional(value) {
|
|
246
614
|
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
247
615
|
return normalized || undefined;
|
|
248
616
|
}
|
|
617
|
+
function isRecord(value) {
|
|
618
|
+
return typeof value === 'object' && value !== null;
|
|
619
|
+
}
|
|
620
|
+
function normalizeCommandEnv(value) {
|
|
621
|
+
if (!value) {
|
|
622
|
+
return undefined;
|
|
623
|
+
}
|
|
624
|
+
const entries = Object.entries(value)
|
|
625
|
+
.map(([key, entryValue]) => [key.trim(), String(entryValue)])
|
|
626
|
+
.filter(([key]) => Boolean(key));
|
|
627
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
628
|
+
}
|
|
629
|
+
function normalizeCredentialSource(value) {
|
|
630
|
+
return value === 'local' ? 'local' : value === 'cloud' ? 'cloud' : undefined;
|
|
631
|
+
}
|
|
632
|
+
function normalizePositiveInteger(value) {
|
|
633
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
634
|
+
return undefined;
|
|
635
|
+
}
|
|
636
|
+
const normalized = Math.trunc(value);
|
|
637
|
+
return normalized > 0 ? normalized : undefined;
|
|
638
|
+
}
|
|
639
|
+
function createAbortError(message = 'The operation was aborted.') {
|
|
640
|
+
const error = new Error(message);
|
|
641
|
+
error.name = 'AbortError';
|
|
642
|
+
return error;
|
|
643
|
+
}
|
|
644
|
+
function isAbortError(error) {
|
|
645
|
+
return error instanceof Error && (error.name === 'AbortError' || error.message.toLowerCase().includes('aborted'));
|
|
646
|
+
}
|
|
647
|
+
function throwIfAborted(signal) {
|
|
648
|
+
if (!signal?.aborted) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const reason = signal.reason;
|
|
652
|
+
if (reason instanceof Error) {
|
|
653
|
+
throw reason;
|
|
654
|
+
}
|
|
655
|
+
throw createAbortError(typeof reason === 'string' && reason.trim() ? reason : undefined);
|
|
656
|
+
}
|
|
657
|
+
function withAbortSignal(promise, signal) {
|
|
658
|
+
if (!signal) {
|
|
659
|
+
return promise;
|
|
660
|
+
}
|
|
661
|
+
throwIfAborted(signal);
|
|
662
|
+
return new Promise((resolve, reject) => {
|
|
663
|
+
const onAbort = () => reject(signal.reason instanceof Error
|
|
664
|
+
? signal.reason
|
|
665
|
+
: createAbortError(typeof signal.reason === 'string' && signal.reason.trim() ? signal.reason : undefined));
|
|
666
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
667
|
+
promise
|
|
668
|
+
.then(resolve, reject)
|
|
669
|
+
.finally(() => signal.removeEventListener('abort', onAbort));
|
|
670
|
+
});
|
|
671
|
+
}
|
|
249
672
|
function resolveWorkspaceMetadata(cwd, argv) {
|
|
250
673
|
const git = isGitWorkspace(cwd);
|
|
251
674
|
return {
|
|
@@ -253,6 +676,7 @@ function resolveWorkspaceMetadata(cwd, argv) {
|
|
|
253
676
|
repository: normalizeOptional(argv.repository) ?? (git ? gitOutput(cwd, ['remote', 'get-url', 'origin']) : undefined),
|
|
254
677
|
branch: normalizeOptional(argv.branch) ?? (git ? gitOutput(cwd, ['branch', '--show-current']) : undefined),
|
|
255
678
|
worktree: normalizeOptional(argv.worktree) ?? (git ? gitOutput(cwd, ['rev-parse', '--show-toplevel']) : undefined),
|
|
679
|
+
baseRevision: git ? gitOutput(cwd, ['rev-parse', 'HEAD']) : undefined,
|
|
256
680
|
};
|
|
257
681
|
}
|
|
258
682
|
function isGitWorkspace(cwd) {
|