@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.
- package/README.md +58 -23
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +334 -162
- package/dist/index.js.map +1 -1
- package/dist/lib/account-session.js +4 -8
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +228 -178
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/auto-mode/archive.js +38 -7
- package/dist/lib/auto-mode/archive.js.map +1 -1
- package/dist/lib/auto-mode/auth.js.map +1 -1
- package/dist/lib/auto-mode/display.js +71 -45
- package/dist/lib/auto-mode/display.js.map +1 -1
- package/dist/lib/auto-mode/format.js +9 -7
- package/dist/lib/auto-mode/format.js.map +1 -1
- package/dist/lib/auto-mode/hooks/claude.js +12 -2
- package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
- package/dist/lib/auto-mode/hooks/codex.js +17 -7
- package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
- package/dist/lib/auto-mode/hooks/index.js +28 -8
- package/dist/lib/auto-mode/hooks/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-ai.js +20 -37
- package/dist/lib/auto-mode/pod-ai.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +124 -195
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/auto-mode/pod-persistence.js +169 -90
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
- package/dist/lib/auto-mode/runner.js +683 -81
- package/dist/lib/auto-mode/runner.js.map +1 -1
- package/dist/lib/auto-mode/secretary.js +186 -41
- package/dist/lib/auto-mode/secretary.js.map +1 -1
- package/dist/lib/auto-mode-command.js +32 -32
- package/dist/lib/auto-mode-command.js.map +1 -1
- package/dist/lib/chat-api.js +242 -50
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js +164 -17
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/credentials-store.js +33 -42
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/linx-cloud-errors.js +61 -0
- package/dist/lib/linx-cloud-errors.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +8 -5
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/login-command.js +9 -2
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +3 -20
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +143 -17
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js +2 -6
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
- package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
- package/dist/lib/pi-adapter/backend-command.js +2 -0
- package/dist/lib/pi-adapter/backend-command.js.map +1 -0
- package/dist/lib/pi-adapter/backend-credentials.js +80 -0
- package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
- package/dist/lib/pi-adapter/branding.js +246 -108
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/control-state.js +72 -0
- package/dist/lib/pi-adapter/control-state.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +2634 -30
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +382 -210
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +531 -64
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +81 -85
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/pod-status-output.js +54 -0
- package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +458 -228
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session-control.js +509 -0
- package/dist/lib/pi-adapter/session-control.js.map +1 -0
- package/dist/lib/pi-adapter/session.js +35 -22
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +89 -32
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/sync-recovery.js +89 -0
- package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
- package/dist/lib/pi-adapter/web-fetch.js +13 -14
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
- package/dist/lib/pod-chat-store.js +254 -78
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +156 -35
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/solid-auth-store.js +27 -0
- package/dist/lib/solid-auth-store.js.map +1 -0
- package/dist/lib/solid-auth.js +2 -4
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/solid-client-credentials-login.js +100 -0
- package/dist/lib/solid-client-credentials-login.js.map +1 -0
- package/dist/lib/solid-local-store.js +31 -0
- package/dist/lib/solid-local-store.js.map +1 -0
- package/dist/lib/symphony/archive.js +328 -18
- package/dist/lib/symphony/archive.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +2222 -0
- package/dist/lib/symphony/pod-projection.js.map +1 -0
- package/dist/lib/symphony-command.js +602 -178
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/lib/sync-checkpoint-store.js +74 -0
- package/dist/lib/sync-checkpoint-store.js.map +1 -0
- package/dist/skills/symphony/SKILL.md +665 -0
- package/package.json +15 -9
- package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
- package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
- package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
- package/vendor/agent-runtime/dist/auto-mode.js +288 -31
- package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
- package/vendor/agent-runtime/dist/control-plane.js +79 -0
- package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
- package/vendor/agent-runtime/dist/file-sync.js +314 -0
- package/vendor/agent-runtime/dist/index.d.ts +7 -0
- package/vendor/agent-runtime/dist/index.js +7 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
- package/vendor/agent-runtime/dist/reconciler.js +361 -0
- package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
- package/vendor/agent-runtime/dist/symphony.js +362 -57
- package/vendor/agent-runtime/dist/sync.d.ts +271 -0
- package/vendor/agent-runtime/dist/sync.js +550 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
- package/vendor/agent-runtime/dist/turn-controller.js +2 -2
- package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
- package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
- package/vendor/agent-runtime/package.json +8 -1
- package/vendor/pi-web-access/CHANGELOG.md +387 -0
- package/vendor/pi-web-access/LICENSE +21 -0
- package/vendor/pi-web-access/README.md +352 -0
- package/vendor/pi-web-access/activity.ts +101 -0
- package/vendor/pi-web-access/banner.png +0 -0
- package/vendor/pi-web-access/chrome-cookies.ts +322 -0
- package/vendor/pi-web-access/code-search.ts +107 -0
- package/vendor/pi-web-access/curator-page.ts +3359 -0
- package/vendor/pi-web-access/curator-server.ts +605 -0
- package/vendor/pi-web-access/exa.ts +520 -0
- package/vendor/pi-web-access/extract.ts +641 -0
- package/vendor/pi-web-access/gemini-api.ts +112 -0
- package/vendor/pi-web-access/gemini-search.ts +361 -0
- package/vendor/pi-web-access/gemini-url-context.ts +126 -0
- package/vendor/pi-web-access/gemini-web-config.ts +52 -0
- package/vendor/pi-web-access/gemini-web.ts +396 -0
- package/vendor/pi-web-access/github-api.ts +196 -0
- package/vendor/pi-web-access/github-extract.ts +634 -0
- package/vendor/pi-web-access/index.ts +2346 -0
- package/vendor/pi-web-access/package.json +45 -0
- package/vendor/pi-web-access/pdf-extract.ts +192 -0
- package/vendor/pi-web-access/perplexity.ts +195 -0
- package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
- package/vendor/pi-web-access/rsc-extract.ts +338 -0
- package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
- package/vendor/pi-web-access/storage.ts +72 -0
- package/vendor/pi-web-access/summary-review.ts +276 -0
- package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
- package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
- package/vendor/pi-web-access/utils.ts +44 -0
- package/vendor/pi-web-access/video-extract.ts +378 -0
- package/vendor/pi-web-access/youtube-extract.ts +310 -0
- package/dist/lib/pi-adapter/auth.js +0 -68
- package/dist/lib/pi-adapter/auth.js.map +0 -1
- package/dist/lib/pi-adapter/pod-tools.js +0 -140
- package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
- package/dist/skills/drizzle-solid/SKILL.md +0 -340
- package/dist/skills/pod-storage/SKILL.md +0 -100
- package/dist/skills/solid-modeling/SKILL.md +0 -274
- 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
|
|
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.
|
|
31
|
-
|
|
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.
|
|
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.
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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.
|
|
121
|
-
|
|
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
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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
|
|
695
|
+
agentUri,
|
|
326
696
|
chatUri,
|
|
327
|
-
sessionUri: context.
|
|
328
|
-
|
|
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
|