@undefineds.co/linx 0.3.20 → 0.3.23

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 (99) hide show
  1. package/dist/generated/version.js +1 -1
  2. package/dist/index.js +6 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/auto-mode/pod-persistence.js +53 -3
  5. package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
  6. package/dist/lib/auto-mode/secretary.js +2 -2
  7. package/dist/lib/auto-mode/secretary.js.map +1 -1
  8. package/dist/lib/chat-api.js +23 -61
  9. package/dist/lib/chat-api.js.map +1 -1
  10. package/dist/lib/codex-plugin/index.js +1 -0
  11. package/dist/lib/codex-plugin/index.js.map +1 -1
  12. package/dist/lib/codex-plugin/symphony-mcp.js +335 -0
  13. package/dist/lib/codex-plugin/symphony-mcp.js.map +1 -0
  14. package/dist/lib/linx-cloud-errors.js +0 -5
  15. package/dist/lib/linx-cloud-errors.js.map +1 -1
  16. package/dist/lib/linx-status-line.js +1 -8
  17. package/dist/lib/linx-status-line.js.map +1 -1
  18. package/dist/lib/linx-tui-contract.js +2 -1
  19. package/dist/lib/linx-tui-contract.js.map +1 -1
  20. package/dist/lib/models.js +3 -2
  21. package/dist/lib/models.js.map +1 -1
  22. package/dist/lib/pi-adapter/auth.js +68 -0
  23. package/dist/lib/pi-adapter/auth.js.map +1 -0
  24. package/dist/lib/pi-adapter/branding.js +67 -110
  25. package/dist/lib/pi-adapter/branding.js.map +1 -1
  26. package/dist/lib/pi-adapter/interactive.js +341 -101
  27. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  28. package/dist/lib/pi-adapter/pod-mirror.js +38 -107
  29. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  30. package/dist/lib/pi-adapter/pod-native.js +2 -0
  31. package/dist/lib/pi-adapter/pod-native.js.map +1 -1
  32. package/dist/lib/pi-adapter/pod-tools.js +140 -0
  33. package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
  34. package/dist/lib/pi-adapter/runtime.js +2 -12
  35. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  36. package/dist/lib/pi-adapter/session.js +13 -17
  37. package/dist/lib/pi-adapter/session.js.map +1 -1
  38. package/dist/lib/pi-adapter/stream.js +2 -20
  39. package/dist/lib/pi-adapter/stream.js.map +1 -1
  40. package/dist/lib/pod-chat-store.js +53 -4
  41. package/dist/lib/pod-chat-store.js.map +1 -1
  42. package/dist/lib/resource-identity.js +2 -0
  43. package/dist/lib/resource-identity.js.map +1 -0
  44. package/dist/lib/status-line-command.js +2 -2
  45. package/dist/lib/status-line-command.js.map +1 -1
  46. package/dist/lib/symphony/archive.js +15 -37
  47. package/dist/lib/symphony/archive.js.map +1 -1
  48. package/dist/lib/symphony/pod-projection.js +189 -1346
  49. package/dist/lib/symphony/pod-projection.js.map +1 -1
  50. package/dist/lib/symphony-command.js +209 -109
  51. package/dist/lib/symphony-command.js.map +1 -1
  52. package/dist/plugins/linx-symphony-codex/.codex-plugin/plugin.json +38 -0
  53. package/dist/plugins/linx-symphony-codex/.mcp.json +10 -0
  54. package/dist/plugins/linx-symphony-codex/README.md +9 -0
  55. package/dist/plugins/linx-symphony-codex/hooks.json +60 -0
  56. package/dist/plugins/linx-symphony-codex/scripts/symphony-hook-events.mjs +119 -0
  57. package/dist/plugins/linx-symphony-codex/scripts/symphony-mcp.mjs +335 -0
  58. package/dist/plugins/linx-symphony-codex/skills/symphony/SKILL.md +791 -0
  59. package/dist/skills/symphony/SKILL.md +7 -0
  60. package/dist/skills/xpod-cli/SKILL.md +2 -13
  61. package/package.json +4 -4
  62. package/vendor/agent-runtime/dist/chat-reconciler.d.ts +33 -0
  63. package/vendor/agent-runtime/dist/chat-reconciler.js +108 -0
  64. package/vendor/agent-runtime/dist/index.d.ts +4 -1
  65. package/vendor/agent-runtime/dist/index.js +4 -1
  66. package/vendor/agent-runtime/dist/matrix-client.d.ts +149 -0
  67. package/vendor/agent-runtime/dist/matrix-client.js +220 -0
  68. package/vendor/agent-runtime/dist/pod-resource-identity.d.ts +17 -0
  69. package/vendor/agent-runtime/dist/pod-resource-identity.js +54 -0
  70. package/vendor/agent-runtime/dist/reconciler.d.ts +0 -11
  71. package/vendor/agent-runtime/dist/reconciler.js +5 -43
  72. package/vendor/agent-runtime/dist/symphony.d.ts +272 -27
  73. package/vendor/agent-runtime/dist/symphony.js +1268 -21
  74. package/vendor/agent-runtime/dist/workspace.d.ts +61 -0
  75. package/vendor/agent-runtime/dist/workspace.js +81 -0
  76. package/vendor/agent-runtime/package.json +5 -1
  77. package/vendor/stores/dist/current-pod-base.d.ts +2 -0
  78. package/vendor/stores/dist/current-pod-base.js +14 -0
  79. package/vendor/stores/dist/exact-records.d.ts +7 -0
  80. package/vendor/stores/dist/exact-records.js +87 -0
  81. package/vendor/stores/dist/index.d.ts +1 -0
  82. package/vendor/stores/dist/index.js +1 -0
  83. package/vendor/stores/dist/login.d.ts +51 -0
  84. package/vendor/stores/dist/login.js +195 -0
  85. package/vendor/stores/dist/pod-collection.d.ts +28 -0
  86. package/vendor/stores/dist/pod-collection.js +194 -0
  87. package/vendor/stores/dist/pod-write-guard.d.ts +5 -0
  88. package/vendor/stores/dist/pod-write-guard.js +133 -0
  89. package/vendor/stores/dist/symphony-control.d.ts +245 -0
  90. package/vendor/stores/dist/symphony-control.js +2175 -0
  91. package/vendor/stores/package.json +14 -0
  92. package/dist/lib/capture/persistence.js +0 -377
  93. package/dist/lib/capture/persistence.js.map +0 -1
  94. package/dist/lib/capture/tool.js +0 -242
  95. package/dist/lib/capture/tool.js.map +0 -1
  96. package/dist/skills/basic/SKILL.md +0 -46
  97. package/dist/skills/capture/SKILL.md +0 -165
  98. package/vendor/agent-runtime/dist/coordination.d.ts +0 -93
  99. package/vendor/agent-runtime/dist/coordination.js +0 -145
@@ -0,0 +1,2175 @@
1
+ import { completeSymphonyWorkerRun, finalizeSymphonyRunPlanAfterWorkers, getSymphonyArchiveKey, normalizeSymphonyRuntimeDeliveryResult, parseSymphonyRuntimeDeliveryResult, recordSymphonyWorkerRuntimeEvent, startSymphonyWorkerRun, } from '../../agent-runtime/dist/symphony.js';
2
+ import { decideThreadControlEvent } from '../../agent-runtime/dist/thread-reconciler-controller.js';
3
+ import { autoModeApprovalActionUri, autoModeApprovalRequestMessage, autoModeApprovalRisk, autoModeApprovalToolName, encodeAutoModeApprovalOptions, } from '../../agent-runtime/dist/auto-mode.js';
4
+ import { agentResource, approvalResource, chatResource, chatRepository, ContactClass, ContactType, contactResource, deliveryResource, evidenceResource, inboxNotificationResource, inputRequestResource, issueResource, messageResource, ReportKind, ReportOutcome, ReportStatus, reportResource, runResource, runStepResource, sessionResource, taskResource, threadResource, threadRepository, } from '@undefineds.co/models';
5
+ import { insertExactRecordOnce, resolvePodResourceTemplateValue, upsertExactRecord, } from '@undefineds.co/drizzle-solid';
6
+ export const SYMPHONY_SECRETARY_AGENT_ID = '__secretary__';
7
+ export const SYMPHONY_CHAT_ID = 'symphony';
8
+ export const SYMPHONY_POLICY_VERSION = 'linx-symphony-session/v1';
9
+ export const SYMPHONY_WORKER_POD_ACCESS_POLICY_VERSION = 'linx-symphony-worker-pod-access/v1';
10
+ export const SYMPHONY_ARCHIVE_PROVENANCE_VERSION = 'linx-symphony-archive/v1';
11
+ export const SYMPHONY_RUNTIME_REQUEST_POLICY_VERSION = 'linx-symphony-runtime-request/v1';
12
+ export async function persistSymphonyControlState(input) {
13
+ const rows = buildSymphonyControlRows(input);
14
+ await input.db.init([
15
+ contactResource,
16
+ chatResource,
17
+ threadResource,
18
+ messageResource,
19
+ issueResource,
20
+ taskResource,
21
+ deliveryResource,
22
+ sessionResource,
23
+ runResource,
24
+ runStepResource,
25
+ evidenceResource,
26
+ reportResource,
27
+ ]).catch(() => undefined);
28
+ for (const row of rows.contacts) {
29
+ await upsertSymphonyContact(input.db, row);
30
+ }
31
+ for (const row of rows.chats) {
32
+ await upsertSymphonyChat(input.db, row);
33
+ }
34
+ for (const row of rows.threads) {
35
+ await upsertSymphonyThread(input.db, row);
36
+ }
37
+ for (const row of rows.messages) {
38
+ await insertExactRecordOnce(input.db, messageResource, String(row.id), row);
39
+ }
40
+ for (const row of rows.issues) {
41
+ await upsertSymphonyIssue(input.db, row);
42
+ }
43
+ for (const row of rows.tasks) {
44
+ await upsertSymphonyTask(input.db, row);
45
+ }
46
+ for (const row of rows.deliveries) {
47
+ await upsertSymphonyDelivery(input.db, row);
48
+ }
49
+ for (const row of rows.sessions) {
50
+ await upsertSymphonySession(input.db, row);
51
+ }
52
+ for (const row of rows.runs) {
53
+ await upsertSymphonyRun(input.db, row);
54
+ }
55
+ for (const row of rows.runSteps) {
56
+ await insertExactRecordOnce(input.db, runStepResource, String(row.id), row);
57
+ }
58
+ for (const row of rows.evidence) {
59
+ await insertExactRecordOnce(input.db, evidenceResource, String(row.id), row);
60
+ }
61
+ for (const row of rows.reports) {
62
+ await upsertSymphonyReport(input.db, row);
63
+ }
64
+ return {
65
+ plan: input.plan,
66
+ rows,
67
+ };
68
+ }
69
+ export async function runAndPersistSymphonyWorkerGoalPlan(input) {
70
+ await persistSymphonyControlState({
71
+ db: input.db,
72
+ webId: input.webId,
73
+ plan: input.plan,
74
+ stage: 'planned',
75
+ });
76
+ let worker = startSymphonyWorkerRun({
77
+ worker: input.plan.workers[0],
78
+ now: input.now,
79
+ randomId: `${input.randomId ?? 'symphony'}-runtime-start`,
80
+ });
81
+ let runningPlan = withSymphonyPrimaryWorker(input.plan, worker);
82
+ await persistSymphonyControlState({
83
+ db: input.db,
84
+ webId: input.webId,
85
+ plan: runningPlan,
86
+ stage: 'running',
87
+ });
88
+ const adapterResult = await input.runtimeAdapter.run({
89
+ plan: runningPlan,
90
+ worker,
91
+ prompt: worker.delivery.projection.prompt,
92
+ signal: input.signal,
93
+ });
94
+ return applyAndPersistSymphonyWorkerRuntimeResult({
95
+ db: input.db,
96
+ webId: input.webId,
97
+ plan: runningPlan,
98
+ worker,
99
+ result: adapterResult,
100
+ now: input.now,
101
+ randomId: input.randomId,
102
+ });
103
+ }
104
+ export async function persistSymphonyWorkerDelivery(input) {
105
+ const result = typeof input.delivery === 'string'
106
+ ? parseSymphonyRuntimeDeliveryResult(input.delivery)
107
+ : normalizeSymphonyRuntimeDeliveryResult(input.delivery);
108
+ if (!result) {
109
+ throw new Error('Invalid Symphony worker delivery: expected symphonyDelivery/final report JSON with status, events, or report.');
110
+ }
111
+ return applyAndPersistSymphonyWorkerRuntimeResult({
112
+ db: input.db,
113
+ webId: input.webId,
114
+ plan: input.plan,
115
+ ...(input.worker ? { worker: input.worker } : {}),
116
+ result,
117
+ now: input.now,
118
+ randomId: input.randomId,
119
+ });
120
+ }
121
+ export async function applyAndPersistSymphonyWorkerRuntimeResult(input) {
122
+ let worker = input.worker ?? input.plan.workers[0] ?? {
123
+ task: input.plan.task,
124
+ taskRecord: input.plan.taskRecord,
125
+ delivery: input.plan.delivery,
126
+ session: input.plan.session,
127
+ };
128
+ let runningPlan = withSymphonyPrimaryWorker(input.plan, worker);
129
+ if (worker.session.status === 'planned') {
130
+ await persistSymphonyControlState({
131
+ db: input.db,
132
+ webId: input.webId,
133
+ plan: runningPlan,
134
+ stage: 'planned',
135
+ });
136
+ worker = startSymphonyWorkerRun({
137
+ worker,
138
+ now: input.now,
139
+ randomId: `${input.randomId ?? 'symphony'}-delivery-runtime-start`,
140
+ message: `${worker.session.backend} worker runtime result was ingested.`,
141
+ payload: {
142
+ source: 'symphony-delivery',
143
+ issue: input.plan.issue.uri,
144
+ task: worker.task,
145
+ delivery: worker.delivery.uri,
146
+ session: worker.session.uri,
147
+ backend: worker.session.backend,
148
+ },
149
+ });
150
+ runningPlan = withSymphonyPrimaryWorker(input.plan, worker);
151
+ await persistSymphonyControlState({
152
+ db: input.db,
153
+ webId: input.webId,
154
+ plan: runningPlan,
155
+ stage: 'running',
156
+ });
157
+ }
158
+ for (const [index, event] of (input.result.events ?? []).entries()) {
159
+ const eventDate = readRuntimeEventDate(event);
160
+ worker = recordSymphonyWorkerRuntimeEvent({
161
+ worker,
162
+ stepType: event.stepType,
163
+ ...(event.message ? { message: event.message } : {}),
164
+ ...(event.payload ? { payload: event.payload } : {}),
165
+ ...(eventDate ? { now: eventDate } : {}),
166
+ randomId: event.randomId ?? `${input.randomId ?? 'symphony'}-runtime-event-${index + 1}`,
167
+ });
168
+ }
169
+ const status = input.result.status ?? ((input.result.exitCode ?? 0) === 0 ? 'completed' : 'failed');
170
+ const exitCode = input.result.exitCode ?? (status === 'completed' ? 0 : 1);
171
+ const completed = completeSymphonyWorkerRun({
172
+ issue: runningPlan.issue,
173
+ worker,
174
+ status,
175
+ exitCode,
176
+ ...(input.result.autoModeSessionId ? { autoModeSessionId: input.result.autoModeSessionId } : {}),
177
+ ...(input.result.reportText ? { reportText: input.result.reportText } : {}),
178
+ now: input.now,
179
+ randomId: `${input.randomId ?? 'symphony'}-runtime-complete`,
180
+ });
181
+ const finalized = finalizeSymphonyRunPlanAfterWorkers({
182
+ plan: runningPlan,
183
+ workers: [completed.worker],
184
+ followUpIssues: completed.followUpIssues,
185
+ now: input.now,
186
+ });
187
+ const finalPlan = finalized.plan;
188
+ await persistSymphonyControlState({
189
+ db: input.db,
190
+ webId: input.webId,
191
+ plan: finalPlan,
192
+ stage: status === 'completed' ? 'completed' : 'failed',
193
+ });
194
+ return {
195
+ plan: finalPlan,
196
+ worker: completed.worker,
197
+ status,
198
+ exitCode,
199
+ ...(input.result.autoModeSessionId ? { autoModeSessionId: input.result.autoModeSessionId } : {}),
200
+ };
201
+ }
202
+ export async function persistSymphonyInteractionRequest(input) {
203
+ const rows = buildSymphonyInteractionRequestRows(input);
204
+ await input.db.init([
205
+ approvalResource,
206
+ inputRequestResource,
207
+ inboxNotificationResource,
208
+ runStepResource,
209
+ ]).catch(() => undefined);
210
+ if (rows.approval) {
211
+ await upsertSymphonyApprovalRequest(input.db, rows.approval);
212
+ }
213
+ if (rows.inputRequest) {
214
+ await upsertSymphonyInputRequest(input.db, rows.inputRequest);
215
+ }
216
+ await insertExactRecordOnce(input.db, inboxNotificationResource, String(rows.inboxNotification.id), rows.inboxNotification);
217
+ await insertExactRecordOnce(input.db, runStepResource, String(rows.runStep.id), rows.runStep);
218
+ return {
219
+ plan: input.plan,
220
+ rows,
221
+ };
222
+ }
223
+ export async function listOpenSymphonyIssuesFromControlState(input) {
224
+ await input.db.init([issueResource]).catch(() => undefined);
225
+ const rows = await input.db.select().from(issueResource).execute();
226
+ return rows
227
+ .map((row) => issueRowToSymphonyIssueRecord(row, input.webId))
228
+ .filter((issue) => issue !== null)
229
+ .filter((issue) => !isClosedIssueStatus(issue.status))
230
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
231
+ }
232
+ export async function listRunningSymphonyWorkersFromControlState(input) {
233
+ await input.db.init([sessionResource]).catch(() => undefined);
234
+ const rows = await input.db.select().from(sessionResource).execute();
235
+ return rows
236
+ .filter(isSymphonySessionRow)
237
+ .flatMap((row) => extractRunningSymphonyWorkersFromSession(row))
238
+ .sort(compareWorkerStatusUpdatedAt)
239
+ .map(({ updatedAt: _updatedAt, ...worker }) => worker);
240
+ }
241
+ export async function listRecentSymphonyReportsFromControlState(input) {
242
+ await input.db.init([reportResource, deliveryResource]).catch(() => undefined);
243
+ const [reportRows, deliveryRows] = await Promise.all([
244
+ selectAllRows(input.db, reportResource).catch(() => []),
245
+ selectAllRows(input.db, deliveryResource).catch(() => []),
246
+ ]);
247
+ return [
248
+ ...reportRows.map(reportRowToSymphonyReportStatus),
249
+ ...deliveryRows.map(deliveryRowToSymphonyReportStatus),
250
+ ]
251
+ .filter((report) => report !== null)
252
+ .sort((left, right) => right.sortAt - left.sortAt)
253
+ .slice(0, input.limit ?? 5)
254
+ .map(({ sortAt: _sortAt, ...report }) => report);
255
+ }
256
+ async function selectAllRows(db, resource) {
257
+ return await db.select().from(resource).execute();
258
+ }
259
+ function issueRowToSymphonyIssueRecord(row, webId) {
260
+ const record = asRecord(row);
261
+ const id = normalizeString(record?.id);
262
+ const title = normalizeString(record?.title);
263
+ if (!record || !id || !title) {
264
+ return null;
265
+ }
266
+ const status = normalizeIssueStatus(record.status);
267
+ const priority = normalizeIssuePriority(record.priority);
268
+ const tasks = Array.isArray(record.tasks)
269
+ ? record.tasks.map((item) => normalizeString(item)).filter((item) => Boolean(item))
270
+ : [];
271
+ const createdAt = toIsoDate(record.createdAt);
272
+ const updatedAt = toIsoDate(record.updatedAt) ?? createdAt;
273
+ return {
274
+ uri: symphonyIssueUriFromResourceId(id),
275
+ title,
276
+ description: normalizeString(record.description),
277
+ status,
278
+ priority,
279
+ source: normalizeIssueSource(record.source),
280
+ issuer: {
281
+ source: 'user',
282
+ webId: normalizeString(record.createdBy) ?? webId,
283
+ ...(normalizeString(record.chat) ? { chat: normalizeString(record.chat) } : {}),
284
+ ...(normalizeString(record.thread) ? { thread: normalizeString(record.thread) } : {}),
285
+ },
286
+ tasks,
287
+ deliveries: [],
288
+ sessions: [],
289
+ ...(normalizeString(record.chat) ? { chat: normalizeString(record.chat) } : {}),
290
+ ...(normalizeString(record.thread) ? { thread: normalizeString(record.thread) } : {}),
291
+ createdAt,
292
+ updatedAt,
293
+ ...(record.closedAt ? { closedAt: toIsoDate(record.closedAt) ?? updatedAt } : {}),
294
+ };
295
+ }
296
+ function symphonyIssueUriFromResourceId(id) {
297
+ const normalized = resolvePodResourceTemplateValue(issueResource, id) ?? id;
298
+ return `urn:undefineds:linx:issue:${normalized}`;
299
+ }
300
+ function normalizeIssueStatus(value) {
301
+ const normalized = normalizeString(value);
302
+ if (normalized === 'open'
303
+ || normalized === 'triaging'
304
+ || normalized === 'in_progress'
305
+ || normalized === 'blocked'
306
+ || normalized === 'resolved'
307
+ || normalized === 'closed') {
308
+ return normalized;
309
+ }
310
+ return 'open';
311
+ }
312
+ function normalizeIssuePriority(value) {
313
+ const normalized = normalizeString(value);
314
+ if (normalized === 'low' || normalized === 'medium' || normalized === 'high' || normalized === 'urgent') {
315
+ return normalized;
316
+ }
317
+ return 'medium';
318
+ }
319
+ function normalizeIssueSource(value) {
320
+ const normalized = normalizeString(value);
321
+ if (normalized === 'cli'
322
+ || normalized === 'web'
323
+ || normalized === 'service'
324
+ || normalized === 'tui'
325
+ || normalized === 'mcp'
326
+ || normalized === 'runtime'
327
+ || normalized === 'control-plane') {
328
+ return normalized;
329
+ }
330
+ return 'control-plane';
331
+ }
332
+ function isClosedIssueStatus(status) {
333
+ return status === 'closed' || status === 'resolved';
334
+ }
335
+ function isSymphonySessionRow(row) {
336
+ if (!row || typeof row !== 'object') {
337
+ return false;
338
+ }
339
+ const record = row;
340
+ const metadata = asRecord(record.metadata);
341
+ return metadata?.kind === 'symphony-run'
342
+ || record.policyVersion === SYMPHONY_POLICY_VERSION
343
+ || (typeof record.tool === 'string' && record.tool.startsWith('symphony:'));
344
+ }
345
+ function extractRunningSymphonyWorkersFromSession(row) {
346
+ const metadata = asRecord(row.metadata) ?? {};
347
+ const sessionStatus = normalizePodSymphonySessionStatus(metadata.status ?? row.status);
348
+ const workers = Array.isArray(metadata.workers) ? metadata.workers : [];
349
+ const updatedAt = safeOptionalDate(row.updatedAt);
350
+ if (workers.length === 0) {
351
+ if (sessionStatus !== 'running') {
352
+ return [];
353
+ }
354
+ return [{
355
+ status: sessionStatus,
356
+ backend: normalizeString(metadata.backend) ?? parseBackendFromTool(row.tool) ?? 'unknown',
357
+ mode: normalizeString(metadata.mode) ?? 'auto',
358
+ cwd: normalizeString(metadata.workspacePath),
359
+ autoModeSessionId: normalizeString(metadata.autoModeSessionId),
360
+ target: normalizeSymphonyWorkerTarget(asRecord(metadata.target)),
361
+ updatedAt,
362
+ }];
363
+ }
364
+ return workers
365
+ .map((item) => asRecord(item))
366
+ .filter((item) => item !== null)
367
+ .map((worker) => ({
368
+ status: normalizePodSymphonySessionStatus(worker.status ?? worker.taskStatus ?? sessionStatus),
369
+ backend: normalizeString(worker.backend) ?? normalizeString(metadata.backend) ?? parseBackendFromTool(row.tool) ?? 'unknown',
370
+ mode: normalizeString(worker.mode) ?? normalizeString(metadata.mode) ?? 'auto',
371
+ cwd: normalizeString(worker.workspacePath) ?? normalizeString(metadata.workspacePath),
372
+ autoModeSessionId: normalizeString(worker.autoModeSessionId) ?? normalizeString(metadata.autoModeSessionId),
373
+ target: normalizeSymphonyWorkerTarget(asRecord(worker.target), worker, asRecord(metadata.target)),
374
+ updatedAt,
375
+ }))
376
+ .filter((worker) => worker.status === 'running');
377
+ }
378
+ function deliveryRowToSymphonyReportStatus(row) {
379
+ const record = asRecord(row);
380
+ if (!record) {
381
+ return null;
382
+ }
383
+ const metadata = asRecord(record.metadata);
384
+ const payload = asRecord(record.payload);
385
+ if (record.kind !== 'report' && metadata?.reportKind !== 'worker-completion' && payload?.kind !== 'symphony_report') {
386
+ return null;
387
+ }
388
+ const completedAt = safeOptionalDate(record.completedAt);
389
+ const updatedAt = safeOptionalDate(record.updatedAt);
390
+ const createdAt = safeOptionalDate(record.createdAt);
391
+ const sortAt = completedAt?.getTime() ?? updatedAt?.getTime() ?? createdAt?.getTime() ?? 0;
392
+ const agent = normalizeString(payload?.agent);
393
+ const title = normalizeString(record.objective);
394
+ const summary = normalizeString(payload?.summary);
395
+ const task = normalizeString(record.task);
396
+ const archive = asRecord(metadata?.archive);
397
+ const delivery = normalizeString(payload?.delivery) ?? normalizeString(archive?.delivery);
398
+ const reportDelivery = normalizeString(payload?.reportDelivery) ?? normalizeString(record.id);
399
+ const run = normalizeString(payload?.run) ?? normalizeString(record.object);
400
+ const chat = normalizeString(record.chat);
401
+ const thread = normalizeString(record.thread);
402
+ const autoModeSessionId = normalizeString(payload?.autoModeSessionId);
403
+ const error = normalizeString(payload?.error) ?? normalizeString(record.error);
404
+ return {
405
+ status: normalizeString(payload?.outcome) ?? normalizeString(record.status) ?? 'completed',
406
+ backend: normalizeString(payload?.backend) ?? 'unknown',
407
+ ...(agent ? { agent } : {}),
408
+ ...(title ? { title } : {}),
409
+ ...(summary ? { summary } : {}),
410
+ ...(task ? { task } : {}),
411
+ ...(delivery ? { delivery } : {}),
412
+ ...(reportDelivery ? { reportDelivery } : {}),
413
+ ...(run ? { run } : {}),
414
+ ...(chat ? { chat } : {}),
415
+ ...(thread ? { thread } : {}),
416
+ ...(autoModeSessionId ? { autoModeSessionId } : {}),
417
+ ...(error ? { error } : {}),
418
+ ...(completedAt ? { completedAt: completedAt.toISOString() } : {}),
419
+ ...(updatedAt ? { updatedAt: updatedAt.toISOString() } : {}),
420
+ sortAt,
421
+ };
422
+ }
423
+ function reportRowToSymphonyReportStatus(row) {
424
+ const record = asRecord(row);
425
+ if (!record) {
426
+ return null;
427
+ }
428
+ const metadata = asRecord(record.metadata);
429
+ const metricFacts = asRecord(record.metricFacts);
430
+ if (metadata?.surface !== 'symphony' && metadata?.reportKind !== 'worker-final-package') {
431
+ return null;
432
+ }
433
+ const publishedAt = safeOptionalDate(record.publishedAt);
434
+ const updatedAt = safeOptionalDate(record.updatedAt);
435
+ const createdAt = safeOptionalDate(record.createdAt);
436
+ const sortAt = publishedAt?.getTime() ?? updatedAt?.getTime() ?? createdAt?.getTime() ?? 0;
437
+ const archive = asRecord(metadata?.archive);
438
+ const reportDelivery = normalizeString(record.id);
439
+ const delivery = normalizeString(record.delivery) ?? normalizeString(archive?.delivery);
440
+ const run = normalizeString(record.run) ?? normalizeString(record.about);
441
+ const error = normalizeString(metadata?.error);
442
+ return {
443
+ status: normalizeString(record.outcome) ?? normalizeString(record.status) ?? 'completed',
444
+ backend: normalizeString(metricFacts?.backend) ?? normalizeString(metadata?.backend) ?? 'unknown',
445
+ ...(normalizeString(record.actor) ? { agent: normalizeString(record.actor) } : {}),
446
+ ...(normalizeString(record.summary) ? { title: normalizeString(record.summary), summary: normalizeString(record.summary) } : {}),
447
+ ...(normalizeString(record.task) ? { task: normalizeString(record.task) } : {}),
448
+ ...(delivery ? { delivery } : {}),
449
+ ...(reportDelivery ? { reportDelivery } : {}),
450
+ ...(run ? { run } : {}),
451
+ ...(normalizeString(record.thread) ? { thread: normalizeString(record.thread) } : {}),
452
+ ...(normalizeString(metricFacts?.autoModeSessionId) ? { autoModeSessionId: normalizeString(metricFacts?.autoModeSessionId) } : {}),
453
+ ...(error ? { error } : {}),
454
+ ...(publishedAt ? { completedAt: publishedAt.toISOString() } : {}),
455
+ ...(updatedAt ? { updatedAt: updatedAt.toISOString() } : {}),
456
+ sortAt,
457
+ };
458
+ }
459
+ function normalizeSymphonyWorkerTarget(target, worker = {}, fallback = null) {
460
+ const normalized = {
461
+ label: normalizeString(target?.label) ?? normalizeString(worker.title) ?? normalizeString(fallback?.label),
462
+ agent: normalizeString(target?.agent) ?? normalizeString(worker.agent) ?? normalizeString(fallback?.agent),
463
+ chat: normalizeString(target?.chat) ?? normalizeString(worker.chat) ?? normalizeString(fallback?.chat),
464
+ };
465
+ return Object.values(normalized).some(Boolean) ? normalized : undefined;
466
+ }
467
+ function compareWorkerStatusUpdatedAt(left, right) {
468
+ return (right.updatedAt?.getTime() ?? 0) - (left.updatedAt?.getTime() ?? 0);
469
+ }
470
+ function normalizePodSymphonySessionStatus(value) {
471
+ const normalized = normalizeString(value);
472
+ if (normalized === 'active')
473
+ return 'running';
474
+ if (normalized === 'error')
475
+ return 'failed';
476
+ if (normalized === 'queued')
477
+ return 'planned';
478
+ return normalized ?? 'planned';
479
+ }
480
+ function parseBackendFromTool(value) {
481
+ const tool = normalizeString(value);
482
+ if (!tool?.startsWith('symphony:')) {
483
+ return undefined;
484
+ }
485
+ return tool.slice('symphony:'.length) || undefined;
486
+ }
487
+ function withSymphonyPrimaryWorker(plan, worker) {
488
+ return {
489
+ ...plan,
490
+ task: worker.task,
491
+ taskRecord: worker.taskRecord,
492
+ delivery: worker.delivery,
493
+ session: worker.session,
494
+ workers: [worker],
495
+ };
496
+ }
497
+ export function buildSymphonyControlRows(input) {
498
+ const stage = input.stage ?? inferSymphonyControlStage(input.plan);
499
+ const stages = input.stages?.length ? input.stages : [stage];
500
+ const followUpIssues = input.plan.followUpIssues ?? [];
501
+ const issues = [
502
+ buildSymphonyIssueRow(input.plan, input.webId),
503
+ ...followUpIssues.map((issue) => buildSymphonyIssueRow(input.plan, input.webId, issue)),
504
+ ];
505
+ return {
506
+ contacts: buildSymphonyContactRows(input.plan, input.webId),
507
+ chats: [buildSymphonyChatRow(input.plan, input.webId, stage)],
508
+ threads: buildSymphonyThreadRows(input.plan, input.webId, stage),
509
+ messages: stages.map((currentStage) => buildSymphonyStatusMessageRow(input.plan, input.webId, currentStage)),
510
+ issue: issues[0],
511
+ issues,
512
+ tasks: input.plan.workers.map((worker) => buildSymphonyTaskRow(input.plan, input.webId, worker)),
513
+ deliveries: input.plan.workers.map((worker) => buildSymphonyDeliveryRow(input.plan, input.webId, worker)),
514
+ sessions: input.plan.workers.map((worker) => buildSymphonySessionRow(input.plan, input.webId, worker)),
515
+ runs: input.plan.workers.map((worker) => buildSymphonyRunRow(input.plan, input.webId, worker)),
516
+ runSteps: input.plan.workers.flatMap((worker) => [
517
+ ...stages.map((currentStage) => buildSymphonyRunStepRow(input.plan, input.webId, worker, currentStage)),
518
+ ...(worker.runSteps ?? []).map((step) => buildSymphonyRuntimeRunStepRow(input.plan, input.webId, worker, step)),
519
+ ]),
520
+ evidence: input.plan.workers.flatMap((worker) => buildSymphonyEvidenceRows(input.plan, input.webId, worker)),
521
+ reports: input.plan.workers.flatMap((worker) => buildSymphonyReportRows(input.plan, input.webId, worker)),
522
+ };
523
+ }
524
+ export function buildSymphonyInteractionRequestRows(input) {
525
+ const worker = input.worker ?? input.plan.workers[0] ?? {
526
+ task: input.plan.task,
527
+ taskRecord: input.plan.taskRecord,
528
+ delivery: input.plan.delivery,
529
+ session: input.plan.session,
530
+ };
531
+ const createdAt = safeDate(input.now);
532
+ const source = input.source ?? defaultInteractionSource(worker);
533
+ const requestKey = stableInteractionRequestKey(input.request, input.randomId);
534
+ const run = buildSymphonyRunIri(input.webId, worker);
535
+ const thread = selectWorkerThreadIri(input.plan, input.webId, worker);
536
+ const chat = selectWorkerChatIri(input.plan, input.webId, worker);
537
+ const requester = agentResource.buildIri(input.webId, {
538
+ id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent),
539
+ });
540
+ const assignedTo = agentResource.buildIri(input.webId, { id: SYMPHONY_SECRETARY_AGENT_ID });
541
+ const session = buildSymphonyWorkerSessionIri(input.webId, worker);
542
+ const task = buildSymphonyTaskIri(input.webId, worker.task);
543
+ const policyVersion = input.policyVersion ?? SYMPHONY_RUNTIME_REQUEST_POLICY_VERSION;
544
+ const control = input.request.kind === 'user-input'
545
+ ? buildSymphonyInputRequestRow({
546
+ plan: input.plan,
547
+ webId: input.webId,
548
+ worker,
549
+ request: input.request,
550
+ requestKey,
551
+ createdAt,
552
+ source,
553
+ session,
554
+ chat,
555
+ thread,
556
+ run,
557
+ task,
558
+ requester,
559
+ assignedTo,
560
+ policyVersion,
561
+ })
562
+ : buildSymphonyApprovalRequestRow({
563
+ plan: input.plan,
564
+ webId: input.webId,
565
+ worker,
566
+ request: input.request,
567
+ requestKey,
568
+ createdAt,
569
+ source,
570
+ session,
571
+ chat,
572
+ thread,
573
+ run,
574
+ task,
575
+ requester,
576
+ assignedTo,
577
+ policyVersion,
578
+ });
579
+ const controlResource = input.request.kind === 'user-input'
580
+ ? inputRequestResource.buildIri(input.webId, {
581
+ id: control.id,
582
+ createdAt,
583
+ })
584
+ : approvalResource.buildIri(input.webId, {
585
+ id: control.id,
586
+ createdAt,
587
+ });
588
+ const inboxNotification = buildSymphonyInteractionInboxNotificationRow({
589
+ requestKey,
590
+ createdAt,
591
+ actor: requester,
592
+ controlResource,
593
+ });
594
+ const runStep = buildSymphonyInteractionRunStepRow({
595
+ plan: input.plan,
596
+ webId: input.webId,
597
+ worker,
598
+ request: input.request,
599
+ requestKey,
600
+ createdAt,
601
+ source,
602
+ run,
603
+ controlResource,
604
+ inboxNotification: inboxNotificationResource.buildIri(input.webId, {
605
+ id: inboxNotification.id,
606
+ }),
607
+ });
608
+ return input.request.kind === 'user-input'
609
+ ? {
610
+ inputRequest: control,
611
+ inboxNotification,
612
+ runStep,
613
+ }
614
+ : {
615
+ approval: control,
616
+ inboxNotification,
617
+ runStep,
618
+ };
619
+ }
620
+ export async function upsertSymphonyApprovalRequest(db, row) {
621
+ await upsertExactRecord(db, approvalResource, { id: row.id, createdAt: row.createdAt }, row, {
622
+ session: row.session,
623
+ chat: row.chat,
624
+ thread: row.thread,
625
+ toolCallId: row.toolCallId,
626
+ toolName: row.toolName,
627
+ target: row.target,
628
+ action: row.action,
629
+ risk: row.risk,
630
+ status: row.status,
631
+ leaseOwner: row.leaseOwner,
632
+ leaseExpiresAt: row.leaseExpiresAt,
633
+ assignedTo: row.assignedTo,
634
+ decisionBy: row.decisionBy,
635
+ decisionRole: row.decisionRole,
636
+ onBehalfOf: row.onBehalfOf,
637
+ reason: row.reason,
638
+ context: row.context,
639
+ approvalOptions: row.approvalOptions,
640
+ policyVersion: row.policyVersion,
641
+ expiresAt: row.expiresAt,
642
+ resolvedAt: row.resolvedAt,
643
+ });
644
+ }
645
+ export async function upsertSymphonyInputRequest(db, row) {
646
+ await upsertExactRecord(db, inputRequestResource, { id: row.id, createdAt: row.createdAt }, row, {
647
+ session: row.session,
648
+ chat: row.chat,
649
+ thread: row.thread,
650
+ run: row.run,
651
+ task: row.task,
652
+ requester: row.requester,
653
+ requestKind: row.requestKind,
654
+ prompt: row.prompt,
655
+ context: row.context,
656
+ inputOptions: row.inputOptions,
657
+ status: row.status,
658
+ leaseOwner: row.leaseOwner,
659
+ leaseExpiresAt: row.leaseExpiresAt,
660
+ assignedTo: row.assignedTo,
661
+ response: row.response,
662
+ answeredBy: row.answeredBy,
663
+ onBehalfOf: row.onBehalfOf,
664
+ reason: row.reason,
665
+ metadata: row.metadata,
666
+ expiresAt: row.expiresAt,
667
+ resolvedAt: row.resolvedAt,
668
+ });
669
+ }
670
+ export async function upsertSymphonyContact(db, row) {
671
+ await upsertExactRecord(db, contactResource, { id: row.id }, row, {
672
+ name: row.name,
673
+ about: row.about,
674
+ rdfType: row.rdfType,
675
+ contactType: row.contactType,
676
+ alias: row.alias,
677
+ note: row.note,
678
+ sortKey: row.sortKey,
679
+ updatedAt: row.updatedAt,
680
+ });
681
+ }
682
+ export async function upsertSymphonyChat(db, row) {
683
+ await upsertExactRecord(db, chatResource, { id: row.id }, row, {
684
+ title: row.title,
685
+ description: row.description,
686
+ contact: row.contact,
687
+ participants: row.participants,
688
+ metadata: row.metadata,
689
+ lastActiveAt: row.lastActiveAt,
690
+ lastMessage: row.lastMessage,
691
+ lastMessagePreview: row.lastMessagePreview,
692
+ updatedAt: row.updatedAt,
693
+ });
694
+ }
695
+ export async function upsertSymphonyThread(db, row) {
696
+ await upsertExactRecord(db, threadResource, { id: row.id }, row, {
697
+ parent: row.parent,
698
+ title: row.title,
699
+ status: row.status,
700
+ workspace: row.workspace,
701
+ metadata: row.metadata,
702
+ updatedAt: row.updatedAt,
703
+ });
704
+ }
705
+ export async function upsertSymphonyReport(db, row) {
706
+ await upsertExactRecord(db, reportResource, { id: row.id }, row, {
707
+ reportKind: row.reportKind,
708
+ status: row.status,
709
+ outcome: row.outcome,
710
+ about: row.about,
711
+ issue: row.issue,
712
+ task: row.task,
713
+ delivery: row.delivery,
714
+ run: row.run,
715
+ thread: row.thread,
716
+ evidence: row.evidence,
717
+ summary: row.summary,
718
+ reviewer: row.reviewer,
719
+ actor: row.actor,
720
+ source: row.source,
721
+ metricFacts: row.metricFacts,
722
+ metadata: row.metadata,
723
+ publishedAt: row.publishedAt,
724
+ updatedAt: row.updatedAt,
725
+ });
726
+ }
727
+ export async function upsertSymphonyIssue(db, row) {
728
+ await upsertExactRecord(db, issueResource, { id: row.id }, row, {
729
+ title: row.title,
730
+ description: row.description,
731
+ status: row.status,
732
+ priority: row.priority,
733
+ labels: row.labels,
734
+ chat: row.chat,
735
+ thread: row.thread,
736
+ parentIssue: row.parentIssue,
737
+ tasks: row.tasks,
738
+ assignedTo: row.assignedTo,
739
+ updatedAt: row.updatedAt,
740
+ closedAt: row.closedAt,
741
+ });
742
+ }
743
+ export async function upsertSymphonyTask(db, row) {
744
+ await upsertExactRecord(db, taskResource, { id: row.id }, row, {
745
+ title: row.title,
746
+ instruction: row.instruction,
747
+ prompt: row.prompt,
748
+ issue: row.issue,
749
+ message: row.message,
750
+ thread: row.thread,
751
+ workspace: row.workspace,
752
+ status: row.status,
753
+ priority: row.priority,
754
+ assignedTo: row.assignedTo,
755
+ source: row.source,
756
+ metadata: row.metadata,
757
+ updatedAt: row.updatedAt,
758
+ });
759
+ }
760
+ export async function upsertSymphonyDelivery(db, row) {
761
+ await upsertExactRecord(db, deliveryResource, { id: row.id }, row, {
762
+ kind: row.kind,
763
+ status: row.status,
764
+ task: row.task,
765
+ source: row.source,
766
+ target: row.target,
767
+ chat: row.chat,
768
+ thread: row.thread,
769
+ targetThread: row.targetThread,
770
+ targetSession: row.targetSession,
771
+ actor: row.actor,
772
+ object: row.object,
773
+ objective: row.objective,
774
+ payload: row.payload,
775
+ projection: row.projection,
776
+ projectedRole: row.projectedRole,
777
+ metadata: row.metadata,
778
+ error: row.error,
779
+ dispatchedAt: row.dispatchedAt,
780
+ completedAt: row.completedAt,
781
+ updatedAt: row.updatedAt,
782
+ });
783
+ }
784
+ export async function upsertSymphonySession(db, row) {
785
+ await upsertExactRecord(db, sessionResource, { id: row.id, createdAt: row.createdAt }, row, {
786
+ owner: row.owner,
787
+ chat: row.chat,
788
+ thread: row.thread,
789
+ status: row.status,
790
+ tool: row.tool,
791
+ tokenUsage: row.tokenUsage,
792
+ messages: row.messages,
793
+ policy: row.policy,
794
+ policyVersion: row.policyVersion,
795
+ metadata: row.metadata,
796
+ updatedAt: row.updatedAt,
797
+ archivedAt: row.archivedAt,
798
+ });
799
+ }
800
+ export async function upsertSymphonyRun(db, row) {
801
+ await upsertExactRecord(db, runResource, { id: row.id }, row, {
802
+ task: row.task,
803
+ delivery: row.delivery,
804
+ trigger: row.trigger,
805
+ input: row.input,
806
+ thread: row.thread,
807
+ workspace: row.workspace,
808
+ status: row.status,
809
+ runner: row.runner,
810
+ prompt: row.prompt,
811
+ externalRunId: row.externalRunId,
812
+ leaseOwner: row.leaseOwner,
813
+ leaseExpiresAt: row.leaseExpiresAt,
814
+ heartbeatAt: row.heartbeatAt,
815
+ cancelRequestedAt: row.cancelRequestedAt,
816
+ error: row.error,
817
+ metadata: row.metadata,
818
+ startedAt: row.startedAt,
819
+ completedAt: row.completedAt,
820
+ updatedAt: row.updatedAt,
821
+ });
822
+ }
823
+ export function buildSymphonyIssueRow(plan, webId, issue = plan.issue) {
824
+ const createdAt = safeDate(issue.createdAt);
825
+ const updatedAt = safeDate(issue.updatedAt);
826
+ return {
827
+ id: buildSymphonyIssueId(issue),
828
+ title: issue.title,
829
+ description: issue.description,
830
+ status: issue.status,
831
+ priority: issue.priority,
832
+ labels: Array.from(new Set(['symphony', ...(issue.labels ?? [])])),
833
+ chat: selectTargetChatIri(plan.session.target?.chat ?? issue.chat, webId, plan),
834
+ thread: selectTargetThreadIri(plan.session.target?.thread ?? issue.thread, webId, plan),
835
+ parentIssue: issue.parentIssue ? normalizeSymphonyIssueIri(webId, issue.parentIssue) : undefined,
836
+ tasks: Array.from(new Set((issue.tasks?.length ? issue.tasks : issue.uri === plan.issue.uri ? plan.workers.map((worker) => worker.task) : [])
837
+ .map((task) => normalizeSymphonyTaskIri(webId, task)))),
838
+ createdBy: issue.issuer.webId ?? plan.issue.issuer.webId ?? webId,
839
+ assignedTo: agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID }),
840
+ createdAt,
841
+ updatedAt,
842
+ ...(issue.closedAt ? { closedAt: safeDate(issue.closedAt) } : {}),
843
+ };
844
+ }
845
+ export function buildSymphonyTaskRow(plan, webId, worker) {
846
+ const createdAt = safeDate(worker.taskRecord.createdAt);
847
+ const updatedAt = safeDate(worker.taskRecord.updatedAt);
848
+ const workerAgent = agentResource.buildIri(webId, {
849
+ id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent),
850
+ });
851
+ return {
852
+ id: taskResource.buildId({ id: buildSymphonyTaskKey(worker.task) }),
853
+ title: worker.taskRecord.title,
854
+ instruction: worker.taskRecord.objective,
855
+ prompt: worker.delivery.projection.prompt,
856
+ issue: buildSymphonyIssueIri(webId, plan.issue),
857
+ message: lastItem(plan.issue.messages),
858
+ thread: selectWorkerThreadIri(plan, webId, worker),
859
+ workspace: pathToWorkspaceUri(worker.session.cwd) ?? pathToWorkspaceUri(plan.session.cwd) ?? 'file:///',
860
+ status: mapSymphonyTaskStatus(worker.taskRecord.status),
861
+ priority: plan.issue.priority,
862
+ assignedTo: workerAgent,
863
+ source: buildSymphonyIssueIri(webId, plan.issue),
864
+ metadata: {
865
+ surface: 'symphony',
866
+ ...buildSymphonyArchiveMetadata({ task: worker.taskRecord.uri }),
867
+ acceptanceCriteria: worker.taskRecord.acceptanceCriteria,
868
+ acceptanceReview: worker.taskRecord.acceptanceReview ?? worker.delivery.acceptanceReview ?? worker.session.acceptanceReview,
869
+ backend: worker.session.backend,
870
+ target: worker.session.target,
871
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
872
+ spaceContract: buildSymphonySpaceContract(plan, webId, worker),
873
+ podAccessPolicy: buildSymphonyWorkerPodAccessPolicy(plan, webId, worker),
874
+ reconciler: buildSymphonyReconcilerMetadata(worker),
875
+ },
876
+ createdAt,
877
+ updatedAt,
878
+ };
879
+ }
880
+ export function buildSymphonyDeliveryRow(plan, webId, worker) {
881
+ const createdAt = safeDate(worker.delivery.createdAt);
882
+ const updatedAt = safeDate(worker.delivery.updatedAt);
883
+ const secretaryAgent = agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID });
884
+ const workerAgent = agentResource.buildIri(webId, {
885
+ id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent),
886
+ });
887
+ return {
888
+ id: deliveryResource.buildId({
889
+ id: getSymphonyArchiveKey(worker.delivery.uri),
890
+ task: buildSymphonyTaskIri(webId, worker.task),
891
+ createdAt,
892
+ }),
893
+ kind: worker.delivery.type,
894
+ status: worker.delivery.status,
895
+ task: buildSymphonyTaskIri(webId, worker.task),
896
+ source: secretaryAgent,
897
+ target: workerAgent,
898
+ chat: selectWorkerChatIri(plan, webId, worker),
899
+ thread: selectWorkerThreadIri(plan, webId, worker),
900
+ targetThread: selectWorkerThreadIri(plan, webId, worker),
901
+ targetSession: worker.session.uri,
902
+ actor: secretaryAgent,
903
+ object: buildSymphonyTaskIri(webId, worker.task),
904
+ objective: worker.taskRecord.objective,
905
+ payload: {
906
+ issue: buildSymphonyIssueIri(webId, plan.issue),
907
+ acceptanceCriteria: worker.taskRecord.acceptanceCriteria,
908
+ backend: worker.session.backend,
909
+ mode: worker.session.mode,
910
+ target: worker.session.target,
911
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
912
+ spaceContract: buildSymphonySpaceContract(plan, webId, worker),
913
+ podAccessPolicy: buildSymphonyWorkerPodAccessPolicy(plan, webId, worker),
914
+ reconciler: buildSymphonyReconcilerMetadata(worker),
915
+ acceptanceReview: worker.delivery.acceptanceReview ?? worker.taskRecord.acceptanceReview ?? worker.session.acceptanceReview,
916
+ },
917
+ projection: worker.delivery.projection,
918
+ projectedRole: worker.delivery.projection.runtimeRole,
919
+ metadata: {
920
+ surface: 'symphony',
921
+ ...buildSymphonyArchiveMetadata({
922
+ issue: plan.issue.uri,
923
+ task: worker.task,
924
+ delivery: worker.delivery.uri,
925
+ session: worker.session.uri,
926
+ }),
927
+ autoModeSessionId: worker.delivery.autoModeSessionId,
928
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
929
+ spaceContract: buildSymphonySpaceContract(plan, webId, worker),
930
+ podAccessPolicy: buildSymphonyWorkerPodAccessPolicy(plan, webId, worker),
931
+ reconciler: buildSymphonyReconcilerMetadata(worker),
932
+ },
933
+ error: worker.delivery.error,
934
+ createdAt,
935
+ dispatchedAt: worker.delivery.status === 'dispatched' || worker.delivery.status === 'completed'
936
+ ? updatedAt
937
+ : undefined,
938
+ completedAt: worker.delivery.completedAt ? safeDate(worker.delivery.completedAt) : undefined,
939
+ updatedAt,
940
+ };
941
+ }
942
+ export function buildSymphonySessionRow(plan, webId, worker = plan.workers[0] ?? {
943
+ task: plan.task,
944
+ taskRecord: plan.taskRecord,
945
+ delivery: plan.delivery,
946
+ session: plan.session,
947
+ }) {
948
+ const createdAt = safeDate(worker.session.createdAt);
949
+ const updatedAt = safeDate(worker.session.updatedAt);
950
+ const status = worker.session.status === 'completed'
951
+ ? 'completed'
952
+ : worker.session.status === 'failed'
953
+ ? 'error'
954
+ : worker.session.status === 'running'
955
+ ? 'active'
956
+ : 'queued';
957
+ const workerSummary = buildSymphonyWorkerSummary(plan, webId, worker);
958
+ return {
959
+ id: buildSymphonySessionRecordId(worker.session),
960
+ owner: webId,
961
+ chat: selectWorkerChatIri(plan, webId, worker),
962
+ thread: selectWorkerThreadIri(plan, webId, worker),
963
+ status,
964
+ tool: `symphony:${worker.session.backend}`,
965
+ tokenUsage: 0,
966
+ messages: worker.session.messages,
967
+ policyVersion: SYMPHONY_POLICY_VERSION,
968
+ metadata: {
969
+ kind: 'symphony-run',
970
+ surface: 'symphony',
971
+ status: worker.session.status,
972
+ issue: plan.issue.uri,
973
+ task: worker.task,
974
+ delivery: worker.delivery.uri,
975
+ session: worker.session.uri,
976
+ issuer: plan.issue.issuer,
977
+ worker: workerSummary,
978
+ workers: [workerSummary],
979
+ backend: worker.session.backend,
980
+ mode: worker.session.mode,
981
+ model: worker.session.model,
982
+ workspacePath: worker.session.cwd,
983
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
984
+ reconciler: buildSymphonyReconcilerMetadata(worker),
985
+ autoModeSessionId: worker.session.autoModeSessionId,
986
+ exitCode: worker.session.exitCode,
987
+ dryRun: worker.session.dryRun,
988
+ error: worker.session.error ?? worker.delivery.error ?? plan.issue.error,
989
+ target: worker.session.target,
990
+ },
991
+ createdAt,
992
+ updatedAt,
993
+ ...(status === 'completed' || status === 'error' ? { archivedAt: updatedAt } : {}),
994
+ };
995
+ }
996
+ export function buildSymphonyRunRow(plan, webId, worker) {
997
+ const createdAt = safeDate(worker.session.createdAt);
998
+ const updatedAt = safeDate(worker.session.updatedAt);
999
+ return {
1000
+ id: runResource.buildId({
1001
+ id: getSymphonyArchiveKey(worker.session.uri),
1002
+ task: buildSymphonyTaskIri(webId, worker.task),
1003
+ createdAt,
1004
+ }),
1005
+ task: buildSymphonyTaskIri(webId, worker.task),
1006
+ delivery: buildSymphonyDeliveryIri(webId, worker),
1007
+ trigger: lastItem(plan.issue.messages) ?? buildSymphonyIssueIri(webId, plan.issue),
1008
+ input: buildSymphonyDeliveryIri(webId, worker),
1009
+ thread: selectWorkerThreadIri(plan, webId, worker),
1010
+ workspace: pathToWorkspaceUri(worker.session.cwd) ?? pathToWorkspaceUri(plan.session.cwd) ?? 'file:///',
1011
+ status: mapSymphonyRunStatus(worker.session.status),
1012
+ runner: worker.session.backend,
1013
+ prompt: worker.delivery.projection.prompt,
1014
+ externalRunId: worker.session.autoModeSessionId,
1015
+ error: worker.session.error,
1016
+ metadata: {
1017
+ surface: 'symphony',
1018
+ ...buildSymphonyArchiveMetadata({ session: worker.session.uri }),
1019
+ mode: worker.session.mode,
1020
+ model: worker.session.model,
1021
+ target: worker.session.target,
1022
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
1023
+ spaceContract: buildSymphonySpaceContract(plan, webId, worker),
1024
+ podAccessPolicy: buildSymphonyWorkerPodAccessPolicy(plan, webId, worker),
1025
+ reconciler: buildSymphonyReconcilerMetadata(worker),
1026
+ acceptanceReview: worker.session.acceptanceReview ?? worker.taskRecord.acceptanceReview ?? worker.delivery.acceptanceReview,
1027
+ exitCode: worker.session.exitCode,
1028
+ dryRun: worker.session.dryRun,
1029
+ },
1030
+ createdAt,
1031
+ startedAt: worker.session.status === 'running' || worker.session.status === 'completed' || worker.session.status === 'failed'
1032
+ ? updatedAt
1033
+ : undefined,
1034
+ completedAt: worker.session.completedAt ? safeDate(worker.session.completedAt) : undefined,
1035
+ updatedAt,
1036
+ };
1037
+ }
1038
+ export function buildSymphonyRunStepRow(plan, webId, worker, stage) {
1039
+ const run = buildSymphonyRunIri(webId, worker);
1040
+ const createdAt = stage === 'planned' ? safeDate(worker.session.createdAt) : safeDate(worker.session.updatedAt);
1041
+ const stepType = stage === 'planned'
1042
+ ? 'run.created'
1043
+ : stage === 'running'
1044
+ ? 'run.started'
1045
+ : stage === 'completed'
1046
+ ? 'run.completed'
1047
+ : 'run.failed';
1048
+ return {
1049
+ id: runStepResource.buildId({
1050
+ id: `${getSymphonyArchiveKey(worker.session.uri)}-${stage}`,
1051
+ run,
1052
+ }),
1053
+ run,
1054
+ stepType,
1055
+ message: buildStatusContent(plan, stage),
1056
+ payload: {
1057
+ surface: 'symphony',
1058
+ stage,
1059
+ issue: buildSymphonyIssueIri(webId, plan.issue),
1060
+ task: buildSymphonyTaskIri(webId, worker.task),
1061
+ delivery: buildSymphonyDeliveryIri(webId, worker),
1062
+ archive: buildSymphonyArchiveRefs({ session: worker.session.uri }),
1063
+ autoModeSessionId: worker.session.autoModeSessionId,
1064
+ },
1065
+ createdAt,
1066
+ };
1067
+ }
1068
+ export function buildSymphonyRuntimeRunStepRow(plan, webId, worker, step) {
1069
+ const run = buildSymphonyRunIri(webId, worker);
1070
+ return {
1071
+ id: runStepResource.buildId({
1072
+ id: getSymphonyArchiveKey(step.uri),
1073
+ run,
1074
+ }),
1075
+ run,
1076
+ stepType: step.stepType,
1077
+ message: step.message,
1078
+ payload: {
1079
+ surface: 'symphony',
1080
+ issue: buildSymphonyIssueIri(webId, plan.issue),
1081
+ task: buildSymphonyTaskIri(webId, worker.task),
1082
+ delivery: buildSymphonyDeliveryIri(webId, worker),
1083
+ session: buildSymphonyWorkerSessionIri(webId, worker),
1084
+ archive: buildSymphonyArchiveRefs({
1085
+ issue: step.issue,
1086
+ task: step.task,
1087
+ delivery: step.delivery,
1088
+ session: step.session,
1089
+ }),
1090
+ ...(step.payload ?? {}),
1091
+ },
1092
+ createdAt: safeDate(step.createdAt),
1093
+ };
1094
+ }
1095
+ export function buildSymphonyContactRows(plan, webId) {
1096
+ const createdAt = safeDate(plan.issue.createdAt);
1097
+ const updatedAt = safeDate(plan.session.updatedAt);
1098
+ const agents = [
1099
+ {
1100
+ id: SYMPHONY_SECRETARY_AGENT_ID,
1101
+ name: 'AI Secretary',
1102
+ about: agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID }),
1103
+ sortKey: '00-ai-secretary',
1104
+ note: 'Default Secretary contact for LinX-managed Symphony work.',
1105
+ },
1106
+ ...plan.workers.map((worker, index) => {
1107
+ const id = buildWorkerAgentId(worker.session.backend, worker.session.target.agent);
1108
+ const label = worker.session.target.label ?? worker.session.target.agent ?? backendDisplayName(worker.session.backend);
1109
+ return {
1110
+ id,
1111
+ name: label,
1112
+ about: agentResource.buildIri(webId, { id }),
1113
+ sortKey: `10-worker-${index + 1}-${label.toLowerCase()}`,
1114
+ note: `${backendDisplayName(worker.session.backend)} worker contact for Symphony task ${worker.taskRecord.title}.`,
1115
+ };
1116
+ }),
1117
+ ];
1118
+ return agents.map((agent) => ({
1119
+ id: agent.id === SYMPHONY_SECRETARY_AGENT_ID ? SYMPHONY_SECRETARY_AGENT_ID : `${agent.id}-contact`,
1120
+ name: agent.name,
1121
+ about: agent.about,
1122
+ rdfType: ContactClass.AGENT,
1123
+ contactType: ContactType.AGENT,
1124
+ alias: agent.name,
1125
+ note: agent.note,
1126
+ sortKey: agent.sortKey,
1127
+ createdAt,
1128
+ updatedAt,
1129
+ }));
1130
+ }
1131
+ export function buildSymphonyChatRow(plan, webId, stage) {
1132
+ const createdAt = safeDate(plan.issue.createdAt);
1133
+ const updatedAt = safeDate(plan.session.updatedAt);
1134
+ const secretaryAgent = agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID });
1135
+ const workerAgents = plan.workers.map((worker) => agentResource.buildIri(webId, {
1136
+ id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent),
1137
+ }));
1138
+ const targetChat = selectTargetChatIri(plan.session.target?.chat, webId, plan);
1139
+ const message = buildSymphonyStatusMessageRow(plan, webId, stage);
1140
+ return {
1141
+ id: buildTargetChatId(plan, webId),
1142
+ title: plan.session.target?.label ?? (targetChat === buildSymphonyChatUri(webId) ? 'AI Secretary · Symphony' : 'Symphony Delegation'),
1143
+ participants: Array.from(new Set([webId, secretaryAgent, ...workerAgents])),
1144
+ metadata: {
1145
+ kind: targetChat === buildSymphonyChatUri(webId) ? 'symphony-control-room' : 'symphony-target-room',
1146
+ surface: 'symphony',
1147
+ secretaryAgent,
1148
+ currentBackend: plan.session.backend,
1149
+ currentStage: stage,
1150
+ target: plan.session.target,
1151
+ members: [
1152
+ { uri: webId, role: 'user', label: 'User' },
1153
+ { uri: secretaryAgent, role: 'secretary', label: 'AI Secretary' },
1154
+ ...plan.workers.map((worker) => ({
1155
+ uri: agentResource.buildIri(webId, {
1156
+ id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent),
1157
+ }),
1158
+ role: 'worker',
1159
+ label: worker.session.target.label ?? worker.session.target.agent ?? backendDisplayName(worker.session.backend),
1160
+ })),
1161
+ ],
1162
+ },
1163
+ lastActiveAt: updatedAt,
1164
+ lastMessage: buildSymphonyMessageIri(webId, plan, message),
1165
+ lastMessagePreview: normalizeTitle(message.content, 100),
1166
+ createdAt,
1167
+ updatedAt,
1168
+ };
1169
+ }
1170
+ export function buildSymphonyThreadRows(plan, webId, stage) {
1171
+ return collectSymphonyThreadProjectionGroups(plan, webId)
1172
+ .map((group) => buildSymphonyThreadRow(plan, webId, stage, group));
1173
+ }
1174
+ export function buildSymphonyThreadRow(plan, webId, stage, group) {
1175
+ const workers = group?.workers ?? plan.workers;
1176
+ const primaryWorker = workers[0] ?? {
1177
+ task: plan.task,
1178
+ taskRecord: plan.taskRecord,
1179
+ delivery: plan.delivery,
1180
+ session: plan.session,
1181
+ };
1182
+ const createdAt = safeDate(primaryWorker.session.createdAt);
1183
+ const updatedAt = safeDate(primaryWorker.session.updatedAt);
1184
+ const chat = group?.chat ?? selectTargetChatIri(plan.session.target?.chat, webId, plan);
1185
+ const thread = group?.thread ?? selectTargetThreadIri(plan.session.target?.thread, webId, plan);
1186
+ const workspace = pathToWorkspaceUri(primaryWorker.session.cwd) ?? pathToWorkspaceUri(plan.session.cwd);
1187
+ return {
1188
+ id: threadRepository.idForChat(chat, thread),
1189
+ parent: chat,
1190
+ title: normalizeTitle(plan.issue.title || plan.issue.description || 'Symphony Task'),
1191
+ ...(workspace ? { workspace } : {}),
1192
+ metadata: {
1193
+ kind: 'symphony-run',
1194
+ surface: 'symphony',
1195
+ stage,
1196
+ status: primaryWorker.session.status,
1197
+ issue: plan.issue.uri,
1198
+ task: primaryWorker.task,
1199
+ delivery: primaryWorker.delivery.uri,
1200
+ session: primaryWorker.session.uri,
1201
+ issuer: plan.issue.issuer,
1202
+ workers: workers.map((worker) => buildSymphonyWorkerSummary(plan, webId, worker)),
1203
+ backend: primaryWorker.session.backend,
1204
+ mode: primaryWorker.session.mode,
1205
+ model: primaryWorker.session.model,
1206
+ workspacePath: primaryWorker.session.cwd,
1207
+ workspace: buildSymphonyWorkspaceMetadata(plan, primaryWorker),
1208
+ reconciler: buildSymphonyReconcilerMetadata(primaryWorker),
1209
+ autoModeSessionId: primaryWorker.session.autoModeSessionId,
1210
+ exitCode: primaryWorker.session.exitCode,
1211
+ error: primaryWorker.session.error ?? primaryWorker.delivery.error ?? plan.issue.error,
1212
+ target: primaryWorker.session.target,
1213
+ },
1214
+ createdAt,
1215
+ updatedAt,
1216
+ };
1217
+ }
1218
+ export function buildSymphonyStatusMessageRow(plan, webId, stage) {
1219
+ const createdAt = stage === 'planned' ? safeDate(plan.issue.createdAt) : safeDate(plan.session.updatedAt);
1220
+ const chat = selectTargetChatIri(plan.session.target?.chat, webId, plan);
1221
+ const thread = selectTargetThreadIri(plan.session.target?.thread, webId, plan);
1222
+ const secretaryAgent = agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID });
1223
+ return {
1224
+ id: messageResource.buildId({
1225
+ id: `${buildSymphonyThreadId(plan)}-${stage}`,
1226
+ chat,
1227
+ thread,
1228
+ createdAt,
1229
+ }),
1230
+ scope: chat,
1231
+ chat,
1232
+ thread,
1233
+ maker: secretaryAgent,
1234
+ role: 'assistant',
1235
+ content: buildStatusContent(plan, stage),
1236
+ richContent: JSON.stringify({
1237
+ blocks: [buildProgressBlock(plan, stage)],
1238
+ symphony: {
1239
+ stage,
1240
+ issue: plan.issue.uri,
1241
+ task: plan.task,
1242
+ delivery: plan.delivery.uri,
1243
+ session: plan.session.uri,
1244
+ issuer: plan.issue.issuer,
1245
+ workers: plan.workers.map((worker) => ({
1246
+ task: worker.task,
1247
+ title: worker.taskRecord.title,
1248
+ objective: worker.taskRecord.objective,
1249
+ acceptanceCriteria: worker.taskRecord.acceptanceCriteria,
1250
+ taskStatus: worker.taskRecord.status,
1251
+ delivery: worker.delivery.uri,
1252
+ session: worker.session.uri,
1253
+ backend: worker.session.backend,
1254
+ agent: worker.session.target.agent,
1255
+ status: worker.session.status,
1256
+ autoModeSessionId: worker.session.autoModeSessionId,
1257
+ acceptanceReview: worker.taskRecord.acceptanceReview ?? worker.delivery.acceptanceReview ?? worker.session.acceptanceReview,
1258
+ })),
1259
+ },
1260
+ }),
1261
+ status: 'completed',
1262
+ metadata: {
1263
+ surface: 'symphony',
1264
+ stage,
1265
+ issue: buildSymphonyIssueIri(webId, plan.issue),
1266
+ workers: plan.workers.map((worker) => buildSymphonyWorkerSummary(plan, webId, worker)),
1267
+ },
1268
+ senderName: 'AI Secretary',
1269
+ routeTargetAgent: secretaryAgent,
1270
+ coordinationId: plan.issue.uri,
1271
+ createdAt,
1272
+ updatedAt: createdAt,
1273
+ };
1274
+ }
1275
+ export function buildSymphonyEvidenceRows(plan, webId, worker) {
1276
+ if (worker.session.status !== 'completed' && worker.session.status !== 'failed') {
1277
+ return [];
1278
+ }
1279
+ const review = worker.session.acceptanceReview ?? worker.taskRecord.acceptanceReview ?? worker.delivery.acceptanceReview;
1280
+ const createdAt = safeDate(worker.session.completedAt ?? worker.session.updatedAt);
1281
+ const run = buildSymphonyRunIri(webId, worker);
1282
+ const task = buildSymphonyTaskIri(webId, worker.task);
1283
+ const delivery = buildSymphonyDeliveryIri(webId, worker);
1284
+ const issue = buildSymphonyIssueIri(webId, plan.issue);
1285
+ const actor = agentResource.buildIri(webId, { id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent) });
1286
+ const finalEvidence = {
1287
+ id: evidenceResource.buildId({
1288
+ id: `${getSymphonyArchiveKey(worker.session.uri)}-final`,
1289
+ createdAt,
1290
+ about: run,
1291
+ }),
1292
+ evidenceKind: 'runtime_log',
1293
+ about: run,
1294
+ issue,
1295
+ task,
1296
+ delivery,
1297
+ run,
1298
+ thread: selectWorkerThreadIri(plan, webId, worker),
1299
+ summary: review?.summary ?? worker.session.error ?? worker.delivery.error ?? `${worker.taskRecord.title} ${worker.session.status}.`,
1300
+ actor,
1301
+ outcome: review?.outcome ?? (worker.session.status === 'failed' ? 'blocked' : 'accepted'),
1302
+ metadata: {
1303
+ surface: 'symphony',
1304
+ ...buildSymphonyArchiveMetadata({
1305
+ issue: plan.issue.uri,
1306
+ task: worker.task,
1307
+ delivery: worker.delivery.uri,
1308
+ session: worker.session.uri,
1309
+ }),
1310
+ backend: worker.session.backend,
1311
+ autoModeSessionId: worker.session.autoModeSessionId,
1312
+ exitCode: worker.session.exitCode,
1313
+ error: worker.session.error ?? worker.delivery.error ?? worker.taskRecord.error,
1314
+ acceptanceReview: review,
1315
+ evidence: review?.evidence ?? [],
1316
+ commands: review?.commands ?? [],
1317
+ changedFiles: review?.changedFiles ?? [],
1318
+ risks: review?.risks ?? [],
1319
+ followUps: review?.followUps ?? [],
1320
+ },
1321
+ createdAt,
1322
+ };
1323
+ const implementationChangeRequest = review?.implementationChangeRequest
1324
+ ? buildSymphonyImplementationChangeRequestEvidenceRow({
1325
+ plan,
1326
+ webId,
1327
+ worker,
1328
+ issue,
1329
+ task,
1330
+ delivery,
1331
+ run,
1332
+ actor,
1333
+ implementationChangeRequest: review.implementationChangeRequest,
1334
+ })
1335
+ : undefined;
1336
+ return implementationChangeRequest ? [finalEvidence, implementationChangeRequest] : [finalEvidence];
1337
+ }
1338
+ function buildSymphonyImplementationChangeRequestEvidenceRow(input) {
1339
+ const createdAt = safeDate(input.implementationChangeRequest.createdAt);
1340
+ const sourceRunSteps = (input.worker.runSteps ?? []).map((step) => {
1341
+ const row = buildSymphonyRuntimeRunStepRow(input.plan, input.webId, input.worker, step);
1342
+ return runStepResource.buildIri(input.webId, { id: row.id, run: row.run });
1343
+ });
1344
+ return {
1345
+ id: evidenceResource.buildId({
1346
+ id: `${getSymphonyArchiveKey(input.worker.session.uri)}-implementation-change-request`,
1347
+ createdAt,
1348
+ about: input.task,
1349
+ }),
1350
+ evidenceKind: 'review_finding',
1351
+ about: input.task,
1352
+ issue: input.issue,
1353
+ task: input.task,
1354
+ delivery: input.delivery,
1355
+ run: input.run,
1356
+ thread: selectWorkerThreadIri(input.plan, input.webId, input.worker),
1357
+ summary: input.implementationChangeRequest.summary,
1358
+ source: sourceRunSteps[sourceRunSteps.length - 1] ?? input.run,
1359
+ actor: input.actor,
1360
+ outcome: 'blocked',
1361
+ metadata: {
1362
+ surface: 'symphony',
1363
+ recordKind: 'implementation_change_request',
1364
+ ...buildSymphonyArchiveMetadata({
1365
+ issue: input.plan.issue.uri,
1366
+ task: input.worker.task,
1367
+ delivery: input.worker.delivery.uri,
1368
+ session: input.worker.session.uri,
1369
+ }),
1370
+ implementationChangeRequest: input.implementationChangeRequest,
1371
+ sourceRunSteps,
1372
+ basedOnRunSteps: input.implementationChangeRequest.basedOnRunSteps,
1373
+ recommends: input.implementationChangeRequest.recommendedNextShape,
1374
+ invalidates: input.task,
1375
+ },
1376
+ createdAt,
1377
+ };
1378
+ }
1379
+ export function buildSymphonyReportRows(plan, webId, worker) {
1380
+ if (worker.session.status !== 'completed' && worker.session.status !== 'failed') {
1381
+ return [];
1382
+ }
1383
+ const evidence = buildSymphonyEvidenceRows(plan, webId, worker);
1384
+ const review = worker.session.acceptanceReview ?? worker.taskRecord.acceptanceReview ?? worker.delivery.acceptanceReview;
1385
+ const createdAt = safeDate(worker.session.completedAt ?? worker.session.updatedAt);
1386
+ const run = buildSymphonyRunIri(webId, worker);
1387
+ const task = buildSymphonyTaskIri(webId, worker.task);
1388
+ const delivery = buildSymphonyDeliveryIri(webId, worker);
1389
+ const issue = buildSymphonyIssueIri(webId, plan.issue);
1390
+ const workerAgent = agentResource.buildIri(webId, { id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent) });
1391
+ const secretaryAgent = agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID });
1392
+ return [{
1393
+ id: reportResource.buildId({
1394
+ id: `${getSymphonyArchiveKey(worker.session.uri)}-final`,
1395
+ task,
1396
+ delivery,
1397
+ run,
1398
+ createdAt,
1399
+ }),
1400
+ reportKind: ReportKind.HANDOFF,
1401
+ status: ReportStatus.PUBLISHED,
1402
+ outcome: mapSymphonyReportOutcome(worker, review?.outcome),
1403
+ about: run,
1404
+ issue,
1405
+ task,
1406
+ delivery,
1407
+ run,
1408
+ thread: selectWorkerThreadIri(plan, webId, worker),
1409
+ evidence: evidence.map((row) => evidenceResource.buildIri(webId, { id: row.id, createdAt: row.createdAt, about: row.about })),
1410
+ summary: review?.summary ?? worker.session.error ?? worker.delivery.error ?? `${worker.taskRecord.title} ${worker.session.status}.`,
1411
+ reviewer: secretaryAgent,
1412
+ actor: workerAgent,
1413
+ source: delivery,
1414
+ metricFacts: {
1415
+ backend: worker.session.backend,
1416
+ mode: worker.session.mode,
1417
+ exitCode: worker.session.exitCode,
1418
+ autoModeSessionId: worker.session.autoModeSessionId,
1419
+ },
1420
+ metadata: {
1421
+ surface: 'symphony',
1422
+ reportKind: 'worker-final-package',
1423
+ ...buildSymphonyArchiveMetadata({
1424
+ issue: plan.issue.uri,
1425
+ task: worker.task,
1426
+ delivery: worker.delivery.uri,
1427
+ session: worker.session.uri,
1428
+ }),
1429
+ acceptanceReview: review,
1430
+ reusableExtraction: review?.reusableExtraction,
1431
+ },
1432
+ createdAt,
1433
+ publishedAt: createdAt,
1434
+ updatedAt: createdAt,
1435
+ }];
1436
+ }
1437
+ function buildSymphonyApprovalRequestRow(input) {
1438
+ const approvalOptions = encodeAutoModeApprovalOptions(input.request.approvalOptions);
1439
+ const context = encodeRequestContext({
1440
+ surface: 'symphony',
1441
+ source: input.source,
1442
+ requestKind: input.request.kind,
1443
+ requester: input.requester,
1444
+ decisionSource: 'secretary-policy-or-human',
1445
+ valueSource: 'runtime-request',
1446
+ message: autoModeApprovalRequestMessage(input.request),
1447
+ targetRuntimeSession: input.session,
1448
+ run: input.run,
1449
+ task: input.task,
1450
+ worker: buildSymphonyWorkerSummary(input.plan, input.webId, input.worker),
1451
+ routing: {
1452
+ firstResponder: SYMPHONY_SECRETARY_AGENT_ID,
1453
+ unresolvedSurface: 'inbox',
1454
+ rule: 'secretary-before-human',
1455
+ },
1456
+ ...(input.request.kind === 'command-approval' && input.request.command ? { command: input.request.command } : {}),
1457
+ ...(input.request.kind === 'command-approval' && input.request.cwd ? { cwd: input.request.cwd } : {}),
1458
+ });
1459
+ return {
1460
+ id: input.requestKey,
1461
+ session: input.session,
1462
+ chat: input.chat,
1463
+ thread: input.thread,
1464
+ toolCallId: extractInteractionToolCallId(input.request, input.requestKey),
1465
+ toolName: autoModeApprovalToolName(input.request),
1466
+ target: input.run,
1467
+ action: autoModeApprovalActionUri(input.request),
1468
+ risk: autoModeApprovalRisk(input.request),
1469
+ status: 'pending',
1470
+ assignedTo: input.assignedTo,
1471
+ onBehalfOf: input.webId,
1472
+ reason: 'AI Secretary should resolve this runtime approval before escalating to the human user.',
1473
+ context,
1474
+ ...(approvalOptions ? { approvalOptions } : {}),
1475
+ policyVersion: input.policyVersion,
1476
+ createdAt: input.createdAt,
1477
+ ...(input.request.expiresAt ? { expiresAt: safeDate(input.request.expiresAt) } : {}),
1478
+ };
1479
+ }
1480
+ function buildSymphonyInputRequestRow(input) {
1481
+ const context = encodeRequestContext({
1482
+ surface: 'symphony',
1483
+ source: input.source,
1484
+ requestKind: input.request.kind,
1485
+ requester: input.requester,
1486
+ decisionSource: 'secretary-policy-or-human',
1487
+ valueSource: 'secretary-or-human-response',
1488
+ targetRuntimeSession: input.session,
1489
+ run: input.run,
1490
+ task: input.task,
1491
+ worker: buildSymphonyWorkerSummary(input.plan, input.webId, input.worker),
1492
+ routing: {
1493
+ firstResponder: SYMPHONY_SECRETARY_AGENT_ID,
1494
+ unresolvedSurface: 'inbox',
1495
+ rule: 'secretary-before-human',
1496
+ },
1497
+ });
1498
+ return {
1499
+ id: input.requestKey,
1500
+ session: input.session,
1501
+ chat: input.chat,
1502
+ thread: input.thread,
1503
+ run: input.run,
1504
+ task: input.task,
1505
+ requester: input.requester,
1506
+ requestKind: input.request.kind,
1507
+ prompt: input.request.message,
1508
+ context,
1509
+ inputOptions: encodeRequestContext({
1510
+ questions: input.request.questions,
1511
+ }),
1512
+ status: 'pending',
1513
+ assignedTo: input.assignedTo,
1514
+ onBehalfOf: input.webId,
1515
+ reason: 'AI Secretary should answer this runtime input request before escalating to the human user.',
1516
+ metadata: {
1517
+ surface: 'symphony',
1518
+ source: input.source,
1519
+ policyVersion: input.policyVersion,
1520
+ decisionSource: 'secretary-policy-or-human',
1521
+ valueSource: 'secretary-or-human-response',
1522
+ requester: input.requester,
1523
+ targetRuntimeSession: input.session,
1524
+ run: input.run,
1525
+ task: input.task,
1526
+ },
1527
+ createdAt: input.createdAt,
1528
+ ...(input.request.expiresAt ? { expiresAt: safeDate(input.request.expiresAt) } : {}),
1529
+ };
1530
+ }
1531
+ function buildSymphonyInteractionInboxNotificationRow(input) {
1532
+ return {
1533
+ id: `${input.requestKey}-inbox`,
1534
+ actor: input.actor,
1535
+ object: input.controlResource,
1536
+ createdAt: input.createdAt,
1537
+ };
1538
+ }
1539
+ function buildSymphonyInteractionRunStepRow(input) {
1540
+ return {
1541
+ id: runStepResource.buildId({
1542
+ id: `${input.requestKey}-requested`,
1543
+ run: input.run,
1544
+ }),
1545
+ run: input.run,
1546
+ stepType: input.request.kind === 'user-input' ? 'input.required' : 'approval.required',
1547
+ message: input.request.message,
1548
+ payload: {
1549
+ surface: 'symphony',
1550
+ source: input.source,
1551
+ requestKind: input.request.kind,
1552
+ issue: buildSymphonyIssueIri(input.webId, input.plan.issue),
1553
+ task: buildSymphonyTaskIri(input.webId, input.worker.task),
1554
+ delivery: buildSymphonyDeliveryIri(input.webId, input.worker),
1555
+ session: buildSymphonyWorkerSessionIri(input.webId, input.worker),
1556
+ run: input.run,
1557
+ controlResource: input.controlResource,
1558
+ inboxNotification: input.inboxNotification,
1559
+ routing: {
1560
+ firstResponder: SYMPHONY_SECRETARY_AGENT_ID,
1561
+ unresolvedSurface: 'inbox',
1562
+ },
1563
+ request: summarizeInteractionRequest(input.request),
1564
+ },
1565
+ createdAt: input.createdAt,
1566
+ };
1567
+ }
1568
+ export function buildSymphonyWorkerSummary(plan, webId, worker) {
1569
+ return {
1570
+ task: worker.task,
1571
+ title: worker.taskRecord.title,
1572
+ objective: worker.taskRecord.objective,
1573
+ acceptanceCriteria: worker.taskRecord.acceptanceCriteria,
1574
+ taskStatus: worker.taskRecord.status,
1575
+ delivery: worker.delivery.uri,
1576
+ session: worker.session.uri,
1577
+ sessionResource: buildSymphonyWorkerSessionIri(webId, worker),
1578
+ backend: worker.session.backend,
1579
+ agent: worker.session.target.agent,
1580
+ status: worker.session.status,
1581
+ autoModeSessionId: worker.session.autoModeSessionId,
1582
+ target: worker.session.target,
1583
+ thread: selectWorkerThreadIri(plan, webId, worker),
1584
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
1585
+ podAccessPolicy: buildSymphonyWorkerPodAccessPolicy(plan, webId, worker),
1586
+ reconciler: buildSymphonyReconcilerMetadata(worker),
1587
+ acceptanceReview: worker.taskRecord.acceptanceReview ?? worker.delivery.acceptanceReview ?? worker.session.acceptanceReview,
1588
+ };
1589
+ }
1590
+ export function buildSymphonyReconcilerMetadata(worker) {
1591
+ const fallbackDispatch = createFallbackSymphonyDispatchDecision(worker);
1592
+ const taskDecisions = worker.taskRecord.reconciler?.decisions ?? [fallbackDispatch];
1593
+ const deliveryDecisions = worker.delivery.reconciler?.decisions ?? [fallbackDispatch];
1594
+ const sessionDecisions = worker.session.reconciler?.decisions ?? [fallbackDispatch];
1595
+ const allDecisions = [...taskDecisions, ...deliveryDecisions, ...sessionDecisions];
1596
+ const latest = lastItem(allDecisions);
1597
+ return {
1598
+ taskDecisions,
1599
+ deliveryDecisions,
1600
+ sessionDecisions,
1601
+ ...(latest ? { latest } : {}),
1602
+ };
1603
+ }
1604
+ export function buildSymphonyWorkerPodAccessPolicy(plan, webId, worker) {
1605
+ return {
1606
+ version: SYMPHONY_WORKER_POD_ACCESS_POLICY_VERSION,
1607
+ authority: '__secretary__-control-lane',
1608
+ assigned: {
1609
+ issue: buildSymphonyIssueIri(webId, plan.issue),
1610
+ task: buildSymphonyTaskIri(webId, worker.task),
1611
+ delivery: buildSymphonyDeliveryIri(webId, worker),
1612
+ run: buildSymphonyRunIri(webId, worker),
1613
+ session: buildSymphonyWorkerSessionIri(webId, worker),
1614
+ archive: buildSymphonyArchiveRefs({
1615
+ issue: plan.issue.uri,
1616
+ task: worker.task,
1617
+ delivery: worker.delivery.uri,
1618
+ session: worker.session.uri,
1619
+ }),
1620
+ },
1621
+ spaceContract: buildSymphonySpaceContract(plan, webId, worker),
1622
+ workspace: buildSymphonyWorkspaceMetadata(plan, worker),
1623
+ artifactContract: {
1624
+ pathScope: 'worker-environment-local',
1625
+ identity: [
1626
+ 'repoRelativePath',
1627
+ 'baseRevision',
1628
+ 'checksum',
1629
+ 'etag',
1630
+ 'patchUri',
1631
+ 'artifactUri',
1632
+ ],
1633
+ rule: 'absolute-paths-are-not-cross-environment-identities',
1634
+ },
1635
+ readScope: [
1636
+ 'assigned-control-records',
1637
+ 'source-context',
1638
+ 'existing-evidence',
1639
+ ],
1640
+ writeScope: [
1641
+ 'run',
1642
+ 'runStep',
1643
+ 'progress',
1644
+ 'blocker',
1645
+ 'evidence',
1646
+ 'deliveryReport',
1647
+ 'implementationChangeRequest',
1648
+ ],
1649
+ forbiddenScope: [
1650
+ 'issueClosure',
1651
+ 'specTruth',
1652
+ 'acceptanceCriteria',
1653
+ 'workSplit',
1654
+ 'releaseBoundary',
1655
+ 'roadmapState',
1656
+ 'grant',
1657
+ 'siblingWorkerState',
1658
+ ],
1659
+ noPodFallback: 'return-structured-report-for-secretary-to-persist',
1660
+ documentationAuthority: {
1661
+ controlRecords: 'pod',
1662
+ implementationRecords: 'repository',
1663
+ localControlRecords: 'portable-runtime-fallback-or-pod-mirror',
1664
+ rule: 'repository-docs-reference-pod-issue-without-becoming-issue-truth',
1665
+ },
1666
+ };
1667
+ }
1668
+ export function buildSymphonySpaceContract(plan, webId, worker) {
1669
+ return {
1670
+ control: {
1671
+ authority: 'pod-control-records',
1672
+ sharedRecords: [
1673
+ buildSymphonyIssueIri(webId, plan.issue),
1674
+ buildSymphonyTaskIri(webId, worker.task),
1675
+ buildSymphonyDeliveryIri(webId, worker),
1676
+ buildSymphonyRunIri(webId, worker),
1677
+ buildSymphonyWorkerSessionIri(webId, worker),
1678
+ ],
1679
+ },
1680
+ runtimeSession: {
1681
+ relation: resolveSymphonyRuntimeSessionRelation(plan, webId, worker),
1682
+ secretaryThread: selectTargetThreadIri(plan.issue.thread, webId, plan),
1683
+ workerThread: selectWorkerThreadIri(plan, webId, worker),
1684
+ workerSession: worker.session.uri,
1685
+ topologyRule: 'session-topology-is-explicit-not-derived-from-workspace-sharing',
1686
+ },
1687
+ workspace: {
1688
+ relation: 'thread-environment-scoped',
1689
+ allocation: 'thread',
1690
+ thread: selectWorkerThreadIri(plan, webId, worker),
1691
+ sameThreadSameEnvironmentSharing: 'preferred',
1692
+ independentWorkIsolation: 'separate-worktree-when-needed',
1693
+ crossEnvironmentIdentity: 'artifact-or-revision-evidence-required',
1694
+ },
1695
+ };
1696
+ }
1697
+ export function buildSymphonyWorkspaceMetadata(plan, worker) {
1698
+ const workspace = normalizeWorkerWorkspace(worker.session.workspace ?? plan.session.workspace, worker.session.cwd ?? plan.session.cwd);
1699
+ return {
1700
+ path: workspace.path,
1701
+ kind: workspace.kind,
1702
+ ...(workspace.container ? { container: workspace.container } : {}),
1703
+ ...(workspace.repository ? { repository: workspace.repository } : {}),
1704
+ ...(workspace.branch ? { branch: workspace.branch } : {}),
1705
+ ...(workspace.worktree ? { worktree: workspace.worktree } : {}),
1706
+ ...(workspace.baseRevision ? { baseRevision: workspace.baseRevision } : {}),
1707
+ environment: workspace.environment ?? {
1708
+ kind: 'backend-runtime',
1709
+ runtime: worker.session.backend,
1710
+ },
1711
+ pathAuthority: 'worker-environment',
1712
+ equivalenceRequires: ['baseRevision', 'checksum-or-etag-or-artifact-uri'],
1713
+ };
1714
+ }
1715
+ export function buildSymphonyArchiveRefs(refs) {
1716
+ const archive = {
1717
+ version: SYMPHONY_ARCHIVE_PROVENANCE_VERSION,
1718
+ };
1719
+ for (const [key, value] of Object.entries(refs)) {
1720
+ if (typeof value === 'string' && value.trim()) {
1721
+ archive[key] = value;
1722
+ }
1723
+ }
1724
+ return archive;
1725
+ }
1726
+ export function buildSymphonyArchiveMetadata(refs) {
1727
+ return {
1728
+ archive: buildSymphonyArchiveRefs(refs),
1729
+ };
1730
+ }
1731
+ export function buildSymphonyIssueId(issue) {
1732
+ return getSymphonyArchiveKey(issue.uri);
1733
+ }
1734
+ export function buildSymphonyIssueIri(webId, issue) {
1735
+ return issueResource.buildIri(webId, { id: getSymphonyArchiveKey(typeof issue === 'string' ? issue : issue.uri) });
1736
+ }
1737
+ export function normalizeSymphonyIssueIri(webId, issue) {
1738
+ if (/^https?:\/\//u.test(issue)) {
1739
+ return issue;
1740
+ }
1741
+ return buildSymphonyIssueIri(webId, issue);
1742
+ }
1743
+ export function buildSymphonyTaskKey(task) {
1744
+ return getSymphonyArchiveKey(task);
1745
+ }
1746
+ export function buildSymphonyTaskIri(webId, task) {
1747
+ return taskResource.buildIri(webId, { id: buildSymphonyTaskKey(task) });
1748
+ }
1749
+ export function normalizeSymphonyTaskIri(webId, task) {
1750
+ if (/^https?:\/\//u.test(task)) {
1751
+ return task;
1752
+ }
1753
+ return buildSymphonyTaskIri(webId, task);
1754
+ }
1755
+ export function buildSymphonyDeliveryIri(webId, worker) {
1756
+ return deliveryResource.buildIri(webId, {
1757
+ id: getSymphonyArchiveKey(worker.delivery.uri),
1758
+ task: buildSymphonyTaskIri(webId, worker.task),
1759
+ createdAt: safeDate(worker.delivery.createdAt),
1760
+ });
1761
+ }
1762
+ export function buildSymphonyRunIri(webId, worker) {
1763
+ return runResource.buildIri(webId, {
1764
+ id: getSymphonyArchiveKey(worker.session.uri),
1765
+ task: buildSymphonyTaskIri(webId, worker.task),
1766
+ createdAt: safeDate(worker.session.createdAt),
1767
+ });
1768
+ }
1769
+ export function buildSymphonyWorkerSessionIri(webId, worker) {
1770
+ return sessionResource.buildIri(webId, {
1771
+ id: buildSymphonySessionRecordId(worker.session),
1772
+ createdAt: worker.session.createdAt,
1773
+ });
1774
+ }
1775
+ export function buildSymphonyControlSessionIri(webId, plan) {
1776
+ return sessionResource.buildIri(webId, {
1777
+ id: buildSymphonyThreadId(plan),
1778
+ createdAt: plan.session.createdAt,
1779
+ });
1780
+ }
1781
+ export function buildSymphonySessionRecordId(session) {
1782
+ return session.uri
1783
+ .trim()
1784
+ .replace(/^urn:undefineds:linx:session:/u, '')
1785
+ .replace(/[^a-zA-Z0-9._-]/gu, '-')
1786
+ .replace(/-+/gu, '-')
1787
+ .replace(/^-|-$/gu, '') || 'symphony-session';
1788
+ }
1789
+ export function buildSymphonyThreadId(plan) {
1790
+ return buildSymphonySessionRecordId(plan.session);
1791
+ }
1792
+ export function buildSymphonyChatUri(webId) {
1793
+ return chatRepository.iri(webId, SYMPHONY_CHAT_ID);
1794
+ }
1795
+ export function buildTargetChatId(plan, webId) {
1796
+ return chatRepository.target(selectTargetChatIri(plan.session.target?.chat, webId, plan)).id;
1797
+ }
1798
+ export function buildSymphonyMessageIri(webId, plan, row) {
1799
+ return messageResource.buildIri(webId, {
1800
+ id: String(row.id),
1801
+ chat: selectTargetChatIri(plan.session.target?.chat, webId, plan),
1802
+ thread: selectTargetThreadIri(plan.session.target?.thread, webId, plan),
1803
+ createdAt: row.createdAt,
1804
+ });
1805
+ }
1806
+ export function selectTargetChatIri(value, webId, plan) {
1807
+ if (!value) {
1808
+ const thread = plan?.session.target?.thread;
1809
+ if (thread) {
1810
+ return chatRepository.iri(webId, threadRepository.chatIdFromRef(thread) ?? SYMPHONY_CHAT_ID);
1811
+ }
1812
+ return buildSymphonyChatUri(webId);
1813
+ }
1814
+ return chatRepository.iri(webId, value);
1815
+ }
1816
+ export function selectTargetThreadIri(value, webId, plan) {
1817
+ if (!value) {
1818
+ return selectDefaultThreadIri(webId, plan);
1819
+ }
1820
+ return threadRepository.iriForChat(webId, selectTargetChatIri(plan.session.target?.chat, webId, plan), value);
1821
+ }
1822
+ export function selectDefaultThreadIri(webId, plan) {
1823
+ const targetThread = plan.session.target?.thread;
1824
+ if (targetThread) {
1825
+ return targetThread;
1826
+ }
1827
+ return threadRepository.iriForChat(webId, selectTargetChatIri(plan.session.target?.chat, webId, plan), buildSymphonyThreadId(plan));
1828
+ }
1829
+ export function readWorkerChatRef(worker) {
1830
+ return worker.session.target?.chat
1831
+ ?? worker.session.chat
1832
+ ?? worker.taskRecord.chat
1833
+ ?? worker.delivery.chat;
1834
+ }
1835
+ export function readWorkerThreadRef(worker) {
1836
+ return worker.session.target?.thread
1837
+ ?? worker.session.thread
1838
+ ?? worker.taskRecord.thread
1839
+ ?? worker.delivery.thread;
1840
+ }
1841
+ export function readWorkerMessages(worker) {
1842
+ return worker.session.target?.messages
1843
+ ?? worker.session.messages
1844
+ ?? worker.taskRecord.messages
1845
+ ?? worker.delivery.messages
1846
+ ?? [];
1847
+ }
1848
+ export function selectWorkerChatIri(plan, webId, worker) {
1849
+ const chat = readWorkerChatRef(worker);
1850
+ if (chat) {
1851
+ return selectTargetChatIri(chat, webId, plan);
1852
+ }
1853
+ const thread = readWorkerThreadRef(worker);
1854
+ if (thread) {
1855
+ return chatRepository.iri(webId, threadRepository.chatIdFromRef(thread) ?? chatRepository.idFromRef(selectTargetChatIri(undefined, webId, plan)) ?? SYMPHONY_CHAT_ID);
1856
+ }
1857
+ return selectTargetChatIri(undefined, webId, plan);
1858
+ }
1859
+ export function selectWorkerThreadIri(plan, webId, worker) {
1860
+ const thread = readWorkerThreadRef(worker);
1861
+ if (thread) {
1862
+ return selectTargetThreadIri(thread, webId, plan);
1863
+ }
1864
+ const chat = selectWorkerChatIri(plan, webId, worker);
1865
+ if (chat.endsWith('#this')) {
1866
+ return `${chat.slice(0, -'#this'.length)}#${encodeURIComponent(buildSymphonySessionRecordId(worker.session))}`;
1867
+ }
1868
+ return selectTargetThreadIri(undefined, webId, plan);
1869
+ }
1870
+ export function buildWorkerThreadId(plan, webId, worker) {
1871
+ return threadRepository.idFromRef(selectWorkerThreadIri(plan, webId, worker))
1872
+ ?? buildSymphonySessionRecordId(worker.session);
1873
+ }
1874
+ function collectSymphonyThreadProjectionGroups(plan, webId) {
1875
+ const groups = new Map();
1876
+ for (const worker of plan.workers) {
1877
+ const chat = selectWorkerChatIri(plan, webId, worker);
1878
+ const thread = selectWorkerThreadIri(plan, webId, worker);
1879
+ const key = `${chat}\0${thread}`;
1880
+ const existing = groups.get(key);
1881
+ if (existing) {
1882
+ existing.workers.push(worker);
1883
+ }
1884
+ else {
1885
+ groups.set(key, { chat, thread, workers: [worker] });
1886
+ }
1887
+ }
1888
+ return Array.from(groups.values());
1889
+ }
1890
+ export function buildWorkerAgentId(backend, agent) {
1891
+ const suffix = (agent ?? `${backend}-worker`)
1892
+ .trim()
1893
+ .replace(/[^a-zA-Z0-9._-]/gu, '-')
1894
+ .replace(/-+/gu, '-')
1895
+ .replace(/^-|-$/gu, '');
1896
+ return `symphony-${suffix || `${backend}-worker`}`;
1897
+ }
1898
+ export function mapSymphonyTaskStatus(status) {
1899
+ if (status === 'running')
1900
+ return 'active';
1901
+ if (status === 'pending')
1902
+ return 'open';
1903
+ return status;
1904
+ }
1905
+ export function mapSymphonyRunStatus(status) {
1906
+ if (status === 'planned')
1907
+ return 'queued';
1908
+ if (status === 'running')
1909
+ return 'running';
1910
+ if (status === 'completed')
1911
+ return 'completed';
1912
+ if (status === 'failed')
1913
+ return 'failed';
1914
+ return 'queued';
1915
+ }
1916
+ export function mapSymphonyReportOutcome(worker, outcome) {
1917
+ if (worker.session.status === 'failed')
1918
+ return ReportOutcome.BLOCKED;
1919
+ if (outcome === 'accepted')
1920
+ return ReportOutcome.ACCEPTED;
1921
+ if (outcome === 'rejected')
1922
+ return ReportOutcome.REJECTED;
1923
+ if (outcome === 'blocked')
1924
+ return ReportOutcome.BLOCKED;
1925
+ if (outcome === 'follow_up')
1926
+ return ReportOutcome.DEFERRED;
1927
+ return ReportOutcome.ACCEPTED;
1928
+ }
1929
+ export function inferSymphonyControlStage(plan) {
1930
+ if (plan.workers.some((worker) => worker.session.status === 'failed'))
1931
+ return 'failed';
1932
+ if (plan.workers.every((worker) => worker.session.status === 'completed'))
1933
+ return 'completed';
1934
+ if (plan.workers.some((worker) => worker.session.status === 'running'))
1935
+ return 'running';
1936
+ return 'planned';
1937
+ }
1938
+ export function buildStatusContent(plan, stage) {
1939
+ if (stage === 'planned') {
1940
+ return `I created a Symphony issue with ${plan.workers.length} worker${plan.workers.length === 1 ? '' : 's'}.\n\n${plan.issue.description ?? plan.issue.title}`;
1941
+ }
1942
+ if (stage === 'running') {
1943
+ const running = plan.workers
1944
+ .filter((worker) => worker.session.status === 'running')
1945
+ .map((worker) => worker.session.target.label ?? worker.session.target.agent ?? backendDisplayName(worker.session.backend));
1946
+ return `Symphony workers are active: ${running.length > 0 ? running.join(', ') : plan.workers.length}.\n\nIssue: ${plan.issue.uri}`;
1947
+ }
1948
+ if (stage === 'completed') {
1949
+ return `Symphony issue completed.\n\nWorkers: ${plan.workers.length}`;
1950
+ }
1951
+ return `Symphony issue failed.\n\n${plan.issue.error ?? plan.session.error ?? plan.delivery.error ?? 'Backend did not complete successfully.'}`;
1952
+ }
1953
+ function buildProgressBlock(plan, stage) {
1954
+ const statusByStage = {
1955
+ planned: 'pending',
1956
+ running: 'running',
1957
+ completed: 'done',
1958
+ failed: 'error',
1959
+ };
1960
+ const workerSteps = plan.workers.map((worker, index) => ({
1961
+ id: `${buildSymphonyThreadId(plan)}-worker-${index + 1}`,
1962
+ label: `${worker.session.target.label ?? worker.session.target.agent ?? backendDisplayName(worker.session.backend)} worker`,
1963
+ status: worker.session.status === 'completed'
1964
+ ? 'done'
1965
+ : worker.session.status === 'failed'
1966
+ ? 'error'
1967
+ : worker.session.status === 'running'
1968
+ ? 'running'
1969
+ : statusByStage[stage],
1970
+ detail: worker.session.autoModeSessionId ?? worker.session.uri,
1971
+ }));
1972
+ return {
1973
+ type: 'task_progress',
1974
+ task: plan.task,
1975
+ title: plan.issue.title,
1976
+ steps: [
1977
+ {
1978
+ id: `${buildSymphonyThreadId(plan)}-plan`,
1979
+ label: 'Secretary created task projection',
1980
+ status: stage === 'planned' ? 'running' : 'done',
1981
+ detail: plan.issue.uri,
1982
+ },
1983
+ ...workerSteps,
1984
+ {
1985
+ id: `${buildSymphonyThreadId(plan)}-finish`,
1986
+ label: 'Archive Symphony result',
1987
+ status: stage === 'completed' ? 'done' : stage === 'failed' ? 'error' : 'pending',
1988
+ detail: plan.issue.error ?? plan.session.error ?? `${plan.workers.length} worker${plan.workers.length === 1 ? '' : 's'}`,
1989
+ },
1990
+ ],
1991
+ currentStep: stage === 'planned' ? 1 : stage === 'running' ? 2 : workerSteps.length + 2,
1992
+ totalSteps: workerSteps.length + 2,
1993
+ };
1994
+ }
1995
+ function createFallbackSymphonyDispatchDecision(worker) {
1996
+ return decideThreadControlEvent({
1997
+ policy: {
1998
+ kind: 'symphony',
1999
+ assignedWorkerAgent: worker.delivery.targetAgent,
2000
+ secretaryAgent: SYMPHONY_SECRETARY_AGENT_ID,
2001
+ },
2002
+ event: {
2003
+ type: 'delivery.submitted',
2004
+ ...(readWorkerChatRef(worker) ? { chat: readWorkerChatRef(worker) } : {}),
2005
+ ...(readWorkerThreadRef(worker) ? { thread: readWorkerThreadRef(worker) } : {}),
2006
+ resource: worker.delivery.uri,
2007
+ actor: {
2008
+ id: SYMPHONY_SECRETARY_AGENT_ID,
2009
+ role: 'secretary',
2010
+ },
2011
+ data: {
2012
+ deliveryType: worker.delivery.type,
2013
+ issue: worker.delivery.issue,
2014
+ task: worker.delivery.task,
2015
+ delivery: worker.delivery.uri,
2016
+ session: worker.session.uri,
2017
+ },
2018
+ },
2019
+ now: safeDate(worker.delivery.createdAt),
2020
+ randomId: `${worker.delivery.uri}-dispatch`,
2021
+ }).summary;
2022
+ }
2023
+ function resolveSymphonyRuntimeSessionRelation(plan, webId, worker) {
2024
+ const secretaryThread = selectTargetThreadIri(plan.issue.thread, webId, plan);
2025
+ const workerThread = selectWorkerThreadIri(plan, webId, worker);
2026
+ if (secretaryThread === workerThread) {
2027
+ return 'same-thread-or-room';
2028
+ }
2029
+ return 'runtime-projected-worker-session';
2030
+ }
2031
+ function normalizeWorkerWorkspace(workspace, fallbackPath) {
2032
+ return {
2033
+ path: workspace?.path ?? fallbackPath,
2034
+ kind: workspace?.kind ?? 'folder',
2035
+ ...(workspace?.repository ? { repository: workspace.repository } : {}),
2036
+ ...(workspace?.branch ? { branch: workspace.branch } : {}),
2037
+ ...(workspace?.worktree ? { worktree: workspace.worktree } : {}),
2038
+ ...(workspace?.container ? { container: workspace.container } : {}),
2039
+ ...(workspace?.baseRevision ? { baseRevision: workspace.baseRevision } : {}),
2040
+ ...(workspace?.environment ? { environment: workspace.environment } : {}),
2041
+ };
2042
+ }
2043
+ function backendDisplayName(backend) {
2044
+ if (backend === 'codex')
2045
+ return 'Codex';
2046
+ if (backend === 'claude')
2047
+ return 'Claude Code';
2048
+ if (backend === 'codebuddy')
2049
+ return 'CodeBuddy';
2050
+ return backend;
2051
+ }
2052
+ function normalizeTitle(text, width = 72) {
2053
+ const normalized = text.replace(/\s+/g, ' ').trim();
2054
+ if (!normalized)
2055
+ return 'Symphony Task';
2056
+ if (normalized.length <= width)
2057
+ return normalized;
2058
+ return `${normalized.slice(0, Math.max(0, width - 3))}...`;
2059
+ }
2060
+ function pathToWorkspaceUri(path) {
2061
+ if (!path.trim()) {
2062
+ return undefined;
2063
+ }
2064
+ return `file://${path}`;
2065
+ }
2066
+ function defaultInteractionSource(worker) {
2067
+ if (worker.session.backend === 'codex') {
2068
+ return 'codex-app-server';
2069
+ }
2070
+ return 'runtime';
2071
+ }
2072
+ function stableInteractionRequestKey(request, randomId) {
2073
+ const suffix = (randomId?.trim() || stableHash(JSON.stringify(summarizeInteractionRequest(request))))
2074
+ .replace(/[^a-zA-Z0-9._-]/gu, '-')
2075
+ .replace(/-+/gu, '-')
2076
+ .replace(/^-|-$/gu, '')
2077
+ .slice(0, 48);
2078
+ return `symphony-${request.kind}-${suffix || 'runtime-request'}`;
2079
+ }
2080
+ function stableHash(value) {
2081
+ let hash = 2166136261;
2082
+ for (let index = 0; index < value.length; index += 1) {
2083
+ hash ^= value.charCodeAt(index);
2084
+ hash = Math.imul(hash, 16777619);
2085
+ }
2086
+ return (hash >>> 0).toString(36);
2087
+ }
2088
+ function extractInteractionToolCallId(request, fallback) {
2089
+ const raw = recordFromUnknown(request.raw);
2090
+ const params = recordFromUnknown(raw?.params);
2091
+ const toolCall = recordFromUnknown(params?.toolCall);
2092
+ return stringFromUnknown(toolCall?.toolCallId)
2093
+ ?? stringFromUnknown(params?.toolCallId)
2094
+ ?? stringFromUnknown(raw?.id)
2095
+ ?? fallback;
2096
+ }
2097
+ function summarizeInteractionRequest(request) {
2098
+ if (request.kind === 'user-input') {
2099
+ return {
2100
+ kind: request.kind,
2101
+ message: request.message,
2102
+ questions: request.questions.map((question) => ({
2103
+ id: question.id,
2104
+ header: question.header,
2105
+ question: question.question,
2106
+ options: question.options,
2107
+ })),
2108
+ ...(request.timeoutMs ? { timeoutMs: request.timeoutMs } : {}),
2109
+ ...(request.expiresAt ? { expiresAt: request.expiresAt } : {}),
2110
+ };
2111
+ }
2112
+ return {
2113
+ kind: request.kind,
2114
+ message: request.message,
2115
+ toolName: autoModeApprovalToolName(request),
2116
+ action: autoModeApprovalActionUri(request),
2117
+ risk: autoModeApprovalRisk(request),
2118
+ ...(request.kind === 'command-approval' && request.command ? { command: request.command } : {}),
2119
+ ...(request.kind === 'command-approval' && request.cwd ? { cwd: request.cwd } : {}),
2120
+ ...(request.kind === 'file-change-approval' && request.reason ? { reason: request.reason } : {}),
2121
+ ...(request.kind === 'permissions-approval' ? { permissions: request.permissions } : {}),
2122
+ ...(request.approvalOptions ? { approvalOptions: request.approvalOptions } : {}),
2123
+ ...(request.timeoutMs ? { timeoutMs: request.timeoutMs } : {}),
2124
+ ...(request.expiresAt ? { expiresAt: request.expiresAt } : {}),
2125
+ };
2126
+ }
2127
+ function encodeRequestContext(value) {
2128
+ try {
2129
+ return JSON.stringify(value);
2130
+ }
2131
+ catch {
2132
+ return JSON.stringify({ error: 'unserializable_context' });
2133
+ }
2134
+ }
2135
+ function recordFromUnknown(value) {
2136
+ return value && typeof value === 'object' && !Array.isArray(value)
2137
+ ? value
2138
+ : null;
2139
+ }
2140
+ function asRecord(value) {
2141
+ return recordFromUnknown(value);
2142
+ }
2143
+ function stringFromUnknown(value) {
2144
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
2145
+ }
2146
+ function normalizeString(value) {
2147
+ return stringFromUnknown(value);
2148
+ }
2149
+ function readRuntimeEventDate(event) {
2150
+ const record = asRecord(event);
2151
+ if (!record) {
2152
+ return undefined;
2153
+ }
2154
+ if (record.now instanceof Date) {
2155
+ return record.now;
2156
+ }
2157
+ return safeOptionalDate(record.createdAt ?? record.created_at ?? record.timestamp);
2158
+ }
2159
+ function safeOptionalDate(value) {
2160
+ if (!value) {
2161
+ return undefined;
2162
+ }
2163
+ const date = value instanceof Date ? value : new Date(String(value));
2164
+ return Number.isFinite(date.getTime()) ? date : undefined;
2165
+ }
2166
+ function toIsoDate(value) {
2167
+ return (safeOptionalDate(value) ?? new Date()).toISOString();
2168
+ }
2169
+ function lastItem(items) {
2170
+ return items && items.length > 0 ? items[items.length - 1] : undefined;
2171
+ }
2172
+ function safeDate(input) {
2173
+ const date = input instanceof Date ? input : new Date(input ?? Date.now());
2174
+ return Number.isFinite(date.getTime()) ? date : new Date();
2175
+ }