@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
package/dist/index.js CHANGED
@@ -1,26 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import './lib/node-warning-filter.js';
3
3
  import { readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
4
5
  import yargs from 'yargs';
5
6
  import { hideBin } from 'yargs/helpers';
6
7
  import { aiCommand } from './lib/ai-command.js';
7
8
  import { resolveAccountBaseUrl } from './lib/account-api.js';
8
9
  import { loadCredentials } from './lib/credentials-store.js';
9
10
  import { loginCommand, logoutCommand, whoamiCommand } from './lib/login-command.js';
10
- import { DefaultPackageManager, SettingsManager, runPrintMode } from '@mariozechner/pi-coding-agent';
11
+ import { DefaultPackageManager, SettingsManager, SessionSelectorComponent, runPrintMode } from '@earendil-works/pi-coding-agent';
12
+ import { ProcessTerminal, TUI } from '@earendil-works/pi-tui';
11
13
  import { promptText } from './lib/prompt.js';
12
14
  import { buildAutoModeOptions, isAutoModeRequest, runAutoModeCommand, } from './lib/auto-mode-command.js';
13
- import { formatAutoModeSessionSummary, listArchivedAutoModeSessions, loadArchivedAutoModeSession, resumeAutoModeSession, } from './lib/auto-mode/index.js';
14
- import { symphonyCommand } from './lib/symphony-command.js';
15
15
  import { resolveRuntimeTarget } from './lib/runtime-target.js';
16
16
  import { createCodexNativeProxy } from './lib/codex-plugin/index.js';
17
- import { bootstrapPiInteractiveMode, createPiRuntimeAdapter, resolveLinxInteractiveLoginReason, resolveLinxStartupLoginPromptDecision } from './lib/pi-adapter/index.js';
18
- import { isOidcLoginExpiredError } from './lib/oidc-auth.js';
19
- import { createPodDataSession } from './lib/pod-data-session.js';
17
+ import { bootstrapLinxInteractiveMode, createLinxRuntimeAdapter, resolveLinxInteractiveLoginReason, resolveLinxStartupLoginPromptDecision, } from './lib/pi-adapter/index.js';
18
+ import { clearDefaultPodDataSession, createPodDataSession, getDefaultPodDataSession } from './lib/pod-data-session.js';
20
19
  import { DEFAULT_LINX_CLOUD_MODEL_ID, FALLBACK_LINX_CLOUD_MODEL_IDS } from './lib/default-model.js';
21
- import { createLinxPiSessionManager, formatLinxPiSessionSummary, listLinxPiSessions, resolveLinxPiSession, } from './lib/pi-adapter/session.js';
20
+ import { createLinxPiSessionManager, listLinxPiSessions, } from './lib/pi-adapter/session.js';
22
21
  import { LinxPiPodMirror } from './lib/pi-adapter/pod-mirror.js';
22
+ import { listPendingPiPodMirrorSync, retryPendingPiPodMirrorSync } from './lib/pi-adapter/sync-recovery.js';
23
23
  import { LINX_AGENT_DIR } from './lib/pi-adapter/branding.js';
24
+ import { createFileSyncCheckpointStore } from './lib/sync-checkpoint-store.js';
25
+ import { deriveLinxPiStartupControlState, hydrateLinxPiControlState } from './lib/pi-adapter/control-state.js';
26
+ import { drizzle, solidResources } from './lib/models.js';
27
+ import { formatLinxCliErrorMessage } from './lib/linx-cloud-errors.js';
24
28
  function readPackageVersion() {
25
29
  try {
26
30
  const raw = readFileSync(new URL('../package.json', import.meta.url), 'utf-8');
@@ -77,11 +81,10 @@ async function resolveContext(urlOverride) {
77
81
  issuerUrl: podSession.credentials.url,
78
82
  runtimeUrlOverride: urlOverride,
79
83
  });
80
- const apiKey = await resolvePodRuntimeAuthToken(podSession);
81
84
  const session = podSession.solidSession;
82
85
  await runtime.initPodData(session);
83
86
  const chatId = await runtime.getOrCreateDefaultChat(session);
84
- return { runtimeUrl: target.runtimeUrl, apiKey, session, podSession, chatId, runtime };
87
+ return { runtimeUrl: target.runtimeUrl, authFetch: podSession.runtimeFetch, session, podSession, chatId, runtime };
85
88
  }
86
89
  async function resolveRuntimeAuthContext(urlOverride) {
87
90
  const runtime = await loadChatRuntime();
@@ -90,10 +93,9 @@ async function resolveRuntimeAuthContext(urlOverride) {
90
93
  issuerUrl: podSession.credentials.url,
91
94
  runtimeUrlOverride: urlOverride,
92
95
  });
93
- const apiKey = await resolvePodRuntimeAuthToken(podSession);
94
96
  return {
95
97
  runtimeUrl: target.runtimeUrl,
96
- apiKey,
98
+ authFetch: podSession.runtimeFetch,
97
99
  session: podSession.solidSession,
98
100
  podSession,
99
101
  runtime,
@@ -109,43 +111,76 @@ async function createLinxPodDataSession() {
109
111
  }
110
112
  return podSession;
111
113
  }
112
- async function resolvePodRuntimeAuthToken(podSession) {
113
- try {
114
- return await podSession.getRuntimeAuthToken();
115
- }
116
- catch (error) {
117
- if (isOidcLoginExpiredError(error)) {
118
- throw new Error('LinX Cloud login expired. Run `linx login` to re-authorize.');
119
- }
120
- throw error;
114
+ async function resolveStartupLinxPodDataSession() {
115
+ if (!loadCredentials()) {
116
+ return null;
121
117
  }
118
+ return createLinxPodDataSession();
122
119
  }
123
120
  async function runSingleTurn(options) {
124
121
  const { ctx, threadId, model, prompt } = options;
125
- const history = await ctx.runtime.loadMessages(ctx.session, threadId);
126
- await ctx.runtime.saveUserMessage(ctx.session, ctx.chatId, threadId, prompt);
122
+ const history = await tryLoadMessages(ctx, threadId);
127
123
  const reply = await ctx.runtime.createRemoteCompletion({
128
124
  runtimeUrl: ctx.runtimeUrl,
129
- apiKey: ctx.apiKey,
125
+ authFetch: ctx.authFetch,
130
126
  model,
131
127
  messages: [...ctx.runtime.toOpenAiMessages(history), { role: 'user', content: prompt }],
132
128
  });
133
129
  const replyText = typeof reply === 'string' ? reply : reply.content ?? '';
134
- await ctx.runtime.saveAssistantMessage(ctx.session, ctx.chatId, threadId, replyText);
135
130
  process.stdout.write(`\n${replyText}\n\n`);
131
+ await persistSingleTurnBestEffort(ctx, threadId, prompt, replyText);
132
+ }
133
+ async function tryLoadMessages(ctx, threadId) {
134
+ try {
135
+ return await ctx.runtime.loadMessages(ctx.session, threadId);
136
+ }
137
+ catch (error) {
138
+ process.stderr.write(`Warning: failed to load Pod chat history; continuing without history: ${formatLinxCliErrorMessage(error)}\n`);
139
+ return [];
140
+ }
141
+ }
142
+ async function persistSingleTurnBestEffort(ctx, threadId, prompt, replyText) {
143
+ try {
144
+ await ctx.runtime.saveUserMessage(ctx.session, ctx.chatId, threadId, prompt);
145
+ await ctx.runtime.saveAssistantMessage(ctx.session, ctx.chatId, threadId, replyText);
146
+ }
147
+ catch (error) {
148
+ process.stderr.write(`Warning: failed to persist Pod chat turn: ${formatLinxCliErrorMessage(error)}\n`);
149
+ }
136
150
  }
137
151
  async function resolveThreadId(options) {
138
- const { ctx, continueMode, explicitThreadId, workspace } = options;
152
+ const { ctx, continueMode, explicitThreadId, workspace, bestEffort } = options;
139
153
  if (explicitThreadId) {
140
154
  return explicitThreadId;
141
155
  }
142
156
  if (continueMode) {
143
- const latest = await ctx.runtime.getLatestThreadId(ctx.session, ctx.chatId);
157
+ const latest = bestEffort
158
+ ? await tryGetLatestThreadId(ctx)
159
+ : await ctx.runtime.getLatestThreadId(ctx.session, ctx.chatId);
144
160
  if (latest) {
145
161
  return latest;
146
162
  }
147
163
  }
148
- return ctx.runtime.createThread(ctx.session, ctx.chatId, workspace || process.cwd(), 'CLI Session');
164
+ if (!bestEffort) {
165
+ return ctx.runtime.createThread(ctx.session, ctx.chatId, workspace || process.cwd(), 'CLI Session');
166
+ }
167
+ try {
168
+ return await ctx.runtime.createThread(ctx.session, ctx.chatId, workspace || process.cwd(), 'CLI Session');
169
+ }
170
+ catch (error) {
171
+ const fallbackThreadId = `local-${Date.now().toString(36)}`;
172
+ process.stderr.write(`Warning: failed to create Pod chat thread; using temporary thread ${fallbackThreadId}: ${formatLinxCliErrorMessage(error)}\n`);
173
+ return fallbackThreadId;
174
+ }
175
+ }
176
+ async function tryGetLatestThreadId(ctx) {
177
+ try {
178
+ return await ctx.runtime.getLatestThreadId(ctx.session, ctx.chatId);
179
+ }
180
+ catch (error) {
181
+ process.stderr.write(`Warning: failed to load latest Pod chat thread: ${formatLinxCliErrorMessage(error)}\n`);
182
+ return null;
183
+ }
149
184
  }
150
185
  async function runInteractive(options) {
151
186
  const { ctx, initialThreadId, initialModel, initialPrompt } = options;
@@ -262,39 +297,82 @@ function printConfiguredLinxPackages(packageManager) {
262
297
  async function runPiCommand(argv) {
263
298
  const firstPromptToken = Array.isArray(argv.prompt) ? argv.prompt[0] : undefined;
264
299
  // Reject old command aliases explicitly; auto-mode is only selected through flags.
265
- if (firstPromptToken === 'automode' || firstPromptToken === 'watch') {
300
+ if (firstPromptToken === 'automode' || firstPromptToken === 'watch' || firstPromptToken === 'resume') {
266
301
  throw new Error(`Unknown command: ${firstPromptToken}`);
267
302
  }
303
+ if (argv.resume) {
304
+ const selectedSession = await selectLinxSession(cwdFromArg(argv.cwd));
305
+ if (!selectedSession) {
306
+ process.stdout.write('No session selected\n');
307
+ return;
308
+ }
309
+ await runPiCommand({
310
+ ...argv,
311
+ resume: false,
312
+ session: selectedSession,
313
+ });
314
+ return;
315
+ }
268
316
  if (isAutoModeRequest(argv)) {
269
317
  await runAutoModeCommand(argv);
270
318
  return;
271
319
  }
272
- const backend = 'cloud';
320
+ if (argv['pi-sync-status']) {
321
+ await runPiSyncStatusCommand();
322
+ return;
323
+ }
324
+ if (argv['pi-sync-retry']) {
325
+ await runPiSyncRetryCommand({
326
+ cwd: argv.cwd || process.cwd(),
327
+ sessionId: argv['pi-sync-retry'],
328
+ });
329
+ return;
330
+ }
331
+ if (argv.backend) {
332
+ await runAutoModeCommand({
333
+ ...argv,
334
+ plain: Boolean(argv.plain || argv.print),
335
+ });
336
+ return;
337
+ }
338
+ const cwd = argv.cwd || process.cwd();
273
339
  const startupLoginPrompt = await resolveLinxStartupLoginPromptDecision({
274
- backend,
340
+ backend: 'cloud',
275
341
  print: argv.print,
276
342
  issuerUrl: resolveAccountBaseUrl(),
343
+ resolveSession: resolveStartupLinxPodDataSession,
277
344
  });
278
- const adapter = createPiRuntimeAdapter({
279
- createNativeProxy(options) {
280
- return createCodexNativeProxy({
281
- cwd: options?.cwd,
282
- model: options?.model,
283
- listenPort: options?.listenPort,
284
- });
285
- },
345
+ const sessionManager = await createLinxPiSessionManager({
346
+ cwd,
347
+ agentDir: LINX_AGENT_DIR,
348
+ session: argv.session,
349
+ last: Boolean(argv.continue || argv.last),
350
+ });
351
+ const restoreAutoFromHydration = Boolean(argv.session || argv.continue || argv.last);
352
+ const controlState = await resolvePiStartupControlState({
353
+ requestedAuto: typeof argv.auto === 'boolean' ? argv.auto : undefined,
354
+ hydrateFromPod: !argv.print && !startupLoginPrompt.shouldPrompt,
355
+ restoreAutoFromHydration,
356
+ sessionManager,
357
+ });
358
+ const autoEnabled = controlState.autoEnabled;
359
+ const symphonyEnabled = controlState.symphonyEnabled;
360
+ const adapter = createLinxRuntimeAdapter({
286
361
  async createRemoteCompletion(options) {
287
362
  const chatApi = await import('./lib/chat-api.js');
288
363
  return chatApi.createRemoteCompletionResult(options);
289
364
  },
290
- async listRemoteModels(session, runtimeUrl, apiKey) {
365
+ async listRemoteModels(authFetch, runtimeUrl, options) {
291
366
  const chatApi = await import('./lib/chat-api.js');
292
- return chatApi.listRemoteModels(session, runtimeUrl, apiKey, { fallback: false, timeoutMs: 5000 });
367
+ return chatApi.listRemoteModels(authFetch, runtimeUrl, options ?? { fallback: false, timeoutMs: 5000 });
293
368
  },
294
369
  }, {
295
- cwd: argv.cwd || process.cwd(),
370
+ cwd,
296
371
  model: argv.model,
297
- backend,
372
+ backend: 'cloud',
373
+ autoEnabled,
374
+ symphonyEnabled,
375
+ getPodDataSession: getDefaultPodDataSession,
298
376
  port: argv.port,
299
377
  providerConfig: {
300
378
  baseUrl: String(argv['runtime-url'] ?? 'https://api.undefineds.co/v1'),
@@ -302,42 +380,14 @@ async function runPiCommand(argv) {
302
380
  },
303
381
  });
304
382
  await adapter.start();
305
- const sessionManager = await createLinxPiSessionManager({
306
- cwd: adapter.cwd,
307
- agentDir: LINX_AGENT_DIR,
308
- session: argv.session,
309
- last: argv.last,
310
- });
311
383
  const runtime = await adapter.createRuntime({
312
384
  cwd: adapter.cwd,
313
385
  agentDir: LINX_AGENT_DIR,
314
386
  sessionManager,
315
387
  });
316
- const podMirror = new LinxPiPodMirror({
317
- cwd: adapter.cwd,
318
- sessionManager,
319
- onError(error) {
320
- if (process.env.LINX_DEBUG === '1') {
321
- const message = error instanceof Error ? error.stack || error.message : String(error);
322
- process.stderr.write(`[linx pod mirror] ${message}\n`);
323
- }
324
- },
325
- });
326
- const unsubscribePodMirror = runtime.session.subscribe((event) => {
327
- podMirror.handleEvent(event);
328
- });
329
- const interactive = bootstrapPiInteractiveMode(runtime);
330
- const bridge = runtime;
331
- const loginPromptReason = resolveLinxInteractiveLoginReason({
332
- startupDecision: startupLoginPrompt,
333
- runtimePromptOnStart: bridge.linxAuthBridge?.shouldPromptLoginOnStart,
334
- });
335
- if (loginPromptReason) {
336
- interactive.requestLogin?.(loginPromptReason);
337
- }
388
+ const prompt = (argv.prompt ?? []).join(' ').trim();
338
389
  try {
339
390
  if (argv.print) {
340
- const prompt = (argv.prompt ?? []).join(' ').trim();
341
391
  const exitCode = await runPrintMode(runtime, {
342
392
  mode: 'text',
343
393
  initialMessage: prompt || undefined,
@@ -347,94 +397,192 @@ async function runPiCommand(argv) {
347
397
  }
348
398
  return;
349
399
  }
350
- await interactive.run();
400
+ const podMirror = new LinxPiPodMirror({
401
+ cwd: adapter.cwd,
402
+ sessionManager,
403
+ autoEnabled,
404
+ symphonyEnabled,
405
+ checkpointStore: createFileSyncCheckpointStore({
406
+ dir: join(LINX_AGENT_DIR, 'sync', 'pi-pod-mirror', sessionManager.getSessionId()),
407
+ }),
408
+ onError(error) {
409
+ if (process.env.LINX_DEBUG === '1') {
410
+ const message = error instanceof Error ? error.stack || error.message : String(error);
411
+ process.stderr.write(`[linx pod mirror] ${message}\n`);
412
+ }
413
+ },
414
+ });
415
+ runtime.__linxPodMirror = podMirror;
416
+ const unsubscribePodMirror = runtime.session.subscribe((event) => {
417
+ podMirror.handleEvent(event);
418
+ });
419
+ const interactive = bootstrapLinxInteractiveMode(runtime, {
420
+ initialMessage: prompt || undefined,
421
+ restoredAuto: autoEnabled && restoreAutoFromHydration,
422
+ onAutoControlChange(enabled) {
423
+ void podMirror.syncAutoControlState(enabled);
424
+ },
425
+ onSymphonyControlChange(enabled) {
426
+ void podMirror.syncSymphonyControlState(enabled);
427
+ },
428
+ });
429
+ const bridge = runtime;
430
+ const loginPromptReason = resolveLinxInteractiveLoginReason({
431
+ startupDecision: startupLoginPrompt,
432
+ runtimePromptOnStart: bridge.linxAuthBridge?.shouldPromptLoginOnStart,
433
+ });
434
+ if (loginPromptReason) {
435
+ interactive.requestLogin?.(loginPromptReason);
436
+ }
437
+ try {
438
+ await interactive.run();
439
+ }
440
+ finally {
441
+ unsubscribePodMirror();
442
+ await podMirror.close().catch(() => undefined);
443
+ interactive.stop();
444
+ }
351
445
  }
352
446
  finally {
353
- unsubscribePodMirror();
354
- await podMirror.close().catch(() => undefined);
355
- interactive.stop();
356
447
  await adapter.close();
448
+ clearDefaultPodDataSession();
357
449
  }
358
450
  }
359
- async function runResumeCommand(argv) {
360
- const cwd = typeof argv.cwd === 'string' ? argv.cwd : process.cwd();
361
- const session = typeof argv.session === 'string' ? argv.session : undefined;
362
- const piSessions = await listLinxPiSessions(cwd, LINX_AGENT_DIR);
363
- const autoModeSessions = listArchivedAutoModeSessions();
364
- if (!session && !argv.last) {
365
- if (piSessions.length === 0 && autoModeSessions.length === 0) {
366
- process.stdout.write('No LinX sessions found.\n');
367
- return;
368
- }
369
- if (piSessions.length > 0) {
370
- process.stdout.write('LinX sessions:\n');
371
- process.stdout.write(`${piSessions.map((item) => ` ${formatLinxPiSessionSummary(item)}`).join('\n')}\n`);
372
- }
373
- if (autoModeSessions.length > 0) {
374
- process.stdout.write('Auto-mode sessions:\n');
375
- process.stdout.write(`${autoModeSessions.map((item) => ` ${formatAutoModeSessionSummary(item)}`).join('\n')}\n`);
376
- }
377
- return;
451
+ async function resolvePiStartupControlState(options) {
452
+ if (!options.hydrateFromPod) {
453
+ return {
454
+ autoEnabled: options.requestedAuto === true,
455
+ symphonyEnabled: false,
456
+ };
378
457
  }
379
- if (argv.last && !session) {
380
- const latestPi = piSessions[0];
381
- const latestAutoMode = autoModeSessions[0];
382
- const latestPiTime = latestPi?.modified.getTime() ?? 0;
383
- const latestAutoModeTime = latestAutoMode ? Date.parse(latestAutoMode.endedAt ?? latestAutoMode.startedAt) : 0;
384
- if (latestAutoMode && latestAutoModeTime > latestPiTime) {
385
- const exitCode = await resumeAutoModeSession(latestAutoMode, {
386
- cwd,
387
- model: argv.model,
388
- });
389
- if (exitCode !== 0) {
390
- process.exitCode = exitCode;
391
- }
392
- return;
393
- }
458
+ const session = await createLinxPodDataSession().catch(() => null);
459
+ if (!session) {
460
+ return {
461
+ autoEnabled: options.requestedAuto === true,
462
+ symphonyEnabled: false,
463
+ };
394
464
  }
395
- if (session) {
396
- try {
397
- await resolveLinxPiSession(session, cwd, undefined);
398
- await runPiCommand({
399
- cwd,
400
- model: typeof argv.model === 'string' ? argv.model : undefined,
401
- 'runtime-url': typeof argv['runtime-url'] === 'string' ? argv['runtime-url'] : undefined,
402
- session,
403
- last: false,
404
- });
405
- return;
406
- }
407
- catch {
408
- const autoModeSession = loadArchivedAutoModeSession(session);
409
- if (autoModeSession) {
410
- const exitCode = await resumeAutoModeSession(autoModeSession, {
411
- cwd,
412
- model: argv.model,
413
- });
414
- if (exitCode !== 0) {
415
- process.exitCode = exitCode;
465
+ try {
466
+ const db = drizzle(session.solidSession, {
467
+ logger: false,
468
+ disableInteropDiscovery: true,
469
+ podUrl: session.podUrl,
470
+ resourcePreparation: 'off',
471
+ schema: solidResources,
472
+ });
473
+ const hydration = await hydrateLinxPiControlState({
474
+ db,
475
+ sessionId: options.sessionManager.getSessionId(),
476
+ createdAt: getPiSessionCreatedAt(options.sessionManager),
477
+ onError(error) {
478
+ if (process.env.LINX_DEBUG === '1') {
479
+ const message = error instanceof Error ? error.stack || error.message : String(error);
480
+ process.stderr.write(`[linx control state] ${message}\n`);
416
481
  }
482
+ },
483
+ });
484
+ return {
485
+ ...deriveLinxPiStartupControlState({
486
+ requestedAuto: options.requestedAuto,
487
+ hydration,
488
+ restoreAutoFromHydration: options.restoreAutoFromHydration,
489
+ }),
490
+ };
491
+ }
492
+ finally {
493
+ await session.close().catch(() => undefined);
494
+ }
495
+ }
496
+ function getPiSessionCreatedAt(sessionManager) {
497
+ const entryDate = sessionManager.getEntries()
498
+ .map((entry) => toDate(entry.timestamp))
499
+ .find((date) => date instanceof Date);
500
+ return entryDate ?? parseTimestampFromUuidLikeId(sessionManager.getSessionId()) ?? new Date();
501
+ }
502
+ function parseTimestampFromUuidLikeId(id) {
503
+ const prefix = id.replace(/-/g, '').slice(0, 12);
504
+ if (!/^[\da-f]{12}$/i.test(prefix)) {
505
+ return null;
506
+ }
507
+ const millis = Number.parseInt(prefix, 16);
508
+ if (!Number.isFinite(millis) || millis <= 0) {
509
+ return null;
510
+ }
511
+ const date = new Date(millis);
512
+ return Number.isNaN(date.getTime()) ? null : date;
513
+ }
514
+ function toDate(value) {
515
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
516
+ return value;
517
+ }
518
+ if (typeof value === 'number' || typeof value === 'string') {
519
+ const date = new Date(value);
520
+ return Number.isNaN(date.getTime()) ? null : date;
521
+ }
522
+ return null;
523
+ }
524
+ function cwdFromArg(cwd) {
525
+ return typeof cwd === 'string' && cwd.trim() ? cwd : process.cwd();
526
+ }
527
+ async function selectLinxSession(cwd) {
528
+ return new Promise((resolve) => {
529
+ const ui = new TUI(new ProcessTerminal());
530
+ let resolved = false;
531
+ const finish = (sessionPath) => {
532
+ if (resolved) {
417
533
  return;
418
534
  }
419
- }
535
+ resolved = true;
536
+ ui.stop();
537
+ resolve(sessionPath);
538
+ };
539
+ const loadSessions = () => listLinxPiSessions(cwd, LINX_AGENT_DIR, { podSessionSource: null });
540
+ const selector = new SessionSelectorComponent(loadSessions, loadSessions, (sessionPath) => finish(sessionPath), () => finish(null), () => {
541
+ ui.stop();
542
+ process.exit(0);
543
+ }, () => ui.requestRender(), { showRenameHint: false });
544
+ ui.addChild(selector);
545
+ ui.setFocus(selector.getSessionList());
546
+ ui.start();
547
+ });
548
+ }
549
+ async function runPiSyncStatusCommand() {
550
+ const sessions = await listPendingPiPodMirrorSync(LINX_AGENT_DIR);
551
+ if (sessions.length === 0) {
552
+ process.stdout.write('No pending LinX Pod sync sessions.\n');
553
+ return;
420
554
  }
421
- await runPiCommand({
422
- cwd,
423
- model: typeof argv.model === 'string' ? argv.model : undefined,
424
- 'runtime-url': typeof argv['runtime-url'] === 'string' ? argv['runtime-url'] : undefined,
425
- session,
426
- last: Boolean(argv.last) || !session,
555
+ process.stdout.write(`${sessions.map((session) => {
556
+ const failed = session.checkpoints.filter((checkpoint) => checkpoint.status === 'failed').length;
557
+ const partial = session.checkpoints.filter((checkpoint) => checkpoint.status === 'partial').length;
558
+ const latest = session.checkpoints.at(-1)?.completedAt ?? 'unknown';
559
+ return `${session.sessionId} · failed=${failed} partial=${partial} latest=${latest}`;
560
+ }).join('\n')}\n`);
561
+ }
562
+ async function runPiSyncRetryCommand(options) {
563
+ const result = await retryPendingPiPodMirrorSync({
564
+ cwd: options.cwd,
565
+ agentDir: LINX_AGENT_DIR,
566
+ sessionId: options.sessionId,
427
567
  });
568
+ if (!result.attempted) {
569
+ process.stdout.write(`LinX Pod sync skipped: ${options.sessionId}\n`);
570
+ return;
571
+ }
572
+ const status = result.results.map((item) => item.status).join(', ') || 'none';
573
+ process.stdout.write(status === 'none'
574
+ ? `LinX Pod sync has no replayable local projections: ${options.sessionId}\n`
575
+ : `Retried LinX Pod sync: ${options.sessionId} (${status})\n`);
428
576
  }
429
577
  function buildPiCommand(command) {
430
578
  const configured = buildAutoModeOptions(command)
431
579
  .option('cwd', {
432
580
  type: 'string',
433
- describe: 'Workspace path for the Pi session',
581
+ describe: 'Workspace path for the LinX session',
434
582
  })
435
583
  .option('model', {
436
584
  type: 'string',
437
- describe: 'Model id to expose through the Pi runtime adapter; defaults to the last LinX selection',
585
+ describe: 'Model id to expose through the LinX runtime adapter; defaults to the last LinX selection',
438
586
  })
439
587
  .option('runtime-url', {
440
588
  type: 'string',
@@ -448,12 +596,32 @@ function buildPiCommand(command) {
448
596
  })
449
597
  .option('session', {
450
598
  type: 'string',
451
- describe: 'Resume a specific LinX/Pi session id or JSONL file',
599
+ describe: 'Resume a specific LinX session id or JSONL file',
600
+ })
601
+ .option('continue', {
602
+ alias: 'c',
603
+ type: 'boolean',
604
+ default: false,
605
+ describe: 'Continue previous LinX session',
606
+ })
607
+ .option('resume', {
608
+ alias: 'r',
609
+ type: 'boolean',
610
+ default: false,
611
+ describe: 'Select a LinX session to resume',
452
612
  })
453
613
  .option('last', {
454
614
  type: 'boolean',
455
615
  default: false,
456
- describe: 'Continue the most recent local LinX/Pi session for this workspace',
616
+ hidden: true,
617
+ })
618
+ .option('pi-sync-status', {
619
+ type: 'boolean',
620
+ hidden: true,
621
+ })
622
+ .option('pi-sync-retry', {
623
+ type: 'string',
624
+ hidden: true,
457
625
  })
458
626
  .positional('prompt', {
459
627
  array: true,
@@ -464,7 +632,7 @@ function buildPiCommand(command) {
464
632
  }
465
633
  const defaultPiCommand = {
466
634
  command: '$0 [prompt..]',
467
- describe: 'Run LinX, or control an external agent backend with --backend',
635
+ describe: 'Run LinX with the selected runtime backend',
468
636
  builder: buildPiCommand,
469
637
  handler: runPiCommand,
470
638
  };
@@ -489,6 +657,23 @@ const execCommand = {
489
657
  await runPiCommand({ ...argv, print: true });
490
658
  },
491
659
  };
660
+ const retiredSymphonyCommand = {
661
+ command: 'symphony [args..]',
662
+ describe: false,
663
+ builder(command) {
664
+ return command
665
+ .help(false)
666
+ .version(false)
667
+ .positional('args', {
668
+ array: true,
669
+ type: 'string',
670
+ describe: 'Retired Symphony CLI arguments',
671
+ });
672
+ },
673
+ handler() {
674
+ throw new Error('`linx symphony` is not a product command. Enter the TUI, run `/symphony on`, then send the objective as normal chat to Secretary.');
675
+ },
676
+ };
492
677
  const cli = yargs(hideBin(process.argv))
493
678
  .scriptName('linx')
494
679
  .version(readPackageVersion())
@@ -499,7 +684,7 @@ const cli = yargs(hideBin(process.argv))
499
684
  .command(logoutCommand)
500
685
  .command(whoamiCommand)
501
686
  .command(aiCommand)
502
- .command(symphonyCommand)
687
+ .command(retiredSymphonyCommand)
503
688
  .command('install [source]', 'Install a LinX package or extension', (command) => command
504
689
  .positional('source', { type: 'string', describe: 'Package source to install' })
505
690
  .option('local', { alias: 'l', type: 'boolean', default: false, describe: 'Install project-locally (.pi/settings.json)' }), async (argv) => {
@@ -538,6 +723,7 @@ const cli = yargs(hideBin(process.argv))
538
723
  continueMode: argv.continue,
539
724
  explicitThreadId: argv.thread,
540
725
  workspace: argv.workspace,
726
+ bestEffort: Boolean(argv.prompt?.join(' ').trim()),
541
727
  });
542
728
  const prompt = argv.prompt?.join(' ').trim() || undefined;
543
729
  if (prompt) {
@@ -552,7 +738,7 @@ const cli = yargs(hideBin(process.argv))
552
738
  const ctx = await resolveRuntimeAuthContext(argv.url);
553
739
  let models;
554
740
  try {
555
- models = await ctx.runtime.listRemoteModels(ctx.session, ctx.runtimeUrl, ctx.apiKey, { fallback: false });
741
+ models = await ctx.runtime.listRemoteModels(ctx.authFetch, ctx.runtimeUrl, { fallback: false });
556
742
  }
557
743
  catch (error) {
558
744
  const message = error instanceof Error ? error.message : String(error);
@@ -568,25 +754,11 @@ const cli = yargs(hideBin(process.argv))
568
754
  }
569
755
  }
570
756
  await ctx.podSession.close();
571
- })
572
- .command('resume [session]', 'Resume a previous LinX or auto-mode session', (command) => command
573
- .positional('session', { type: 'string', describe: 'Session id/prefix or JSONL file to resume' })
574
- .option('last', { type: 'boolean', default: false, describe: 'Resume the most recent LinX or auto-mode session' })
575
- .option('cwd', { type: 'string', describe: 'Workspace path for the resumed session' })
576
- .option('model', { type: 'string', describe: 'Model id to expose through the Pi runtime adapter' })
577
- .option('runtime-url', { type: 'string', default: 'https://api.undefineds.co/v1', describe: 'Cloud runtime API base URL' }), async (argv) => {
578
- await runResumeCommand({
579
- cwd: typeof argv.cwd === 'string' ? argv.cwd : undefined,
580
- model: typeof argv.model === 'string' ? argv.model : undefined,
581
- 'runtime-url': typeof argv['runtime-url'] === 'string' ? argv['runtime-url'] : undefined,
582
- session: typeof argv.session === 'string' ? argv.session : undefined,
583
- last: Boolean(argv.last),
584
- });
585
757
  })
586
758
  .command('fork [thread]', 'Fork a previous interactive session', (command) => command
587
759
  .positional('thread', { type: 'string', describe: 'Thread ID to fork' })
588
760
  .option('last', { type: 'boolean', default: false, describe: 'Fork the most recent thread' }), () => {
589
- throw new Error('Fork is not implemented yet for LinX Pod-backed Pi sessions.');
761
+ throw new Error('Fork is not implemented yet for LinX Pod-backed sessions.');
590
762
  })
591
763
  .command(hiddenPiAliasCommand)
592
764
  .command(hiddenPiFrontendAliasCommand)
@@ -628,18 +800,18 @@ const cli = yargs(hideBin(process.argv))
628
800
  .help()
629
801
  .fail((message, error, yargsInstance) => {
630
802
  if (error) {
631
- console.error(error instanceof Error ? error.message : String(error));
803
+ console.error(formatLinxCliErrorMessage(error));
632
804
  process.exit(1);
633
805
  }
634
806
  if (message) {
635
- console.error(message);
807
+ console.error(formatLinxCliErrorMessage(message));
636
808
  process.exit(1);
637
809
  }
638
810
  yargsInstance.showHelp();
639
811
  process.exit(1);
640
812
  });
641
813
  process.on('unhandledRejection', (error) => {
642
- console.error(error instanceof Error ? error.message : String(error));
814
+ console.error(formatLinxCliErrorMessage(error));
643
815
  process.exit(1);
644
816
  });
645
817
  cli.parse();