@undefineds.co/linx 0.3.5 → 0.3.8

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 +336 -162
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/account-session.js +4 -8
  7. package/dist/lib/account-session.js.map +1 -1
  8. package/dist/lib/ai-command.js +228 -178
  9. package/dist/lib/ai-command.js.map +1 -1
  10. package/dist/lib/auto-mode/archive.js +38 -7
  11. package/dist/lib/auto-mode/archive.js.map +1 -1
  12. package/dist/lib/auto-mode/auth.js.map +1 -1
  13. package/dist/lib/auto-mode/display.js +71 -45
  14. package/dist/lib/auto-mode/display.js.map +1 -1
  15. package/dist/lib/auto-mode/format.js +9 -7
  16. package/dist/lib/auto-mode/format.js.map +1 -1
  17. package/dist/lib/auto-mode/hooks/claude.js +12 -2
  18. package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
  19. package/dist/lib/auto-mode/hooks/codex.js +17 -7
  20. package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
  21. package/dist/lib/auto-mode/hooks/index.js +28 -8
  22. package/dist/lib/auto-mode/hooks/index.js.map +1 -1
  23. package/dist/lib/auto-mode/pod-ai.js +20 -37
  24. package/dist/lib/auto-mode/pod-ai.js.map +1 -1
  25. package/dist/lib/auto-mode/pod-approval.js +124 -195
  26. package/dist/lib/auto-mode/pod-approval.js.map +1 -1
  27. package/dist/lib/auto-mode/pod-persistence.js +169 -90
  28. package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
  29. package/dist/lib/auto-mode/runner.js +683 -81
  30. package/dist/lib/auto-mode/runner.js.map +1 -1
  31. package/dist/lib/auto-mode/secretary.js +186 -41
  32. package/dist/lib/auto-mode/secretary.js.map +1 -1
  33. package/dist/lib/auto-mode-command.js +32 -32
  34. package/dist/lib/auto-mode-command.js.map +1 -1
  35. package/dist/lib/chat-api.js +242 -50
  36. package/dist/lib/chat-api.js.map +1 -1
  37. package/dist/lib/codex-plugin/bridge.js +164 -17
  38. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  39. package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
  40. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  41. package/dist/lib/credentials-store.js +33 -42
  42. package/dist/lib/credentials-store.js.map +1 -1
  43. package/dist/lib/linx-cloud-errors.js +61 -0
  44. package/dist/lib/linx-cloud-errors.js.map +1 -0
  45. package/dist/lib/linx-tui-contract.js +8 -5
  46. package/dist/lib/linx-tui-contract.js.map +1 -1
  47. package/dist/lib/login-command.js +9 -2
  48. package/dist/lib/login-command.js.map +1 -1
  49. package/dist/lib/models.js +3 -20
  50. package/dist/lib/models.js.map +1 -1
  51. package/dist/lib/oidc-auth.js +143 -17
  52. package/dist/lib/oidc-auth.js.map +1 -1
  53. package/dist/lib/oidc-session-storage.js +2 -6
  54. package/dist/lib/oidc-session-storage.js.map +1 -1
  55. package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
  56. package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
  57. package/dist/lib/pi-adapter/backend-command.js +2 -0
  58. package/dist/lib/pi-adapter/backend-command.js.map +1 -0
  59. package/dist/lib/pi-adapter/backend-credentials.js +80 -0
  60. package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
  61. package/dist/lib/pi-adapter/branding.js +246 -108
  62. package/dist/lib/pi-adapter/branding.js.map +1 -1
  63. package/dist/lib/pi-adapter/control-state.js +72 -0
  64. package/dist/lib/pi-adapter/control-state.js.map +1 -0
  65. package/dist/lib/pi-adapter/interactive.js +2634 -30
  66. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  67. package/dist/lib/pi-adapter/pod-approval.js +382 -210
  68. package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
  69. package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
  70. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
  71. package/dist/lib/pi-adapter/pod-mirror.js +531 -64
  72. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  73. package/dist/lib/pi-adapter/pod-native.js +81 -85
  74. package/dist/lib/pi-adapter/pod-native.js.map +1 -1
  75. package/dist/lib/pi-adapter/pod-status-output.js +54 -0
  76. package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
  77. package/dist/lib/pi-adapter/runtime.js +458 -228
  78. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  79. package/dist/lib/pi-adapter/session-control.js +509 -0
  80. package/dist/lib/pi-adapter/session-control.js.map +1 -0
  81. package/dist/lib/pi-adapter/session.js +35 -22
  82. package/dist/lib/pi-adapter/session.js.map +1 -1
  83. package/dist/lib/pi-adapter/stream.js +89 -32
  84. package/dist/lib/pi-adapter/stream.js.map +1 -1
  85. package/dist/lib/pi-adapter/sync-recovery.js +89 -0
  86. package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
  87. package/dist/lib/pi-adapter/web-fetch.js +13 -14
  88. package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
  89. package/dist/lib/pod-chat-store.js +254 -78
  90. package/dist/lib/pod-chat-store.js.map +1 -1
  91. package/dist/lib/pod-data-session.js +156 -35
  92. package/dist/lib/pod-data-session.js.map +1 -1
  93. package/dist/lib/solid-auth-store.js +27 -0
  94. package/dist/lib/solid-auth-store.js.map +1 -0
  95. package/dist/lib/solid-auth.js +2 -4
  96. package/dist/lib/solid-auth.js.map +1 -1
  97. package/dist/lib/solid-client-credentials-login.js +100 -0
  98. package/dist/lib/solid-client-credentials-login.js.map +1 -0
  99. package/dist/lib/solid-local-store.js +31 -0
  100. package/dist/lib/solid-local-store.js.map +1 -0
  101. package/dist/lib/symphony/archive.js +328 -18
  102. package/dist/lib/symphony/archive.js.map +1 -1
  103. package/dist/lib/symphony/pod-projection.js +2222 -0
  104. package/dist/lib/symphony/pod-projection.js.map +1 -0
  105. package/dist/lib/symphony-command.js +602 -178
  106. package/dist/lib/symphony-command.js.map +1 -1
  107. package/dist/lib/sync-checkpoint-store.js +74 -0
  108. package/dist/lib/sync-checkpoint-store.js.map +1 -0
  109. package/dist/skills/symphony/SKILL.md +665 -0
  110. package/package.json +15 -9
  111. package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
  112. package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
  113. package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
  114. package/vendor/agent-runtime/dist/auto-mode.js +288 -31
  115. package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
  116. package/vendor/agent-runtime/dist/control-plane.js +79 -0
  117. package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
  118. package/vendor/agent-runtime/dist/file-sync.js +314 -0
  119. package/vendor/agent-runtime/dist/index.d.ts +7 -0
  120. package/vendor/agent-runtime/dist/index.js +7 -0
  121. package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
  122. package/vendor/agent-runtime/dist/reconciler.js +361 -0
  123. package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
  124. package/vendor/agent-runtime/dist/symphony.js +362 -57
  125. package/vendor/agent-runtime/dist/sync.d.ts +271 -0
  126. package/vendor/agent-runtime/dist/sync.js +550 -0
  127. package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
  128. package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
  129. package/vendor/agent-runtime/dist/turn-controller.js +2 -2
  130. package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
  131. package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
  132. package/vendor/agent-runtime/package.json +8 -1
  133. package/vendor/pi-web-access/CHANGELOG.md +387 -0
  134. package/vendor/pi-web-access/LICENSE +21 -0
  135. package/vendor/pi-web-access/README.md +352 -0
  136. package/vendor/pi-web-access/activity.ts +101 -0
  137. package/vendor/pi-web-access/banner.png +0 -0
  138. package/vendor/pi-web-access/chrome-cookies.ts +322 -0
  139. package/vendor/pi-web-access/code-search.ts +107 -0
  140. package/vendor/pi-web-access/curator-page.ts +3359 -0
  141. package/vendor/pi-web-access/curator-server.ts +605 -0
  142. package/vendor/pi-web-access/exa.ts +520 -0
  143. package/vendor/pi-web-access/extract.ts +641 -0
  144. package/vendor/pi-web-access/gemini-api.ts +112 -0
  145. package/vendor/pi-web-access/gemini-search.ts +361 -0
  146. package/vendor/pi-web-access/gemini-url-context.ts +126 -0
  147. package/vendor/pi-web-access/gemini-web-config.ts +52 -0
  148. package/vendor/pi-web-access/gemini-web.ts +396 -0
  149. package/vendor/pi-web-access/github-api.ts +196 -0
  150. package/vendor/pi-web-access/github-extract.ts +634 -0
  151. package/vendor/pi-web-access/index.ts +2346 -0
  152. package/vendor/pi-web-access/package.json +45 -0
  153. package/vendor/pi-web-access/pdf-extract.ts +192 -0
  154. package/vendor/pi-web-access/perplexity.ts +195 -0
  155. package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  156. package/vendor/pi-web-access/rsc-extract.ts +338 -0
  157. package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
  158. package/vendor/pi-web-access/storage.ts +72 -0
  159. package/vendor/pi-web-access/summary-review.ts +276 -0
  160. package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
  161. package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
  162. package/vendor/pi-web-access/utils.ts +44 -0
  163. package/vendor/pi-web-access/video-extract.ts +378 -0
  164. package/vendor/pi-web-access/youtube-extract.ts +310 -0
  165. package/dist/lib/pi-adapter/auth.js +0 -68
  166. package/dist/lib/pi-adapter/auth.js.map +0 -1
  167. package/dist/lib/pi-adapter/pod-tools.js +0 -140
  168. package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
  169. package/dist/skills/drizzle-solid/SKILL.md +0 -340
  170. package/dist/skills/pod-storage/SKILL.md +0 -100
  171. package/dist/skills/solid-modeling/SKILL.md +0 -274
  172. package/dist/skills/xpod-componentsjs/SKILL.md +0 -284
@@ -1,246 +1,418 @@
1
- import { linxRuntimeEndpointForBackend } from '../../../vendor/agent-runtime/dist/index.js';
2
- import { createFallbackAutoModeSecretaryRecommendation, isTrustedAutoModeCommand, resolveAutoModeAutoApprovalDecision, autoModeApprovalActionUri, autoModeApprovalRequestMessage, autoModeApprovalRisk, autoModeApprovalToolName, } from '../../../vendor/agent-runtime/dist/auto-mode.js';
3
- import { createRemoteApproval, requestRemoteApproval, resolveRemoteAutoModeApproval, } from '../auto-mode/pod-approval.js';
4
- import { resolveAutoModeSecretaryRecommendation } from '../auto-mode/secretary.js';
5
- import { buildAgentUri, buildThreadUri, DEFAULT_SECRETARY_CHAT_ID } from './pod-mirror-mapping.js';
6
- export function installLinxPiRemoteApproval(options) {
7
- const agent = options.session.agent;
8
- const originalBeforeToolCall = agent.beforeToolCall?.bind(agent);
9
- agent.beforeToolCall = async (context, signal) => {
10
- const originalResult = await originalBeforeToolCall?.(context, signal);
11
- if (originalResult?.block) {
12
- return originalResult;
13
- }
14
- const request = buildPiToolApprovalRequest(context, options.cwd);
15
- if (!request) {
16
- return originalResult;
17
- }
18
- const decision = await resolveLinxPiToolApproval({
19
- request,
20
- record: buildPiApprovalRecord(options, context),
21
- mode: options.mode ?? 'smart',
22
- pollMs: options.pollMs,
23
- signal,
24
- runtime: options.runtime,
25
- resolveSecretaryRecommendation: options.resolveSecretaryRecommendation ?? resolveAutoModeSecretaryRecommendation,
26
- });
27
- return mapApprovalDecisionToBeforeToolCallResult(decision, request);
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createRemoteApproval, resolveRemoteAutoModeApproval, waitForRemoteAutoModeApproval, } from '../auto-mode/pod-approval.js';
3
+ import { DEFAULT_SECRETARY_CHAT_ID, secretaryAgentUri, secretaryThreadUri } from './pod-mirror-mapping.js';
4
+ const EXTENSION_UI_POLICY_VERSION = 'linx-pi-extension-ui/v1';
5
+ const DEFAULT_POLL_MS = 1000;
6
+ const EXTENSION_UI_INPUT_QUESTION_ID = 'runtime';
7
+ export function createPodBackedExtensionUiContext(baseUi, options = {}) {
8
+ return {
9
+ ...baseUi,
10
+ select(title, choices, opts) {
11
+ return selectWithPodApproval(baseUi, title, choices, opts, options);
12
+ },
13
+ confirm(title, message, opts) {
14
+ return confirmWithPodApproval(baseUi, title, message, opts, options);
15
+ },
16
+ input(title, placeholder, opts) {
17
+ return inputWithAutoSecretary(baseUi, title, placeholder, opts, options);
18
+ },
28
19
  };
29
20
  }
30
- export async function resolveLinxPiToolApproval(input) {
31
- const fallbackDecision = resolvePiFallbackAutoDecision(input);
32
- if (fallbackDecision) {
33
- return fallbackDecision;
34
- }
35
- const rawRecommendation = await input.resolveSecretaryRecommendation({
36
- mode: input.mode,
37
- record: input.record,
38
- request: input.request,
39
- }).catch(() => createFallbackAutoModeSecretaryRecommendation({
40
- mode: input.mode,
41
- request: input.request,
42
- }));
43
- const recommendation = normalizePiApprovalRecommendation(input, rawRecommendation);
44
- if (recommendation?.canAutoDecide && recommendation.decision && recommendation.source === 'model') {
45
- return resolvePiRemoteApproval(input, recommendation);
21
+ async function selectWithPodApproval(baseUi, title, choices, opts, options) {
22
+ if (opts?.signal?.aborted || choices.length === 0) {
23
+ return baseUi.select(title, choices, opts);
24
+ }
25
+ if (!shouldPodBackSelect(choices)) {
26
+ return baseUi.select(title, choices, opts);
46
27
  }
47
- const autoDecision = resolvePiAutoDecision(input, recommendation);
48
- if (autoDecision) {
49
- return autoDecision;
28
+ const approvalOptions = choices.map((label, index) => buildSelectApprovalOption(label, index));
29
+ const autoResult = await resolveAutoSecretaryExtensionUiInput({
30
+ title,
31
+ kind: 'select',
32
+ choices,
33
+ approvalOptions,
34
+ timeoutMs: opts?.timeout,
35
+ options,
36
+ mapResponse: (response) => {
37
+ if (!response) {
38
+ return undefined;
39
+ }
40
+ if (response.kind === 'approval') {
41
+ return choiceFromRemoteDecision(response.decision, choices, approvalOptions);
42
+ }
43
+ const answer = response.answers[EXTENSION_UI_INPUT_QUESTION_ID]?.answers[0];
44
+ if (!answer) {
45
+ return undefined;
46
+ }
47
+ return choices.includes(answer) ? answer : undefined;
48
+ },
49
+ });
50
+ if (autoResult.resolved) {
51
+ return autoResult.value;
50
52
  }
51
- return resolvePiRemoteApproval(input, recommendation)
52
- .catch(() => 'decline');
53
+ const result = await raceLocalAndRemote({
54
+ title,
55
+ kind: 'select',
56
+ choices,
57
+ approvalOptions,
58
+ opts,
59
+ options,
60
+ runLocal: (signal) => baseUi.select(title, choices, { ...opts, signal }),
61
+ mapLocalToDecision: (selected) => podDecisionFromSelectedChoice(selected, approvalOptions),
62
+ mapRemoteToLocal: (decision) => choiceFromRemoteDecision(decision, choices, approvalOptions),
63
+ localResolutionNote: (selected) => selected
64
+ ? encodeExtensionUiNote({ kind: 'select', selectedOptionId: optionIdForChoice(selected, choices), selectedLabel: selected })
65
+ : encodeExtensionUiNote({ kind: 'select', cancelled: true }),
66
+ });
67
+ return result;
53
68
  }
54
- function normalizePiApprovalRecommendation(input, recommendation) {
55
- if (!isApprovalRecommendation(input.request, recommendation)) {
56
- return null;
69
+ async function confirmWithPodApproval(baseUi, title, message, opts, options) {
70
+ if (opts?.signal?.aborted) {
71
+ return baseUi.confirm(title, message, opts);
72
+ }
73
+ const approvalOptions = [
74
+ { optionId: 'yes', label: 'Yes', kind: 'allow_once' },
75
+ { optionId: 'no', label: 'No', kind: 'reject_once' },
76
+ ];
77
+ const autoResult = await resolveAutoSecretaryExtensionUiInput({
78
+ title,
79
+ message,
80
+ kind: 'confirm',
81
+ choices: ['Yes', 'No'],
82
+ approvalOptions,
83
+ timeoutMs: opts?.timeout,
84
+ options,
85
+ mapResponse: (response) => {
86
+ if (!response) {
87
+ return undefined;
88
+ }
89
+ if (response.kind === 'approval') {
90
+ return response.decision === 'accept' || response.decision === 'accept_for_session';
91
+ }
92
+ const answer = response.answers[EXTENSION_UI_INPUT_QUESTION_ID]?.answers[0]?.toLowerCase();
93
+ if (!answer) {
94
+ return undefined;
95
+ }
96
+ if (['yes', 'y', 'true', 'allow', 'approve', 'confirm'].includes(answer)) {
97
+ return true;
98
+ }
99
+ if (['no', 'n', 'false', 'deny', 'decline', 'reject'].includes(answer)) {
100
+ return false;
101
+ }
102
+ return undefined;
103
+ },
104
+ });
105
+ if (autoResult.resolved) {
106
+ return autoResult.value ?? false;
107
+ }
108
+ const result = await raceLocalAndRemote({
109
+ title,
110
+ message,
111
+ kind: 'confirm',
112
+ choices: ['Yes', 'No'],
113
+ approvalOptions,
114
+ opts,
115
+ options,
116
+ runLocal: (signal) => baseUi.confirm(title, message, { ...opts, signal }),
117
+ mapLocalToDecision: (confirmed) => confirmed ? 'accept' : 'decline',
118
+ mapRemoteToLocal: (decision) => decision === 'accept' || decision === 'accept_for_session',
119
+ localResolutionNote: (confirmed) => encodeExtensionUiNote({
120
+ kind: 'confirm',
121
+ selectedOptionId: confirmed ? 'yes' : 'no',
122
+ selectedLabel: confirmed ? 'Yes' : 'No',
123
+ }),
124
+ });
125
+ return result ?? false;
126
+ }
127
+ async function inputWithAutoSecretary(baseUi, title, placeholder, opts, options) {
128
+ if (opts?.signal?.aborted) {
129
+ return baseUi.input(title, placeholder, opts);
130
+ }
131
+ const autoResult = await resolveAutoSecretaryExtensionUiInput({
132
+ title,
133
+ message: placeholder,
134
+ kind: 'input',
135
+ choices: [],
136
+ approvalOptions: [],
137
+ timeoutMs: opts?.timeout,
138
+ options,
139
+ mapResponse: (response) => {
140
+ if (!response) {
141
+ return undefined;
142
+ }
143
+ if (response.kind !== 'user-input') {
144
+ return undefined;
145
+ }
146
+ return response.answers[EXTENSION_UI_INPUT_QUESTION_ID]?.answers[0];
147
+ },
148
+ });
149
+ if (autoResult.resolved) {
150
+ return autoResult.value;
151
+ }
152
+ return baseUi.input(title, placeholder, opts);
153
+ }
154
+ async function resolveAutoSecretaryExtensionUiInput(input) {
155
+ const sessionControl = input.options.sessionControl;
156
+ if (!sessionControl) {
157
+ return { resolved: false };
57
158
  }
58
- if (recommendation.source === 'fallback' && recommendation.canAutoDecide && !isTrustedPiFallbackApproval(input.request)) {
159
+ const request = buildExtensionUiInteractionRequest(input);
160
+ const response = await sessionControl.resolveInteractionRequest({ request });
161
+ if (!response) {
162
+ return { resolved: false };
163
+ }
164
+ const value = input.mapResponse(response);
165
+ return value === undefined ? { resolved: false } : { resolved: true, value };
166
+ }
167
+ function buildExtensionUiInteractionRequest(input) {
168
+ if (input.kind === 'input' || input.approvalOptions.length === 0) {
59
169
  return {
60
- ...recommendation,
61
- canAutoDecide: false,
62
- decision: undefined,
170
+ kind: 'user-input',
171
+ message: [input.title, input.message].filter(Boolean).join('\n') || 'Input required',
172
+ questions: [buildExtensionUiQuestion(input)],
173
+ ...(input.timeoutMs ? { timeoutMs: input.timeoutMs } : {}),
174
+ raw: buildExtensionUiRaw(input),
63
175
  };
64
176
  }
65
- return recommendation;
177
+ return {
178
+ kind: 'codex-approval',
179
+ message: [input.title, input.message].filter(Boolean).join('\n') || 'Approval required',
180
+ approvalOptions: input.approvalOptions,
181
+ ...(input.timeoutMs ? { timeoutMs: input.timeoutMs } : {}),
182
+ raw: buildExtensionUiRaw(input),
183
+ };
66
184
  }
67
- async function resolvePiRemoteApproval(input, recommendation) {
68
- const subject = buildPiApprovalSubject(input.record);
69
- const request = buildPiRemoteApprovalRequest(input.request);
70
- if (recommendation?.canAutoDecide && recommendation.decision) {
71
- const remote = await createRemoteApproval({
72
- subject,
73
- request,
74
- runtime: input.runtime,
75
- });
76
- await resolveRemoteAutoModeApproval({
185
+ function buildExtensionUiQuestion(input) {
186
+ return {
187
+ id: EXTENSION_UI_INPUT_QUESTION_ID,
188
+ header: input.kind === 'input' ? 'Input' : 'Select',
189
+ question: [input.title, input.message].filter(Boolean).join('\n') || 'Input required',
190
+ options: input.choices.map((label) => ({ label })),
191
+ };
192
+ }
193
+ function buildExtensionUiRaw(input) {
194
+ return {
195
+ source: 'pi-extension-ui',
196
+ kind: input.kind,
197
+ title: input.title,
198
+ ...(input.message ? { message: input.message } : {}),
199
+ choices: input.choices,
200
+ ...(input.options.cwd ? { cwd: input.options.cwd } : {}),
201
+ sessionId: resolveSessionId(input.options.sessionId),
202
+ };
203
+ }
204
+ async function raceLocalAndRemote(input) {
205
+ const localAbort = new AbortController();
206
+ const removeInputAbort = linkAbortSignal(input.opts?.signal, () => localAbort.abort());
207
+ const remoteAbort = new AbortController();
208
+ const removeRemoteAbort = linkAbortSignal(input.opts?.signal, () => remoteAbort.abort());
209
+ const localPromise = input.runLocal(localAbort.signal).then((value) => ({
210
+ source: 'local',
211
+ value,
212
+ }));
213
+ const remoteReadyPromise = createExtensionUiRemoteApproval({
214
+ title: input.title,
215
+ message: input.message,
216
+ kind: input.kind,
217
+ choices: input.choices,
218
+ approvalOptions: input.approvalOptions,
219
+ timeoutMs: input.opts?.timeout,
220
+ options: input.options,
221
+ }).catch((error) => {
222
+ input.options.onWarning?.(error);
223
+ return null;
224
+ });
225
+ const remotePromise = remoteReadyPromise.then(async (remote) => {
226
+ if (!remote) {
227
+ return { source: 'remote-unavailable' };
228
+ }
229
+ const decision = await waitForRemoteAutoModeApproval({
77
230
  approvalId: remote.id,
78
231
  approvalUri: remote.approvalUri,
79
- decision: recommendation.decision,
80
- decisionRole: 'secretary',
81
- note: recommendation.reason ?? 'resolved by AI secretary',
82
- runtime: input.runtime,
83
- }).catch(() => undefined);
84
- return recommendation.decision;
85
- }
86
- return requestRemoteApproval({
87
- subject,
88
- request,
89
- pollMs: input.pollMs,
90
- signal: input.signal,
91
- runtime: input.runtime,
232
+ pollMs: input.options.pollMs ?? DEFAULT_POLL_MS,
233
+ signal: remoteAbort.signal,
234
+ runtime: input.options.runtime,
235
+ });
236
+ return {
237
+ source: 'remote',
238
+ value: input.mapRemoteToLocal(decision),
239
+ };
92
240
  });
241
+ void localPromise.catch(() => undefined);
242
+ void remotePromise.catch(() => undefined);
243
+ try {
244
+ const winner = await Promise.race([localPromise, remotePromise]);
245
+ if (winner.source === 'remote-unavailable') {
246
+ return (await localPromise).value;
247
+ }
248
+ if (winner.source === 'local') {
249
+ remoteAbort.abort();
250
+ void remoteReadyPromise.then((remote) => {
251
+ if (!remote) {
252
+ return undefined;
253
+ }
254
+ return resolveRemoteAutoModeApproval({
255
+ approvalId: remote.id,
256
+ approvalUri: remote.approvalUri,
257
+ decision: input.mapLocalToDecision(winner.value),
258
+ decisionRole: 'human',
259
+ note: input.localResolutionNote(winner.value),
260
+ runtime: input.options.runtime,
261
+ });
262
+ }).catch((error) => input.options.onWarning?.(error));
263
+ return winner.value;
264
+ }
265
+ localAbort.abort();
266
+ return winner.value;
267
+ }
268
+ finally {
269
+ removeInputAbort();
270
+ removeRemoteAbort();
271
+ }
93
272
  }
94
- function buildPiApprovalSubject(record) {
95
- return ({ webId }) => ({
96
- sessionUri: buildThreadUri(webId, DEFAULT_SECRETARY_CHAT_ID, record.id),
97
- actorUri: buildAgentUri(webId),
98
- policyVersion: 'linx-pi-remote-approval/v1',
273
+ async function createExtensionUiRemoteApproval(input) {
274
+ return createRemoteApproval({
275
+ subject: ({ webId }) => buildExtensionUiApprovalSubject(webId, input.options),
276
+ request: ({ sessionUri }) => buildExtensionUiApprovalRequest({
277
+ title: input.title,
278
+ message: input.message,
279
+ kind: input.kind,
280
+ choices: input.choices,
281
+ approvalOptions: input.approvalOptions,
282
+ sessionUri,
283
+ cwd: input.options.cwd,
284
+ timeoutMs: input.timeoutMs,
285
+ }),
286
+ runtime: input.options.runtime,
99
287
  });
100
288
  }
101
- function buildPiRemoteApprovalRequest(request) {
102
- const details = buildLinxPiApprovalDetails(request);
289
+ function buildExtensionUiApprovalSubject(webId, options) {
290
+ const sessionId = resolveSessionId(options.sessionId) ?? 'linx-pi-extension-ui';
291
+ const sessionUri = secretaryThreadUri(webId, sessionId, DEFAULT_SECRETARY_CHAT_ID);
103
292
  return {
104
- kind: request.kind,
105
- message: details.message,
106
- toolCallId: extractPiToolCallId(request),
107
- toolName: details.toolName,
108
- action: details.action,
109
- risk: details.risk,
110
- ...(request.kind === 'command-approval' && request.command ? { command: request.command } : {}),
111
- ...(request.kind === 'command-approval' && request.cwd ? { cwd: request.cwd } : {}),
293
+ sessionUri,
294
+ actorUri: secretaryAgentUri(webId),
295
+ target: sessionUri,
296
+ policyVersion: EXTENSION_UI_POLICY_VERSION,
112
297
  };
113
298
  }
114
- function extractPiToolCallId(request) {
115
- const raw = isRecord(request.raw) ? request.raw : {};
116
- const params = isRecord(raw.params) ? raw.params : {};
117
- const toolCall = isRecord(params.toolCall) ? params.toolCall : {};
118
- return typeof toolCall.toolCallId === 'string' && toolCall.toolCallId.trim()
119
- ? toolCall.toolCallId.trim()
120
- : `${autoModeApprovalToolName(request)}-${Date.now()}`;
121
- }
122
- function resolvePiAutoDecision(input, recommendation) {
123
- if (recommendation?.canAutoDecide && recommendation.decision) {
124
- if (recommendation.source === 'fallback' && !isTrustedPiFallbackApproval(input.request)) {
125
- return null;
126
- }
127
- return recommendation.decision;
299
+ function buildExtensionUiApprovalRequest(input) {
300
+ const prompt = [input.title, input.message].filter(Boolean).join('\n');
301
+ return {
302
+ kind: 'codex-approval',
303
+ message: prompt,
304
+ toolCallId: `extension-ui-${input.kind}-${randomUUID()}`,
305
+ toolName: `extension-ui-${input.kind}`,
306
+ action: 'https://undefineds.co/ns#runtimeApproval',
307
+ risk: 'medium',
308
+ approvalOptions: input.approvalOptions,
309
+ ...(input.timeoutMs ? { timeoutMs: input.timeoutMs } : {}),
310
+ context: JSON.stringify({
311
+ source: 'pi-extension-ui',
312
+ kind: input.kind,
313
+ title: input.title,
314
+ ...(input.message ? { message: input.message } : {}),
315
+ choices: input.choices,
316
+ ...(input.cwd ? { cwd: input.cwd } : {}),
317
+ }),
318
+ entry: input.sessionUri,
319
+ };
320
+ }
321
+ function shouldPodBackSelect(choices) {
322
+ const inferred = choices.map((choice) => inferApprovalOptionKind(choice));
323
+ const hasAllow = inferred.some((kind) => kind === 'allow_once' || kind === 'allow_always');
324
+ const hasReject = inferred.some((kind) => kind === 'reject_once' || kind === 'reject_always' || kind === 'cancel');
325
+ return hasAllow && hasReject;
326
+ }
327
+ function buildSelectApprovalOption(label, index) {
328
+ const kind = inferApprovalOptionKind(label);
329
+ return {
330
+ optionId: String(index),
331
+ label,
332
+ ...(kind ? { kind } : {}),
333
+ };
334
+ }
335
+ function inferApprovalOptionKind(label) {
336
+ const normalized = label.toLowerCase();
337
+ if (/\b(always|session|trust)\b/u.test(normalized)) {
338
+ return 'allow_always';
339
+ }
340
+ if (/\b(allow|approve|yes|ok|confirm|proceed|continue)\b/u.test(normalized)) {
341
+ return 'allow_once';
342
+ }
343
+ if (/\b(cancel|escape)\b/u.test(normalized)) {
344
+ return 'cancel';
128
345
  }
129
- return resolvePiFallbackAutoDecision(input);
346
+ if (/\b(block|deny|decline|reject|no)\b/u.test(normalized)) {
347
+ return 'reject_once';
348
+ }
349
+ return undefined;
130
350
  }
131
- function resolvePiFallbackAutoDecision(input) {
132
- if (input.request.kind === 'command-approval') {
133
- return resolveAutoModeAutoApprovalDecision({
134
- mode: input.mode,
135
- request: input.request,
136
- });
351
+ function decisionFromSelectedChoice(selected, approvalOptions) {
352
+ if (!selected) {
353
+ return 'cancel';
137
354
  }
138
- if (input.mode === 'auto' && (input.request.kind === 'file-change-approval'
139
- || input.request.kind === 'permissions-approval')) {
355
+ const option = approvalOptions.find((entry) => entry.label === selected);
356
+ if (option?.kind === 'allow_always') {
140
357
  return 'accept_for_session';
141
358
  }
142
- return null;
143
- }
144
- function isTrustedPiFallbackApproval(request) {
145
- return request.kind === 'command-approval' && isTrustedAutoModeCommand(request.command);
359
+ if (option?.kind === 'reject_once' || option?.kind === 'reject_always') {
360
+ return 'decline';
361
+ }
362
+ if (option?.kind === 'cancel') {
363
+ return 'cancel';
364
+ }
365
+ return 'accept';
146
366
  }
147
- function isApprovalRecommendation(request, recommendation) {
148
- return Boolean(recommendation && recommendation.kind === request.kind);
367
+ function podDecisionFromSelectedChoice(selected, approvalOptions) {
368
+ const decision = decisionFromSelectedChoice(selected, approvalOptions);
369
+ // Extension UI options belong to the extension, not LinX's reusable grant
370
+ // layer. Preserve the exact option in the note without creating a grant.
371
+ return decision === 'accept_for_session' ? 'accept' : decision;
149
372
  }
150
- function buildPiToolApprovalRequest(context, cwd) {
151
- const toolName = typeof context.toolCall.name === 'string' ? context.toolCall.name : undefined;
152
- if (!toolName) {
153
- return null;
373
+ function choiceFromRemoteDecision(decision, choices, approvalOptions) {
374
+ if (decision === 'cancel') {
375
+ return undefined;
154
376
  }
155
- const toolCallId = typeof context.toolCall.id === 'string' && context.toolCall.id.trim()
156
- ? context.toolCall.id.trim()
157
- : `${toolName}-${Date.now()}`;
158
- const raw = {
159
- endpoint: linxRuntimeEndpointForBackend('linx'),
160
- params: {
161
- toolCall: {
162
- toolCallId,
163
- name: toolName,
164
- },
165
- },
166
- piToolCall: context.toolCall,
167
- args: context.args,
168
- };
169
- if (toolName === 'bash') {
170
- const args = isRecord(context.args) ? context.args : {};
171
- const command = typeof args.command === 'string' ? args.command : undefined;
172
- return {
173
- kind: 'command-approval',
174
- message: command ? `Approve command: ${command}` : 'Approve bash command',
175
- command,
176
- cwd,
177
- raw,
178
- };
377
+ const preferredKinds = decision === 'accept_for_session'
378
+ ? ['allow_always', 'allow_once']
379
+ : decision === 'accept'
380
+ ? ['allow_once', 'allow_always']
381
+ : ['reject_once', 'reject_always', 'cancel'];
382
+ for (const kind of preferredKinds) {
383
+ const option = approvalOptions.find((entry) => entry.kind === kind);
384
+ if (option) {
385
+ return choices[Number(option.optionId)] ?? option.label;
386
+ }
179
387
  }
180
- if (toolName === 'edit' || toolName === 'write') {
181
- return {
182
- kind: 'file-change-approval',
183
- message: `Approve ${toolName} tool call`,
184
- reason: summarizeToolArgs(toolName, context.args),
185
- raw,
186
- };
388
+ if (decision === 'decline') {
389
+ return undefined;
187
390
  }
188
- return null;
391
+ return choices[0];
189
392
  }
190
- function buildPiApprovalRecord(options, context) {
191
- const sessionId = getPiSessionId(options.session);
192
- return {
193
- id: sessionId,
194
- backend: 'codex',
195
- runtime: 'local',
196
- transport: 'native',
197
- mode: options.mode ?? 'smart',
198
- cwd: options.cwd,
199
- passthroughArgs: [],
200
- credentialSource: 'cloud',
201
- resolvedCredentialSource: 'cloud',
202
- approvalSource: 'hybrid',
203
- command: 'linx',
204
- args: [],
205
- status: 'running',
206
- startedAt: new Date(context.assistantMessage.timestamp ?? Date.now()).toISOString(),
207
- archiveDir: '',
208
- eventsFile: '',
209
- };
393
+ function optionIdForChoice(selected, choices) {
394
+ const index = choices.findIndex((choice) => choice === selected);
395
+ return index >= 0 ? String(index) : undefined;
210
396
  }
211
- function getPiSessionId(session) {
212
- const manager = session.sessionManager;
213
- return manager?.getSessionId?.() || 'linx-pi-session';
397
+ function encodeExtensionUiNote(value) {
398
+ return JSON.stringify({
399
+ source: 'pi-extension-ui',
400
+ ...value,
401
+ });
214
402
  }
215
- function mapApprovalDecisionToBeforeToolCallResult(decision, request) {
216
- if (decision === 'accept' || decision === 'accept_for_session') {
217
- return undefined;
218
- }
219
- return {
220
- block: true,
221
- reason: decision === 'cancel'
222
- ? `LinX cancelled ${autoModeApprovalRequestMessage(request)}`
223
- : `LinX denied ${autoModeApprovalRequestMessage(request)}`,
224
- };
403
+ function resolveSessionId(value) {
404
+ const resolved = typeof value === 'function' ? value() : value;
405
+ return typeof resolved === 'string' && resolved.trim() ? resolved.trim() : undefined;
225
406
  }
226
- function summarizeToolArgs(toolName, args) {
227
- if (!isRecord(args)) {
228
- return toolName;
407
+ function linkAbortSignal(signal, onAbort) {
408
+ if (!signal) {
409
+ return () => undefined;
229
410
  }
230
- const path = typeof args.path === 'string' ? args.path : undefined;
231
- const filePath = typeof args.filePath === 'string' ? args.filePath : undefined;
232
- const target = path ?? filePath;
233
- return target ? `${toolName} ${target}` : toolName;
234
- }
235
- function isRecord(value) {
236
- return typeof value === 'object' && value !== null && !Array.isArray(value);
237
- }
238
- export function buildLinxPiApprovalDetails(request) {
239
- return {
240
- message: autoModeApprovalRequestMessage(request),
241
- toolName: autoModeApprovalToolName(request),
242
- action: autoModeApprovalActionUri(request),
243
- risk: autoModeApprovalRisk(request),
244
- };
411
+ if (signal.aborted) {
412
+ onAbort();
413
+ return () => undefined;
414
+ }
415
+ signal.addEventListener('abort', onAbort, { once: true });
416
+ return () => signal.removeEventListener('abort', onAbort);
245
417
  }
246
418
  //# sourceMappingURL=pod-approval.js.map