@undefineds.co/linx 0.3.5 → 0.3.8

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