@undefineds.co/linx 0.3.8 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/generated/version.js +2 -2
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +216 -2
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/linx-status-line.js +335 -0
- package/dist/lib/linx-status-line.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +3 -3
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/models.js +2 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +326 -231
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +52 -2
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +14 -4
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +24 -1
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/status-line-command.js +108 -0
- package/dist/lib/status-line-command.js.map +1 -0
- package/dist/lib/symphony/pod-projection.js +357 -17
- package/dist/lib/symphony/pod-projection.js.map +1 -1
- package/dist/lib/symphony-command.js +20 -21
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/skills/symphony/SKILL.md +119 -10
- package/dist/skills/xpod-cli/SKILL.md +70 -0
- package/package.json +9 -3
- package/vendor/agent-runtime/dist/client-inbox-subscription.d.ts +56 -0
- package/vendor/agent-runtime/dist/client-inbox-subscription.js +93 -0
- package/vendor/agent-runtime/dist/index.d.ts +1 -0
- package/vendor/agent-runtime/dist/index.js +1 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +60 -1
- package/vendor/agent-runtime/dist/reconciler.js +150 -6
- package/vendor/agent-runtime/dist/symphony.js +6 -5
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +2 -1
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +4 -0
|
@@ -7,7 +7,7 @@ import { decideThreadControlEvent } from '../../../vendor/agent-runtime/dist/thr
|
|
|
7
7
|
import { createLinxPodSyncScope } from '../../../vendor/agent-runtime/dist/sync.js';
|
|
8
8
|
import { insertExactRecordOnce, resolvePodResourceTemplateValue, upsertExactRecord, } from '@undefineds.co/drizzle-solid';
|
|
9
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';
|
|
10
|
+
import { ContactClass, ContactType, chatRepository, agentResource, contactResource, deliveryResource, ideaResource, issueResource, messageResource, reportResource, runResource, runStepResource, sessionResource, taskResource, threadRepository, } from '../models.js';
|
|
11
11
|
import { pathToWorkspaceUri } from '../pi-adapter/pod-mirror-mapping.js';
|
|
12
12
|
import { getSymphonyHome } from './archive.js';
|
|
13
13
|
const SYMPHONY_CHAT_ID = 'symphony';
|
|
@@ -16,6 +16,191 @@ const SYMPHONY_CONTACT_ID = 'symphony';
|
|
|
16
16
|
const SYMPHONY_POLICY_VERSION = 'linx-symphony-session/v1';
|
|
17
17
|
const SYMPHONY_WORKER_POD_ACCESS_POLICY_VERSION = 'linx-symphony-worker-pod-access/v1';
|
|
18
18
|
const SYMPHONY_ARCHIVE_PROVENANCE_VERSION = 'linx-symphony-archive/v1';
|
|
19
|
+
function ensureTrailingSlash(value) {
|
|
20
|
+
return value.endsWith('/') ? value : `${value}/`;
|
|
21
|
+
}
|
|
22
|
+
function podBaseUrlFromWebId(webId) {
|
|
23
|
+
const marker = '/profile/card#me';
|
|
24
|
+
if (webId.includes(marker)) {
|
|
25
|
+
return `${webId.slice(0, webId.indexOf(marker) + 1)}`;
|
|
26
|
+
}
|
|
27
|
+
const url = new URL(webId);
|
|
28
|
+
return `${url.origin}/`;
|
|
29
|
+
}
|
|
30
|
+
function podFileUrlFromWebId(webId, path) {
|
|
31
|
+
return new URL(path.replace(/^\/+/, ''), podBaseUrlFromWebId(webId)).toString();
|
|
32
|
+
}
|
|
33
|
+
function podFileUrl(podSession, path) {
|
|
34
|
+
return new URL(path.replace(/^\/+/, ''), ensureTrailingSlash(podSession.podUrl)).toString();
|
|
35
|
+
}
|
|
36
|
+
async function writePodFileToSession(session, file) {
|
|
37
|
+
const url = podFileUrl(session, file.path);
|
|
38
|
+
await ensurePodResourceContainers(session.fetch, url);
|
|
39
|
+
const response = await session.fetch(url, {
|
|
40
|
+
method: 'PUT',
|
|
41
|
+
headers: { 'Content-Type': file.contentType },
|
|
42
|
+
body: file.content.endsWith('\n') ? file.content : `${file.content}\n`,
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const details = await response.text().catch(() => '');
|
|
46
|
+
const suffix = details.trim() ? ` - ${details.trim().slice(0, 500)}` : '';
|
|
47
|
+
throw new Error(`Failed to write Symphony Pod file ${url}: ${response.status} ${response.statusText}${suffix}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function ensurePodResourceContainers(fetcher, resourceUrl) {
|
|
51
|
+
for (const containerUrl of containerUrlsForResource(resourceUrl)) {
|
|
52
|
+
const existing = await fetcher(containerUrl, { method: 'HEAD' }).catch(() => null);
|
|
53
|
+
if (existing?.ok)
|
|
54
|
+
continue;
|
|
55
|
+
if (existing && existing.status !== 404 && existing.status !== 405)
|
|
56
|
+
continue;
|
|
57
|
+
const response = await fetcher(containerUrl, {
|
|
58
|
+
method: 'PUT',
|
|
59
|
+
headers: {
|
|
60
|
+
Link: '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
|
|
61
|
+
'Content-Type': 'text/turtle; charset=utf-8',
|
|
62
|
+
},
|
|
63
|
+
body: '',
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok && response.status !== 409) {
|
|
66
|
+
throw new Error(`Failed to ensure Symphony Pod container ${containerUrl}: ${response.status} ${response.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function containerUrlsForResource(resourceUrl) {
|
|
71
|
+
const url = new URL(resourceUrl);
|
|
72
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
73
|
+
const containers = [];
|
|
74
|
+
let path = '/';
|
|
75
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
76
|
+
path += `${parts[index]}/`;
|
|
77
|
+
containers.push(new URL(path, url.origin).toString());
|
|
78
|
+
}
|
|
79
|
+
return containers;
|
|
80
|
+
}
|
|
81
|
+
function datePathParts(input) {
|
|
82
|
+
const date = safeDate(input);
|
|
83
|
+
return {
|
|
84
|
+
yyyy: String(date.getUTCFullYear()),
|
|
85
|
+
MM: String(date.getUTCMonth() + 1).padStart(2, '0'),
|
|
86
|
+
dd: String(date.getUTCDate()).padStart(2, '0'),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildSymphonyIssueDocumentPath(issue) {
|
|
90
|
+
return `/.data/issues/${buildSymphonyIssueId(issue)}/issue.md`;
|
|
91
|
+
}
|
|
92
|
+
function buildSymphonyIdeaDocumentPath(idea) {
|
|
93
|
+
const { yyyy, MM, dd } = datePathParts(idea.createdAt);
|
|
94
|
+
return `/.data/ideas/${yyyy}/${MM}/${dd}/${getSymphonyArchiveKey(idea.uri)}/idea.md`;
|
|
95
|
+
}
|
|
96
|
+
function buildSymphonyReportDocumentPath(worker) {
|
|
97
|
+
const { yyyy, MM, dd } = datePathParts(worker.session.completedAt ?? worker.session.updatedAt);
|
|
98
|
+
return `/.data/reports/${yyyy}/${MM}/${dd}/${getSymphonyArchiveKey(worker.session.uri)}-report.md`;
|
|
99
|
+
}
|
|
100
|
+
function renderMarkdownList(items) {
|
|
101
|
+
const values = (items ?? []).map((item) => item.trim()).filter(Boolean);
|
|
102
|
+
return values.length > 0 ? values.map((item) => `- ${item}`).join('\n') : '- None recorded.';
|
|
103
|
+
}
|
|
104
|
+
function renderSymphonyIssueMarkdown(plan) {
|
|
105
|
+
const issue = plan.issue;
|
|
106
|
+
const sections = [
|
|
107
|
+
`# ${issue.title || buildSymphonyIssueId(issue)}`,
|
|
108
|
+
'',
|
|
109
|
+
'## Summary',
|
|
110
|
+
issue.description?.trim() || issue.title || 'No summary recorded.',
|
|
111
|
+
'',
|
|
112
|
+
'## Status',
|
|
113
|
+
`- Status: ${issue.status}`,
|
|
114
|
+
`- Priority: ${issue.priority}`,
|
|
115
|
+
`- Source: ${issue.source}`,
|
|
116
|
+
'',
|
|
117
|
+
'## Acceptance Criteria',
|
|
118
|
+
renderMarkdownList(plan.workers.flatMap((worker) => worker.taskRecord.acceptanceCriteria ?? [])),
|
|
119
|
+
'',
|
|
120
|
+
'## Tasks',
|
|
121
|
+
renderMarkdownList(plan.workers.map((worker) => `${worker.taskRecord.title}: ${worker.taskRecord.objective}`)),
|
|
122
|
+
'',
|
|
123
|
+
'## Source Context',
|
|
124
|
+
`- Chat: ${issue.chat ?? plan.session.chat ?? 'not recorded'}`,
|
|
125
|
+
`- Thread: ${issue.thread ?? plan.session.thread ?? 'not recorded'}`,
|
|
126
|
+
`- Messages: ${(issue.messages ?? []).join(', ') || 'not recorded'}`,
|
|
127
|
+
'',
|
|
128
|
+
'## Control Records',
|
|
129
|
+
`- Issue: ${issue.uri}`,
|
|
130
|
+
...plan.workers.flatMap((worker) => [
|
|
131
|
+
`- Task: ${worker.task}`,
|
|
132
|
+
`- Delivery: ${worker.delivery.uri}`,
|
|
133
|
+
`- Session: ${worker.session.uri}`,
|
|
134
|
+
]),
|
|
135
|
+
];
|
|
136
|
+
return sections.join('\n');
|
|
137
|
+
}
|
|
138
|
+
function renderSymphonyIdeaMarkdown(idea) {
|
|
139
|
+
return [
|
|
140
|
+
`# ${idea.summary || getSymphonyArchiveKey(idea.uri)}`,
|
|
141
|
+
'',
|
|
142
|
+
'## Input',
|
|
143
|
+
idea.input?.trim() || idea.summary || 'No input recorded.',
|
|
144
|
+
'',
|
|
145
|
+
'## Current Understanding',
|
|
146
|
+
idea.currentUnderstanding?.trim() || 'No current understanding recorded.',
|
|
147
|
+
'',
|
|
148
|
+
'## Open Questions',
|
|
149
|
+
renderMarkdownList(idea.openQuestions),
|
|
150
|
+
'',
|
|
151
|
+
'## Conflicts',
|
|
152
|
+
renderMarkdownList(idea.conflicts),
|
|
153
|
+
'',
|
|
154
|
+
'## Next Step',
|
|
155
|
+
idea.nextStep?.trim() || 'No next step recorded.',
|
|
156
|
+
'',
|
|
157
|
+
'## Source Context',
|
|
158
|
+
`- Status: ${idea.status}`,
|
|
159
|
+
`- Commitment: ${idea.commitment}`,
|
|
160
|
+
`- Chat: ${idea.chat ?? 'not recorded'}`,
|
|
161
|
+
`- Thread: ${idea.thread ?? 'not recorded'}`,
|
|
162
|
+
`- Messages: ${(idea.messages ?? []).join(', ') || 'not recorded'}`,
|
|
163
|
+
`- Idea: ${idea.uri}`,
|
|
164
|
+
].join('\n');
|
|
165
|
+
}
|
|
166
|
+
function renderSymphonyReportMarkdown(plan, worker, stage) {
|
|
167
|
+
const status = worker.session.status === 'failed' || stage === 'failed' ? 'failed' : 'completed';
|
|
168
|
+
const summary = status === 'completed'
|
|
169
|
+
? `${worker.taskRecord.title} completed.`
|
|
170
|
+
: `${worker.taskRecord.title} failed: ${worker.session.error ?? worker.delivery.error ?? 'worker did not complete successfully.'}`;
|
|
171
|
+
return [
|
|
172
|
+
`# ${worker.taskRecord.title} — ${status}`,
|
|
173
|
+
'',
|
|
174
|
+
'## Summary',
|
|
175
|
+
summary,
|
|
176
|
+
'',
|
|
177
|
+
'## Outcome',
|
|
178
|
+
`- Status: ${status}`,
|
|
179
|
+
`- Backend: ${worker.session.backend}`,
|
|
180
|
+
`- Agent: ${worker.session.target.agent ?? worker.delivery.targetAgent}`,
|
|
181
|
+
`- Auto mode session: ${worker.session.autoModeSessionId ?? 'not recorded'}`,
|
|
182
|
+
`- Exit code: ${worker.session.exitCode ?? 'not recorded'}`,
|
|
183
|
+
'',
|
|
184
|
+
'## Task',
|
|
185
|
+
worker.taskRecord.objective,
|
|
186
|
+
'',
|
|
187
|
+
'## Acceptance Criteria',
|
|
188
|
+
renderMarkdownList(worker.taskRecord.acceptanceCriteria),
|
|
189
|
+
'',
|
|
190
|
+
'## Linked Control Records',
|
|
191
|
+
`- Issue: ${buildSymphonyIssueId(plan.issue)}`,
|
|
192
|
+
`- Task: ${worker.task}`,
|
|
193
|
+
`- Delivery: ${worker.delivery.uri}`,
|
|
194
|
+
`- Session: ${worker.session.uri}`,
|
|
195
|
+
`- Run status: ${worker.session.status}`,
|
|
196
|
+
'',
|
|
197
|
+
...(worker.session.error || worker.delivery.error || worker.taskRecord.error ? [
|
|
198
|
+
'## Error',
|
|
199
|
+
worker.session.error ?? worker.delivery.error ?? worker.taskRecord.error ?? '',
|
|
200
|
+
'',
|
|
201
|
+
] : []),
|
|
202
|
+
].join('\n');
|
|
203
|
+
}
|
|
19
204
|
function normalizeSymphonyRunPlan(plan) {
|
|
20
205
|
const workers = Array.isArray(plan.workers) && plan.workers.length > 0
|
|
21
206
|
? plan.workers
|
|
@@ -119,12 +304,14 @@ async function createDefaultRuntime() {
|
|
|
119
304
|
issueResource: models.issueResource,
|
|
120
305
|
taskResource: models.taskResource,
|
|
121
306
|
deliveryResource: models.deliveryResource,
|
|
307
|
+
reportResource: models.reportResource,
|
|
122
308
|
runResource: models.runResource,
|
|
123
309
|
runStepResource: models.runStepResource,
|
|
124
310
|
agentResource: models.agentResource,
|
|
125
311
|
contactResource: models.contactTable,
|
|
126
312
|
auditResource: models.auditResource,
|
|
127
313
|
inboxNotificationResource: models.inboxNotificationResource,
|
|
314
|
+
writePodFile: writePodFileToSession,
|
|
128
315
|
};
|
|
129
316
|
}
|
|
130
317
|
function selectTargetChatIri(value, webId, plan) {
|
|
@@ -211,6 +398,13 @@ function buildSymphonyReportDeliveryIri(webId, worker) {
|
|
|
211
398
|
createdAt: safeDate(worker.session.completedAt ?? worker.session.updatedAt),
|
|
212
399
|
});
|
|
213
400
|
}
|
|
401
|
+
function buildSymphonyReportIri(webId, worker) {
|
|
402
|
+
return reportResource.buildIri(webId, {
|
|
403
|
+
id: `${getSymphonyArchiveKey(worker.session.uri)}-report`,
|
|
404
|
+
task: buildSymphonyTaskIri(webId, worker.task),
|
|
405
|
+
createdAt: safeDate(worker.session.completedAt ?? worker.session.updatedAt),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
214
408
|
function buildSymphonyRunIri(webId, worker) {
|
|
215
409
|
return runResource.buildIri(webId, {
|
|
216
410
|
id: getSymphonyArchiveKey(worker.session.uri),
|
|
@@ -808,8 +1002,11 @@ function buildSymphonyIssueRow(plan, webId) {
|
|
|
808
1002
|
const updatedAt = safeDate(plan.issue.updatedAt);
|
|
809
1003
|
return {
|
|
810
1004
|
id: buildSymphonyIssueId(plan.issue),
|
|
1005
|
+
// File-primary: title remains a compact index label for existing Issue schemas.
|
|
1006
|
+
// The full problem statement and acceptance narrative live in issue.md.
|
|
811
1007
|
title: plan.issue.title,
|
|
812
|
-
|
|
1008
|
+
document: podFileUrlFromWebId(webId, buildSymphonyIssueDocumentPath(plan.issue)),
|
|
1009
|
+
description: undefined,
|
|
813
1010
|
status: plan.issue.status,
|
|
814
1011
|
priority: plan.issue.priority,
|
|
815
1012
|
labels: ['symphony'],
|
|
@@ -830,15 +1027,17 @@ function buildSymphonyIdeaRow(idea, webId) {
|
|
|
830
1027
|
return {
|
|
831
1028
|
id: getSymphonyArchiveKey(idea.uri),
|
|
832
1029
|
summary: idea.summary,
|
|
833
|
-
|
|
1030
|
+
document: podFileUrlFromWebId(webId, buildSymphonyIdeaDocumentPath(idea)),
|
|
1031
|
+
// File-primary: the source text lives in idea.md; TTL keeps only routing/index facts.
|
|
1032
|
+
input: undefined,
|
|
834
1033
|
status: idea.status,
|
|
835
1034
|
commitment: idea.commitment,
|
|
836
1035
|
affectedArea: idea.affectedArea,
|
|
837
|
-
currentUnderstanding:
|
|
838
|
-
openQuestions:
|
|
1036
|
+
currentUnderstanding: undefined,
|
|
1037
|
+
openQuestions: undefined,
|
|
839
1038
|
related: idea.relatedRecords,
|
|
840
|
-
conflicts:
|
|
841
|
-
nextStep:
|
|
1039
|
+
conflicts: undefined,
|
|
1040
|
+
nextStep: undefined,
|
|
842
1041
|
promotedTo: idea.promotedTo,
|
|
843
1042
|
chat: idea.chat,
|
|
844
1043
|
thread: idea.thread,
|
|
@@ -846,6 +1045,8 @@ function buildSymphonyIdeaRow(idea, webId) {
|
|
|
846
1045
|
createdBy: webId,
|
|
847
1046
|
metadata: {
|
|
848
1047
|
surface: 'symphony',
|
|
1048
|
+
filePrimary: true,
|
|
1049
|
+
documentPath: buildSymphonyIdeaDocumentPath(idea),
|
|
849
1050
|
...buildSymphonyArchiveMetadata({ idea: idea.uri }),
|
|
850
1051
|
},
|
|
851
1052
|
createdAt,
|
|
@@ -965,6 +1166,61 @@ function buildSymphonyDeliveryRow(plan, webId, worker) {
|
|
|
965
1166
|
updatedAt,
|
|
966
1167
|
};
|
|
967
1168
|
}
|
|
1169
|
+
function buildSymphonyReportRow(plan, webId, worker, stage) {
|
|
1170
|
+
const completedAt = safeDate(worker.session.completedAt ?? worker.session.updatedAt);
|
|
1171
|
+
const workerAgent = agentResource.buildIri(webId, {
|
|
1172
|
+
id: buildWorkerAgentId(worker.session.backend, worker.session.target.agent),
|
|
1173
|
+
});
|
|
1174
|
+
const run = buildSymphonyRunIri(webId, worker);
|
|
1175
|
+
const task = buildSymphonyTaskIri(webId, worker.task);
|
|
1176
|
+
const status = worker.session.status === 'failed' || stage === 'failed' ? 'failed' : 'completed';
|
|
1177
|
+
const summary = status === 'completed'
|
|
1178
|
+
? `${worker.taskRecord.title} completed.`
|
|
1179
|
+
: `${worker.taskRecord.title} failed: ${worker.session.error ?? worker.delivery.error ?? 'worker did not complete successfully.'}`;
|
|
1180
|
+
return {
|
|
1181
|
+
id: reportResource.buildId({
|
|
1182
|
+
id: `${getSymphonyArchiveKey(worker.session.uri)}-report`,
|
|
1183
|
+
task,
|
|
1184
|
+
createdAt: completedAt,
|
|
1185
|
+
}),
|
|
1186
|
+
reportKind: 'handoff',
|
|
1187
|
+
status: 'published',
|
|
1188
|
+
outcome: status === 'completed' ? 'accepted' : 'blocked',
|
|
1189
|
+
about: run,
|
|
1190
|
+
issue: buildSymphonyIssueIri(webId, plan.issue),
|
|
1191
|
+
task,
|
|
1192
|
+
delivery: buildSymphonyDeliveryIri(webId, worker),
|
|
1193
|
+
run,
|
|
1194
|
+
thread: selectWorkerThreadIri(plan, webId, worker),
|
|
1195
|
+
evidence: [
|
|
1196
|
+
buildSymphonyRunStepIri(webId, worker, stage),
|
|
1197
|
+
],
|
|
1198
|
+
summary,
|
|
1199
|
+
actor: workerAgent,
|
|
1200
|
+
source: podFileUrlFromWebId(webId, buildSymphonyReportDocumentPath(worker)),
|
|
1201
|
+
metricFacts: {
|
|
1202
|
+
backend: worker.session.backend,
|
|
1203
|
+
agent: worker.session.target.agent,
|
|
1204
|
+
autoModeSessionId: worker.session.autoModeSessionId,
|
|
1205
|
+
exitCode: worker.session.exitCode,
|
|
1206
|
+
},
|
|
1207
|
+
metadata: {
|
|
1208
|
+
surface: 'symphony',
|
|
1209
|
+
filePrimary: true,
|
|
1210
|
+
reportFile: buildSymphonyReportDocumentPath(worker),
|
|
1211
|
+
reportDelivery: buildSymphonyReportDeliveryIri(webId, worker),
|
|
1212
|
+
...buildSymphonyArchiveMetadata({
|
|
1213
|
+
issue: plan.issue.uri,
|
|
1214
|
+
task: worker.task,
|
|
1215
|
+
delivery: worker.delivery.uri,
|
|
1216
|
+
session: worker.session.uri,
|
|
1217
|
+
}),
|
|
1218
|
+
},
|
|
1219
|
+
createdAt: completedAt,
|
|
1220
|
+
publishedAt: completedAt,
|
|
1221
|
+
updatedAt: completedAt,
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
968
1224
|
function buildSymphonyReportDeliveryRow(plan, webId, worker, stage) {
|
|
969
1225
|
const completedAt = safeDate(worker.session.completedAt ?? worker.session.updatedAt);
|
|
970
1226
|
const workerAgent = agentResource.buildIri(webId, {
|
|
@@ -972,6 +1228,7 @@ function buildSymphonyReportDeliveryRow(plan, webId, worker, stage) {
|
|
|
972
1228
|
});
|
|
973
1229
|
const secretaryAgent = agentResource.buildIri(webId, { id: SYMPHONY_SECRETARY_AGENT_ID });
|
|
974
1230
|
const run = buildSymphonyRunIri(webId, worker);
|
|
1231
|
+
const report = buildSymphonyReportIri(webId, worker);
|
|
975
1232
|
const task = buildSymphonyTaskIri(webId, worker.task);
|
|
976
1233
|
const originalDelivery = buildSymphonyDeliveryIri(webId, worker);
|
|
977
1234
|
const status = worker.session.status === 'failed' || stage === 'failed' ? 'failed' : 'completed';
|
|
@@ -994,7 +1251,7 @@ function buildSymphonyReportDeliveryRow(plan, webId, worker, stage) {
|
|
|
994
1251
|
targetThread: selectTargetThreadIri(plan.issue.thread ?? worker.session.target?.thread, webId, plan),
|
|
995
1252
|
targetSession: buildSymphonyControlSessionUri(webId, plan),
|
|
996
1253
|
actor: workerAgent,
|
|
997
|
-
object:
|
|
1254
|
+
object: report,
|
|
998
1255
|
objective: summary,
|
|
999
1256
|
payload: {
|
|
1000
1257
|
kind: 'symphony_report',
|
|
@@ -1003,6 +1260,7 @@ function buildSymphonyReportDeliveryRow(plan, webId, worker, stage) {
|
|
|
1003
1260
|
issue: buildSymphonyIssueIri(webId, plan.issue),
|
|
1004
1261
|
task,
|
|
1005
1262
|
delivery: originalDelivery,
|
|
1263
|
+
report,
|
|
1006
1264
|
reportDelivery: buildSymphonyReportDeliveryIri(webId, worker),
|
|
1007
1265
|
session: buildSymphonyControlSessionUri(webId, plan),
|
|
1008
1266
|
run,
|
|
@@ -1011,6 +1269,7 @@ function buildSymphonyReportDeliveryRow(plan, webId, worker, stage) {
|
|
|
1011
1269
|
autoModeSessionId: worker.session.autoModeSessionId,
|
|
1012
1270
|
exitCode: worker.session.exitCode,
|
|
1013
1271
|
error: worker.session.error ?? worker.delivery.error ?? worker.taskRecord.error,
|
|
1272
|
+
reportFile: buildSymphonyReportDocumentPath(worker),
|
|
1014
1273
|
evidence: {
|
|
1015
1274
|
statusMessage: buildSymphonyMessageUri(webId, plan, buildStatusMessageRow(plan, webId, stage)),
|
|
1016
1275
|
runStep: buildSymphonyRunStepIri(webId, worker, stage),
|
|
@@ -1030,6 +1289,8 @@ function buildSymphonyReportDeliveryRow(plan, webId, worker, stage) {
|
|
|
1030
1289
|
session: worker.session.uri,
|
|
1031
1290
|
}),
|
|
1032
1291
|
reportKind: 'worker-completion',
|
|
1292
|
+
filePrimary: true,
|
|
1293
|
+
reportFile: buildSymphonyReportDocumentPath(worker),
|
|
1033
1294
|
},
|
|
1034
1295
|
error: status === 'failed' ? worker.session.error ?? worker.delivery.error ?? worker.taskRecord.error : undefined,
|
|
1035
1296
|
createdAt: completedAt,
|
|
@@ -1371,6 +1632,7 @@ async function upsertIssue(db, runtime, row) {
|
|
|
1371
1632
|
await upsertExactRecord(db, runtime.issueResource, { id: row.id }, row, {
|
|
1372
1633
|
title: row.title,
|
|
1373
1634
|
description: row.description,
|
|
1635
|
+
document: row.document,
|
|
1374
1636
|
status: row.status,
|
|
1375
1637
|
priority: row.priority,
|
|
1376
1638
|
labels: row.labels,
|
|
@@ -1385,6 +1647,7 @@ async function upsertIssue(db, runtime, row) {
|
|
|
1385
1647
|
async function upsertIdea(db, runtime, row) {
|
|
1386
1648
|
await upsertExactRecord(db, runtime.ideaResource, { id: row.id }, row, {
|
|
1387
1649
|
summary: row.summary,
|
|
1650
|
+
document: row.document,
|
|
1388
1651
|
input: row.input,
|
|
1389
1652
|
status: row.status,
|
|
1390
1653
|
commitment: row.commitment,
|
|
@@ -1419,6 +1682,32 @@ async function upsertTask(db, runtime, row) {
|
|
|
1419
1682
|
updatedAt: row.updatedAt,
|
|
1420
1683
|
});
|
|
1421
1684
|
}
|
|
1685
|
+
async function upsertReport(db, runtime, row) {
|
|
1686
|
+
await upsertExactRecord(db, runtime.reportResource, {
|
|
1687
|
+
id: row.id,
|
|
1688
|
+
task: row.task,
|
|
1689
|
+
createdAt: row.createdAt,
|
|
1690
|
+
}, row, {
|
|
1691
|
+
reportKind: row.reportKind,
|
|
1692
|
+
status: row.status,
|
|
1693
|
+
outcome: row.outcome,
|
|
1694
|
+
about: row.about,
|
|
1695
|
+
issue: row.issue,
|
|
1696
|
+
task: row.task,
|
|
1697
|
+
delivery: row.delivery,
|
|
1698
|
+
run: row.run,
|
|
1699
|
+
thread: row.thread,
|
|
1700
|
+
evidence: row.evidence,
|
|
1701
|
+
summary: row.summary,
|
|
1702
|
+
reviewer: row.reviewer,
|
|
1703
|
+
actor: row.actor,
|
|
1704
|
+
source: row.source,
|
|
1705
|
+
metricFacts: row.metricFacts,
|
|
1706
|
+
metadata: row.metadata,
|
|
1707
|
+
publishedAt: row.publishedAt,
|
|
1708
|
+
updatedAt: row.updatedAt,
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1422
1711
|
async function upsertDelivery(db, runtime, row) {
|
|
1423
1712
|
await upsertExactRecord(db, runtime.deliveryResource, { id: row.id }, row, {
|
|
1424
1713
|
kind: row.kind,
|
|
@@ -1538,6 +1827,7 @@ function collectSymphonyProjectionResources(webId, plan, stages) {
|
|
|
1538
1827
|
add('runStep', buildSymphonyRunStepIri(webId, worker, stage));
|
|
1539
1828
|
}
|
|
1540
1829
|
if (worker.session.status === 'completed' || worker.session.status === 'failed') {
|
|
1830
|
+
add('report', buildSymphonyReportIri(webId, worker));
|
|
1541
1831
|
add('delivery', buildSymphonyReportDeliveryIri(webId, worker));
|
|
1542
1832
|
}
|
|
1543
1833
|
}
|
|
@@ -1552,7 +1842,7 @@ function projectionStagesForStatus(status) {
|
|
|
1552
1842
|
return ['planned', 'running', 'failed'];
|
|
1553
1843
|
return ['planned'];
|
|
1554
1844
|
}
|
|
1555
|
-
export async function
|
|
1845
|
+
export async function persistSymphonyControlStateToPod(plan, options = {}) {
|
|
1556
1846
|
const normalizedPlan = normalizeSymphonyRunPlan(plan);
|
|
1557
1847
|
const runtime = options.runtime ?? await createDefaultRuntime();
|
|
1558
1848
|
const podSession = await runtime.getPodDataSession();
|
|
@@ -1575,9 +1865,12 @@ export async function persistSymphonyProjectionToPod(plan, options = {}) {
|
|
|
1575
1865
|
const projected = withTargetRefs(normalizedPlan, refs, podSession.webId);
|
|
1576
1866
|
const resources = collectSymphonyProjectionResources(podSession.webId, projected, stages);
|
|
1577
1867
|
const latestMessage = buildStatusMessageRow(projected, podSession.webId, stage);
|
|
1578
|
-
const
|
|
1579
|
-
|
|
1580
|
-
|
|
1868
|
+
const controlWrite = createLinxPodSyncScope({
|
|
1869
|
+
source: 'symphony-control-state',
|
|
1870
|
+
plane: 'control-plane',
|
|
1871
|
+
});
|
|
1872
|
+
await controlWrite.runOperations({
|
|
1873
|
+
action: 'symphony.write',
|
|
1581
1874
|
resourceBindings: {
|
|
1582
1875
|
session: {
|
|
1583
1876
|
uri: buildSymphonyControlSessionUri(podSession.webId, projected),
|
|
@@ -1609,6 +1902,7 @@ export async function persistSymphonyProjectionToPod(plan, options = {}) {
|
|
|
1609
1902
|
stages,
|
|
1610
1903
|
latestMessage,
|
|
1611
1904
|
shouldUpsertChat: !normalizedPlan.session.target?.chat,
|
|
1905
|
+
podSession,
|
|
1612
1906
|
}),
|
|
1613
1907
|
});
|
|
1614
1908
|
return {
|
|
@@ -1617,6 +1911,8 @@ export async function persistSymphonyProjectionToPod(plan, options = {}) {
|
|
|
1617
1911
|
resources,
|
|
1618
1912
|
};
|
|
1619
1913
|
}
|
|
1914
|
+
/** @deprecated Use persistSymphonyControlStateToPod for LinX-owned Symphony records. */
|
|
1915
|
+
export const persistSymphonyProjectionToPod = persistSymphonyControlStateToPod;
|
|
1620
1916
|
export async function mirrorSymphonyProjectionJsonLdFromPod(projection, options = {}) {
|
|
1621
1917
|
const runtime = options.runtime ?? await createDefaultRuntime();
|
|
1622
1918
|
const podSession = await runtime.getPodDataSession();
|
|
@@ -1680,6 +1976,8 @@ function resolveProjectionResourceModel(runtime, kind) {
|
|
|
1680
1976
|
return runtime.taskResource;
|
|
1681
1977
|
if (kind === 'delivery')
|
|
1682
1978
|
return runtime.deliveryResource;
|
|
1979
|
+
if (kind === 'report')
|
|
1980
|
+
return runtime.reportResource;
|
|
1683
1981
|
if (kind === 'run')
|
|
1684
1982
|
return runtime.runResource;
|
|
1685
1983
|
if (kind === 'runStep')
|
|
@@ -1782,9 +2080,12 @@ export async function persistSymphonyIdeaToPod(idea, options = {}) {
|
|
|
1782
2080
|
return null;
|
|
1783
2081
|
}
|
|
1784
2082
|
const db = runtime.createDb(podSession);
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
2083
|
+
const ideaWrite = createLinxPodSyncScope({
|
|
2084
|
+
source: 'symphony-control-state',
|
|
2085
|
+
plane: 'control-plane',
|
|
2086
|
+
});
|
|
2087
|
+
await ideaWrite.runOperations({
|
|
2088
|
+
action: 'symphony.idea.write',
|
|
1788
2089
|
resourceBindings: {
|
|
1789
2090
|
idea: {
|
|
1790
2091
|
uri: ideaResource.buildIri(podSession.webId, {
|
|
@@ -1814,7 +2115,18 @@ export async function persistSymphonyIdeaToPod(idea, options = {}) {
|
|
|
1814
2115
|
},
|
|
1815
2116
|
},
|
|
1816
2117
|
{
|
|
1817
|
-
id: 'symphony.idea.
|
|
2118
|
+
id: 'symphony.idea.write-file-primary-document',
|
|
2119
|
+
kind: 'upsert',
|
|
2120
|
+
apply: async () => {
|
|
2121
|
+
await runtime.writePodFile?.(podSession, {
|
|
2122
|
+
path: buildSymphonyIdeaDocumentPath(idea),
|
|
2123
|
+
content: renderSymphonyIdeaMarkdown(idea),
|
|
2124
|
+
contentType: 'text/markdown; charset=utf-8',
|
|
2125
|
+
});
|
|
2126
|
+
},
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
id: 'symphony.idea.upsert-meta',
|
|
1818
2130
|
kind: 'upsert',
|
|
1819
2131
|
apply: () => upsertIdea(db, runtime, buildSymphonyIdeaRow(idea, podSession.webId)),
|
|
1820
2132
|
},
|
|
@@ -2094,6 +2406,7 @@ function buildSymphonyProjectionOperations(input) {
|
|
|
2094
2406
|
input.runtime.issueResource,
|
|
2095
2407
|
input.runtime.taskResource,
|
|
2096
2408
|
input.runtime.deliveryResource,
|
|
2409
|
+
input.runtime.reportResource,
|
|
2097
2410
|
input.runtime.runResource,
|
|
2098
2411
|
input.runtime.runStepResource,
|
|
2099
2412
|
input.runtime.agentResource,
|
|
@@ -2104,7 +2417,18 @@ function buildSymphonyProjectionOperations(input) {
|
|
|
2104
2417
|
},
|
|
2105
2418
|
},
|
|
2106
2419
|
{
|
|
2107
|
-
id: 'symphony.
|
|
2420
|
+
id: 'symphony.write-file-primary-documents',
|
|
2421
|
+
kind: 'upsert',
|
|
2422
|
+
apply: async () => {
|
|
2423
|
+
await input.runtime.writePodFile?.(input.podSession, {
|
|
2424
|
+
path: buildSymphonyIssueDocumentPath(input.plan.issue),
|
|
2425
|
+
content: renderSymphonyIssueMarkdown(input.plan),
|
|
2426
|
+
contentType: 'text/markdown; charset=utf-8',
|
|
2427
|
+
});
|
|
2428
|
+
},
|
|
2429
|
+
},
|
|
2430
|
+
{
|
|
2431
|
+
id: 'symphony.upsert-issue-meta',
|
|
2108
2432
|
kind: 'upsert',
|
|
2109
2433
|
apply: () => upsertIssue(input.db, input.runtime, buildSymphonyIssueRow(input.plan, input.webId)),
|
|
2110
2434
|
},
|
|
@@ -2177,6 +2501,22 @@ function buildSymphonyReportOperations(input) {
|
|
|
2177
2501
|
}
|
|
2178
2502
|
const terminalStage = input.stage;
|
|
2179
2503
|
return input.plan.workers.flatMap((worker, index) => [
|
|
2504
|
+
{
|
|
2505
|
+
id: `symphony.write-report-file:${index + 1}`,
|
|
2506
|
+
kind: 'upsert',
|
|
2507
|
+
apply: async () => {
|
|
2508
|
+
await input.runtime.writePodFile?.(input.podSession, {
|
|
2509
|
+
path: buildSymphonyReportDocumentPath(worker),
|
|
2510
|
+
content: renderSymphonyReportMarkdown(input.plan, worker, terminalStage),
|
|
2511
|
+
contentType: 'text/markdown; charset=utf-8',
|
|
2512
|
+
});
|
|
2513
|
+
},
|
|
2514
|
+
},
|
|
2515
|
+
{
|
|
2516
|
+
id: `symphony.upsert-report:${index + 1}`,
|
|
2517
|
+
kind: 'upsert',
|
|
2518
|
+
apply: () => upsertReport(input.db, input.runtime, buildSymphonyReportRow(input.plan, input.webId, worker, terminalStage)),
|
|
2519
|
+
},
|
|
2180
2520
|
{
|
|
2181
2521
|
id: `symphony.upsert-report-delivery:${index + 1}`,
|
|
2182
2522
|
kind: 'upsert',
|