@undefineds.co/linx 0.3.4 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +334 -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
@@ -1,78 +1,260 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
2
+ import { createAgentRuntimeConfigSnapshot, } from '../../../vendor/agent-runtime/dist/index.js';
3
+ import { buildLinxSessionControlState, mergeLinxSessionControlMetadata, } from '../../../vendor/agent-runtime/dist/control-plane.js';
4
+ import { createLinxPodSyncQueue, listLinxSyncCheckpoints, } from '../../../vendor/agent-runtime/dist/sync.js';
5
+ import { upsertExactRecord } from '@undefineds.co/drizzle-solid';
1
6
  import { DEFAULT_LINX_CLOUD_MODEL_ID } from '../default-model.js';
2
7
  import { getDefaultPodDataSession } from '../pod-data-session.js';
3
- import { agentResource, auditResource, chatResource, drizzle, messageResource, sessionResource, solidResources, threadResource, } from '../models.js';
8
+ import { agentResource, auditResource, chatResource, drizzle, messageResource, sessionResource, skillResource, solidResources, threadResource, } from '../models.js';
4
9
  export { buildPodMessageRow } from './pod-mirror-mapping.js';
5
- import { DEFAULT_SECRETARY_CHAT_ID, PI_AGENT_ID, buildPodMessageRow as buildPodMessageRowFromMapping, buildThreadTitle, buildToolAuditId, calculateTokenUsage, pathToWorkspaceUri, } from './pod-mirror-mapping.js';
10
+ import { DEFAULT_SECRETARY_CHAT_ID, PI_AGENT_ID, buildPodMessageRow as buildPodMessageRowFromMapping, buildThreadTitle, buildToolAuditId, calculateTokenUsage, getActiveSessionEntries, pathToWorkspaceUri, sanitizePodLiteralText, secretaryThreadResourceId, } from './pod-mirror-mapping.js';
6
11
  const PI_POLICY_VERSION = 'linx-pi-pod-mirror/v1';
12
+ const PI_SYMPHONY_SKILL_ID = 'symphony';
13
+ const POD_MIRROR_TRANSIENT_RETRY_DELAYS_MS = [250, 1_000, 2_500];
7
14
  export class LinxPiPodMirror {
8
15
  options;
9
16
  contextPromise = null;
10
- queue = Promise.resolve();
17
+ queue;
11
18
  seenMessageIds = new Set();
12
19
  messageResourceRefs = new Set();
13
20
  runtimePromise;
21
+ syncCheckpoints = new Map();
22
+ syncResults = [];
23
+ projectionDisabledReason = null;
14
24
  closed = false;
25
+ taskSeq = 0;
15
26
  constructor(options) {
16
27
  this.options = options;
17
28
  this.runtimePromise = createDefaultRuntime(options.runtime);
29
+ this.queue = createLinxPodSyncQueue({
30
+ source: 'pi-runtime',
31
+ target: 'pod',
32
+ direction: 'local-to-core',
33
+ plane: 'projection',
34
+ authority: 'core',
35
+ metadata: {
36
+ cwd: options.cwd,
37
+ },
38
+ onError: options.onError,
39
+ checkpoint: {
40
+ writeCheckpoint: async (checkpoint) => {
41
+ this.syncCheckpoints.set(checkpoint.id, checkpoint);
42
+ await options.checkpointStore?.writeCheckpoint(checkpoint);
43
+ },
44
+ readCheckpoint: options.checkpointStore?.readCheckpoint?.bind(options.checkpointStore),
45
+ listCheckpoints: options.checkpointStore?.listCheckpoints?.bind(options.checkpointStore),
46
+ deleteCheckpoint: options.checkpointStore?.deleteCheckpoint?.bind(options.checkpointStore),
47
+ },
48
+ onResult: (result) => {
49
+ this.syncResults.push(result);
50
+ },
51
+ });
18
52
  }
19
53
  handleEvent(event) {
20
54
  if (this.closed || !isRecord(event)) {
21
55
  return;
22
56
  }
57
+ if (this.projectionDisabledReason) {
58
+ return;
59
+ }
23
60
  if (event.type === 'message_end') {
24
- this.enqueue(async () => {
61
+ this.enqueue('message_end', async () => {
25
62
  const message = event.message;
26
63
  const entry = this.resolveLatestEntryForMessage(message);
27
64
  if (!entry || this.seenMessageIds.has(entry.id)) {
28
65
  return;
29
66
  }
30
- this.seenMessageIds.add(entry.id);
31
- await this.persistEntry(entry);
67
+ if (await this.persistEntry(entry)) {
68
+ this.seenMessageIds.add(entry.id);
69
+ }
32
70
  });
33
71
  return;
34
72
  }
35
73
  if (event.type === 'tool_execution_start') {
36
- this.enqueue(() => this.persistToolAudit('tool_execution_started', event));
74
+ this.enqueue('tool_execution_start', () => this.persistToolAudit('tool_execution_started', event));
37
75
  return;
38
76
  }
39
77
  if (event.type === 'tool_execution_end') {
40
- this.enqueue(() => this.persistToolAudit(event.isError ? 'tool_execution_failed' : 'tool_execution_completed', event));
78
+ this.enqueue('tool_execution_end', () => this.persistToolAudit(event.isError ? 'tool_execution_failed' : 'tool_execution_completed', event));
41
79
  return;
42
80
  }
43
81
  if (event.type === 'agent_end') {
44
- this.enqueue(() => this.persistUnseenMessageEntries());
82
+ this.enqueue('agent_end', () => this.persistUnseenMessageEntries());
45
83
  }
46
84
  }
47
85
  async flush() {
48
- await this.queue;
86
+ await this.queue.flush();
87
+ }
88
+ getSyncCheckpoints() {
89
+ return [...this.syncCheckpoints.values()];
90
+ }
91
+ getSyncResults() {
92
+ return [...this.syncResults];
93
+ }
94
+ syncAutoControlState(enabled) {
95
+ this.options.autoEnabled = enabled;
96
+ if (this.projectionDisabledReason) {
97
+ return Promise.resolve(null);
98
+ }
99
+ return this.enqueue('auto_control_state', async () => {
100
+ const context = await this.getContext();
101
+ if (!context) {
102
+ throw new Error('Pod data session unavailable for Pi control-plane sync');
103
+ }
104
+ const refs = resolvePiResourceRefs(context, this.options);
105
+ if (this.options.syncConversationRoot) {
106
+ await ensurePiConversationRoot(context, this.options, refs);
107
+ }
108
+ await persistRuntimeSession(context, this.options, refs, 'active', this.messageResourceRefs);
109
+ }, {
110
+ autoEnabled: enabled,
111
+ }, {
112
+ plane: 'control-plane',
113
+ authority: 'core',
114
+ });
115
+ }
116
+ syncSymphonyControlState(enabled) {
117
+ this.options.symphonyEnabled = enabled;
118
+ if (this.projectionDisabledReason) {
119
+ return Promise.resolve(null);
120
+ }
121
+ return this.enqueue('symphony_control_state', async () => {
122
+ const context = await this.getContext();
123
+ if (!context) {
124
+ throw new Error('Pod data session unavailable for Pi control-plane sync');
125
+ }
126
+ const refs = resolvePiResourceRefs(context, this.options);
127
+ if (this.options.syncConversationRoot) {
128
+ await ensurePiConversationRoot(context, this.options, refs);
129
+ }
130
+ await persistRuntimeSession(context, this.options, refs, 'active', this.messageResourceRefs);
131
+ }, {
132
+ symphonyEnabled: enabled,
133
+ }, {
134
+ plane: 'control-plane',
135
+ authority: 'core',
136
+ });
137
+ }
138
+ syncRewindProjection(input) {
139
+ if (this.projectionDisabledReason) {
140
+ return Promise.resolve(null);
141
+ }
142
+ return this.enqueue('rewind_projection', async () => {
143
+ const context = await this.getContext();
144
+ if (!context) {
145
+ throw new Error('Pod data session unavailable for Pi rewind projection');
146
+ }
147
+ const refs = resolvePiResourceRefs(context, this.options);
148
+ if (this.options.syncConversationRoot) {
149
+ await ensurePiConversationRoot(context, this.options, refs);
150
+ }
151
+ await archivePreviousRuntimeSession(context, this.options, input);
152
+ await markAbandonedPreviousMessages(context, this.options, input);
153
+ this.seenMessageIds.clear();
154
+ this.messageResourceRefs.clear();
155
+ await this.persistUnseenMessageEntries();
156
+ await persistRuntimeSession(context, this.options, refs, 'active', this.messageResourceRefs);
157
+ }, {
158
+ previousSessionId: input.previousSessionId,
159
+ cleanSessionId: input.cleanSessionId,
160
+ abandonedEntries: input.abandonedEntries?.length ?? 0,
161
+ }, {
162
+ plane: 'projection',
163
+ authority: 'core',
164
+ });
165
+ }
166
+ async replayPendingSync() {
167
+ const checkpointStore = this.options.checkpointStore;
168
+ if (!checkpointStore) {
169
+ return [];
170
+ }
171
+ const pending = await listLinxSyncCheckpoints(checkpointStore, {
172
+ source: 'pi-runtime',
173
+ target: 'pod',
174
+ plane: 'projection',
175
+ status: ['failed', 'partial'],
176
+ metadata: {
177
+ resourceBindings: {
178
+ session: {
179
+ local: this.options.sessionManager.getSessionId(),
180
+ },
181
+ },
182
+ },
183
+ });
184
+ const replayablePending = pending.filter(isReplayablePiProjectionCheckpoint);
185
+ if (replayablePending.length === 0) {
186
+ return [];
187
+ }
188
+ const result = await this.enqueue('retry_pending_projection', () => this.persistUnseenMessageEntries(), {
189
+ retryOf: replayablePending.map((checkpoint) => checkpoint.id),
190
+ });
191
+ if (result?.status === 'completed') {
192
+ await Promise.all(replayablePending.map((checkpoint) => checkpointStore.deleteCheckpoint?.(checkpoint.id)));
193
+ }
194
+ return result ? [result] : [];
49
195
  }
50
196
  async close() {
51
197
  if (this.closed) {
52
198
  return;
53
199
  }
54
200
  this.closed = true;
55
- this.enqueue(async () => {
201
+ if (this.projectionDisabledReason) {
202
+ await this.queue.flush();
203
+ return;
204
+ }
205
+ this.enqueue('close', async () => {
56
206
  const context = await this.getContext();
57
207
  if (!context) {
58
208
  return;
59
209
  }
60
210
  await persistRuntimeSession(context, this.options, resolvePiResourceRefs(context, this.options), 'completed', this.messageResourceRefs);
61
211
  });
62
- await this.queue;
212
+ await this.queue.flush();
63
213
  }
64
- enqueue(task) {
65
- this.queue = this.queue
66
- .then(task, task)
67
- .catch((error) => {
68
- this.options.onError?.(error);
214
+ enqueue(description, task, metadata = {}, sync = {}) {
215
+ if (this.projectionDisabledReason) {
216
+ return Promise.resolve(null);
217
+ }
218
+ const action = `pi-pod-mirror.${description}`;
219
+ return this.queue.enqueue({
220
+ id: this.nextTaskId(),
221
+ action,
222
+ description,
223
+ kind: 'custom',
224
+ plane: sync.plane,
225
+ authority: sync.authority,
226
+ resourceBindings: createPiPodMirrorSyncResourceBindings(this.options),
227
+ metadata,
228
+ resolveResourceBindings: async () => {
229
+ const podContext = await this.getContext();
230
+ return podContext ? createPiPodMirrorSyncResourceBindings(this.options, resolvePiResourceRefs(podContext, this.options)) : undefined;
231
+ },
232
+ run: async (context) => {
233
+ if (this.projectionDisabledReason) {
234
+ return;
235
+ }
236
+ try {
237
+ await runWithTransientPodMirrorRetry(context, () => task(context));
238
+ }
239
+ catch (error) {
240
+ if (isPodMirrorCircuitBreakerError(error)) {
241
+ this.projectionDisabledReason = formatPodMirrorCircuitBreakerReason(error);
242
+ }
243
+ throw error;
244
+ }
245
+ },
69
246
  });
70
247
  }
248
+ nextTaskId() {
249
+ const sessionId = this.options.sessionManager.getSessionId();
250
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
251
+ return `pi-pod-mirror:${sessionId}:${timestamp}:${++this.taskSeq}`;
252
+ }
71
253
  resolveLatestEntryForMessage(message) {
72
254
  if (!message) {
73
255
  return null;
74
256
  }
75
- const entries = [...this.options.sessionManager.getEntries()].reverse();
257
+ const entries = [...getActiveSessionEntries(this.options.sessionManager)].reverse();
76
258
  const timestamp = typeof message.timestamp === 'number'
77
259
  ? message.timestamp
78
260
  : null;
@@ -94,8 +276,11 @@ export class LinxPiPodMirror {
94
276
  }
95
277
  async persistEntry(entry) {
96
278
  const context = await this.getContext();
97
- if (!context || entry.type !== 'message') {
98
- return;
279
+ if (entry.type !== 'message') {
280
+ return true;
281
+ }
282
+ if (!context) {
283
+ throw new Error('Pod data session unavailable for Pi projection');
99
284
  }
100
285
  const refs = resolvePiResourceRefs(context, this.options);
101
286
  if (this.options.syncConversationRoot) {
@@ -103,7 +288,7 @@ export class LinxPiPodMirror {
103
288
  }
104
289
  const row = buildPodMessageRowFromMapping(context.webId, this.options, entry);
105
290
  if (!row) {
106
- return;
291
+ return true;
107
292
  }
108
293
  const resourceRef = await persistMessage(context, normalizePodMessageRow(context, row, refs));
109
294
  this.messageResourceRefs.add(resourceRef);
@@ -111,14 +296,16 @@ export class LinxPiPodMirror {
111
296
  if (this.options.syncConversationRoot) {
112
297
  await touchPiConversation(context, this.options, refs, row.content);
113
298
  }
299
+ return true;
114
300
  }
115
301
  async persistUnseenMessageEntries() {
116
- for (const entry of this.options.sessionManager.getEntries()) {
302
+ for (const entry of getActiveSessionEntries(this.options.sessionManager)) {
117
303
  if (entry.type !== 'message' || this.seenMessageIds.has(entry.id)) {
118
304
  continue;
119
305
  }
120
- this.seenMessageIds.add(entry.id);
121
- await this.persistEntry(entry);
306
+ if (await this.persistEntry(entry)) {
307
+ this.seenMessageIds.add(entry.id);
308
+ }
122
309
  }
123
310
  }
124
311
  async persistToolAudit(action, event) {
@@ -175,12 +362,12 @@ export class LinxPiPodMirror {
175
362
  async function ensurePiConversationRoot(context, options, refs) {
176
363
  const now = new Date();
177
364
  const threadId = options.sessionManager.getSessionId();
178
- await upsertByResource(context.db, chatResource, { id: DEFAULT_SECRETARY_CHAT_ID }, {
365
+ await upsertExactRecord(context.db, chatResource, { id: DEFAULT_SECRETARY_CHAT_ID }, {
179
366
  id: DEFAULT_SECRETARY_CHAT_ID,
180
367
  title: 'AI Secretary',
181
368
  participants: [context.webId, refs.agentUri],
182
369
  metadata: {
183
- kind: 'ai-secretary',
370
+ kind: 'secretary-chat',
184
371
  surface: 'cli',
185
372
  agent: refs.agentUri,
186
373
  },
@@ -191,16 +378,17 @@ async function ensurePiConversationRoot(context, options, refs) {
191
378
  title: 'AI Secretary',
192
379
  participants: [context.webId, refs.agentUri],
193
380
  metadata: {
194
- kind: 'ai-secretary',
381
+ kind: 'secretary-chat',
195
382
  surface: 'cli',
196
383
  agent: refs.agentUri,
197
384
  },
198
385
  lastActiveAt: now,
199
386
  updatedAt: now,
200
387
  });
201
- await upsertByResource(context.db, threadResource, { id: threadId, chat: refs.chatUri }, {
202
- id: threadId,
203
- chat: refs.chatUri,
388
+ const threadResourceId = secretaryThreadResourceId(threadId);
389
+ await upsertExactRecord(context.db, threadResource, { id: threadResourceId }, {
390
+ id: threadResourceId,
391
+ parent: refs.chatUri,
204
392
  title: buildThreadTitle(options.sessionManager),
205
393
  workspace: pathToWorkspaceUri(options.cwd),
206
394
  metadata: buildThreadMetadata(options),
@@ -212,25 +400,85 @@ async function ensurePiConversationRoot(context, options, refs) {
212
400
  metadata: buildThreadMetadata(options),
213
401
  updatedAt: now,
214
402
  });
215
- await upsertByResource(context.db, agentResource, { id: PI_AGENT_ID }, {
216
- id: PI_AGENT_ID,
403
+ await upsertExactRecord(context.db, agentResource, { id: PI_AGENT_ID }, {
404
+ id: agentResource.buildId({ id: PI_AGENT_ID }),
217
405
  name: 'LinX CLI Assistant',
406
+ root: refs.agentUri,
407
+ hasSkill: [refs.symphonySkillUri],
218
408
  provider: 'undefineds',
409
+ backend: 'linx',
410
+ runtime: 'pi',
411
+ transport: 'pi-runtime',
412
+ credentialSource: 'pod-session',
219
413
  model: DEFAULT_LINX_CLOUD_MODEL_ID,
414
+ enabled: true,
415
+ metadata: {
416
+ kind: 'secretary-agent',
417
+ surface: 'cli',
418
+ fileBackedSkills: true,
419
+ },
220
420
  createdAt: now,
221
421
  updatedAt: now,
222
422
  }, {
223
423
  name: 'LinX CLI Assistant',
424
+ root: refs.agentUri,
425
+ hasSkill: [refs.symphonySkillUri],
224
426
  provider: 'undefineds',
427
+ backend: 'linx',
428
+ runtime: 'pi',
429
+ transport: 'pi-runtime',
430
+ credentialSource: 'pod-session',
225
431
  model: DEFAULT_LINX_CLOUD_MODEL_ID,
432
+ enabled: true,
433
+ metadata: {
434
+ kind: 'secretary-agent',
435
+ surface: 'cli',
436
+ fileBackedSkills: true,
437
+ },
438
+ updatedAt: now,
439
+ });
440
+ await upsertExactRecord(context.db, skillResource, {
441
+ id: PI_SYMPHONY_SKILL_ID,
442
+ agent: refs.agentUri,
443
+ }, {
444
+ id: skillResource.buildId({
445
+ id: PI_SYMPHONY_SKILL_ID,
446
+ agent: refs.agentUri,
447
+ }),
448
+ agent: refs.agentUri,
449
+ root: refs.symphonySkillUri,
450
+ name: PI_SYMPHONY_SKILL_ID,
451
+ displayName: 'Symphony',
452
+ enabled: true,
453
+ source: 'linx-cli:skills/symphony',
454
+ loadPolicy: 'file-backed',
455
+ metadata: {
456
+ file: 'SKILL.md',
457
+ scope: 'linx-cli',
458
+ },
459
+ createdAt: now,
460
+ updatedAt: now,
461
+ }, {
462
+ agent: refs.agentUri,
463
+ root: refs.symphonySkillUri,
464
+ name: PI_SYMPHONY_SKILL_ID,
465
+ displayName: 'Symphony',
466
+ enabled: true,
467
+ source: 'linx-cli:skills/symphony',
468
+ loadPolicy: 'file-backed',
469
+ metadata: {
470
+ file: 'SKILL.md',
471
+ scope: 'linx-cli',
472
+ },
226
473
  updatedAt: now,
227
474
  });
228
475
  }
229
- async function persistRuntimeSession(context, options, refs, status = 'active', messageResourceRefs = new Set()) {
476
+ async function persistRuntimeSession(context, options, refs, status = 'active', _messageResourceRefs = new Set()) {
230
477
  const now = new Date();
231
478
  const threadId = options.sessionManager.getSessionId();
232
479
  const runtimeSessionId = threadId;
233
480
  const createdAt = getSessionCreatedAt(options.sessionManager);
481
+ const activeMessageResourceRefs = resolveActiveMessageResourceRefs(context, options, refs);
234
482
  const metadata = {
235
483
  cwd: options.cwd,
236
484
  sessionFile: options.sessionManager.getSessionFile(),
@@ -238,37 +486,145 @@ async function persistRuntimeSession(context, options, refs, status = 'active',
238
486
  runtimeSessionId,
239
487
  surface: 'cli',
240
488
  threadUri: refs.threadUri,
241
- messageResources: [...messageResourceRefs],
489
+ messages: [...activeMessageResourceRefs],
490
+ runtimeSnapshot: createPiRuntimeSnapshot(refs, createdAt),
242
491
  };
492
+ const controlState = buildLinxSessionControlState({
493
+ autoEnabled: options.autoEnabled === true,
494
+ symphonyEnabled: options.symphonyEnabled === true,
495
+ updatedAt: now,
496
+ updatedBy: 'cli',
497
+ });
498
+ const sessionMetadata = mergeLinxSessionControlMetadata(metadata, controlState);
243
499
  const row = {
244
500
  id: runtimeSessionId,
245
- ownerWebId: context.webId,
501
+ owner: context.webId,
246
502
  chat: refs.chatUri,
247
503
  thread: refs.threadUri,
248
504
  sessionType: 'direct',
249
505
  status,
250
506
  tool: 'linx',
251
- tokenUsage: calculateTokenUsage(options.sessionManager.getEntries()),
252
- messageResources: [...messageResourceRefs],
507
+ tokenUsage: calculateTokenUsage(getActiveSessionEntries(options.sessionManager)),
508
+ messages: [...activeMessageResourceRefs],
253
509
  policyVersion: PI_POLICY_VERSION,
254
- metadata,
510
+ metadata: sessionMetadata,
255
511
  createdAt,
256
512
  updatedAt: now,
257
513
  };
258
514
  await upsertByIri(context.db, sessionResource, refs.sessionUri, row, {
259
- ownerWebId: context.webId,
515
+ owner: context.webId,
260
516
  chat: refs.chatUri,
261
517
  thread: refs.threadUri,
262
518
  sessionType: 'direct',
263
519
  status,
264
520
  tool: 'linx',
265
521
  tokenUsage: row.tokenUsage,
266
- messageResources: [...messageResourceRefs],
522
+ messages: [...activeMessageResourceRefs],
267
523
  policyVersion: PI_POLICY_VERSION,
268
- metadata,
524
+ metadata: sessionMetadata,
269
525
  updatedAt: now,
270
526
  });
271
527
  }
528
+ async function archivePreviousRuntimeSession(context, options, input) {
529
+ const previousSessionId = normalizeString(input.previousSessionId);
530
+ if (!previousSessionId || previousSessionId === options.sessionManager.getSessionId()) {
531
+ return;
532
+ }
533
+ const previousCreatedAt = toDate(input.previousCreatedAt)
534
+ ?? parseTimestampFromUuidLikeId(previousSessionId)
535
+ ?? new Date();
536
+ const previousRefs = resolvePiResourceRefsForSession(context, options, previousSessionId, previousCreatedAt);
537
+ const existing = await context.db.findByIri(sessionResource, previousRefs.sessionUri);
538
+ if (!existing) {
539
+ return;
540
+ }
541
+ const now = new Date();
542
+ const existingMetadataValue = existing.metadata;
543
+ const existingMetadata = isRecord(existingMetadataValue)
544
+ ? existingMetadataValue
545
+ : {};
546
+ await context.db.updateByIri(sessionResource, previousRefs.sessionUri, {
547
+ status: 'archived',
548
+ archivedAt: now,
549
+ updatedAt: now,
550
+ metadata: {
551
+ ...existingMetadata,
552
+ rewoundAt: now.toISOString(),
553
+ rewoundToSessionId: options.sessionManager.getSessionId(),
554
+ rewoundToSessionFile: normalizeString(input.cleanSessionFile),
555
+ rewindPreviousSessionFile: normalizeString(input.previousSessionFile),
556
+ },
557
+ });
558
+ }
559
+ async function markAbandonedPreviousMessages(context, options, input) {
560
+ const previousSessionId = normalizeString(input.previousSessionId);
561
+ const abandonedEntries = Array.isArray(input.abandonedEntries) ? input.abandonedEntries : [];
562
+ if (!previousSessionId || abandonedEntries.length === 0) {
563
+ return;
564
+ }
565
+ const previousCreatedAt = toDate(input.previousCreatedAt)
566
+ ?? parseTimestampFromUuidLikeId(previousSessionId)
567
+ ?? new Date();
568
+ const previousRefs = resolvePiResourceRefsForSession(context, options, previousSessionId, previousCreatedAt);
569
+ const previousSessionManager = {
570
+ getSessionId: () => previousSessionId,
571
+ };
572
+ const now = new Date();
573
+ for (const entry of abandonedEntries) {
574
+ if (entry.type !== 'message') {
575
+ continue;
576
+ }
577
+ const row = buildPodMessageRowFromMapping(context.webId, { sessionManager: previousSessionManager }, entry);
578
+ if (!row) {
579
+ continue;
580
+ }
581
+ const normalized = normalizePodMessageRow(context, row, previousRefs);
582
+ const resourceRef = messageResource.buildIri(context.webId, {
583
+ id: normalized.id,
584
+ chat: normalized.chat,
585
+ thread: normalized.thread,
586
+ createdAt: normalized.createdAt,
587
+ });
588
+ const existing = await context.db.findByIri(messageResource, resourceRef);
589
+ if (!existing) {
590
+ continue;
591
+ }
592
+ const existingMetadataValue = existing.metadata;
593
+ const existingMetadata = isRecord(existingMetadataValue)
594
+ ? existingMetadataValue
595
+ : {};
596
+ await context.db.updateByIri(messageResource, resourceRef, {
597
+ status: 'abandoned',
598
+ updatedAt: now,
599
+ metadata: {
600
+ ...existingMetadata,
601
+ rewoundAt: now.toISOString(),
602
+ rewoundFromSessionId: previousSessionId,
603
+ rewoundToSessionId: options.sessionManager.getSessionId(),
604
+ },
605
+ });
606
+ }
607
+ }
608
+ function resolveActiveMessageResourceRefs(context, options, refs) {
609
+ const activeRefs = new Set();
610
+ for (const entry of getActiveSessionEntries(options.sessionManager)) {
611
+ if (entry.type !== 'message') {
612
+ continue;
613
+ }
614
+ const row = buildPodMessageRowFromMapping(context.webId, options, entry);
615
+ if (!row) {
616
+ continue;
617
+ }
618
+ const normalized = normalizePodMessageRow(context, row, refs);
619
+ activeRefs.add(messageResource.buildIri(context.webId, {
620
+ id: normalized.id,
621
+ chat: normalized.chat,
622
+ thread: normalized.thread,
623
+ createdAt: normalized.createdAt,
624
+ }));
625
+ }
626
+ return activeRefs;
627
+ }
272
628
  async function persistMessage(context, row) {
273
629
  const insert = {
274
630
  id: row.id,
@@ -276,20 +632,30 @@ async function persistMessage(context, row) {
276
632
  thread: row.thread,
277
633
  maker: row.maker,
278
634
  role: row.role,
279
- content: row.content,
280
- ...(row.richContent ? { richContent: row.richContent } : {}),
635
+ content: sanitizePodLiteralText(row.content),
636
+ ...(row.richContent ? { richContent: sanitizePodLiteralText(row.richContent) } : {}),
281
637
  status: row.status,
282
638
  createdAt: row.createdAt,
283
639
  updatedAt: row.updatedAt,
284
640
  };
285
- await insertResource(context.db, messageResource, insert);
286
- return context.db.resolveLocatorIri(messageResource, { id: row.id, chat: row.chat, createdAt: row.createdAt });
641
+ const resourceRef = messageResource.buildIri(context.webId, { id: row.id, chat: row.chat, thread: row.thread, createdAt: row.createdAt });
642
+ await upsertByIri(context.db, messageResource, resourceRef, insert, {
643
+ chat: row.chat,
644
+ thread: row.thread,
645
+ maker: row.maker,
646
+ role: row.role,
647
+ content: sanitizePodLiteralText(row.content),
648
+ ...(row.richContent ? { richContent: sanitizePodLiteralText(row.richContent) } : {}),
649
+ status: row.status,
650
+ updatedAt: row.updatedAt,
651
+ });
652
+ return resourceRef;
287
653
  }
288
654
  async function touchPiConversation(context, options, refs, preview) {
289
655
  const now = new Date();
290
656
  const threadId = options.sessionManager.getSessionId();
291
657
  await context.db.updateById(chatResource, DEFAULT_SECRETARY_CHAT_ID, {
292
- lastMessagePreview: preview.slice(0, 100),
658
+ lastMessagePreview: sanitizePodLiteralText(preview).slice(0, 100),
293
659
  lastActiveAt: now,
294
660
  updatedAt: now,
295
661
  });
@@ -320,12 +686,30 @@ function createPodMirrorDb(session) {
320
686
  function resolvePiResourceRefs(context, options) {
321
687
  const sessionId = options.sessionManager.getSessionId();
322
688
  const createdAt = getSessionCreatedAt(options.sessionManager);
323
- const chatUri = context.db.resolveLocatorIri(chatResource, { id: DEFAULT_SECRETARY_CHAT_ID });
689
+ return resolvePiResourceRefsForSession(context, options, sessionId, createdAt);
690
+ }
691
+ function resolvePiResourceRefsForSession(context, _options, sessionId, createdAt) {
692
+ const chatUri = chatResource.buildIri(context.webId, { id: DEFAULT_SECRETARY_CHAT_ID });
693
+ const agentUri = agentResource.buildIri(context.webId, { id: PI_AGENT_ID });
324
694
  return {
325
- agentUri: context.db.resolveLocatorIri(agentResource, { id: PI_AGENT_ID }),
695
+ agentUri,
326
696
  chatUri,
327
- sessionUri: context.db.resolveLocatorIri(sessionResource, { id: sessionId, createdAt }),
328
- threadUri: context.db.resolveLocatorIri(threadResource, { id: sessionId, chat: chatUri }),
697
+ sessionUri: sessionResource.buildIri(context.webId, { id: sessionId, createdAt }),
698
+ symphonySkillUri: skillResource.buildIri(context.webId, {
699
+ id: PI_SYMPHONY_SKILL_ID,
700
+ agent: agentUri,
701
+ }),
702
+ threadUri: threadResource.buildIri(context.webId, { id: secretaryThreadResourceId(sessionId) }),
703
+ };
704
+ }
705
+ function createPiPodMirrorSyncResourceBindings(options, refs) {
706
+ const sessionId = options.sessionManager.getSessionId();
707
+ return {
708
+ chat: { uri: refs?.chatUri, local: DEFAULT_SECRETARY_CHAT_ID },
709
+ thread: { uri: refs?.threadUri, local: sessionId },
710
+ session: { uri: refs?.sessionUri, local: sessionId },
711
+ agent: { uri: refs?.agentUri, local: PI_AGENT_ID },
712
+ skill: { uri: refs?.symphonySkillUri, local: PI_SYMPHONY_SKILL_ID },
329
713
  };
330
714
  }
331
715
  function normalizePodMessageRow(context, row, refs) {
@@ -336,20 +720,6 @@ function normalizePodMessageRow(context, row, refs) {
336
720
  maker: row.role === 'user' ? context.webId : refs.agentUri,
337
721
  };
338
722
  }
339
- async function upsertByResource(db, resource, target, insert, update) {
340
- const id = String(target.id ?? '');
341
- const iri = db.resolveLocatorIri(resource, target);
342
- const existing = id ? await db.findById(resource, id) : await db.findByIri(resource, iri);
343
- if (!existing) {
344
- await db.insert(resource).values(insert).execute();
345
- return;
346
- }
347
- if (id) {
348
- await db.updateById(resource, id, update);
349
- return;
350
- }
351
- await db.updateByIri(resource, iri, update);
352
- }
353
723
  async function upsertByIri(db, resource, iri, insert, update) {
354
724
  const existing = await db.findByIri(resource, iri);
355
725
  if (!existing) {
@@ -358,6 +728,56 @@ async function upsertByIri(db, resource, iri, insert, update) {
358
728
  }
359
729
  await db.updateByIri(resource, iri, update);
360
730
  }
731
+ async function runWithTransientPodMirrorRetry(context, task) {
732
+ for (let attempt = 0; attempt <= POD_MIRROR_TRANSIENT_RETRY_DELAYS_MS.length; attempt += 1) {
733
+ try {
734
+ await task();
735
+ return;
736
+ }
737
+ catch (error) {
738
+ if (attempt >= POD_MIRROR_TRANSIENT_RETRY_DELAYS_MS.length || !isTransientPodMirrorError(error)) {
739
+ throw error;
740
+ }
741
+ await delay(POD_MIRROR_TRANSIENT_RETRY_DELAYS_MS[attempt], undefined, { signal: context.signal });
742
+ }
743
+ }
744
+ }
745
+ function isTransientPodMirrorError(error) {
746
+ const message = error instanceof Error ? error.message : String(error);
747
+ const normalized = message.toLowerCase();
748
+ if (normalized.includes('401') || normalized.includes('unauthorized')) {
749
+ return false;
750
+ }
751
+ if (normalized.includes('invalid unicode') || normalized.includes('surrogate pair') || normalized.includes('parse error')) {
752
+ return false;
753
+ }
754
+ return normalized.includes('http status 502')
755
+ || normalized.includes('http status 503')
756
+ || normalized.includes('http status 504')
757
+ || normalized.includes('502 bad gateway')
758
+ || normalized.includes('503 service unavailable')
759
+ || normalized.includes('504 gateway timeout')
760
+ || normalized.includes('bad gateway')
761
+ || normalized.includes('service unavailable')
762
+ || normalized.includes('gateway timeout')
763
+ || normalized.includes('fetch failed')
764
+ || normalized.includes('timed out')
765
+ || normalized.includes('timeout');
766
+ }
767
+ function isPodMirrorCircuitBreakerError(error) {
768
+ const message = error instanceof Error ? error.message : String(error);
769
+ const normalized = message.toLowerCase();
770
+ return normalized.includes('401')
771
+ || normalized.includes('403')
772
+ || normalized.includes('unauthorized')
773
+ || normalized.includes('forbidden')
774
+ || normalized.includes('invalid solid token')
775
+ || normalized.includes('linx cloud login expired');
776
+ }
777
+ function formatPodMirrorCircuitBreakerReason(error) {
778
+ const message = error instanceof Error ? error.message : String(error);
779
+ return `Pod projection disabled for this session after auth/permission failure: ${message}`;
780
+ }
361
781
  async function insertResource(db, resource, insert) {
362
782
  await db.insert(resource).values(insert).execute();
363
783
  }
@@ -415,7 +835,54 @@ function buildThreadMetadata(options) {
415
835
  sessionFile: options.sessionManager.getSessionFile(),
416
836
  };
417
837
  }
838
+ function createPiRuntimeSnapshot(refs, createdAt) {
839
+ const skills = [
840
+ {
841
+ id: skillResource.buildId({
842
+ id: PI_SYMPHONY_SKILL_ID,
843
+ agent: refs.agentUri,
844
+ }),
845
+ name: PI_SYMPHONY_SKILL_ID,
846
+ source: 'linx-cli:skills/symphony',
847
+ loadPolicy: 'file-backed',
848
+ enabled: true,
849
+ },
850
+ ];
851
+ return createAgentRuntimeConfigSnapshot({
852
+ agent: PI_AGENT_ID,
853
+ role: 'secretary',
854
+ label: 'AI Secretary',
855
+ model: DEFAULT_LINX_CLOUD_MODEL_ID,
856
+ runtime: {
857
+ backend: 'linx',
858
+ model: DEFAULT_LINX_CLOUD_MODEL_ID,
859
+ credentialSource: 'pod-session',
860
+ runtime: 'pi',
861
+ transport: 'pi-runtime',
862
+ },
863
+ skills,
864
+ }, {
865
+ createdAt,
866
+ source: 'linx-cli.pi-pod-mirror',
867
+ });
868
+ }
869
+ function isReplayablePiProjectionCheckpoint(checkpoint) {
870
+ const syncTaskDescription = checkpoint.metadata?.syncTaskDescription ?? checkpoint.metadata?.taskDescription;
871
+ const replayableTask = syncTaskDescription === 'message_end'
872
+ || syncTaskDescription === 'agent_end'
873
+ || syncTaskDescription === 'retry_pending_projection';
874
+ if (!replayableTask) {
875
+ return false;
876
+ }
877
+ if (!checkpoint.failures || checkpoint.failures.length === 0) {
878
+ return checkpoint.status === 'partial';
879
+ }
880
+ return checkpoint.failures.some((failure) => isTransientPodMirrorError(failure.message));
881
+ }
418
882
  function isRecord(value) {
419
883
  return typeof value === 'object' && value !== null;
420
884
  }
885
+ function normalizeString(value) {
886
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
887
+ }
421
888
  //# sourceMappingURL=pod-mirror.js.map