@undefineds.co/linx 0.3.5 → 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
@@ -40,6 +40,87 @@ export function autoModeApprovalRequestMessage(request) {
40
40
  export const DEFAULT_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS = 5_000;
41
41
  export const MIN_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS = 5_000;
42
42
  export const MAX_AUTO_MODE_SECRETARY_REACTION_WINDOW_MS = 60_000;
43
+ export function resolveAutoModeCommandRoute(input) {
44
+ const text = normalizeAutoModeCommandText(input);
45
+ if (!text) {
46
+ return null;
47
+ }
48
+ const autoRoute = resolveAutoModeAutoControlCommand(text);
49
+ if (autoRoute) {
50
+ return autoRoute;
51
+ }
52
+ return resolveAutoModePeerCommand(text);
53
+ }
54
+ export function resolveAutoModePeerCommand(input) {
55
+ const text = normalizeAutoModeCommandText(input);
56
+ if (text !== '/goal' && !text.startsWith('/goal ')) {
57
+ return null;
58
+ }
59
+ const argument = text.slice('/goal'.length).trim();
60
+ const goalMode = inferSecretaryGoalModeFromGoalPeerCommand(argument);
61
+ return {
62
+ kind: 'peer-command',
63
+ targetRole: 'peer-command',
64
+ command: 'goal',
65
+ text,
66
+ ...(goalMode === undefined ? {} : { secretaryBehavior: { goalMode } }),
67
+ };
68
+ }
69
+ function resolveAutoModeAutoControlCommand(text) {
70
+ if (text === '/auto' || text === '/auto status') {
71
+ return {
72
+ kind: 'control-command',
73
+ targetRole: 'control-command',
74
+ command: 'auto',
75
+ text,
76
+ auto: { action: 'status' },
77
+ };
78
+ }
79
+ if (text === '/auto on') {
80
+ return {
81
+ kind: 'control-command',
82
+ targetRole: 'control-command',
83
+ command: 'auto',
84
+ text,
85
+ auto: { action: 'set', enabled: true },
86
+ };
87
+ }
88
+ if (text === '/auto off') {
89
+ return {
90
+ kind: 'control-command',
91
+ targetRole: 'control-command',
92
+ command: 'auto',
93
+ text,
94
+ auto: { action: 'set', enabled: false },
95
+ };
96
+ }
97
+ if (text.startsWith('/auto ')) {
98
+ const initialInput = text.slice('/auto'.length).trim();
99
+ if (initialInput && initialInput !== 'on' && initialInput !== 'off' && initialInput !== 'status') {
100
+ return {
101
+ kind: 'control-command',
102
+ targetRole: 'control-command',
103
+ command: 'auto',
104
+ text,
105
+ auto: { action: 'set', enabled: true, initialInput },
106
+ };
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+ function normalizeAutoModeCommandText(input) {
112
+ return typeof input === 'string' ? input.trim() : '';
113
+ }
114
+ function inferSecretaryGoalModeFromGoalPeerCommand(argument) {
115
+ if (!argument || argument === 'status') {
116
+ return undefined;
117
+ }
118
+ const firstToken = argument.split(/\s+/, 1)[0]?.toLowerCase();
119
+ if (firstToken === 'pause' || firstToken === 'close' || firstToken === 'cancel') {
120
+ return false;
121
+ }
122
+ return true;
123
+ }
43
124
  export const AUTO_MODE_HOME_DIRNAME = 'auto-mode';
44
125
  export const AUTO_MODE_SESSIONS_DIRNAME = 'sessions';
45
126
  export const AUTO_MODE_SESSION_FILE_NAME = 'session.json';
@@ -208,6 +289,119 @@ export function normalizeAutoModeApprovalOptions(value) {
208
289
  })
209
290
  .filter((option) => option !== null);
210
291
  }
292
+ export function parseAutoModeApprovalOptions(value) {
293
+ if (Array.isArray(value)) {
294
+ return normalizeAutoModeApprovalOptions(value);
295
+ }
296
+ if (typeof value !== 'string' || !value.trim()) {
297
+ return [];
298
+ }
299
+ try {
300
+ const parsed = JSON.parse(value);
301
+ return normalizeAutoModeApprovalOptions(parsed);
302
+ }
303
+ catch {
304
+ return [];
305
+ }
306
+ }
307
+ export function encodeAutoModeApprovalOptions(options) {
308
+ return options && options.length > 0 ? JSON.stringify(options) : undefined;
309
+ }
310
+ export function buildAutoModeApprovalDecisionReason(options, note) {
311
+ const input = typeof options === 'string' ? { decision: options, note } : options;
312
+ const selectedOptionId = input.selectedOption?.optionId ?? input.selectedOptionId;
313
+ const selectedLabel = input.selectedOption?.label ?? input.selectedLabel;
314
+ return JSON.stringify({
315
+ ...(input.source?.trim() ? { source: input.source.trim() } : {}),
316
+ ...(input.decision ? { decision: input.decision } : {}),
317
+ ...(input.note?.trim() ? { note: input.note.trim() } : {}),
318
+ ...(selectedOptionId?.trim() ? { selectedOptionId: selectedOptionId.trim() } : {}),
319
+ ...(selectedLabel?.trim() ? { selectedLabel: selectedLabel.trim() } : {}),
320
+ });
321
+ }
322
+ export function parseAutoModeApprovalDecisionReason(value) {
323
+ if (typeof value !== 'string' || !value.trim()) {
324
+ return null;
325
+ }
326
+ try {
327
+ const parsed = JSON.parse(value);
328
+ const record = recordFromUnknown(parsed);
329
+ if (!record) {
330
+ return null;
331
+ }
332
+ const decision = normalizeAutoModeApprovalDecision(record.decision);
333
+ const nested = parseAutoModeApprovalDecisionReason(record.note);
334
+ const source = stringFromUnknown(record.source);
335
+ const note = stringFromUnknown(record.note);
336
+ const selectedOptionId = stringFromUnknown(record.selectedOptionId) ?? nested?.selectedOptionId;
337
+ const selectedLabel = stringFromUnknown(record.selectedLabel) ?? nested?.selectedLabel;
338
+ if (!source && !decision && !note && !selectedOptionId && !selectedLabel) {
339
+ return null;
340
+ }
341
+ return {
342
+ ...(source ? { source } : {}),
343
+ ...(decision ? { decision } : {}),
344
+ ...(note ? { note } : {}),
345
+ ...(selectedOptionId ? { selectedOptionId } : {}),
346
+ ...(selectedLabel ? { selectedLabel } : {}),
347
+ };
348
+ }
349
+ catch {
350
+ return null;
351
+ }
352
+ }
353
+ export function autoModeApprovalDecisionForOption(option) {
354
+ if (option.kind === 'allow_for_session') {
355
+ return 'accept_for_session';
356
+ }
357
+ if (option.kind === 'allow_always') {
358
+ return 'accept_always';
359
+ }
360
+ if (option.kind === 'reject_once' || option.kind === 'reject_always') {
361
+ return 'decline';
362
+ }
363
+ if (option.kind === 'cancel') {
364
+ return 'cancel';
365
+ }
366
+ return 'accept';
367
+ }
368
+ export function autoModeApprovalDecisionForStoredApproval(input) {
369
+ if (input.status === 'pending') {
370
+ return null;
371
+ }
372
+ const parsed = parseAutoModeApprovalDecisionReason(input.reason);
373
+ if (input.status === 'rejected') {
374
+ return parsed?.decision === 'cancel' ? 'cancel' : 'decline';
375
+ }
376
+ const option = parsed?.selectedOptionId
377
+ ? parseAutoModeApprovalOptions(input.approvalOptions).find((entry) => entry.optionId === parsed.selectedOptionId)
378
+ : null;
379
+ if (option) {
380
+ return autoModeApprovalDecisionForOption(option);
381
+ }
382
+ if (parsed?.decision === 'accept_for_session') {
383
+ return 'accept_for_session';
384
+ }
385
+ if (parsed?.decision === 'accept_always') {
386
+ return 'accept_always';
387
+ }
388
+ if (parsed?.decision === 'decline' || parsed?.decision === 'cancel') {
389
+ return parsed.decision;
390
+ }
391
+ return input.status === 'approved' ? 'accept' : null;
392
+ }
393
+ export function shouldMaterializeAutoModeGrant(decision) {
394
+ return decision === 'accept_for_session' || decision === 'accept_always';
395
+ }
396
+ export function autoModeGrantScopeForDecision(decision) {
397
+ if (decision === 'accept_for_session') {
398
+ return 'session';
399
+ }
400
+ if (decision === 'accept_always') {
401
+ return 'durable';
402
+ }
403
+ return null;
404
+ }
211
405
  function normalizeDurationMs(value, unit) {
212
406
  const numeric = typeof value === 'number'
213
407
  ? value
@@ -274,10 +468,12 @@ function selectAcpPermissionOption(options, decision) {
274
468
  return undefined;
275
469
  }
276
470
  const preferredKinds = decision === 'accept'
277
- ? ['allow_once', 'allow_always']
471
+ ? ['allow_once', 'allow_for_session', 'allow_always']
278
472
  : decision === 'accept_for_session'
279
- ? ['allow_always', 'allow_once']
280
- : ['reject_once', 'reject_always'];
473
+ ? ['allow_for_session', 'allow_always', 'allow_once']
474
+ : decision === 'accept_always'
475
+ ? ['allow_always', 'allow_for_session', 'allow_once']
476
+ : ['reject_once', 'reject_always'];
281
477
  for (const kind of preferredKinds) {
282
478
  const match = options.find((option) => option.kind === kind && typeof option.optionId === 'string');
283
479
  if (match && typeof match.optionId === 'string') {
@@ -307,11 +503,11 @@ export function createAutoModeSessionId(options = {}) {
307
503
  const stamp = now.toISOString().replace(/[:.]/g, '-');
308
504
  return `auto_${stamp}_${randomId}`;
309
505
  }
310
- export function normalizeAutoModeCredentialSource(_source) {
311
- return 'cloud';
506
+ export function normalizeAutoModeCredentialSource(source) {
507
+ return source === 'local' ? 'local' : 'cloud';
312
508
  }
313
- export function shouldAttemptCloudCredentialProbe(_requestedSource, _localAuthStatus) {
314
- return true;
509
+ export function shouldAttemptCloudCredentialProbe(requestedSource, _localAuthStatus) {
510
+ return normalizeAutoModeCredentialSource(requestedSource) === 'cloud';
315
511
  }
316
512
  export function formatAutoModeAutoFallbackMessage(localMessage, detail) {
317
513
  return `${localMessage} Cloud credential fallback unavailable: ${detail}`;
@@ -319,6 +515,20 @@ export function formatAutoModeAutoFallbackMessage(localMessage, detail) {
319
515
  export function resolveAutoModeCredentialSourceResolution(input) {
320
516
  const requestedSource = normalizeAutoModeCredentialSource(input.requestedSource);
321
517
  const cloudCredentialProbe = input.cloudCredentialProbe;
518
+ if (requestedSource === 'local') {
519
+ if (input.localAuthStatus.state === 'unauthenticated') {
520
+ return {
521
+ requestedSource,
522
+ authStatus: input.localAuthStatus,
523
+ error: input.localAuthStatus.message ?? input.defaultLocalMessage ?? 'Local backend authentication unavailable.',
524
+ };
525
+ }
526
+ return {
527
+ requestedSource,
528
+ resolvedSource: 'local',
529
+ authStatus: input.localAuthStatus,
530
+ };
531
+ }
322
532
  if (cloudCredentialProbe?.status === 'available') {
323
533
  return {
324
534
  requestedSource,
@@ -333,27 +543,22 @@ export function resolveAutoModeCredentialSourceResolution(input) {
333
543
  };
334
544
  }
335
545
  export function resolveAutoModeAutoApprovalDecision(input) {
336
- const { mode, request } = input;
546
+ const { request } = input;
547
+ const autoEnabled = isAutoModeSecretaryControlEnabled(input);
337
548
  if (request.kind === 'command-approval') {
338
- if (mode === 'auto') {
549
+ if (autoEnabled) {
339
550
  return 'accept_for_session';
340
551
  }
341
- if (mode === 'smart' && isTrustedAutoModeCommand(request.command)) {
342
- return 'accept';
343
- }
344
552
  return null;
345
553
  }
346
554
  if (request.kind === 'file-change-approval') {
347
- if (mode === 'auto') {
555
+ if (autoEnabled) {
348
556
  return 'accept_for_session';
349
557
  }
350
- if (mode === 'smart') {
351
- return 'accept';
352
- }
353
558
  return null;
354
559
  }
355
560
  if (request.kind === 'permissions-approval') {
356
- if (mode === 'auto') {
561
+ if (autoEnabled) {
357
562
  return 'accept_for_session';
358
563
  }
359
564
  return null;
@@ -361,14 +566,15 @@ export function resolveAutoModeAutoApprovalDecision(input) {
361
566
  return null;
362
567
  }
363
568
  export function createFallbackAutoModeSecretaryRecommendation(input) {
364
- if (input.mode === 'manual' || input.request.kind === 'user-input') {
569
+ if (!isAutoModeSecretaryControlEnabled(input) || input.request.kind === 'user-input') {
365
570
  return null;
366
571
  }
367
572
  const decision = resolveAutoModeAutoApprovalDecision({
368
573
  mode: input.mode,
574
+ autoEnabled: input.autoEnabled,
369
575
  request: input.request,
370
576
  });
371
- const secretaryDecision = decision === 'accept_for_session' ? 'accept' : decision;
577
+ const secretaryDecision = normalizeAutoModeDecisionForSecretary(decision);
372
578
  if (!secretaryDecision) {
373
579
  return null;
374
580
  }
@@ -376,7 +582,7 @@ export function createFallbackAutoModeSecretaryRecommendation(input) {
376
582
  kind: input.request.kind,
377
583
  canAutoDecide: true,
378
584
  decision: secretaryDecision,
379
- confidence: input.mode === 'auto' ? 0.7 : 0.6,
585
+ confidence: 0.7,
380
586
  reason: 'Matched local fallback policy while AI secretary was unavailable.',
381
587
  reactionWindowMs: 0,
382
588
  source: 'fallback',
@@ -444,6 +650,9 @@ export function autoModeApprovalDecisionLabel(decision) {
444
650
  if (decision === 'accept_for_session') {
445
651
  return 'Grant';
446
652
  }
653
+ if (decision === 'accept_always') {
654
+ return 'Always allow';
655
+ }
447
656
  if (decision === 'decline') {
448
657
  return 'Deny';
449
658
  }
@@ -527,7 +736,7 @@ function normalizeSecretaryApprovalDecision(value) {
527
736
  return undefined;
528
737
  }
529
738
  const normalized = value.trim().toLowerCase().replace(/-/g, '_');
530
- if (['accept', 'allow', 'allow_once', 'approve', 'yes', 'accept_for_session', 'allow_always', 'grant', 'session', 'approve_for_session'].includes(normalized)) {
739
+ if (['accept', 'allow', 'allow_once', 'approve', 'yes', 'accept_for_session', 'accept_always', 'allow_for_session', 'allow_always', 'grant', 'session', 'approve_for_session'].includes(normalized)) {
531
740
  return 'accept';
532
741
  }
533
742
  if (['decline', 'deny', 'reject', 'reject_once', 'reject_always', 'no'].includes(normalized)) {
@@ -538,6 +747,37 @@ function normalizeSecretaryApprovalDecision(value) {
538
747
  }
539
748
  return undefined;
540
749
  }
750
+ function normalizeAutoModeDecisionForSecretary(decision) {
751
+ if (decision === 'accept' || decision === 'accept_for_session' || decision === 'accept_always') {
752
+ return 'accept';
753
+ }
754
+ if (decision === 'decline' || decision === 'cancel') {
755
+ return decision;
756
+ }
757
+ return undefined;
758
+ }
759
+ function normalizeAutoModeApprovalDecision(value) {
760
+ if (typeof value !== 'string') {
761
+ return undefined;
762
+ }
763
+ const normalized = value.trim().toLowerCase().replace(/-/g, '_');
764
+ if (['accept', 'allow', 'allow_once', 'approve', 'yes'].includes(normalized)) {
765
+ return 'accept';
766
+ }
767
+ if (['accept_for_session', 'allow_for_session', 'session', 'approve_for_session'].includes(normalized)) {
768
+ return 'accept_for_session';
769
+ }
770
+ if (['accept_always', 'allow_always', 'grant', 'always', 'approve_always'].includes(normalized)) {
771
+ return 'accept_always';
772
+ }
773
+ if (['decline', 'deny', 'reject', 'reject_once', 'reject_always', 'no'].includes(normalized)) {
774
+ return 'decline';
775
+ }
776
+ if (['cancel', 'abort'].includes(normalized)) {
777
+ return 'cancel';
778
+ }
779
+ return undefined;
780
+ }
541
781
  function parseJsonObjectFromText(text) {
542
782
  const trimmed = text.trim();
543
783
  if (!trimmed) {
@@ -630,6 +870,9 @@ function normalizeAutoModeAuthText(value, depth = 0) {
630
870
  ]);
631
871
  }
632
872
  export function getAutoModeAuthLoginCommand(backend) {
873
+ if (backend === 'linx') {
874
+ return null;
875
+ }
633
876
  if (backend === 'claude') {
634
877
  return 'claude auth login';
635
878
  }
@@ -640,11 +883,18 @@ export function getAutoModeAuthLoginCommand(backend) {
640
883
  }
641
884
  export function formatAutoModeBackendAuthMessage(backend, detail) {
642
885
  const command = getAutoModeAuthLoginCommand(backend);
643
- const label = backend === 'claude'
644
- ? 'Claude Code'
645
- : backend === 'codebuddy'
646
- ? 'CodeBuddy Code'
647
- : 'Codex';
886
+ const label = backend === 'linx'
887
+ ? 'LinX'
888
+ : backend === 'claude'
889
+ ? 'Claude Code'
890
+ : backend === 'codebuddy'
891
+ ? 'CodeBuddy Code'
892
+ : 'Codex';
893
+ if (backend === 'linx') {
894
+ return detail
895
+ ? `${label} backend is unavailable. Native message: ${detail}`
896
+ : `${label} backend is unavailable.`;
897
+ }
648
898
  if (backend === 'codebuddy') {
649
899
  return detail
650
900
  ? `${label} is not authenticated. Open \`codebuddy\` and complete login first. Native message: ${detail}`
@@ -821,12 +1071,13 @@ export function normalizeCodexAppServerInteractionRequest(message) {
821
1071
  return null;
822
1072
  }
823
1073
  export function resolveAutoModeInteractionAutoResponse(input) {
824
- const { mode, request } = input;
1074
+ const { request } = input;
825
1075
  if (request.kind === 'user-input' || request.kind === 'codex-approval') {
826
1076
  return null;
827
1077
  }
828
1078
  const decision = resolveAutoModeAutoApprovalDecision({
829
- mode,
1079
+ mode: input.mode,
1080
+ autoEnabled: input.autoEnabled,
830
1081
  request,
831
1082
  });
832
1083
  if (!decision) {
@@ -834,12 +1085,17 @@ export function resolveAutoModeInteractionAutoResponse(input) {
834
1085
  }
835
1086
  return buildCodexApprovalResponse(request, decision);
836
1087
  }
1088
+ function isAutoModeSecretaryControlEnabled(input) {
1089
+ return typeof input.autoEnabled === 'boolean'
1090
+ ? input.autoEnabled
1091
+ : input.mode === 'auto';
1092
+ }
837
1093
  export function buildCodexApprovalResponse(request, decision) {
838
1094
  if (request.kind === 'permissions-approval') {
839
1095
  if (decision === 'accept') {
840
1096
  return { permissions: request.permissions, scope: 'turn' };
841
1097
  }
842
- if (decision === 'accept_for_session') {
1098
+ if (decision === 'accept_for_session' || decision === 'accept_always') {
843
1099
  return { permissions: request.permissions, scope: 'session' };
844
1100
  }
845
1101
  return { permissions: {}, scope: 'turn' };
@@ -848,7 +1104,7 @@ export function buildCodexApprovalResponse(request, decision) {
848
1104
  if (decision === 'accept') {
849
1105
  return { decision: 'approved' };
850
1106
  }
851
- if (decision === 'accept_for_session') {
1107
+ if (decision === 'accept_for_session' || decision === 'accept_always') {
852
1108
  return { decision: 'approved_for_session' };
853
1109
  }
854
1110
  if (decision === 'cancel') {
@@ -859,7 +1115,7 @@ export function buildCodexApprovalResponse(request, decision) {
859
1115
  if (decision === 'accept') {
860
1116
  return { decision: 'accept' };
861
1117
  }
862
- if (decision === 'accept_for_session') {
1118
+ if (decision === 'accept_for_session' || decision === 'accept_always') {
863
1119
  return { decision: 'acceptForSession' };
864
1120
  }
865
1121
  if (decision === 'cancel') {
@@ -1380,6 +1636,7 @@ export function buildAutoModeThreadMetadata(record) {
1380
1636
  runtime: record.runtime,
1381
1637
  transport: record.transport,
1382
1638
  mode: record.mode,
1639
+ ...(record.autoEnabled !== undefined ? { autoEnabled: record.autoEnabled } : {}),
1383
1640
  ...(record.goalMode !== undefined ? { goalMode: record.goalMode } : {}),
1384
1641
  cwd: record.cwd,
1385
1642
  model: record.model,
@@ -0,0 +1,28 @@
1
+ export type LinxSessionControlSurface = 'cli' | 'app' | 'desktop' | 'runtime';
2
+ export interface LinxSessionControlState {
3
+ autoEnabled?: boolean;
4
+ symphonyEnabled?: boolean;
5
+ updatedAt: string;
6
+ updatedBy?: LinxSessionControlSurface | string;
7
+ }
8
+ export interface LinxSessionControlMetadata {
9
+ controlPlane: {
10
+ linxSession: LinxSessionControlState;
11
+ };
12
+ }
13
+ export declare function buildLinxSessionControlState(input: {
14
+ autoEnabled?: boolean;
15
+ symphonyEnabled?: boolean;
16
+ updatedAt?: Date | string;
17
+ updatedBy?: LinxSessionControlSurface | string;
18
+ }): LinxSessionControlState;
19
+ export declare function buildLinxSessionControlMetadata(input: {
20
+ autoEnabled?: boolean;
21
+ symphonyEnabled?: boolean;
22
+ updatedAt?: Date | string;
23
+ updatedBy?: LinxSessionControlSurface | string;
24
+ }): LinxSessionControlMetadata;
25
+ export declare function mergeLinxSessionControlMetadata(metadata: Record<string, unknown> | null | undefined, state: LinxSessionControlState): Record<string, unknown>;
26
+ export declare function readLinxSessionControlMetadata(metadata: Record<string, unknown> | null | undefined): LinxSessionControlState | null;
27
+ export declare function resolveLinxSessionAutoEnabled(metadata: Record<string, unknown> | null | undefined): boolean | null;
28
+ export declare function resolveLinxSessionSymphonyEnabled(metadata: Record<string, unknown> | null | undefined): boolean | null;
@@ -0,0 +1,79 @@
1
+ export function buildLinxSessionControlState(input) {
2
+ return {
3
+ ...(input.autoEnabled !== undefined ? { autoEnabled: input.autoEnabled } : {}),
4
+ ...(input.symphonyEnabled !== undefined ? { symphonyEnabled: input.symphonyEnabled } : {}),
5
+ updatedAt: toControlStateIsoString(input.updatedAt ?? new Date()),
6
+ ...(input.updatedBy ? { updatedBy: input.updatedBy } : {}),
7
+ };
8
+ }
9
+ export function buildLinxSessionControlMetadata(input) {
10
+ return {
11
+ controlPlane: {
12
+ linxSession: buildLinxSessionControlState(input),
13
+ },
14
+ };
15
+ }
16
+ export function mergeLinxSessionControlMetadata(metadata, state) {
17
+ const existing = isRecord(metadata) ? metadata : {};
18
+ const existingControlPlane = isRecord(existing.controlPlane) ? existing.controlPlane : {};
19
+ const existingSession = isRecord(existingControlPlane.linxSession) ? existingControlPlane.linxSession : {};
20
+ return {
21
+ ...existing,
22
+ controlPlane: {
23
+ ...existingControlPlane,
24
+ linxSession: {
25
+ ...existingSession,
26
+ ...state,
27
+ },
28
+ },
29
+ };
30
+ }
31
+ export function readLinxSessionControlMetadata(metadata) {
32
+ if (!isRecord(metadata)) {
33
+ return null;
34
+ }
35
+ const controlPlane = isRecord(metadata.controlPlane) ? metadata.controlPlane : null;
36
+ const session = controlPlane && isRecord(controlPlane.linxSession) ? controlPlane.linxSession : null;
37
+ if (!session) {
38
+ return null;
39
+ }
40
+ const updatedAt = typeof session.updatedAt === 'string'
41
+ ? session.updatedAt
42
+ : undefined;
43
+ const autoEnabled = typeof session.autoEnabled === 'boolean'
44
+ ? session.autoEnabled
45
+ : undefined;
46
+ const symphonyEnabled = typeof session.symphonyEnabled === 'boolean'
47
+ ? session.symphonyEnabled
48
+ : undefined;
49
+ const updatedBy = typeof session.updatedBy === 'string'
50
+ ? session.updatedBy
51
+ : undefined;
52
+ if (autoEnabled === undefined && symphonyEnabled === undefined && !updatedAt && !updatedBy) {
53
+ return null;
54
+ }
55
+ return {
56
+ ...(autoEnabled !== undefined ? { autoEnabled } : {}),
57
+ ...(symphonyEnabled !== undefined ? { symphonyEnabled } : {}),
58
+ updatedAt: updatedAt ? toControlStateIsoString(updatedAt) : new Date(0).toISOString(),
59
+ ...(updatedBy ? { updatedBy } : {}),
60
+ };
61
+ }
62
+ export function resolveLinxSessionAutoEnabled(metadata) {
63
+ const state = readLinxSessionControlMetadata(metadata);
64
+ return typeof state?.autoEnabled === 'boolean' ? state.autoEnabled : null;
65
+ }
66
+ export function resolveLinxSessionSymphonyEnabled(metadata) {
67
+ const state = readLinxSessionControlMetadata(metadata);
68
+ return typeof state?.symphonyEnabled === 'boolean' ? state.symphonyEnabled : null;
69
+ }
70
+ function toControlStateIsoString(value) {
71
+ if (value instanceof Date) {
72
+ return value.toISOString();
73
+ }
74
+ const date = new Date(value);
75
+ return Number.isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString();
76
+ }
77
+ function isRecord(value) {
78
+ return typeof value === 'object' && value !== null;
79
+ }