@undefineds.co/linx 0.3.13 → 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.
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by LinX release tooling. Keep in sync with apps/cli/package.json.
2
- export const LINX_CLI_VERSION = "0.3.12";
2
+ export const LINX_CLI_VERSION = "0.3.11";
3
3
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  import { getBuiltinModels as getSharedBuiltinModels, } from '@undefineds.co/models/discovery';
2
- import { aiConfigProviderRef, aiConfigModelUri, aiConfigProviderUri, aiConfigRepository, aiModelResource, aiModelTable, aiProviderResource, aiProviderTable, approvalResource, approvalTable, auditTable, buildAIConfigDisconnectPlan, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, claimApprovalRequest, claimInputRequest, chatRepository, credentialResource, credentialTable, ContactClass, ContactType, auditResource, agentResource, agentTable, applySolidComunicaPatches, chatResource, chatTable, contactResource, contactTable, drizzle, eq, getAIConfigProviderFamilyIds, getAIConfigProviderIdsForBackend, getAIConfigProviderCatalog, getAIConfigProviderMetadata, getDefaultAIConfigCredentialId, grantResource, grantTable, ideaResource, inboxNotificationResource, inboxNotificationTable, inputRequestResource, inputRequestTable, initSolidResources, initSolidTables, extractSessionIdFromSessionRef, solidResources, solidSchema, messageResource, messageTable, issueResource, issueTable, taskResource, taskTable, deliveryResource, deliveryTable, runResource, runStepResource, runTable, runStepTable, sessionResource, sessionTable, skillResource, skillTable, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, selectAIConfigCredential, selectAIConfigCredentialForBackend, sessionRepository, threadRepository, threadResource, threadTable, } from '@undefineds.co/models';
3
- export { ContactClass, ContactType, agentResource, agentTable, applySolidComunicaPatches, aiConfigModelUri, aiConfigProviderRef, aiConfigProviderUri, aiConfigRepository, aiModelResource, aiModelTable, aiProviderResource, aiProviderTable, approvalResource, approvalTable, auditTable, buildAIConfigDisconnectPlan, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, claimApprovalRequest, claimInputRequest, chatRepository, chatResource, chatTable, contactResource, contactTable, credentialResource, credentialTable, auditResource, drizzle, eq, getAIConfigProviderFamilyIds, getAIConfigProviderIdsForBackend, getAIConfigProviderCatalog, getAIConfigProviderMetadata, getDefaultAIConfigCredentialId, grantResource, grantTable, ideaResource, inboxNotificationResource, inboxNotificationTable, inputRequestResource, inputRequestTable, extractSessionIdFromSessionRef, solidSchema, solidResources, initSolidResources, initSolidTables, messageResource, messageTable, issueResource, issueTable, taskResource, taskTable, deliveryResource, deliveryTable, runResource, runStepResource, runTable, runStepTable, sessionResource, sessionTable, skillResource, skillTable, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, selectAIConfigCredential, selectAIConfigCredentialForBackend, sessionRepository, threadRepository, threadResource, threadTable, };
2
+ import { aiConfigProviderRef, aiConfigModelUri, aiConfigProviderUri, aiConfigRepository, aiModelResource, aiModelTable, aiProviderResource, aiProviderTable, approvalResource, approvalTable, auditTable, buildAIConfigDisconnectPlan, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, claimApprovalRequest, claimInputRequest, chatRepository, credentialResource, credentialTable, ContactClass, ContactType, auditResource, agentResource, agentTable, applySolidComunicaPatches, chatResource, chatTable, contactResource, contactTable, drizzle, eq, getAIConfigProviderFamilyIds, getAIConfigProviderIdsForBackend, getAIConfigProviderCatalog, getAIConfigProviderMetadata, getDefaultAIConfigCredentialId, grantResource, grantTable, ideaResource, inboxNotificationResource, inboxNotificationTable, inputRequestResource, inputRequestTable, initSolidResources, initSolidTables, extractSessionIdFromSessionRef, solidResources, solidSchema, messageResource, messageTable, issueResource, issueTable, taskResource, taskTable, deliveryResource, deliveryTable, reportResource, runResource, runStepResource, runTable, runStepTable, sessionResource, sessionTable, skillResource, skillTable, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, selectAIConfigCredential, selectAIConfigCredentialForBackend, sessionRepository, threadRepository, threadResource, threadTable, } from '@undefineds.co/models';
3
+ export { ContactClass, ContactType, agentResource, agentTable, applySolidComunicaPatches, aiConfigModelUri, aiConfigProviderRef, aiConfigProviderUri, aiConfigRepository, aiModelResource, aiModelTable, aiProviderResource, aiProviderTable, approvalResource, approvalTable, auditTable, buildAIConfigDisconnectPlan, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, claimApprovalRequest, claimInputRequest, chatRepository, chatResource, chatTable, contactResource, contactTable, credentialResource, credentialTable, auditResource, drizzle, eq, getAIConfigProviderFamilyIds, getAIConfigProviderIdsForBackend, getAIConfigProviderCatalog, getAIConfigProviderMetadata, getDefaultAIConfigCredentialId, grantResource, grantTable, ideaResource, inboxNotificationResource, inboxNotificationTable, inputRequestResource, inputRequestTable, extractSessionIdFromSessionRef, solidSchema, solidResources, initSolidResources, initSolidTables, messageResource, messageTable, issueResource, issueTable, taskResource, taskTable, deliveryResource, deliveryTable, reportResource, runResource, runStepResource, runTable, runStepTable, sessionResource, sessionTable, skillResource, skillTable, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, selectAIConfigCredential, selectAIConfigCredentialForBackend, sessionRepository, threadRepository, threadResource, threadTable, };
4
4
  export function getBuiltinModels() {
5
5
  return getSharedBuiltinModels();
6
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/lib/models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,IAAI,sBAAsB,GAC3C,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,aAAa,EACb,aAAa,EACb,UAAU,EACV,yBAAyB,EACzB,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,OAAO,EACP,EAAE,EACF,4BAA4B,EAC5B,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,aAAa,EACb,UAAU,EACV,YAAY,EACZ,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,8BAA8B,EAC9B,cAAc,EACd,WAAW,EACX,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,kCAAkC,EAClC,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,WAAW,GAmCZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,yBAAyB,EACzB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,OAAO,EACP,EAAE,EACF,4BAA4B,EAC5B,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,aAAa,EACb,UAAU,EACV,YAAY,EACZ,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,8BAA8B,EAC9B,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,kCAAkC,EAClC,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,WAAW,GACZ,CAAA;AAsCD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,sBAAsB,EAAE,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/lib/models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,IAAI,sBAAsB,GAC3C,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,aAAa,EACb,aAAa,EACb,UAAU,EACV,yBAAyB,EACzB,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,OAAO,EACP,EAAE,EACF,4BAA4B,EAC5B,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,aAAa,EACb,UAAU,EACV,YAAY,EACZ,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,8BAA8B,EAC9B,cAAc,EACd,WAAW,EACX,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,WAAW,EACX,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,kCAAkC,EAClC,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,WAAW,GAoCZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,yBAAyB,EACzB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,OAAO,EACP,EAAE,EACF,4BAA4B,EAC5B,gCAAgC,EAChC,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,aAAa,EACb,UAAU,EACV,YAAY,EACZ,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,8BAA8B,EAC9B,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,WAAW,EACX,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,EACb,UAAU,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,kCAAkC,EAClC,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,WAAW,GACZ,CAAA;AAuCD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,sBAAsB,EAAE,CAAA;AACjC,CAAC"}
@@ -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
- description: plan.issue.description,
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
- input: idea.input,
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: idea.currentUnderstanding,
838
- openQuestions: idea.openQuestions,
1036
+ currentUnderstanding: undefined,
1037
+ openQuestions: undefined,
839
1038
  related: idea.relatedRecords,
840
- conflicts: idea.conflicts,
841
- nextStep: idea.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: run,
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
  }
@@ -1612,6 +1902,7 @@ export async function persistSymphonyControlStateToPod(plan, options = {}) {
1612
1902
  stages,
1613
1903
  latestMessage,
1614
1904
  shouldUpsertChat: !normalizedPlan.session.target?.chat,
1905
+ podSession,
1615
1906
  }),
1616
1907
  });
1617
1908
  return {
@@ -1685,6 +1976,8 @@ function resolveProjectionResourceModel(runtime, kind) {
1685
1976
  return runtime.taskResource;
1686
1977
  if (kind === 'delivery')
1687
1978
  return runtime.deliveryResource;
1979
+ if (kind === 'report')
1980
+ return runtime.reportResource;
1688
1981
  if (kind === 'run')
1689
1982
  return runtime.runResource;
1690
1983
  if (kind === 'runStep')
@@ -1822,7 +2115,18 @@ export async function persistSymphonyIdeaToPod(idea, options = {}) {
1822
2115
  },
1823
2116
  },
1824
2117
  {
1825
- id: 'symphony.idea.upsert',
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',
1826
2130
  kind: 'upsert',
1827
2131
  apply: () => upsertIdea(db, runtime, buildSymphonyIdeaRow(idea, podSession.webId)),
1828
2132
  },
@@ -2102,6 +2406,7 @@ function buildSymphonyProjectionOperations(input) {
2102
2406
  input.runtime.issueResource,
2103
2407
  input.runtime.taskResource,
2104
2408
  input.runtime.deliveryResource,
2409
+ input.runtime.reportResource,
2105
2410
  input.runtime.runResource,
2106
2411
  input.runtime.runStepResource,
2107
2412
  input.runtime.agentResource,
@@ -2112,7 +2417,18 @@ function buildSymphonyProjectionOperations(input) {
2112
2417
  },
2113
2418
  },
2114
2419
  {
2115
- id: 'symphony.upsert-issue',
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',
2116
2432
  kind: 'upsert',
2117
2433
  apply: () => upsertIssue(input.db, input.runtime, buildSymphonyIssueRow(input.plan, input.webId)),
2118
2434
  },
@@ -2185,6 +2501,22 @@ function buildSymphonyReportOperations(input) {
2185
2501
  }
2186
2502
  const terminalStage = input.stage;
2187
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
+ },
2188
2520
  {
2189
2521
  id: `symphony.upsert-report-delivery:${index + 1}`,
2190
2522
  kind: 'upsert',