@undefineds.co/linx 0.2.15 → 0.2.17

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 (103) hide show
  1. package/dist/generated/version.js +3 -0
  2. package/dist/generated/version.js.map +1 -0
  3. package/dist/index.js +51 -225
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/account-api.js +1 -1
  6. package/dist/lib/account-api.js.map +1 -1
  7. package/dist/lib/account-session.js +1 -1
  8. package/dist/lib/account-session.js.map +1 -1
  9. package/dist/lib/ai-command.js +59 -32
  10. package/dist/lib/ai-command.js.map +1 -1
  11. package/dist/lib/chat-api.js +19 -177
  12. package/dist/lib/chat-api.js.map +1 -1
  13. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  14. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  15. package/dist/lib/codex-plugin/index.js.map +1 -1
  16. package/dist/lib/codex-plugin/runner.js.map +1 -1
  17. package/dist/lib/credentials-store.js +1 -7
  18. package/dist/lib/credentials-store.js.map +1 -1
  19. package/dist/lib/default-model.js +0 -1
  20. package/dist/lib/default-model.js.map +1 -1
  21. package/dist/lib/login-command.js +2 -17
  22. package/dist/lib/login-command.js.map +1 -1
  23. package/dist/lib/models.js +27 -2
  24. package/dist/lib/models.js.map +1 -1
  25. package/dist/lib/oidc-auth.js +13 -78
  26. package/dist/lib/oidc-auth.js.map +1 -1
  27. package/dist/lib/oidc-session-storage.js.map +1 -1
  28. package/dist/lib/pi-adapter/auth.js +5 -12
  29. package/dist/lib/pi-adapter/auth.js.map +1 -1
  30. package/dist/lib/pi-adapter/branding.js +75 -553
  31. package/dist/lib/pi-adapter/branding.js.map +1 -1
  32. package/dist/lib/pi-adapter/index.js.map +1 -1
  33. package/dist/lib/pi-adapter/interactive.js +4 -154
  34. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  35. package/dist/lib/pi-adapter/runtime.js +23 -138
  36. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  37. package/dist/lib/pi-adapter/stream.js +4 -154
  38. package/dist/lib/pi-adapter/stream.js.map +1 -1
  39. package/dist/lib/pi-adapter/theme.js.map +1 -1
  40. package/dist/lib/pod-chat-store.js +1 -1
  41. package/dist/lib/pod-chat-store.js.map +1 -1
  42. package/dist/lib/profile-identity.js +60 -16
  43. package/dist/lib/profile-identity.js.map +1 -1
  44. package/dist/lib/prompt.js.map +1 -1
  45. package/dist/lib/runtime-target.js +1 -1
  46. package/dist/lib/runtime-target.js.map +1 -1
  47. package/dist/lib/solid-auth.js.map +1 -1
  48. package/dist/lib/thread-utils.js.map +1 -1
  49. package/dist/lib/watch/archive.js +1 -1
  50. package/dist/lib/watch/archive.js.map +1 -1
  51. package/dist/lib/watch/auth.js +1 -1
  52. package/dist/lib/watch/auth.js.map +1 -1
  53. package/dist/lib/watch/codex-composer.js.map +1 -1
  54. package/dist/lib/watch/codex-footer.js.map +1 -1
  55. package/dist/lib/watch/codex-overlay.js.map +1 -1
  56. package/dist/lib/watch/codex-request-form.js +1 -1
  57. package/dist/lib/watch/codex-request-form.js.map +1 -1
  58. package/dist/lib/watch/codex-request-input.js.map +1 -1
  59. package/dist/lib/watch/display.js.map +1 -1
  60. package/dist/lib/watch/format.js.map +1 -1
  61. package/dist/lib/watch/hooks/claude.js.map +1 -1
  62. package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
  63. package/dist/lib/watch/hooks/codex.js.map +1 -1
  64. package/dist/lib/watch/hooks/index.js.map +1 -1
  65. package/dist/lib/watch/hooks/shared.js +1 -0
  66. package/dist/lib/watch/hooks/shared.js.map +1 -1
  67. package/dist/lib/watch/index.js.map +1 -1
  68. package/dist/lib/watch/pod-ai.js +32 -16
  69. package/dist/lib/watch/pod-ai.js.map +1 -1
  70. package/dist/lib/watch/pod-approval.js +203 -481
  71. package/dist/lib/watch/pod-approval.js.map +1 -1
  72. package/dist/lib/watch/pod-persistence.js +37 -24
  73. package/dist/lib/watch/pod-persistence.js.map +1 -1
  74. package/dist/lib/watch/runner.js +1 -4
  75. package/dist/lib/watch/runner.js.map +1 -1
  76. package/dist/lib/watch/types.js.map +1 -1
  77. package/dist/skills/drizzle-solid/SKILL.md +340 -0
  78. package/dist/skills/pod-storage/SKILL.md +60 -0
  79. package/dist/skills/solid-modeling/SKILL.md +274 -0
  80. package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
  81. package/dist/watch-cli.js.map +1 -1
  82. package/package.json +10 -3
  83. package/vendor/client/dist/client/index.d.ts +118 -0
  84. package/vendor/client/dist/client/index.js +260 -0
  85. package/vendor/client/dist/index.d.ts +1 -0
  86. package/vendor/client/dist/index.js +1 -0
  87. package/vendor/client/dist/watch/index.d.ts +226 -0
  88. package/vendor/client/dist/watch/index.js +1114 -0
  89. package/vendor/client/package.json +9 -0
  90. package/dist/lib/node-warning-filter.js +0 -34
  91. package/dist/lib/node-warning-filter.js.map +0 -1
  92. package/dist/lib/pi-adapter/pod-approval.js +0 -8
  93. package/dist/lib/pi-adapter/pod-approval.js.map +0 -1
  94. package/dist/lib/pi-adapter/pod-mirror-mapping.js +0 -189
  95. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +0 -1
  96. package/dist/lib/pi-adapter/pod-mirror.js +0 -334
  97. package/dist/lib/pi-adapter/pod-mirror.js.map +0 -1
  98. package/dist/lib/pi-adapter/pod-native.js +0 -477
  99. package/dist/lib/pi-adapter/pod-native.js.map +0 -1
  100. package/dist/lib/pi-adapter/session.js +0 -727
  101. package/dist/lib/pi-adapter/session.js.map +0 -1
  102. package/dist/lib/pod-data-session.js +0 -70
  103. package/dist/lib/pod-data-session.js.map +0 -1
@@ -1,727 +0,0 @@
1
- import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
2
- import { basename, join, resolve } from 'node:path';
3
- import { CURRENT_SESSION_VERSION, SessionManager, } from '@mariozechner/pi-coding-agent';
4
- import { getDefaultPodDataSession } from '../pod-data-session.js';
5
- import { PI_CHAT_ID, buildChatUri, buildThreadUri } from './pod-mirror-mapping.js';
6
- import { DCT_CREATED, DCT_MODIFIED, SIOC_CONTENT, SIOC_RICH_CONTENT, UDFS_ACTOR, UDFS_CONVERSATION, UDFS_IN_THREAD, UDFS_METADATA, UDFS_SESSION_TOOL, buildMessageResourceUrl, firstIri, firstLiteral, listTurtleResources, listTurtleResourcesRecursive, parseManagedTurtleBlocks, podBaseUrlFromWebId, readTurtleResource, } from './pod-native.js';
7
- export function createNativeLinxPiPodSessionSource(context) {
8
- const source = {
9
- async listSessions(cwd) {
10
- return listPodSessionSnapshots(context, cwd);
11
- },
12
- async findSession(input, cwd) {
13
- const sessions = await source.listSessions(cwd);
14
- const exact = sessions.find((session) => session.id === input);
15
- if (exact) {
16
- return exact;
17
- }
18
- const matches = sessions.filter((session) => session.id.startsWith(input));
19
- if (matches.length === 1) {
20
- return matches[0];
21
- }
22
- return null;
23
- },
24
- };
25
- return source;
26
- }
27
- export async function createLinxPiSessionManager(options) {
28
- const sessionDir = getDefaultLinxPiSessionDir(options.cwd, options.agentDir);
29
- if (options.session?.trim()) {
30
- const session = await resolveLinxPiSession(options.session.trim(), options.cwd, sessionDir, {
31
- podSessionSource: options.podSessionSource,
32
- });
33
- return openAndRepairLinxPiSession(session.path, sessionDir);
34
- }
35
- if (options.last) {
36
- const sessions = await listLinxPiSessions(options.cwd, options.agentDir, {
37
- podSessionSource: options.podSessionSource,
38
- });
39
- if (sessions[0]) {
40
- return openAndRepairLinxPiSession(sessions[0].path, sessionDir);
41
- }
42
- return SessionManager.create(options.cwd, sessionDir);
43
- }
44
- return SessionManager.create(options.cwd, sessionDir);
45
- }
46
- export async function listLinxPiSessions(cwd, agentDir, options = {}) {
47
- const sessionDir = getDefaultLinxPiSessionDir(cwd, agentDir);
48
- const localSessions = await SessionManager.list(cwd, sessionDir);
49
- const podSessions = await hydratePodSessions(cwd, sessionDir, options.podSessionSource, localSessions);
50
- return sortSessionsByModified(mergeSessions(localSessions, podSessions));
51
- }
52
- export async function resolveLinxPiSession(input, cwd, sessionDir, options = {}) {
53
- const directPath = resolve(input);
54
- if (existsSync(directPath) && statSync(directPath).isFile()) {
55
- const manager = SessionManager.open(directPath);
56
- const header = manager.getHeader();
57
- return {
58
- path: directPath,
59
- id: manager.getSessionId(),
60
- cwd: manager.getCwd(),
61
- created: header?.timestamp ? new Date(header.timestamp) : new Date(0),
62
- modified: statSync(directPath).mtime,
63
- messageCount: manager.getEntries().filter((entry) => entry.type === 'message').length,
64
- firstMessage: '(session file)',
65
- allMessagesText: '',
66
- };
67
- }
68
- const localSessions = sessionDir
69
- ? await SessionManager.list(cwd, sessionDir)
70
- : await SessionManager.list(cwd);
71
- const globalSessions = sessionDir ? await SessionManager.listAll() : [];
72
- const sessions = [...localSessions, ...globalSessions];
73
- const exact = sessions.find((session) => session.id === input);
74
- if (exact) {
75
- return exact;
76
- }
77
- const byPrefix = sessions.filter((session) => session.id.startsWith(input));
78
- if (byPrefix.length === 1) {
79
- return byPrefix[0];
80
- }
81
- const byFilePrefix = sessions.filter((session) => session.path.includes(input));
82
- if (byFilePrefix.length === 1) {
83
- return byFilePrefix[0];
84
- }
85
- const scoped = (byPrefix.length > 0 ? byPrefix : byFilePrefix)
86
- .filter((session) => session.cwd === cwd);
87
- if (scoped.length === 1) {
88
- return scoped[0];
89
- }
90
- if (sessionDir) {
91
- const podSession = await hydratePodSession(input, cwd, sessionDir, options.podSessionSource);
92
- if (podSession) {
93
- return podSession;
94
- }
95
- }
96
- if (byPrefix.length > 1 || byFilePrefix.length > 1) {
97
- const matches = sortSessionsByModified([...new Map([...byPrefix, ...byFilePrefix].map((session) => [session.path, session])).values()])
98
- .slice(0, 8)
99
- .map((session) => `- ${formatLinxPiSessionSummary(session)}`)
100
- .join('\n');
101
- throw new Error(`Session id is ambiguous: ${input}\n${matches}`);
102
- }
103
- throw new Error(`No LinX session found for: ${input}`);
104
- }
105
- export function formatLinxPiSessionSummary(session) {
106
- const label = session.name || session.firstMessage || '(no messages)';
107
- const cwd = session.cwd || '(unknown cwd)';
108
- return `${session.id.slice(0, 13)} ${label} ${cwd}`;
109
- }
110
- function sortSessionsByModified(sessions) {
111
- return [...sessions].sort((a, b) => b.modified.getTime() - a.modified.getTime());
112
- }
113
- function mergeSessions(localSessions, podSessions) {
114
- const merged = new Map();
115
- for (const session of podSessions) {
116
- merged.set(session.id, session);
117
- }
118
- for (const session of localSessions) {
119
- merged.set(session.id, session);
120
- }
121
- return [...merged.values()];
122
- }
123
- function getDefaultLinxPiSessionDir(cwd, agentDir) {
124
- const safePath = `--${cwd.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`;
125
- return join(agentDir, 'sessions', safePath);
126
- }
127
- async function hydratePodSessions(cwd, sessionDir, source, localSessions) {
128
- const resolvedSource = await resolvePodSessionSource(source);
129
- if (!resolvedSource) {
130
- return [];
131
- }
132
- const localIds = new Set(localSessions.map((session) => session.id));
133
- const snapshots = await resolvedSource.listSessions(cwd).catch(() => []);
134
- const hydrated = [];
135
- for (const snapshot of snapshots) {
136
- if (localIds.has(snapshot.id)) {
137
- continue;
138
- }
139
- hydrated.push(materializePodSessionSnapshot(snapshot, cwd, sessionDir));
140
- }
141
- return hydrated;
142
- }
143
- async function hydratePodSession(input, cwd, sessionDir, source) {
144
- const resolvedSource = await resolvePodSessionSource(source);
145
- if (!resolvedSource) {
146
- return null;
147
- }
148
- const snapshot = await resolvedSource.findSession(input, cwd).catch(() => null);
149
- if (!snapshot) {
150
- return null;
151
- }
152
- return materializePodSessionSnapshot(snapshot, cwd, sessionDir);
153
- }
154
- async function resolvePodSessionSource(source) {
155
- if (source !== undefined) {
156
- return source;
157
- }
158
- return createDefaultLinxPiPodSessionSource().catch(() => null);
159
- }
160
- function materializePodSessionSnapshot(snapshot, fallbackCwd, sessionDir) {
161
- mkdirSync(sessionDir, { recursive: true });
162
- const created = toDate(snapshot.createdAt) ?? toDate(snapshot.updatedAt) ?? new Date();
163
- const modified = toDate(snapshot.updatedAt) ?? created;
164
- const cwd = snapshot.cwd?.trim() || fallbackCwd;
165
- const entries = buildPodSessionEntries(snapshot);
166
- const sessionFile = getMaterializedSessionFile(snapshot, sessionDir, created);
167
- const header = {
168
- type: 'session',
169
- version: CURRENT_SESSION_VERSION,
170
- id: snapshot.id,
171
- timestamp: created.toISOString(),
172
- cwd,
173
- };
174
- const lines = [header, ...entries].map((entry) => JSON.stringify(entry));
175
- writeFileSync(sessionFile, `${lines.join('\n')}\n`);
176
- const manager = openAndRepairLinxPiSession(sessionFile, sessionDir);
177
- const info = buildSessionInfoFromManager(manager, sessionFile, created, modified);
178
- return snapshot.name ? { ...info, name: snapshot.name } : info;
179
- }
180
- function openAndRepairLinxPiSession(path, sessionDir) {
181
- const manager = SessionManager.open(path, sessionDir);
182
- repairDanglingLinxPiToolCalls(manager);
183
- return manager;
184
- }
185
- export function repairDanglingLinxPiToolCalls(manager) {
186
- let repaired = 0;
187
- const maxRepairs = 10;
188
- for (let index = 0; index < maxRepairs; index += 1) {
189
- const repair = findFirstDanglingToolCallRepair(manager.getBranch());
190
- if (!repair) {
191
- break;
192
- }
193
- manager.branch(repair.assistantEntry.id);
194
- for (const toolCall of repair.toolCalls) {
195
- const existingResult = repair.immediateToolResults.get(toolCall.id);
196
- if (existingResult) {
197
- appendReplayedSessionEntry(manager, existingResult);
198
- }
199
- else {
200
- manager.appendMessage(createInterruptedToolResultMessage(toolCall));
201
- }
202
- }
203
- for (const entry of repair.replayEntries) {
204
- const message = entry.type === 'message' ? entry.message : undefined;
205
- if (isToolResultMessageFor(message, repair.toolCallIds)) {
206
- continue;
207
- }
208
- appendReplayedSessionEntry(manager, entry);
209
- }
210
- repaired += repair.toolCalls.filter((toolCall) => !repair.immediateToolResults.has(toolCall.id)).length;
211
- }
212
- return repaired;
213
- }
214
- function findFirstDanglingToolCallRepair(branch) {
215
- for (let index = 0; index < branch.length; index += 1) {
216
- const entry = branch[index];
217
- const message = entry.type === 'message' ? entry.message : undefined;
218
- const toolCalls = extractAssistantToolCalls(message);
219
- if (toolCalls.length === 0) {
220
- continue;
221
- }
222
- const toolCallIds = new Set(toolCalls.map((toolCall) => toolCall.id));
223
- const immediateToolResults = new Map();
224
- let nextIndex = index + 1;
225
- while (nextIndex < branch.length) {
226
- const nextEntry = branch[nextIndex];
227
- const nextMessage = nextEntry.type === 'message' ? nextEntry.message : undefined;
228
- if (!isRecord(nextMessage) || nextMessage.role !== 'toolResult') {
229
- break;
230
- }
231
- const toolCallId = typeof nextMessage.toolCallId === 'string' ? nextMessage.toolCallId : '';
232
- if (toolCallIds.has(toolCallId) && !immediateToolResults.has(toolCallId)) {
233
- immediateToolResults.set(toolCallId, nextEntry);
234
- }
235
- nextIndex += 1;
236
- }
237
- if (toolCalls.every((toolCall) => immediateToolResults.has(toolCall.id))) {
238
- continue;
239
- }
240
- return {
241
- assistantEntry: entry,
242
- toolCalls,
243
- toolCallIds,
244
- immediateToolResults,
245
- replayEntries: branch.slice(nextIndex),
246
- };
247
- }
248
- return null;
249
- }
250
- function extractAssistantToolCalls(message) {
251
- if (!isRecord(message) || message.role !== 'assistant' || !Array.isArray(message.content)) {
252
- return [];
253
- }
254
- return message.content.flatMap((part) => {
255
- if (!isRecord(part) || part.type !== 'toolCall') {
256
- return [];
257
- }
258
- const id = typeof part.id === 'string' ? part.id : '';
259
- const name = typeof part.name === 'string' ? part.name : '';
260
- return id && name ? [{ id, name }] : [];
261
- });
262
- }
263
- function isToolResultMessageFor(message, toolCallIds) {
264
- if (!isRecord(message) || message.role !== 'toolResult') {
265
- return false;
266
- }
267
- const toolCallId = typeof message.toolCallId === 'string' ? message.toolCallId : '';
268
- return toolCallIds.has(toolCallId);
269
- }
270
- function createInterruptedToolResultMessage(toolCall) {
271
- return {
272
- role: 'toolResult',
273
- toolCallId: toolCall.id,
274
- toolName: toolCall.name,
275
- content: [{
276
- type: 'text',
277
- text: `Tool execution was interrupted before LinX received a result for ${toolCall.name}. Treat this tool call as failed and retry only if still needed.`,
278
- }],
279
- details: {
280
- linxRepair: 'dangling-tool-call',
281
- interrupted: true,
282
- },
283
- isError: true,
284
- timestamp: Date.now(),
285
- };
286
- }
287
- function appendReplayedSessionEntry(manager, entry) {
288
- if (entry.type === 'message') {
289
- manager.appendMessage(cloneJson(entry.message));
290
- return;
291
- }
292
- if (entry.type === 'thinking_level_change') {
293
- manager.appendThinkingLevelChange(entry.thinkingLevel);
294
- return;
295
- }
296
- if (entry.type === 'model_change') {
297
- manager.appendModelChange(entry.provider, entry.modelId);
298
- return;
299
- }
300
- if (entry.type === 'session_info' && entry.name) {
301
- manager.appendSessionInfo(entry.name);
302
- return;
303
- }
304
- if (entry.type === 'custom') {
305
- manager.appendCustomEntry(entry.customType, cloneJson(entry.data));
306
- return;
307
- }
308
- if (entry.type === 'custom_message') {
309
- manager.appendCustomMessageEntry(entry.customType, cloneJson(entry.content), entry.display, cloneJson(entry.details));
310
- }
311
- }
312
- function cloneJson(value) {
313
- if (value === undefined) {
314
- return value;
315
- }
316
- return JSON.parse(JSON.stringify(value));
317
- }
318
- function buildPodSessionEntries(snapshot) {
319
- const sortedMessages = [...(snapshot.messages ?? [])].sort((a, b) => {
320
- const aTime = toDate(a.createdAt)?.getTime() ?? 0;
321
- const bTime = toDate(b.createdAt)?.getTime() ?? 0;
322
- return aTime - bTime;
323
- });
324
- const entries = [];
325
- let previousId = null;
326
- for (const row of sortedMessages) {
327
- const parsed = parsePodRichContent(row.richContent);
328
- const message = parsed.message ?? synthesizeAgentMessage(row);
329
- if (!message) {
330
- continue;
331
- }
332
- const id = parsed.entry?.id
333
- ?? extractEntryIdFromPodMessageId(snapshot.id, row.id);
334
- const timestamp = parsed.entry?.timestamp
335
- ?? toDate(row.createdAt)?.toISOString()
336
- ?? new Date().toISOString();
337
- const entry = {
338
- type: 'message',
339
- id,
340
- parentId: parsed.entry?.parentId !== undefined ? parsed.entry.parentId : previousId,
341
- timestamp,
342
- message,
343
- };
344
- entries.push(entry);
345
- previousId = id;
346
- }
347
- return entries;
348
- }
349
- function parsePodRichContent(richContent) {
350
- if (!richContent?.trim()) {
351
- return {};
352
- }
353
- try {
354
- const parsed = JSON.parse(richContent);
355
- if (isRecord(parsed) && parsed.type === 'message' && isRecord(parsed.message)) {
356
- return {
357
- entry: parsed,
358
- message: parsed.message,
359
- };
360
- }
361
- if (isRecord(parsed) && isRecord(parsed.linxPiSessionEntry)) {
362
- const entry = parsed.linxPiSessionEntry;
363
- return {
364
- entry,
365
- message: isRecord(entry.message) ? entry.message : parsed,
366
- };
367
- }
368
- if (isRecord(parsed) && typeof parsed.role === 'string') {
369
- return { message: parsed };
370
- }
371
- }
372
- catch {
373
- return {};
374
- }
375
- return {};
376
- }
377
- function synthesizeAgentMessage(row) {
378
- const role = row.role === 'assistant' || row.role === 'system' ? row.role : 'user';
379
- const timestamp = toDate(row.createdAt)?.getTime() ?? Date.now();
380
- const content = [{ type: 'text', text: row.content ?? '' }];
381
- if (role === 'assistant') {
382
- return {
383
- role: 'assistant',
384
- content,
385
- api: 'openai-completions',
386
- provider: 'undefineds',
387
- model: 'linx-lite',
388
- usage: {
389
- input: 0,
390
- output: 0,
391
- cacheRead: 0,
392
- cacheWrite: 0,
393
- totalTokens: 0,
394
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
395
- },
396
- stopReason: 'stop',
397
- timestamp,
398
- };
399
- }
400
- if (role === 'system') {
401
- return {
402
- role: 'custom',
403
- customType: 'linx-pod-system',
404
- content,
405
- timestamp,
406
- };
407
- }
408
- return {
409
- role: 'user',
410
- content,
411
- timestamp,
412
- };
413
- }
414
- function extractEntryIdFromPodMessageId(sessionId, messageId) {
415
- const prefix = `${sessionId}-`;
416
- return messageId.startsWith(prefix) ? messageId.slice(prefix.length) : messageId;
417
- }
418
- function getMaterializedSessionFile(snapshot, sessionDir, created) {
419
- if (snapshot.sessionFile?.trim()) {
420
- const fileName = basename(snapshot.sessionFile);
421
- if (fileName.endsWith('.jsonl')) {
422
- return join(sessionDir, fileName);
423
- }
424
- }
425
- const fileTimestamp = created.toISOString().replace(/[:.]/g, '-');
426
- return join(sessionDir, `${fileTimestamp}_${snapshot.id}.jsonl`);
427
- }
428
- function buildSessionInfoFromManager(manager, path, created, modified) {
429
- const entries = manager.getEntries();
430
- const messages = entries.filter((entry) => entry.type === 'message');
431
- const allMessages = messages
432
- .map((entry) => extractMessageText(entry.message))
433
- .filter(Boolean);
434
- const firstUserMessage = messages.find((entry) => {
435
- const message = entry.message;
436
- return message?.role === 'user';
437
- });
438
- return {
439
- path,
440
- id: manager.getSessionId(),
441
- cwd: manager.getCwd(),
442
- name: manager.getSessionName(),
443
- created,
444
- modified,
445
- messageCount: messages.length,
446
- firstMessage: firstUserMessage
447
- ? extractMessageText(firstUserMessage.message) || '(no messages)'
448
- : '(no messages)',
449
- allMessagesText: allMessages.join(' '),
450
- };
451
- }
452
- function extractMessageText(message) {
453
- if (!isRecord(message)) {
454
- return '';
455
- }
456
- const content = message.content;
457
- if (typeof content === 'string') {
458
- return content;
459
- }
460
- if (!Array.isArray(content)) {
461
- return '';
462
- }
463
- return content
464
- .map((part) => {
465
- if (typeof part === 'string')
466
- return part;
467
- if (!isRecord(part))
468
- return '';
469
- if (part.type === 'text')
470
- return String(part.text ?? '');
471
- if (part.type === 'thinking')
472
- return String(part.thinking ?? '');
473
- return '';
474
- })
475
- .join('');
476
- }
477
- function toDate(value) {
478
- if (value instanceof Date) {
479
- return Number.isNaN(value.getTime()) ? null : value;
480
- }
481
- if (typeof value === 'string' || typeof value === 'number') {
482
- const parsed = new Date(value);
483
- return Number.isNaN(parsed.getTime()) ? null : parsed;
484
- }
485
- return null;
486
- }
487
- async function createDefaultLinxPiPodSessionSource() {
488
- const contextPromise = createDefaultPodSessionContext();
489
- return {
490
- async listSessions(cwd) {
491
- const context = await contextPromise;
492
- return context ? createNativeLinxPiPodSessionSource(context).listSessions(cwd) : [];
493
- },
494
- async findSession(input, cwd) {
495
- const context = await contextPromise;
496
- return context ? createNativeLinxPiPodSessionSource(context).findSession(input, cwd) : null;
497
- },
498
- };
499
- }
500
- async function createDefaultPodSessionContext() {
501
- const session = await getDefaultPodDataSession();
502
- if (!session) {
503
- return null;
504
- }
505
- return {
506
- webId: session.webId,
507
- fetch: session.fetch,
508
- };
509
- }
510
- async function listPodSessionSnapshots(context, cwd) {
511
- const podBaseUrl = podBaseUrlFromWebId(context.webId);
512
- const [currentSessionUrls, legacySessionUrls] = await Promise.all([
513
- listTurtleResourcesRecursive(context.fetch, `${podBaseUrl}/.data/sessions/`).catch(() => []),
514
- listTurtleResources(context.fetch, `${podBaseUrl}/.data/session/`).catch(() => []),
515
- ]);
516
- const sessionUrls = [...new Set([...currentSessionUrls, ...legacySessionUrls])];
517
- const snapshots = (await mapWithConcurrency(sessionUrls, 6, (sessionUrl) => (readPodSessionSnapshot(context, sessionUrl, { expectedCwd: cwd }).catch(() => null))))
518
- .filter((snapshot) => snapshot !== null);
519
- return snapshots.sort((a, b) => {
520
- const aTime = toDate(a.updatedAt)?.getTime() ?? 0;
521
- const bTime = toDate(b.updatedAt)?.getTime() ?? 0;
522
- return bTime - aTime;
523
- });
524
- }
525
- async function readPodSessionSnapshot(context, sessionUrl, options = {}) {
526
- const turtle = await readTurtleResource(context.fetch, sessionUrl);
527
- if (!turtle) {
528
- return null;
529
- }
530
- const blocks = parseManagedTurtleBlocks(turtle, sessionUrl);
531
- const blockEntries = [...blocks.entries()];
532
- const blockEntry = blockEntries.find(([subject]) => subject === sessionUrl)
533
- ?? blockEntries.find(([, entry]) => firstLiteral(entry, UDFS_SESSION_TOOL) === 'linx');
534
- const subjectUrl = blockEntry?.[0] ?? sessionUrl;
535
- const predicates = blockEntry?.[1];
536
- if (!(predicates instanceof Map)) {
537
- return null;
538
- }
539
- if (firstLiteral(predicates, UDFS_SESSION_TOOL) !== 'linx') {
540
- return null;
541
- }
542
- const chatUri = firstIri(predicates, UDFS_CONVERSATION);
543
- const legacyChatId = firstLiteral(predicates, UDFS_CONVERSATION);
544
- if (chatUri && chatUri !== buildChatUri(context.webId)) {
545
- return null;
546
- }
547
- if (!chatUri && legacyChatId !== PI_CHAT_ID) {
548
- return null;
549
- }
550
- const ownerWebId = firstIri(predicates, UDFS_ACTOR);
551
- if (ownerWebId && ownerWebId !== context.webId) {
552
- return null;
553
- }
554
- const id = decodeURIComponent(subjectUrl.includes('#')
555
- ? subjectUrl.split('#').pop() ?? ''
556
- : sessionUrl.split('/').pop()?.replace(/\.ttl$/, '') ?? '');
557
- if (!id) {
558
- return null;
559
- }
560
- const metadata = parseMetadataPredicates(predicates);
561
- const sessionCwd = typeof metadata.cwd === 'string' ? metadata.cwd : undefined;
562
- if (options.expectedCwd && sessionCwd !== options.expectedCwd) {
563
- return null;
564
- }
565
- const storedThreadUri = firstIri(predicates, UDFS_IN_THREAD);
566
- const legacyThreadId = firstLiteral(predicates, UDFS_IN_THREAD);
567
- const threadUri = storedThreadUri
568
- ?? (typeof metadata.threadUri === 'string'
569
- ? metadata.threadUri
570
- : buildThreadUri(context.webId, PI_CHAT_ID, legacyThreadId ?? id));
571
- const messages = await listPodSessionMessages(context, id, threadUri, metadata.messageResources);
572
- return {
573
- id,
574
- cwd: sessionCwd,
575
- createdAt: normalizeUnknownDate(firstLiteral(predicates, DCT_CREATED)),
576
- updatedAt: normalizeUnknownDate(firstLiteral(predicates, DCT_MODIFIED)),
577
- sessionFile: typeof metadata.sessionFile === 'string' ? metadata.sessionFile : undefined,
578
- messages,
579
- };
580
- }
581
- async function mapWithConcurrency(items, concurrency, mapper) {
582
- const results = new Array(items.length);
583
- let nextIndex = 0;
584
- const workerCount = Math.min(Math.max(1, concurrency), items.length);
585
- await Promise.all(Array.from({ length: workerCount }, async () => {
586
- while (nextIndex < items.length) {
587
- const index = nextIndex;
588
- nextIndex += 1;
589
- results[index] = await mapper(items[index]);
590
- }
591
- }));
592
- return results;
593
- }
594
- async function listPodSessionMessages(context, sessionId, threadUri, messageResources) {
595
- const resolvedThreadUri = typeof threadUri === 'string' && threadUri
596
- ? threadUri
597
- : buildThreadUri(context.webId, PI_CHAT_ID, sessionId);
598
- const urls = normalizeMessageResourceUrls(messageResources);
599
- if (urls.length === 0) {
600
- urls.push(...await candidateMessageResourceUrls(context.fetch, context.webId));
601
- }
602
- const messages = [];
603
- for (const url of urls) {
604
- const turtle = await readTurtleResource(context.fetch, url).catch(() => null);
605
- if (!turtle) {
606
- continue;
607
- }
608
- for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
609
- if (!subject.includes(`${sessionId}-`)) {
610
- continue;
611
- }
612
- const richContent = firstLiteral(predicates, SIOC_RICH_CONTENT);
613
- if (richContent) {
614
- const parsed = parsePodRichContent(richContent);
615
- const entry = parsed.entry;
616
- if (entry?.id && !subject.endsWith(`${sessionId}-${entry.id}`)) {
617
- continue;
618
- }
619
- }
620
- if (turtle.includes(`<${resolvedThreadUri}>`) || subject.includes(`${sessionId}-`)) {
621
- messages.push({
622
- id: decodeURIComponent(subject.split('#').pop() ?? subject),
623
- role: firstLiteral(predicates, 'https://undefineds.co/ns#messageType'),
624
- content: firstLiteral(predicates, SIOC_CONTENT),
625
- richContent,
626
- createdAt: normalizeUnknownDate(firstLiteral(predicates, DCT_CREATED)),
627
- updatedAt: normalizeUnknownDate(firstLiteral(predicates, DCT_MODIFIED)),
628
- });
629
- }
630
- }
631
- }
632
- return messages
633
- .filter((message) => message.id)
634
- .sort((a, b) => {
635
- const aTime = toDate(a.createdAt)?.getTime() ?? 0;
636
- const bTime = toDate(b.createdAt)?.getTime() ?? 0;
637
- return aTime - bTime;
638
- });
639
- }
640
- function normalizeMessageResourceUrls(value) {
641
- if (!Array.isArray(value)) {
642
- return [];
643
- }
644
- return [...new Set(value.filter((entry) => (typeof entry === 'string' && entry.startsWith('http') && entry.endsWith('.ttl'))))];
645
- }
646
- async function candidateMessageResourceUrls(fetcher, webId) {
647
- const chatBase = `${podBaseUrlFromWebId(webId)}/.data/chat/${PI_CHAT_ID}/`;
648
- const discovered = new Set();
649
- const yearContainers = await listChildContainers(fetcher, chatBase);
650
- for (const yearContainer of yearContainers) {
651
- const monthContainers = await listChildContainers(fetcher, yearContainer);
652
- for (const monthContainer of monthContainers) {
653
- const dayContainers = await listChildContainers(fetcher, monthContainer);
654
- for (const dayContainer of dayContainers) {
655
- discovered.add(new URL('messages.ttl', dayContainer).toString());
656
- }
657
- }
658
- }
659
- if (discovered.size === 0) {
660
- const now = new Date();
661
- discovered.add(buildMessageResourceUrl(webId, PI_CHAT_ID, now));
662
- }
663
- return [...discovered].sort();
664
- }
665
- async function listChildContainers(fetcher, containerUrl) {
666
- const response = await fetcher(containerUrl, {
667
- method: 'GET',
668
- headers: { Accept: 'text/turtle, application/ld+json;q=0.8, */*;q=0.1' },
669
- });
670
- if (response.status === 404) {
671
- return [];
672
- }
673
- if (!response.ok) {
674
- return [];
675
- }
676
- const text = await response.text();
677
- const urls = new Set();
678
- const base = new URL(containerUrl);
679
- const relativeRegexp = /[<"]([^<>"']+\/)[>"]/g;
680
- let match;
681
- while ((match = relativeRegexp.exec(text))) {
682
- urls.add(new URL(match[1], base).toString());
683
- }
684
- const absoluteRegexp = /(https?:\/\/[^<>"'\s)]+\/)/g;
685
- for (const absolute of text.matchAll(absoluteRegexp)) {
686
- const url = absolute[1];
687
- if (url.startsWith(containerUrl) && url !== containerUrl) {
688
- urls.add(url);
689
- }
690
- }
691
- return [...urls].sort();
692
- }
693
- function normalizeUnknownDate(value) {
694
- if (value instanceof Date || typeof value === 'string' || typeof value === 'number') {
695
- return value;
696
- }
697
- return undefined;
698
- }
699
- function parseJsonObject(value) {
700
- if (!value) {
701
- return {};
702
- }
703
- try {
704
- const parsed = JSON.parse(value);
705
- return isRecord(parsed) ? parsed : {};
706
- }
707
- catch {
708
- return {};
709
- }
710
- }
711
- function parseMetadataPredicates(predicates) {
712
- const values = predicates.get(UDFS_METADATA) ?? [];
713
- const parsed = values
714
- .filter((entry) => entry.type === 'literal')
715
- .map((entry) => parseJsonObject(entry.value))
716
- .filter((entry) => Object.keys(entry).length > 0);
717
- for (let index = parsed.length - 1; index >= 0; index -= 1) {
718
- if (Array.isArray(parsed[index].messageResources)) {
719
- return parsed[index];
720
- }
721
- }
722
- return parsed[parsed.length - 1] ?? {};
723
- }
724
- function isRecord(value) {
725
- return typeof value === 'object' && value !== null;
726
- }
727
- //# sourceMappingURL=session.js.map