@agentdocumentformat/cli 0.7.8 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +1 -1
  2. package/dist/adf.mjs +59 -8
  3. package/dist/chunks/{APEv2Parser-2CNNHMXV.mjs → APEv2Parser-WIXRJLHB.mjs} +2 -2
  4. package/dist/chunks/{AiffParser-7BR4RL6G.mjs → AiffParser-ZC4QFOVJ.mjs} +5 -5
  5. package/dist/chunks/{App-7RIIOHJJ.mjs → App-4AZZJNN2.mjs} +917 -826
  6. package/dist/chunks/{DsdiffParser-CMD4AX7A.mjs → DsdiffParser-UAPAJHFX.mjs} +5 -5
  7. package/dist/chunks/{DsfParser-UOGZDS2M.mjs → DsfParser-NNYZK2QX.mjs} +6 -6
  8. package/dist/chunks/{FlacParser-HB3T3CD4.mjs → FlacParser-MW2W4LO3.mjs} +6 -6
  9. package/dist/chunks/{MP4Parser-66HBRUSX.mjs → MP4Parser-Z57SYOOQ.mjs} +4 -4
  10. package/dist/chunks/{MpegParser-YW6LIUEW.mjs → MpegParser-XIIPAZVM.mjs} +7 -7
  11. package/dist/chunks/{MusepackParser-PAUSXP7D.mjs → MusepackParser-QWNDEYHJ.mjs} +6 -6
  12. package/dist/chunks/{OggParser-NUBV3GWC.mjs → OggParser-WRX53F77.mjs} +9 -9
  13. package/dist/chunks/{WavPackParser-WQAGT7TC.mjs → WavPackParser-MJ7WCSJ5.mjs} +2 -2
  14. package/dist/chunks/{WaveParser-EWLQVDX4.mjs → WaveParser-RAVLHZ5S.mjs} +7 -7
  15. package/dist/chunks/{adf-schema-US4NFHMR.mjs → adf-schema-AIPBQTR2.mjs} +9 -3
  16. package/dist/chunks/auth-manager-AJ32FZWS.mjs +19 -0
  17. package/dist/chunks/auth-manager-C5OBEQK2.mjs +18 -0
  18. package/dist/chunks/auth-manager-TBNKCJ45.mjs +17 -0
  19. package/dist/chunks/{chatgpt-subscription-E4MXTIAT.mjs → chatgpt-subscription-MMGNW5KJ.mjs} +5 -2
  20. package/dist/chunks/chunk-2Z7X3UUR.mjs +678 -0
  21. package/dist/chunks/chunk-3UGZFWIF.mjs +26 -0
  22. package/dist/chunks/{chunk-XNPZBLYL.mjs → chunk-75DC5EWN.mjs} +13 -2
  23. package/dist/chunks/{chunk-5PO5PKRV.mjs → chunk-7JZXMDDF.mjs} +158 -67
  24. package/dist/chunks/chunk-AEAJDCVK.mjs +517 -0
  25. package/dist/chunks/{chunk-6VQEXLE2.mjs → chunk-BHY7QDCM.mjs} +41 -3
  26. package/dist/chunks/{chunk-CD3ATSXE.mjs → chunk-BR3AD5QZ.mjs} +8 -2
  27. package/dist/chunks/chunk-BS3USVEA.mjs +301 -0
  28. package/dist/chunks/chunk-C2S64Y6F.mjs +203 -0
  29. package/dist/chunks/chunk-D7A77SNL.mjs +976 -0
  30. package/dist/chunks/chunk-DDTTSKGG.mjs +2443 -0
  31. package/dist/chunks/{chunk-VAUOM3IN.mjs → chunk-DLWCKKIO.mjs} +1 -1
  32. package/dist/chunks/{chunk-G6PH7RPW.mjs → chunk-E7LC6SHP.mjs} +3 -3
  33. package/dist/chunks/{chunk-ZESQMP66.mjs → chunk-F3WSCAJD.mjs} +3 -3
  34. package/dist/chunks/{chunk-KXEF4F4E.mjs → chunk-GAZO6UMI.mjs} +1 -1
  35. package/dist/chunks/{chunk-XYTWGHFS.mjs → chunk-H3XLRWVE.mjs} +1 -1
  36. package/dist/chunks/{chunk-7T2YMTWL.mjs → chunk-H4C65MTP.mjs} +4 -4
  37. package/dist/chunks/chunk-HBOFZRUS.mjs +738 -0
  38. package/dist/chunks/{chunk-VN7UH7I5.mjs → chunk-IMVMSU3A.mjs} +251 -68
  39. package/dist/chunks/{chunk-XYV3VCIX.mjs → chunk-JZFONJSL.mjs} +79 -20
  40. package/dist/chunks/chunk-KCIRFUZH.mjs +12799 -0
  41. package/dist/chunks/chunk-KDDVQKCG.mjs +65 -0
  42. package/dist/chunks/chunk-KRPKWOMB.mjs +290 -0
  43. package/dist/chunks/chunk-LKFHERRV.mjs +145 -0
  44. package/dist/chunks/chunk-LTX74NU6.mjs +29 -0
  45. package/dist/chunks/{chunk-HSBV6YFU.mjs → chunk-LVF2DRBR.mjs} +634 -99
  46. package/dist/chunks/chunk-M2F7C2UJ.mjs +425 -0
  47. package/dist/chunks/{chunk-62NE4LG4.mjs → chunk-MQAMI5BF.mjs} +5 -5
  48. package/dist/chunks/chunk-MZRXFWPQ.mjs +278 -0
  49. package/dist/chunks/{chunk-PRMGZZM5.mjs → chunk-NZLEPJ5M.mjs} +2 -0
  50. package/dist/chunks/{chunk-I3UYSITV.mjs → chunk-O7XKQ6CH.mjs} +1 -1
  51. package/dist/chunks/chunk-P3RG5LQA.mjs +244 -0
  52. package/dist/chunks/chunk-PAK5JCO2.mjs +48 -0
  53. package/dist/chunks/chunk-PC63YFB7.mjs +34 -0
  54. package/dist/chunks/{daemon-control-HKFVSQTQ.mjs → chunk-RHBGCOS3.mjs} +67 -26
  55. package/dist/chunks/{chunk-J74STFKE.mjs → chunk-RJDOVMM4.mjs} +362 -58
  56. package/dist/chunks/{chunk-QPLEAE6V.mjs → chunk-SOSPQPEN.mjs} +167 -650
  57. package/dist/chunks/{chunk-LFMB4PAA.mjs → chunk-TKXIH27X.mjs} +974 -113
  58. package/dist/chunks/{chunk-5W7KES6P.mjs → chunk-UZPDETEU.mjs} +1 -1
  59. package/dist/chunks/chunk-V3MM6PCH.mjs +366 -0
  60. package/dist/chunks/chunk-VENQBMOA.mjs +62 -0
  61. package/dist/chunks/chunk-WJGPNHGX.mjs +67 -0
  62. package/dist/chunks/{chunk-Y5ZLKFLE.mjs → chunk-XNNJQCZP.mjs} +196 -165
  63. package/dist/chunks/{chunk-HDBPBORT.mjs → chunk-YRBFIDLB.mjs} +46 -10
  64. package/dist/chunks/{chunk-BNXUAOO4.mjs → chunk-ZGXUOAWZ.mjs} +3186 -959
  65. package/dist/chunks/chunk-ZPB3HYM7.mjs +45 -0
  66. package/dist/chunks/claude-subscription-EB4LAAVV.mjs +29 -0
  67. package/dist/chunks/{cli-NMFIAR7Z.mjs → cli-PCVFB6B4.mjs} +797 -56
  68. package/dist/chunks/{commands-KPJWFRUE.mjs → commands-KOMPXJZS.mjs} +4 -3
  69. package/dist/chunks/{daemon-YDIK3AAM.mjs → daemon-BVDB565V.mjs} +83142 -64424
  70. package/dist/chunks/daemon-control-MKN7G5M3.mjs +59 -0
  71. package/dist/chunks/{daemon-token-WRSULP7G.mjs → daemon-token-L5QKTHU5.mjs} +3 -1
  72. package/dist/chunks/{discord-DSP4HUWI.mjs → discord-IR62565M.mjs} +3 -52
  73. package/dist/chunks/{email-BRG7KUO2.mjs → email-KHHTJBDW.mjs} +4 -2
  74. package/dist/chunks/{grok-subscription-4PYX7TBF.mjs → grok-subscription-E74FS5EG.mjs} +5 -2
  75. package/dist/chunks/{layout-EPJQNFAI.mjs → layout-ECWGJF6M.mjs} +2 -2
  76. package/dist/chunks/{lib-N7FCZWKH.mjs → lib-L27L5PXM.mjs} +22 -22
  77. package/dist/chunks/{open-VAU7PXT6.mjs → open-3OR6WT4T.mjs} +6 -6
  78. package/dist/chunks/{parser-JVY2PWSM.mjs → parser-7GARNT5L.mjs} +1 -1
  79. package/dist/chunks/{pipeline-executor-O77NTGLP.mjs → pipeline-executor-DRYEEFDR.mjs} +6 -5
  80. package/dist/chunks/prompt-RRHZX4YT.mjs +13 -0
  81. package/dist/chunks/remote-access-cli-GLEEP7DI.mjs +268 -0
  82. package/dist/chunks/remote-hello-UFWUBDQI.mjs +37 -0
  83. package/dist/chunks/sdk-OJOAF4DP.mjs +30014 -0
  84. package/dist/chunks/{slack-OCNNF4DX.mjs → slack-IYC5RAB5.mjs} +3 -4
  85. package/dist/chunks/{store-HUVDJPYN.mjs → store-55CQSAOX.mjs} +7 -3
  86. package/dist/chunks/tailscale-WLJATFMB.mjs +25 -0
  87. package/dist/chunks/{theme-SFE3JMA2.mjs → theme-HLBDTF6F.mjs} +1 -1
  88. package/dist/chunks/{themes-YLXLOIJF.mjs → themes-BQ72P446.mjs} +2 -2
  89. package/dist/chunks/{token-6G3HCMNX.mjs → token-6D22WECM.mjs} +20 -16
  90. package/dist/chunks/{token-util-UXYSDQIW.mjs → token-util-YF3ZWBZ4.mjs} +1 -2
  91. package/dist/chunks/{tui-UQJLK7XI.mjs → tui-RAUVFOYY.mjs} +21 -18
  92. package/dist/chunks/{welcome-XHXFV7SH.mjs → welcome-J5JZZAM5.mjs} +1 -1
  93. package/dist/chunks/{whatsapp-AJMRQBT7.mjs → whatsapp-YJFFVN4Q.mjs} +9 -9
  94. package/package.json +1 -1
  95. package/dist/chunks/chunk-GKKCMXIZ.mjs +0 -94
  96. package/dist/chunks/chunk-IHDDCZBS.mjs +0 -112
  97. package/dist/chunks/chunk-KWFOS5WC.mjs +0 -41
  98. package/dist/chunks/chunk-UEZR2CNS.mjs +0 -65
  99. package/dist/chunks/chunk-VMHTYDH4.mjs +0 -172
  100. package/dist/chunks/chunk-Y2QA53AN.mjs +0 -259
  101. package/dist/chunks/{chunk-NBTUWMGP.mjs → chunk-2MTE22CR.mjs} +3 -3
  102. package/dist/chunks/{chunk-APRU6PHC.mjs → chunk-JZ7AQNPM.mjs} +3 -3
  103. package/dist/chunks/{telegram-2EFGKIVK.mjs → telegram-JYY2VDTI.mjs} +5 -5
@@ -5,9 +5,11 @@ const require = __adfCreateRequire(import.meta.url);
5
5
  const __filename = __adfFileURLToPath(import.meta.url);
6
6
  const __dirname = __adfDirname(__filename);
7
7
  import {
8
- auth_flow_exports,
9
- openBrowser
10
- } from "./chunk-6VQEXLE2.mjs";
8
+ DaemonClient
9
+ } from "./chunk-IMVMSU3A.mjs";
10
+ import {
11
+ auth_flow_exports
12
+ } from "./chunk-BHY7QDCM.mjs";
11
13
  import {
12
14
  createMouseController,
13
15
  getScreenText,
@@ -18,34 +20,19 @@ import {
18
20
  readLayout,
19
21
  setMouse,
20
22
  setSidebarHidden
21
- } from "./chunk-APRU6PHC.mjs";
23
+ } from "./chunk-JZ7AQNPM.mjs";
22
24
  import {
23
- PROMPT_PREFILL_KEY,
24
- SERVER_STOPPED_TEXT,
25
- StoreProvider,
26
- askQuestion,
27
- emptyTranscript,
28
- findTracked,
29
- folderName,
30
- isAsyncInput,
31
- isBusyState,
32
- isTrackedKey,
33
- parseServer,
34
- servedText,
35
- serverBadge,
36
- serverText,
37
- shallowEqual,
38
- shortUrl,
39
- siteKind,
40
- siteOf,
41
- stoppedError,
42
- stoppedLabel,
43
- transcriptKey,
44
- useActions,
45
- useClient,
46
- useStore,
47
- useTuiSelector
48
- } from "./chunk-J74STFKE.mjs";
25
+ isMouseGarbage,
26
+ lastRawInput,
27
+ newlineKey,
28
+ noteRawInput,
29
+ noteShiftEnter,
30
+ parseMouse,
31
+ superArrow,
32
+ terminalCaps,
33
+ useTerminalCaps,
34
+ writeTerminal
35
+ } from "./chunk-7XYNI52E.mjs";
49
36
  import {
50
37
  CHANNELS_OVERLAY,
51
38
  CHANNEL_ORDER,
@@ -117,10 +104,7 @@ import {
117
104
  validateCredentials,
118
105
  validateMcpForm,
119
106
  validateProvider
120
- } from "./chunk-QPLEAE6V.mjs";
121
- import {
122
- require_jsx_runtime
123
- } from "./chunk-7CV2SA3H.mjs";
107
+ } from "./chunk-2Z7X3UUR.mjs";
124
108
  import {
125
109
  WELCOME_INTRO,
126
110
  WELCOME_OVERLAY,
@@ -130,36 +114,24 @@ import {
130
114
  nextStepIndex,
131
115
  welcomeFacts,
132
116
  welcomeSteps
133
- } from "./chunk-I3UYSITV.mjs";
117
+ } from "./chunk-O7XKQ6CH.mjs";
118
+ import {
119
+ getPrefs,
120
+ savePrefs
121
+ } from "./chunk-5QU5VY7N.mjs";
134
122
  import {
135
123
  THEMES,
136
124
  applyTheme,
137
125
  findTheme,
138
126
  nextThemeName,
139
127
  themeNames
140
- } from "./chunk-KXEF4F4E.mjs";
141
- import {
142
- DaemonClient
143
- } from "./chunk-VN7UH7I5.mjs";
144
- import {
145
- DaemonError,
146
- MAIN_LOOP,
147
- isCredentialsLocked
148
- } from "./chunk-XNPZBLYL.mjs";
149
- import {
150
- cjs
151
- } from "./chunk-SGUTYISF.mjs";
152
- import {
153
- getPrefs,
154
- savePrefs
155
- } from "./chunk-5QU5VY7N.mjs";
128
+ } from "./chunk-GAZO6UMI.mjs";
156
129
  import {
157
- checkFetchTarget,
158
- context_breakdown_exports
159
- } from "./chunk-Y2QA53AN.mjs";
160
- import "./chunk-2NAP2ZF7.mjs";
161
- import "./chunk-5PO5PKRV.mjs";
162
- import "./chunk-VMHTYDH4.mjs";
130
+ context_breakdown_exports,
131
+ guarded_fetch_exports,
132
+ skills_catalog_schema_exports
133
+ } from "./chunk-HBOFZRUS.mjs";
134
+ import "./chunk-SOSPQPEN.mjs";
163
135
  import {
164
136
  Box_default,
165
137
  Text,
@@ -176,37 +148,69 @@ import {
176
148
  ThemeContext,
177
149
  stateColor,
178
150
  useTheme
179
- } from "./chunk-PRMGZZM5.mjs";
151
+ } from "./chunk-NZLEPJ5M.mjs";
180
152
  import {
181
- isMouseGarbage,
182
- lastRawInput,
183
- newlineKey,
184
- noteRawInput,
185
- noteShiftEnter,
186
- parseMouse,
187
- superArrow,
188
- terminalCaps,
189
- useTerminalCaps,
190
- writeTerminal
191
- } from "./chunk-7XYNI52E.mjs";
153
+ DaemonError,
154
+ MAIN_LOOP,
155
+ PROMPT_PREFILL_KEY,
156
+ SERVER_STOPPED_TEXT,
157
+ StoreProvider,
158
+ askQuestion,
159
+ emptyTranscript,
160
+ findTracked,
161
+ folderName,
162
+ isAsyncInput,
163
+ isBusyState,
164
+ isCredentialsLocked,
165
+ isTrackedKey,
166
+ parseServer,
167
+ servedText,
168
+ serverBadge,
169
+ serverText,
170
+ shallowEqual,
171
+ shortUrl,
172
+ siteKind,
173
+ siteOf,
174
+ stoppedError,
175
+ stoppedLabel,
176
+ transcriptKey,
177
+ useActions,
178
+ useClient,
179
+ useStore,
180
+ useTuiSelector
181
+ } from "./chunk-RJDOVMM4.mjs";
182
+ import "./chunk-75DC5EWN.mjs";
183
+ import {
184
+ require_jsx_runtime
185
+ } from "./chunk-7CV2SA3H.mjs";
186
+ import {
187
+ cjs
188
+ } from "./chunk-SGUTYISF.mjs";
189
+ import "./chunk-7JZXMDDF.mjs";
192
190
  import {
193
191
  require_react
194
192
  } from "./chunk-6GJ4QHRI.mjs";
193
+ import {
194
+ ADF_SKILLS_REGISTRY_URL
195
+ } from "./chunk-E7LC6SHP.mjs";
195
196
  import "./chunk-UYY5M2PB.mjs";
196
- import "./chunk-GKKCMXIZ.mjs";
197
- import "./chunk-UEZR2CNS.mjs";
198
- import "./chunk-IHDDCZBS.mjs";
197
+ import "./chunk-2NAP2ZF7.mjs";
198
+ import "./chunk-LKFHERRV.mjs";
199
+ import "./chunk-AEAJDCVK.mjs";
200
+ import "./chunk-BS3USVEA.mjs";
201
+ import "./chunk-WJGPNHGX.mjs";
202
+ import "./chunk-PAK5JCO2.mjs";
203
+ import "./chunk-ZPB3HYM7.mjs";
204
+ import "./chunk-LTX74NU6.mjs";
199
205
  import {
200
206
  require_dist
201
207
  } from "./chunk-APHX6HSV.mjs";
202
208
  import {
203
- external_exports
204
- } from "./chunk-ZJKXHM4Q.mjs";
209
+ agent_commands_exports
210
+ } from "./chunk-VENQBMOA.mjs";
211
+ import "./chunk-BR3AD5QZ.mjs";
212
+ import "./chunk-ZJKXHM4Q.mjs";
205
213
  import "./chunk-RIX34QHC.mjs";
206
- import {
207
- ADF_SKILLS_REGISTRY_URL
208
- } from "./chunk-G6PH7RPW.mjs";
209
- import "./chunk-CD3ATSXE.mjs";
210
214
  import {
211
215
  __export,
212
216
  __toESM
@@ -216,88 +220,394 @@ import {
216
220
  var import_react60 = __toESM(require_react(), 1);
217
221
 
218
222
  // src/main/tui/app/shell-context.ts
219
- var import_react = __toESM(require_react(), 1), ShellContext = (0, import_react.createContext)(null);
220
- function useShell() {
221
- let value = (0, import_react.useContext)(ShellContext);
222
- if (!value) throw new Error("useShell must be used inside the TUI shell");
223
- return value;
224
- }
225
-
226
- // src/main/tui/app/Shell.tsx
227
- var import_react25 = __toESM(require_react(), 1);
223
+ var import_react = __toESM(require_react(), 1);
228
224
 
229
- // src/main/tui/app/clipboard.ts
230
- import { spawn } from "node:child_process";
231
- function pipeTo(command, args, data) {
232
- return new Promise((resolve3) => {
233
- try {
234
- let child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: !0 });
235
- child.on("error", () => resolve3(!1)), child.on("close", (code) => resolve3(code === 0)), child.stdin.end(data);
236
- } catch {
237
- resolve3(!1);
225
+ // src/shared/utils/skills-panel.ts
226
+ var skills_panel_exports = {};
227
+ __export(skills_panel_exports, {
228
+ MAX_CATALOG_SOURCES: () => MAX_CATALOG_SOURCES,
229
+ MAX_REGISTRY_BYTES: () => MAX_REGISTRY_BYTES,
230
+ MAX_SKILLS: () => MAX_SKILLS,
231
+ MAX_SKILL_FILE_BYTES: () => MAX_SKILL_FILE_BYTES,
232
+ SKILLS_REGISTRY_PATH: () => SKILLS_REGISTRY_PATH,
233
+ SKILLS_STATE_PATH: () => SKILLS_STATE_PATH,
234
+ SKILL_MANIFEST: () => SKILL_MANIFEST,
235
+ addCatalogSource: () => addCatalogSource,
236
+ catalogSourceLabel: () => catalogSourceLabel,
237
+ estimateTokens: () => estimateTokens,
238
+ filterCatalogEntries: () => filterCatalogEntries,
239
+ installedSkillNames: () => installedSkillNames,
240
+ isCatalogUrl: () => isCatalogUrl,
241
+ mergeCatalogResults: () => mergeCatalogResults,
242
+ mergeDisabledList: () => mergeDisabledList,
243
+ normalizeCatalogSources: () => normalizeCatalogSources,
244
+ parseSkillsRegistry: () => parseSkillsRegistry,
245
+ sanitizeDisplayText: () => sanitizeDisplayText,
246
+ skillProblems: () => skillProblems,
247
+ unindexedHint: () => unindexedHint
248
+ });
249
+ var SKILLS_REGISTRY_PATH = "skills-registry.json", SKILLS_STATE_PATH = "skills-state.json", SKILL_MANIFEST = /^skills\/([^/]+)\/SKILL\.md$/, MAX_SKILLS = 48, MAX_REGISTRY_BYTES = 32 * 1024, MAX_SKILL_FILE_BYTES = 256 * 1024;
250
+ function parseSkillsRegistry(text) {
251
+ if (!text) return null;
252
+ try {
253
+ let doc = JSON.parse(text);
254
+ if (doc?.schema !== 1 || typeof doc.skills != "object" || doc.skills === null) return null;
255
+ let entries = [];
256
+ for (let value of Object.values(doc.skills)) {
257
+ let skill = value;
258
+ typeof skill?.name != "string" || typeof skill?.path != "string" || entries.push({
259
+ name: skill.name,
260
+ description: typeof skill.description == "string" ? skill.description : void 0,
261
+ path: skill.path,
262
+ enabled: skill.enabled !== !1
263
+ });
238
264
  }
239
- });
265
+ let rejected = [];
266
+ if (Array.isArray(doc.rejected))
267
+ for (let raw of doc.rejected) {
268
+ let row4 = raw;
269
+ typeof row4?.path != "string" || typeof row4?.reason != "string" || rejected.push({ path: row4.path, reason: row4.reason, name: SKILL_MANIFEST.exec(row4.path)?.[1] ?? null });
270
+ }
271
+ return { entries: entries.sort((a, b) => a.name.localeCompare(b.name)), rejected };
272
+ } catch {
273
+ return null;
274
+ }
240
275
  }
241
- function readFrom(command, args, timeoutMs = 3e3) {
242
- return new Promise((resolve3) => {
276
+ function mergeDisabledList(existingText, name, enabled) {
277
+ let doc = {};
278
+ if (existingText)
243
279
  try {
244
- let child = spawn(command, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: !0 }), chunks = [], timer = setTimeout(() => {
245
- child.kill(), resolve3(null);
246
- }, timeoutMs);
247
- child.stdout.on("data", (chunk2) => chunks.push(chunk2)), child.on("error", () => {
248
- clearTimeout(timer), resolve3(null);
249
- }), child.on("close", (code) => {
250
- clearTimeout(timer), resolve3(code === 0 ? Buffer.concat(chunks).toString("utf8") : null);
251
- });
280
+ let parsed = JSON.parse(existingText);
281
+ parsed && typeof parsed == "object" && !Array.isArray(parsed) && (doc = parsed);
252
282
  } catch {
253
- resolve3(null);
254
283
  }
255
- });
284
+ let current = Array.isArray(doc.disabled) ? doc.disabled.filter((entry) => typeof entry == "string") : [], next = new Set(current);
285
+ return enabled ? next.delete(name) : next.add(name), JSON.stringify({ ...doc, schema: 1, disabled: [...next].sort() }, null, 2) + `
286
+ `;
256
287
  }
257
- function isRemoteSession(env = process.env) {
258
- return !!(env.SSH_TTY || env.SSH_CONNECTION || env.SSH_CLIENT);
288
+ var UNSAFE_DISPLAY_CHARS = /[\u0000-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
289
+ function sanitizeDisplayText(value) {
290
+ return value ? value.replace(UNSAFE_DISPLAY_CHARS, " ").replace(/\s+/g, " ").trim() : "";
259
291
  }
260
- function osc52(text) {
261
- let seq = `\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`;
262
- return process.env.TMUX ? `\x1BPtmux;${seq.replace(/\u001b/g, "\x1B\x1B")}\x1B\\` : seq;
292
+ function isCatalogUrl(value) {
293
+ let trimmed = value.trim();
294
+ if (!trimmed) return !1;
295
+ try {
296
+ return new URL(trimmed).protocol === "https:";
297
+ } catch {
298
+ return !1;
299
+ }
263
300
  }
264
- async function nativeCopy(text) {
265
- if (process.platform === "win32")
266
- return pipeTo("clip", [], Buffer.concat([Buffer.from([255, 254]), Buffer.from(text, "utf16le")]));
267
- let data = Buffer.from(text, "utf8");
268
- if (process.platform === "darwin") return pipeTo("pbcopy", [], data);
269
- for (let [cmd, args] of [["wl-copy", []], ["xclip", ["-selection", "clipboard"]], ["xsel", ["--clipboard", "--input"]]])
270
- if (await pipeTo(cmd, [...args], data)) return !0;
271
- return !1;
301
+ function estimateTokens(bytes) {
302
+ return Math.ceil(bytes / 4);
272
303
  }
273
- var systemWriter = async (text) => process.env.ADF_TUI_CLIPBOARD === "osc52" ? writeTerminal(osc52(text)) : isRemoteSession() ? writeTerminal(osc52(text)) || nativeCopy(text) : await nativeCopy(text) ? !0 : writeTerminal(osc52(text)), systemReader = async () => {
274
- if (process.platform === "win32") {
275
- let text = await readFrom("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-Clipboard -Raw"]);
276
- return text === null ? null : text.replace(/\r?\n$/, "");
277
- }
278
- if (process.platform === "darwin") return readFrom("pbpaste", []);
279
- for (let [cmd, args] of [["wl-paste", ["-n"]], ["xclip", ["-selection", "clipboard", "-o"]], ["xsel", ["--clipboard", "--output"]]]) {
280
- let text = await readFrom(cmd, [...args]);
281
- if (text !== null) return text;
304
+ var MAX_CATALOG_SOURCES = 8;
305
+ function normalizeCatalogSources(stored) {
306
+ if (!Array.isArray(stored)) return [ADF_SKILLS_REGISTRY_URL];
307
+ let sources2 = [], seen = /* @__PURE__ */ new Set();
308
+ for (let raw of stored) {
309
+ if (typeof raw != "string") continue;
310
+ let url2 = raw.trim();
311
+ if (!(!isCatalogUrl(url2) || seen.has(url2)) && (seen.add(url2), sources2.push(url2), sources2.length >= MAX_CATALOG_SOURCES))
312
+ break;
282
313
  }
283
- return null;
284
- }, writer = systemWriter, reader = systemReader;
285
- function copyToClipboard(text) {
286
- return writer(text);
287
- }
288
- function readClipboard() {
289
- return reader();
314
+ return sources2;
290
315
  }
291
-
292
- // src/main/tui/ui/TextInput.tsx
293
- var import_react2 = __toESM(require_react(), 1);
294
-
295
- // src/main/tui/ui/edit.ts
296
- var WORD = /[\p{L}\p{N}_]/u, SPACE = /\s/;
297
- function isWord(ch) {
298
- return ch !== void 0 && WORD.test(ch);
316
+ function addCatalogSource(sources2, raw) {
317
+ let url2 = raw.trim();
318
+ return url2 ? isCatalogUrl(url2) ? sources2.includes(url2) ? { ok: !1, error: "That source is already listed." } : sources2.length >= MAX_CATALOG_SOURCES ? { ok: !1, error: `At most ${MAX_CATALOG_SOURCES} catalog sources.` } : { ok: !0, sources: [...sources2, url2] } : { ok: !1, error: "A catalog source must be an https:// URL." } : { ok: !1, error: "Enter a catalog URL." };
299
319
  }
300
- function isSpace(ch) {
320
+ function catalogSourceLabel(url2, publisher) {
321
+ let named = sanitizeDisplayText(publisher);
322
+ if (named) return named;
323
+ try {
324
+ return new URL(url2).hostname;
325
+ } catch {
326
+ return url2;
327
+ }
328
+ }
329
+ function mergeCatalogResults(results) {
330
+ let merged = [], seen = /* @__PURE__ */ new Set();
331
+ for (let result of results) {
332
+ if (!result.ok) continue;
333
+ let sourceLabel = catalogSourceLabel(result.url, result.publisher);
334
+ for (let entry of result.entries)
335
+ seen.has(entry.name) || (seen.add(entry.name), merged.push({ ...entry, sourceUrl: result.url, sourceLabel }));
336
+ }
337
+ return merged.sort((a, b) => a.name.localeCompare(b.name));
338
+ }
339
+ function filterCatalogEntries(entries, query) {
340
+ let needle = query.trim().toLowerCase();
341
+ if (!needle) return entries;
342
+ let byName2 = [], byDescription = [];
343
+ for (let entry of entries)
344
+ entry.name.toLowerCase().includes(needle) ? byName2.push(entry) : entry.description.toLowerCase().includes(needle) && byDescription.push(entry);
345
+ return [...byName2, ...byDescription];
346
+ }
347
+ function installedSkillNames(files2) {
348
+ let names = /* @__PURE__ */ new Set();
349
+ for (let file of files2) {
350
+ let match = SKILL_MANIFEST.exec(file.path);
351
+ match && names.add(match[1]);
352
+ }
353
+ return names;
354
+ }
355
+ function skillProblems(installedNames, registry) {
356
+ if (!registry) return [];
357
+ let indexed = new Set(registry.entries.map((s) => s.name)), rows = /* @__PURE__ */ new Map();
358
+ for (let name of installedNames)
359
+ indexed.has(name) || rows.set(name, { label: name, path: `skills/${name}/SKILL.md`, reason: null, isPackage: !0 });
360
+ for (let rejection of registry.rejected) {
361
+ let key = rejection.name ?? rejection.path;
362
+ rejection.name && indexed.has(rejection.name) || rows.set(key, {
363
+ label: rejection.name ?? rejection.path,
364
+ path: rejection.path,
365
+ reason: rejection.reason,
366
+ isPackage: rejection.name !== null
367
+ });
368
+ }
369
+ return [...rows.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, row4]) => ({ key, ...row4 }));
370
+ }
371
+ function unindexedHint() {
372
+ return `not indexed \u2014 the folder name must be lowercase kebab-case and match the frontmatter name, description must be one line under 500 characters, and the file must be under ${Math.round(MAX_SKILL_FILE_BYTES / 1024)} KB. The catalog also stops at ${MAX_SKILLS} skills or ${Math.round(MAX_REGISTRY_BYTES / 1024)} KB, whichever comes first.`;
373
+ }
374
+
375
+ // src/main/tui/commands/registry.ts
376
+ function collectCommands(views, builtins = []) {
377
+ let commands3 = [], actions4 = [], conflicts = [], byName2 = /* @__PURE__ */ new Map(), actionIds = /* @__PURE__ */ new Set(), addCommand = (command, owner) => {
378
+ let withView = command.view ? command : { ...command, view: owner };
379
+ for (let name of [command.name, ...command.aliases ?? []]) {
380
+ let key = name.toLowerCase(), existing = byName2.get(key);
381
+ if (existing) {
382
+ conflicts.push(`/${key} from ${owner} is shadowed by ${existing.view ?? "builtin"}`);
383
+ continue;
384
+ }
385
+ byName2.set(key, withView);
386
+ }
387
+ commands3.push(withView);
388
+ }, addAction = (action, owner) => {
389
+ if (actionIds.has(action.id)) {
390
+ conflicts.push(`palette action ${action.id} from ${owner} is a duplicate`);
391
+ return;
392
+ }
393
+ actionIds.add(action.id), actions4.push(action.view ? action : { ...action, view: owner });
394
+ };
395
+ for (let builtin of builtins) {
396
+ for (let command of builtin.commands ?? []) addCommand(command, "builtin");
397
+ for (let action of builtin.actions ?? []) addAction(action, "builtin");
398
+ }
399
+ for (let view2 of views) {
400
+ for (let command of view2.commands ?? []) addCommand(command, view2.id);
401
+ for (let action of view2.actions ?? []) addAction(action, view2.id);
402
+ }
403
+ return {
404
+ commands: commands3,
405
+ actions: actions4,
406
+ conflicts,
407
+ find: (name) => byName2.get(name.toLowerCase())
408
+ };
409
+ }
410
+ function parseSlash(text) {
411
+ let trimmed = text.trimStart();
412
+ if (!trimmed.startsWith("/") || trimmed.startsWith("//")) return null;
413
+ let match = trimmed.slice(1).match(/^(\S*)\s*([\s\S]*)$/);
414
+ if (!match) return null;
415
+ let rest = match[2] ?? "";
416
+ return { name: match[1].toLowerCase(), args: splitArgs(rest), rest };
417
+ }
418
+ function splitArgs(text) {
419
+ let args = [], pattern = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g, m;
420
+ for (; m = pattern.exec(text); ) args.push(m[1] !== void 0 ? m[1].replace(/\\(.)/g, "$1") : m[2] ?? m[3]);
421
+ return args;
422
+ }
423
+ function createScope(store, exit) {
424
+ let state = store.getState(), selected = state.selectedAgentId, stoppedKey = isTrackedKey(selected) ? selected : null, agentId = stoppedKey ? null : selected;
425
+ return {
426
+ store,
427
+ actions: store.actions,
428
+ client: store.client,
429
+ state: () => store.getState(),
430
+ agentId,
431
+ stoppedKey,
432
+ loop: agentId ? state.selectedLoop[agentId] ?? MAIN_LOOP : MAIN_LOOP,
433
+ print: (text, level = "info") => store.actions.toast(text, level),
434
+ exit
435
+ };
436
+ }
437
+ async function runSlash(text, registry, store, exit) {
438
+ let parsed = parseSlash(text);
439
+ if (!parsed) return !1;
440
+ let scope = createScope(store, exit), command = registry.find(parsed.name);
441
+ if (!command)
442
+ return store.actions.toast(`Unknown command /${parsed.name} \u2014 /help lists commands`, "warn"), !0;
443
+ if (command.agentCommand && /[\r\n]/.test(text))
444
+ return store.actions.toast(`/${command.name}: agent commands must be one line`, "warn"), !0;
445
+ if (command.available && !command.available(scope))
446
+ return store.actions.toast(`/${command.name} is not available here`, "warn"), !0;
447
+ let ctx = { ...scope, args: parsed.args, rest: parsed.rest };
448
+ try {
449
+ await command.run(ctx);
450
+ } catch (err) {
451
+ store.actions.toast(`/${command.name}: ${err instanceof Error ? err.message : String(err)}`, "error");
452
+ }
453
+ return !0;
454
+ }
455
+ function completeSlash(text, registry, scope) {
456
+ let parsed = parseSlash(text);
457
+ if (!parsed) return [];
458
+ if (/\s/.test(text.trimStart().slice(1))) {
459
+ let command = registry.find(parsed.name);
460
+ return command?.complete ? command.complete(parsed.rest, scope).map((value) => ({ value: `/${command.name} ${value}`, command })) : [];
461
+ }
462
+ let seen = /* @__PURE__ */ new Set(), out = [];
463
+ for (let command of registry.commands) {
464
+ if (seen.has(command) || command.available && !command.available(scope)) continue;
465
+ [command.name, ...command.aliases ?? []].some((n) => n.startsWith(parsed.name)) && (seen.add(command), out.push({ value: `/${command.name}`, command }));
466
+ }
467
+ let exact = (c) => [c.name, ...c.aliases ?? []].some((n) => n.toLowerCase() === parsed.name);
468
+ return [...out.filter((o) => exact(o.command)), ...out.filter((o) => !exact(o.command))];
469
+ }
470
+
471
+ // src/main/tui/commands/agent-commands.ts
472
+ var { validateAgentCommands, parseAuthorizeToken } = cjs(agent_commands_exports), { sanitizeDisplayText: sanitizeDisplayText2 } = cjs(skills_panel_exports);
473
+ function validAgentCommands(value) {
474
+ if (!Array.isArray(value)) return [];
475
+ let invalid = new Set(validateAgentCommands(value).map((issue) => issue.index));
476
+ return value.slice(0, 64).filter((_, index) => !invalid.has(index));
477
+ }
478
+ function registered(ctx) {
479
+ return validAgentCommands(ctx.agentId ? ctx.state().agents[ctx.agentId]?.config?.commands : void 0);
480
+ }
481
+ async function run(ctx, name, args) {
482
+ if (!ctx.agentId) {
483
+ ctx.print(ctx.stoppedKey ? `Agent is off. Start it, then run /${name} again.` : "Select an agent first.", "warn");
484
+ return;
485
+ }
486
+ await ctx.actions.runAgentCommand(ctx.agentId, ctx.loop, name, args);
487
+ }
488
+ function namedArgs(rest) {
489
+ let match = rest.trimStart().match(/^(\S+)(?:\s+([\s\S]*))?$/);
490
+ return match ? { name: match[1], args: match[2] ?? "" } : void 0;
491
+ }
492
+ var agentCommand = {
493
+ name: "command",
494
+ args: "<name> [args]",
495
+ description: "Run an agent lambda command; add !authorize for one authorized run",
496
+ agentCommand: "*",
497
+ complete: (partial, scope) => registered(scope).filter((command) => command.name.startsWith(partial)).map((command) => command.name),
498
+ run: async (ctx) => {
499
+ let input = namedArgs(ctx.rest);
500
+ if (!input) {
501
+ ctx.print("Usage: /command <name> [args] [!authorize]", "warn");
502
+ return;
503
+ }
504
+ await run(ctx, input.name, input.args);
505
+ }
506
+ };
507
+ function withAgentCommands(base2, value) {
508
+ let dynamic = validAgentCommands(value).filter((command) => !base2.find(command.name)).map((command) => ({
509
+ name: command.name,
510
+ args: sanitizeDisplayText2(command.usage) || void 0,
511
+ description: `${sanitizeDisplayText2(command.description)}${command.locked ? " [locked]" : ""}`,
512
+ view: "chat",
513
+ agentCommand: command.name,
514
+ run: (ctx) => run(ctx, command.name, ctx.rest)
515
+ }));
516
+ return { ...base2, commands: [...base2.commands, ...dynamic], find: (name) => base2.find(name) ?? dynamic.find((command) => command.name === name.toLowerCase()) };
517
+ }
518
+ function isAuthorizedCommandInput(text, registry) {
519
+ let parsed = parseSlash(text), name = parsed && registry.find(parsed.name)?.agentCommand;
520
+ if (!parsed || !name) return !1;
521
+ let args = name === "*" ? namedArgs(parsed.rest)?.args : parsed.rest;
522
+ return args !== void 0 && parseAuthorizeToken(args).authorize;
523
+ }
524
+ function hasCommandArguments(rest) {
525
+ return parseAuthorizeToken(rest).args.length > 0;
526
+ }
527
+
528
+ // src/main/tui/app/shell-context.ts
529
+ var ShellContext = (0, import_react.createContext)(null);
530
+ function useShell() {
531
+ let value = (0, import_react.useContext)(ShellContext), commands3 = useTuiSelector((state) => state.agents[state.selectedAgentId ?? ""]?.config?.commands), registry = (0, import_react.useMemo)(() => value ? withAgentCommands(value.registry, commands3) : void 0, [value?.registry, commands3]);
532
+ if (!value) throw new Error("useShell must be used inside the TUI shell");
533
+ return { ...value, registry };
534
+ }
535
+
536
+ // src/main/tui/app/Shell.tsx
537
+ var import_react25 = __toESM(require_react(), 1);
538
+
539
+ // src/main/tui/app/clipboard.ts
540
+ import { spawn } from "node:child_process";
541
+ function pipeTo(command, args, data) {
542
+ return new Promise((resolve3) => {
543
+ try {
544
+ let child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"], windowsHide: !0 });
545
+ child.on("error", () => resolve3(!1)), child.on("close", (code) => resolve3(code === 0)), child.stdin.end(data);
546
+ } catch {
547
+ resolve3(!1);
548
+ }
549
+ });
550
+ }
551
+ function readFrom(command, args, timeoutMs = 3e3) {
552
+ return new Promise((resolve3) => {
553
+ try {
554
+ let child = spawn(command, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: !0 }), chunks = [], timer = setTimeout(() => {
555
+ child.kill(), resolve3(null);
556
+ }, timeoutMs);
557
+ child.stdout.on("data", (chunk2) => chunks.push(chunk2)), child.on("error", () => {
558
+ clearTimeout(timer), resolve3(null);
559
+ }), child.on("close", (code) => {
560
+ clearTimeout(timer), resolve3(code === 0 ? Buffer.concat(chunks).toString("utf8") : null);
561
+ });
562
+ } catch {
563
+ resolve3(null);
564
+ }
565
+ });
566
+ }
567
+ function isRemoteSession(env = process.env) {
568
+ return !!(env.SSH_TTY || env.SSH_CONNECTION || env.SSH_CLIENT);
569
+ }
570
+ function osc52(text) {
571
+ let seq = `\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`;
572
+ return process.env.TMUX ? `\x1BPtmux;${seq.replace(/\u001b/g, "\x1B\x1B")}\x1B\\` : seq;
573
+ }
574
+ async function nativeCopy(text) {
575
+ if (process.platform === "win32")
576
+ return pipeTo("clip", [], Buffer.concat([Buffer.from([255, 254]), Buffer.from(text, "utf16le")]));
577
+ let data = Buffer.from(text, "utf8");
578
+ if (process.platform === "darwin") return pipeTo("pbcopy", [], data);
579
+ for (let [cmd, args] of [["wl-copy", []], ["xclip", ["-selection", "clipboard"]], ["xsel", ["--clipboard", "--input"]]])
580
+ if (await pipeTo(cmd, [...args], data)) return !0;
581
+ return !1;
582
+ }
583
+ var systemWriter = async (text) => process.env.ADF_TUI_CLIPBOARD === "osc52" ? writeTerminal(osc52(text)) : isRemoteSession() ? writeTerminal(osc52(text)) || nativeCopy(text) : await nativeCopy(text) ? !0 : writeTerminal(osc52(text)), systemReader = async () => {
584
+ if (process.platform === "win32") {
585
+ let text = await readFrom("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-Clipboard -Raw"]);
586
+ return text === null ? null : text.replace(/\r?\n$/, "");
587
+ }
588
+ if (process.platform === "darwin") return readFrom("pbpaste", []);
589
+ for (let [cmd, args] of [["wl-paste", ["-n"]], ["xclip", ["-selection", "clipboard", "-o"]], ["xsel", ["--clipboard", "--output"]]]) {
590
+ let text = await readFrom(cmd, [...args]);
591
+ if (text !== null) return text;
592
+ }
593
+ return null;
594
+ }, writer = systemWriter, reader = systemReader;
595
+ function copyToClipboard(text) {
596
+ return writer(text);
597
+ }
598
+ function readClipboard() {
599
+ return reader();
600
+ }
601
+
602
+ // src/main/tui/ui/TextInput.tsx
603
+ var import_react2 = __toESM(require_react(), 1);
604
+
605
+ // src/main/tui/ui/edit.ts
606
+ var WORD = /[\p{L}\p{N}_]/u, SPACE = /\s/;
607
+ function isWord(ch) {
608
+ return ch !== void 0 && WORD.test(ch);
609
+ }
610
+ function isSpace(ch) {
301
611
  return ch !== void 0 && SPACE.test(ch);
302
612
  }
303
613
  function wordLeft(value, pos) {
@@ -510,10 +820,10 @@ function TextInput(props) {
510
820
  var import_react3 = __toESM(require_react(), 1);
511
821
 
512
822
  // src/main/tui/auth/model.ts
513
- var AUTH_OVERLAY = "auth", SUBSCRIPTIONS = ["chatgpt", "grok"], SUBSCRIPTION_LABELS = { chatgpt: "ChatGPT", grok: "Grok" }, AUTH_EXPLAINER = "The daemon keeps its own sign-in, separate from ADF Studio\u2019s. Agents that use these providers need no changes.";
823
+ var AUTH_OVERLAY = "auth", SUBSCRIPTIONS = ["chatgpt", "grok", "claude"], SUBSCRIPTION_LABELS = { chatgpt: "ChatGPT", grok: "Grok", claude: "Claude" }, CLAUDE_EXPLAINER = "Claude signs in through Claude Code on the daemon host; ADF stores no Claude credentials. The sign-in is shared with Claude Code there.", AUTH_EXPLAINER = "The daemon keeps its own sign-in, separate from ADF Studio\u2019s. Agents that use these providers need no changes.";
514
824
  function subscriptionOfType(type, id) {
515
825
  let t = `${type ?? ""} ${id ?? ""}`.toLowerCase();
516
- return t.includes("chatgpt-subscription") || !type && /chatgpt/.test(t) ? "chatgpt" : t.includes("grok-subscription") || !type && /grok/.test(t) ? "grok" : null;
826
+ return t.includes("chatgpt-subscription") || !type && /chatgpt/.test(t) ? "chatgpt" : t.includes("grok-subscription") || !type && /grok/.test(t) ? "grok" : t.includes("claude-subscription") ? "claude" : null;
517
827
  }
518
828
  function statusOf(auth, provider) {
519
829
  let value = auth?.[provider];
@@ -540,9 +850,14 @@ function formatExpiry(expiresAt, now = Date.now()) {
540
850
  }
541
851
  function describeStatus(status) {
542
852
  if (!status) return { text: "unknown", ok: !1 };
853
+ if (status.installed === !1) return { text: `Claude Code not installed${status.installUrl ? ` (${status.installUrl})` : ""}`, ok: !1 };
854
+ if (status.updateRequired) return { text: `Claude Code ${status.version ?? ""} is too old \xB7 ADF needs ${status.minVersion ?? "a newer version"} or newer (run \`claude update\`)`, ok: !1 };
855
+ let cc = status.version ? ` \xB7 Claude Code ${status.version}` : "";
543
856
  if (status.authenticated) {
857
+ let failed = status.flowError && !status.flowPending ? ` \xB7 last sign-in failed: ${status.flowError}` : "";
858
+ if (status.plan) return { text: `signed in${status.email ? ` as ${status.email}` : ""} \xB7 ${status.plan} plan${cc}${failed}`, ok: !0 };
544
859
  let expiry = formatExpiry(status.expiresAt);
545
- return { text: `signed in${status.email ? ` as ${status.email}` : ""}${expiry ? ` \xB7 token ${expiry.startsWith("expired") ? expiry : `renews in ${expiry}`}` : ""}`, ok: !0 };
860
+ return { text: `signed in${status.email ? ` as ${status.email}` : ""}${expiry ? ` \xB7 token ${expiry.startsWith("expired") ? expiry : `renews in ${expiry}`}` : ""}${failed}`, ok: !0 };
546
861
  }
547
862
  return status.flowPending ? { text: "sign-in in progress", ok: !1 } : status.flowError ? { text: `not signed in (last attempt: ${status.flowError})`, ok: !1 } : { text: "not signed in", ok: !1 };
548
863
  }
@@ -1039,6 +1354,9 @@ function completePath(input, options = {}) {
1039
1354
  }
1040
1355
  return { value: dirPart + (common.length > base2.length ? common : base2), candidates };
1041
1356
  }
1357
+ function lockedNote(fields) {
1358
+ return fields && fields.length > 0 ? `; locked against agent edits: ${fields.join(", ")}` : "";
1359
+ }
1042
1360
 
1043
1361
  // src/main/tui/views/fleet/data.ts
1044
1362
  var import_react4 = __toESM(require_react(), 1);
@@ -1252,13 +1570,14 @@ ${bits}` : "";
1252
1570
  if (!await store.actions.confirm({
1253
1571
  title: "Review required",
1254
1572
  message: `${filePath} has not been reviewed on this daemon.${summary}
1255
- Accept the review and load it?`,
1573
+ Accept the review and load it? Accepting also locks its security-sensitive settings against the agent's own edits, as Studio does.`,
1256
1574
  confirmLabel: "Accept + load",
1257
1575
  danger: !0
1258
1576
  }))
1259
1577
  return store.actions.toast("Not loaded (review declined)", "warn"), !1;
1260
1578
  try {
1261
- return await store.client.acceptReview(filePath), store.actions.toast(`Review accepted for ${filePath}`, "success"), !0;
1579
+ let accepted = await store.client.acceptReview(filePath);
1580
+ return store.actions.toast(`Review accepted for ${filePath}${lockedNote(accepted.lockedFields)}`, "success", 6e3), !0;
1262
1581
  } catch (err) {
1263
1582
  return failure(store, null, "Accept review", err), !1;
1264
1583
  }
@@ -1637,100 +1956,6 @@ function LoopRow({ row: row4, selected, focused, width, nextRun }) {
1637
1956
  ] });
1638
1957
  }
1639
1958
 
1640
- // src/main/tui/commands/registry.ts
1641
- function collectCommands(views, builtins = []) {
1642
- let commands3 = [], actions4 = [], conflicts = [], byName2 = /* @__PURE__ */ new Map(), actionIds = /* @__PURE__ */ new Set(), addCommand = (command, owner) => {
1643
- let withView = command.view ? command : { ...command, view: owner };
1644
- for (let name of [command.name, ...command.aliases ?? []]) {
1645
- let key = name.toLowerCase(), existing = byName2.get(key);
1646
- if (existing) {
1647
- conflicts.push(`/${key} from ${owner} is shadowed by ${existing.view ?? "builtin"}`);
1648
- continue;
1649
- }
1650
- byName2.set(key, withView);
1651
- }
1652
- commands3.push(withView);
1653
- }, addAction = (action, owner) => {
1654
- if (actionIds.has(action.id)) {
1655
- conflicts.push(`palette action ${action.id} from ${owner} is a duplicate`);
1656
- return;
1657
- }
1658
- actionIds.add(action.id), actions4.push(action.view ? action : { ...action, view: owner });
1659
- };
1660
- for (let builtin of builtins) {
1661
- for (let command of builtin.commands ?? []) addCommand(command, "builtin");
1662
- for (let action of builtin.actions ?? []) addAction(action, "builtin");
1663
- }
1664
- for (let view2 of views) {
1665
- for (let command of view2.commands ?? []) addCommand(command, view2.id);
1666
- for (let action of view2.actions ?? []) addAction(action, view2.id);
1667
- }
1668
- return {
1669
- commands: commands3,
1670
- actions: actions4,
1671
- conflicts,
1672
- find: (name) => byName2.get(name.toLowerCase())
1673
- };
1674
- }
1675
- function parseSlash(text) {
1676
- let trimmed = text.trimStart();
1677
- if (!trimmed.startsWith("/") || trimmed.startsWith("//")) return null;
1678
- let match = trimmed.slice(1).match(/^(\S*)\s*([\s\S]*)$/);
1679
- if (!match) return null;
1680
- let rest = match[2] ?? "";
1681
- return { name: match[1].toLowerCase(), args: splitArgs(rest), rest };
1682
- }
1683
- function splitArgs(text) {
1684
- let args = [], pattern = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g, m;
1685
- for (; m = pattern.exec(text); ) args.push(m[1] !== void 0 ? m[1].replace(/\\(.)/g, "$1") : m[2] ?? m[3]);
1686
- return args;
1687
- }
1688
- function createScope(store, exit) {
1689
- let state = store.getState(), selected = state.selectedAgentId, stoppedKey = isTrackedKey(selected) ? selected : null, agentId = stoppedKey ? null : selected;
1690
- return {
1691
- store,
1692
- actions: store.actions,
1693
- client: store.client,
1694
- state: () => store.getState(),
1695
- agentId,
1696
- stoppedKey,
1697
- loop: agentId ? state.selectedLoop[agentId] ?? MAIN_LOOP : MAIN_LOOP,
1698
- print: (text, level = "info") => store.actions.toast(text, level),
1699
- exit
1700
- };
1701
- }
1702
- async function runSlash(text, registry, store, exit) {
1703
- let parsed = parseSlash(text);
1704
- if (!parsed) return !1;
1705
- let scope = createScope(store, exit), command = registry.find(parsed.name);
1706
- if (!command)
1707
- return store.actions.toast(`Unknown command /${parsed.name} \u2014 /help lists commands`, "warn"), !0;
1708
- if (command.available && !command.available(scope))
1709
- return store.actions.toast(`/${command.name} is not available here`, "warn"), !0;
1710
- let ctx = { ...scope, args: parsed.args, rest: parsed.rest };
1711
- try {
1712
- await command.run(ctx);
1713
- } catch (err) {
1714
- store.actions.toast(`/${command.name}: ${err instanceof Error ? err.message : String(err)}`, "error");
1715
- }
1716
- return !0;
1717
- }
1718
- function completeSlash(text, registry, scope) {
1719
- let parsed = parseSlash(text);
1720
- if (!parsed) return [];
1721
- if (/\s/.test(text.trimStart().slice(1))) {
1722
- let command = registry.find(parsed.name);
1723
- return command?.complete ? command.complete(parsed.rest, scope).map((value) => ({ value: `/${command.name} ${value}`, command })) : [];
1724
- }
1725
- let seen = /* @__PURE__ */ new Set(), out = [];
1726
- for (let command of registry.commands) {
1727
- if (seen.has(command) || command.available && !command.available(scope)) continue;
1728
- [command.name, ...command.aliases ?? []].some((n) => n.startsWith(parsed.name)) && (seen.add(command), out.push({ value: `/${command.name}`, command }));
1729
- }
1730
- let exact = (c) => [c.name, ...c.aliases ?? []].some((n) => n.toLowerCase() === parsed.name);
1731
- return [...out.filter((o) => exact(o.command)), ...out.filter((o) => !exact(o.command))];
1732
- }
1733
-
1734
1959
  // src/main/tui/app/StatusBar.tsx
1735
1960
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1), GLOBAL_HINTS = [
1736
1961
  { keys: "tab", label: "focus" },
@@ -1909,6 +2134,8 @@ function insertIntoPrompt(text) {
1909
2134
  return promptApi.current ? (promptApi.current.insert(text), !0) : !1;
1910
2135
  }
1911
2136
  function slashEnterAction(value, command) {
2137
+ if (command.agentCommand && command.agentCommand !== "*")
2138
+ return command.args?.includes("<") && !hasCommandArguments(parseSlash(value)?.rest ?? "") ? { insert: `${value} ` } : { run: value };
1912
2139
  if (/[\\/]$/.test(value)) return { insert: value };
1913
2140
  let required = command.args?.match(/<[^>]+>/g)?.length ?? 0;
1914
2141
  return (parseSlash(value)?.args.length ?? 0) >= required ? { run: value } : { insert: `${value} ` };
@@ -2000,39 +2227,45 @@ function Prompt({ width, focused }) {
2000
2227
  ] }) : null
2001
2228
  ] }, item.key)) }) : null,
2002
2229
  badge ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ComposerTop, { width, focused, badge }) : null,
2003
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { borderStyle: theme2.ascii ? "classic" : "round", borderTop: !badge, borderColor: focused ? theme2.color.borderFocus : theme2.color.border, paddingX: 1, width, flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2004
- TextInput,
2005
- {
2006
- value,
2007
- onChange: (next) => {
2008
- setValue(next), setMenuIndex(0), setDismissed(!1);
2009
- },
2010
- onSubmit: submit,
2011
- placeholder: placeholder3,
2012
- history,
2013
- focused,
2014
- apiRef: promptApi,
2015
- onKey: (_input, key, api) => {
2016
- if (!menuOpen) return !1;
2017
- if (key.escape)
2018
- return setDismissed(!0), !0;
2019
- if (key.upArrow)
2020
- return setMenuIndex((i) => (i - 1 + menu.length) % menu.length), !0;
2021
- if (key.downArrow)
2022
- return setMenuIndex((i) => (i + 1) % menu.length), !0;
2023
- if (key.tab) {
2024
- let next = menu[highlighted].accept();
2025
- return api.setValue(next.value, next.cursor), !0;
2026
- }
2027
- if (key.return && !key.shift && !key.meta) {
2028
- if (api.value !== value) return !1;
2029
- let item = menu[highlighted], outcome = menuEnter(api.value, item.accept(), item.command);
2030
- return "insert" in outcome ? (api.setValue(outcome.insert, outcome.cursor), !0) : (api.setValue(outcome.submit), !1);
2230
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { borderStyle: theme2.ascii ? "classic" : "round", borderTop: !badge, borderColor: focused ? theme2.color.borderFocus : theme2.color.border, paddingX: 1, width, flexDirection: "column", children: [
2231
+ isAuthorizedCommandInput(value, registry) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme2.color.warn, wrap: "truncate-end", children: [
2232
+ theme2.glyph.warn,
2233
+ " Authorized run"
2234
+ ] }),
2235
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2236
+ TextInput,
2237
+ {
2238
+ value,
2239
+ onChange: (next) => {
2240
+ setValue(next), setMenuIndex(0), setDismissed(!1);
2241
+ },
2242
+ onSubmit: submit,
2243
+ placeholder: placeholder3,
2244
+ history,
2245
+ focused,
2246
+ apiRef: promptApi,
2247
+ onKey: (_input, key, api) => {
2248
+ if (!menuOpen) return !1;
2249
+ if (key.escape)
2250
+ return setDismissed(!0), !0;
2251
+ if (key.upArrow)
2252
+ return setMenuIndex((i) => (i - 1 + menu.length) % menu.length), !0;
2253
+ if (key.downArrow)
2254
+ return setMenuIndex((i) => (i + 1) % menu.length), !0;
2255
+ if (key.tab) {
2256
+ let next = menu[highlighted].accept();
2257
+ return api.setValue(next.value, next.cursor), !0;
2258
+ }
2259
+ if (key.return && !key.shift && !key.meta) {
2260
+ if (api.value !== value) return !1;
2261
+ let item = menu[highlighted], outcome = menuEnter(api.value, item.accept(), item.command);
2262
+ return "insert" in outcome ? (api.setValue(outcome.insert, outcome.cursor), !0) : (api.setValue(outcome.submit), !1);
2263
+ }
2264
+ return !1;
2031
2265
  }
2032
- return !1;
2033
2266
  }
2034
- }
2035
- ) })
2267
+ )
2268
+ ] })
2036
2269
  ] });
2037
2270
  }
2038
2271
 
@@ -3693,7 +3926,7 @@ function itemHeight(item, width, flags) {
3693
3926
  case "notice":
3694
3927
  return 1;
3695
3928
  case "context":
3696
- return flags.expanded ? 1 + wrappedHeight(item.text || " ", w - 2) + 1 : 1;
3929
+ return flags.expanded ? 1 + wrappedHeight(item.text || (item.ownerCommand?.status === "running" ? "Running\u2026" : item.ownerCommand ? "(empty)" : " "), w - 2) + (item.ownerCommand?.status === "denied" ? wrappedHeight("Add !authorize to allow restricted calls for one run.", w - 2) : 0) + 1 : 1;
3697
3930
  case "error":
3698
3931
  return wrappedHeight(item.text, w - 2) + 1;
3699
3932
  }
@@ -3735,6 +3968,9 @@ ${answer}` : askQuestion(item.input);
3735
3968
  return item.answer ? `${item.question}
3736
3969
 
3737
3970
  ${item.answer}` : item.question;
3971
+ case "context":
3972
+ return item.ownerCommand ? `${item.ownerCommand.command} [${item.ownerCommand.status}${item.ownerCommand.authorized ? ", authorized" : ""}]
3973
+ ${item.text}` : item.text;
3738
3974
  default:
3739
3975
  return item.text;
3740
3976
  }
@@ -3832,7 +4068,7 @@ function wakesText(timers, now = Date.now(), sep4 = "\xB7") {
3832
4068
  }
3833
4069
 
3834
4070
  // src/main/tui/auth/flow.ts
3835
- var { loginChatGpt, loginGrok, openBrowser: openBrowser2 } = cjs(auth_flow_exports), seams = { openBrowser: openBrowser2 };
4071
+ var { loginChatGpt, loginClaude, loginGrok, openBrowser } = cjs(auth_flow_exports), seams = { openBrowser };
3836
4072
  function openUrl(url2) {
3837
4073
  seams.openBrowser(url2);
3838
4074
  }
@@ -3845,7 +4081,7 @@ async function signIn(client, provider, signal, handlers) {
3845
4081
  startCallbackServer: seams.startCallbackServer
3846
4082
  };
3847
4083
  try {
3848
- return provider === "chatgpt" ? await loginChatGpt(deps, { daemonUrl: client.baseUrl, onStart: (info) => handlers.onChatGpt?.(info) }) : await loginGrok(deps, { onStart: (info) => handlers.onGrok?.(info) });
4084
+ return provider === "claude" ? await loginClaude(deps, { onStart: (info) => handlers.onClaude?.(info) }) : provider === "chatgpt" ? await loginChatGpt(deps, { daemonUrl: client.baseUrl, onStart: (info) => handlers.onChatGpt?.(info) }) : await loginGrok(deps, { onStart: (info) => handlers.onGrok?.(info) });
3849
4085
  } catch (err) {
3850
4086
  return signal.aborted ? { ok: !1, error: "Sign-in cancelled", cancelled: !0 } : { ok: !1, error: err instanceof Error ? err.message : String(err) };
3851
4087
  }
@@ -3854,14 +4090,15 @@ async function signIn(client, provider, signal, handlers) {
3854
4090
  // src/main/tui/auth/AuthDialog.tsx
3855
4091
  var import_jsx_runtime18 = __toESM(require_jsx_runtime(), 1);
3856
4092
  function AuthDialog({ overlay, close, width }) {
3857
- let theme2 = useTheme(), store = useStore(), auth = useTuiSelector((s) => s.auth), props = overlay.props ?? {}, [cursor, setCursor] = (0, import_react15.useState)(Math.max(0, SUBSCRIPTIONS.indexOf(props.login ?? "chatgpt"))), [flow, setFlow] = (0, import_react15.useState)({ kind: "list" }), [note, setNote] = (0, import_react15.useState)(null), abort = (0, import_react15.useRef)(null), dialogWidth = Math.max(50, Math.min(width - 4, 84));
4093
+ let theme2 = useTheme(), store = useStore(), auth = useTuiSelector((s) => s.auth), props = overlay.props ?? {}, [cursor, setCursor] = (0, import_react15.useState)(Math.max(0, SUBSCRIPTIONS.indexOf(props.login ?? "chatgpt"))), [flow, setFlow] = (0, import_react15.useState)({ kind: "list" }), [note, setNote] = (0, import_react15.useState)(null), [pasted, setPasted] = (0, import_react15.useState)(""), abort = (0, import_react15.useRef)(null), dialogWidth = Math.max(50, Math.min(width - 4, 84));
3858
4094
  (0, import_react15.useEffect)(() => (store.actions.refreshAuth(), props.login && start(props.login), () => abort.current?.abort()), []);
3859
4095
  let start = (provider) => {
3860
4096
  abort.current?.abort();
3861
4097
  let ctrl = new AbortController();
3862
- abort.current = ctrl, setNote(null), setFlow({ kind: "running", provider }), signIn(store.client, provider, ctrl.signal, {
4098
+ abort.current = ctrl, setNote(null), setPasted(""), setFlow({ kind: "running", provider }), signIn(store.client, provider, ctrl.signal, {
3863
4099
  onChatGpt: (info) => setFlow({ kind: "running", provider, url: info.authUrl, relay: info.mode === "relay" ? info.redirectUri : void 0 }),
3864
- onGrok: (info) => setFlow({ kind: "running", provider, url: info.verificationUri, code: info.userCode })
4100
+ onGrok: (info) => setFlow({ kind: "running", provider, url: info.verificationUri, code: info.userCode }),
4101
+ onClaude: (info) => setFlow({ kind: "running", provider, url: info.url || void 0, started: !0 })
3865
4102
  }).then((outcome) => {
3866
4103
  if (ctrl.signal.aborted && !outcome.ok) return;
3867
4104
  abort.current = null, store.actions.refreshAuth();
@@ -3870,6 +4107,12 @@ function AuthDialog({ overlay, close, width }) {
3870
4107
  });
3871
4108
  }, cancel = () => {
3872
4109
  flow.kind === "running" && (abort.current?.abort(), abort.current = null, setNote(`${SUBSCRIPTION_LABELS[flow.provider]} sign-in cancelled.`), setFlow({ kind: "list" }));
4110
+ }, sendCode = (value) => {
4111
+ let code = value.trim();
4112
+ return code ? (setPasted(""), setNote("Sending the code to Claude Code\u2026"), store.client.submitClaudeCode(code).then(
4113
+ () => setNote("Code sent. Waiting for Claude Code to finish\u2026"),
4114
+ (err) => setNote(`Could not send the code: ${err instanceof Error ? err.message : String(err)}`)
4115
+ ), !0) : !1;
3873
4116
  }, copyUrl = (url2) => {
3874
4117
  url2 && copyToClipboard(url2).then((ok) => setNote(ok ? "URL copied to the clipboard." : "Could not copy; select the URL with the mouse instead."));
3875
4118
  };
@@ -3894,25 +4137,30 @@ function AuthDialog({ overlay, close, width }) {
3894
4137
  } else (input === "n" || key.escape) && setFlow({ kind: "list" });
3895
4138
  return !0;
3896
4139
  case "running":
3897
- return key.escape ? (cancel(), !0) : (input === "c" && copyUrl(flow.url), !0);
4140
+ return key.escape ? (cancel(), !0) : (input === "c" && flow.provider !== "claude" && copyUrl(flow.url), !0);
3898
4141
  case "done":
3899
4142
  return key.escape || key.return ? (flow.outcome.ok ? close() : setFlow({ kind: "list" }), !0) : input === "r" && !flow.outcome.ok ? (start(flow.provider), !0) : (input === "c" && copyUrl(flow.url), !0);
3900
4143
  }
3901
4144
  }, { layer: "overlay" });
3902
4145
  let noteLine = note ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.muted, wrap: "wrap", children: note }) : null;
3903
4146
  if (flow.kind === "running" || flow.kind === "done") {
3904
- let label2 = SUBSCRIPTION_LABELS[flow.provider], running2 = flow.kind === "running", code = running2 ? flow.code : void 0, url2 = flow.url, hints2 = running2 ? [...url2 ? [{ keys: "c", label: "copy URL" }] : [], { keys: "esc", label: "cancel" }] : flow.outcome.ok ? [{ keys: "enter", label: "close" }] : [{ keys: "r", label: "try again" }, { keys: "esc", label: "back" }];
4147
+ let label2 = SUBSCRIPTION_LABELS[flow.provider], running2 = flow.kind === "running", code = running2 ? flow.code : void 0, url2 = flow.url, claude = flow.provider === "claude", claudeWaiting = running2 && claude && !!flow.started, hints2 = running2 ? claude ? [...claudeWaiting ? [{ keys: "enter", label: "send code" }] : [], { keys: "esc", label: "cancel" }] : [...url2 ? [{ keys: "c", label: "copy URL" }] : [], { keys: "esc", label: "cancel" }] : flow.outcome.ok ? [{ keys: "enter", label: "close" }] : [{ keys: "r", label: "try again" }, { keys: "esc", label: "back" }];
3905
4148
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Modal, { title: `Sign in to ${label2}`, width: dialogWidth, hints: hints2, children: [
3906
- running2 && !url2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Spinner, { label: `asking the daemon to start ${label2} sign-in\u2026` }) : null,
4149
+ running2 && !url2 && !(running2 && flow.started) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Spinner, { label: `asking the daemon to start ${label2} sign-in\u2026` }) : null,
4150
+ claude ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.muted, wrap: "wrap", children: CLAUDE_EXPLAINER }) : null,
3907
4151
  code ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: [
3908
4152
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.muted, children: "Enter this code in the browser:" }),
3909
4153
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { borderStyle: theme2.ascii ? "classic" : "double", borderColor: theme2.color.accent, paddingX: 3, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { bold: !0, color: theme2.color.accent, children: spacedCode(code) }) })
3910
4154
  ] }) : null,
3911
4155
  url2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
3912
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.muted, children: code ? "on this page (the browser should open it):" : "The browser should open. If it does not, open this URL:" }),
4156
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.muted, children: code ? "on this page (the browser should open it):" : claude ? "Claude Code is opening the sign-in page in the browser on the daemon host. If it does not open there, open this URL:" : "The browser should open. If it does not, open this URL:" }),
3913
4157
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.info, wrap: "wrap", children: url2 })
3914
4158
  ] }) : null,
3915
- running2 && url2 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Spinner, { label: flow.relay ? `waiting for you to finish in the browser\u2026 (callback on ${flow.relay})` : "waiting for you to finish in the browser\u2026" }) }) : null,
4159
+ claudeWaiting ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
4160
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: theme2.color.text, children: "If the page shows a code, paste it here (or wait):" }),
4161
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { borderStyle: theme2.ascii ? "classic" : "round", borderColor: theme2.color.borderFocus, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TextInput, { value: pasted, onChange: setPasted, onSubmit: sendCode, focused: !0, keyLayer: "overlay", maxRows: 2, placeholder: "code from the Claude sign-in page" }) })
4162
+ ] }) : null,
4163
+ running2 && (url2 || claudeWaiting) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Spinner, { label: flow.relay ? `waiting for you to finish in the browser\u2026 (callback on ${flow.relay})` : "waiting for you to finish in the browser\u2026" }) }) : null,
3916
4164
  running2 ? null : flow.outcome.ok ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Text, { bold: !0, color: theme2.color.success, children: [
3917
4165
  theme2.glyph.check,
3918
4166
  " Signed in to ",
@@ -4484,8 +4732,8 @@ var import_jsx_runtime24 = __toESM(require_jsx_runtime(), 1), MODEL_OVERLAY = "i
4484
4732
  function providerChoices(providers, auth) {
4485
4733
  return providers.map((p) => {
4486
4734
  let type = String(p.type ?? ""), id = String(p.id ?? ""), name = String(p.name ?? id);
4487
- if (type === "chatgpt-subscription" || type === "grok-subscription") {
4488
- let signedIn = type === "chatgpt-subscription" ? auth?.chatgpt : auth?.grok;
4735
+ if (type === "chatgpt-subscription" || type === "grok-subscription" || type === "claude-subscription") {
4736
+ let signedIn = type === "chatgpt-subscription" ? auth?.chatgpt : type === "grok-subscription" ? auth?.grok : auth?.claude;
4489
4737
  return { id, name, type, access: signedIn?.authenticated ? "signed in" : "not signed in (/login)", ready: !!signedIn?.authenticated };
4490
4738
  }
4491
4739
  if (p.hasApiKey === !0) return { id, name, type, access: "key set", ready: !0 };
@@ -4633,14 +4881,14 @@ var { normalizeAuthProvider } = cjs(auth_flow_exports);
4633
4881
  function openAuth(actions4, props = {}) {
4634
4882
  actions4.pushOverlay({ kind: AUTH_OVERLAY, props: { ...props } });
4635
4883
  }
4636
- var complete = (partial) => ["chatgpt", "grok"].filter((v) => v.startsWith(partial.trim().toLowerCase())), authCommand = {
4884
+ var complete = (partial) => ["chatgpt", "grok", "claude"].filter((v) => v.startsWith(partial.trim().toLowerCase())), authCommand = {
4637
4885
  name: "auth",
4638
- description: "Provider sign-in: ChatGPT and Grok status, sign in, sign out",
4886
+ description: "Provider sign-in: ChatGPT, Grok and Claude status, sign in, sign out",
4639
4887
  run: (ctx) => openAuth(ctx.actions)
4640
4888
  }, loginCommand = {
4641
4889
  name: "login",
4642
- args: "[chatgpt|grok]",
4643
- description: "Sign the daemon in to ChatGPT (browser) or Grok (device code)",
4890
+ args: "[chatgpt|grok|claude]",
4891
+ description: "Sign the daemon in to ChatGPT (browser), Grok (device code) or Claude (Claude Code on the daemon host)",
4644
4892
  complete,
4645
4893
  run: (ctx) => {
4646
4894
  if (!ctx.args[0]) {
@@ -4649,29 +4897,30 @@ var complete = (partial) => ["chatgpt", "grok"].filter((v) => v.startsWith(parti
4649
4897
  }
4650
4898
  let provider = normalizeAuthProvider(ctx.args[0]);
4651
4899
  if (!provider) {
4652
- ctx.print("Usage: /login [chatgpt|grok]", "warn");
4900
+ ctx.print("Usage: /login [chatgpt|grok|claude]", "warn");
4653
4901
  return;
4654
4902
  }
4655
4903
  openAuth(ctx.actions, { login: provider });
4656
4904
  }
4657
4905
  }, logoutCommand = {
4658
4906
  name: "logout",
4659
- args: "<chatgpt|grok>",
4660
- description: "Sign the daemon out of ChatGPT or Grok (asks)",
4907
+ args: "<chatgpt|grok|claude>",
4908
+ description: "Sign the daemon out of ChatGPT, Grok or Claude (asks)",
4661
4909
  complete,
4662
4910
  run: async (ctx) => {
4663
4911
  let provider = normalizeAuthProvider(ctx.args[0]);
4664
4912
  if (!provider) {
4665
- ctx.print("Usage: /logout <chatgpt|grok>", "warn");
4913
+ ctx.print("Usage: /logout <chatgpt|grok|claude>", "warn");
4666
4914
  return;
4667
4915
  }
4668
- let label2 = SUBSCRIPTION_LABELS[provider];
4669
- await ctx.actions.confirm({ title: `Sign out of ${label2}`, message: `Sign the daemon out of ${label2}? Agents using it stop working until you sign in again.`, confirmLabel: "Sign out", danger: !0 }) && await ctx.actions.logoutSubscription(provider);
4916
+ let label2 = SUBSCRIPTION_LABELS[provider], shared = provider === "claude" ? " This also signs Claude Code out on the daemon host." : "";
4917
+ await ctx.actions.confirm({ title: `Sign out of ${label2}`, message: `Sign the daemon out of ${label2}? Agents using it stop working until you sign in again.${shared}`, confirmLabel: "Sign out", danger: !0 }) && await ctx.actions.logoutSubscription(provider);
4670
4918
  }
4671
4919
  }, authActions = [
4672
- { id: "shell.auth", title: "Provider sign-in", hint: "ChatGPT, Grok", group: "Actions", keywords: ["auth", "login", "sign in", "subscription", "account"], run: (ctx) => openAuth(ctx.actions) },
4920
+ { id: "shell.auth", title: "Provider sign-in", hint: "ChatGPT, Grok, Claude", group: "Actions", keywords: ["auth", "login", "sign in", "subscription", "account"], run: (ctx) => openAuth(ctx.actions) },
4673
4921
  { id: "shell.auth.chatgpt", title: "Sign in to ChatGPT", group: "Actions", keywords: ["login", "openai", "codex", "auth"], available: (s) => !statusOf(s.state().auth, "chatgpt")?.authenticated, run: (ctx) => openAuth(ctx.actions, { login: "chatgpt" }) },
4674
- { id: "shell.auth.grok", title: "Sign in to Grok", group: "Actions", keywords: ["login", "xai", "auth", "device code"], available: (s) => !statusOf(s.state().auth, "grok")?.authenticated, run: (ctx) => openAuth(ctx.actions, { login: "grok" }) }
4922
+ { id: "shell.auth.grok", title: "Sign in to Grok", group: "Actions", keywords: ["login", "xai", "auth", "device code"], available: (s) => !statusOf(s.state().auth, "grok")?.authenticated, run: (ctx) => openAuth(ctx.actions, { login: "grok" }) },
4923
+ { id: "shell.auth.claude", title: "Sign in to Claude", group: "Actions", keywords: ["login", "anthropic", "claude code", "auth", "subscription"], available: (s) => !statusOf(s.state().auth, "claude")?.authenticated, run: (ctx) => openAuth(ctx.actions, { login: "claude" }) }
4675
4924
  ];
4676
4925
 
4677
4926
  // src/main/tui/setup/ProviderDialog.tsx
@@ -5535,7 +5784,7 @@ async function editText(text, options = {}) {
5535
5784
  var schemaParse = null;
5536
5785
  async function loadSchema() {
5537
5786
  if (schemaParse) return schemaParse;
5538
- let schema = cjs(await import("./adf-schema-US4NFHMR.mjs")).AgentConfigSchema;
5787
+ let schema = cjs(await import("./adf-schema-AIPBQTR2.mjs")).AgentConfigSchema;
5539
5788
  return schemaParse = (value) => schema.safeParse(value), schemaParse;
5540
5789
  }
5541
5790
  function changedKeysBetween(a, b) {
@@ -5740,6 +5989,10 @@ function TemplatesDialog({ overlay, close: closeOverlay, width, height }) {
5740
5989
  }, openReview = async (id, back) => {
5741
5990
  let r = await act("Review", back, () => store.client.templateReview(id));
5742
5991
  if (r) {
5992
+ if (r.identityReady === !1) {
5993
+ close(), openIdentity(store, { mode: "status", then: TEMPLATES_OVERLAY, thenProps: { id }, reason: `Review: ${r.message ?? "Set up the owner identity first."}` });
5994
+ return;
5995
+ }
5743
5996
  if (!r.needsReview) {
5744
5997
  await refresh2(), done(`${label2(id)} is already reviewed`, back);
5745
5998
  return;
@@ -5780,8 +6033,8 @@ function TemplatesDialog({ overlay, close: closeOverlay, width, height }) {
5780
6033
  }
5781
6034
  await act("Save notes", { kind: "notes", id }, () => store.client.updateTemplate(id, { description, warning })) && (await refresh2(id), done(`Notes of ${label2(id)} saved`, { kind: "detail", id }));
5782
6035
  }, edit = async (id, what, back) => {
5783
- let suspend = (run) => suspendTerminal(async () => {
5784
- await run();
6036
+ let suspend = (run2) => suspendTerminal(async () => {
6037
+ await run2();
5785
6038
  });
5786
6039
  (what === "instructions" ? await editTemplateInstructions(store.actions, suspend, id) : what === "config" ? await editTemplateConfig(store.actions, suspend, id) : await editTemplateSeedFile(store.actions, suspend, id, what)) === "saved" && await refresh2(id), setStep(back);
5787
6040
  }, removeExtra = async (id, path) => {
@@ -5881,290 +6134,140 @@ function TemplatesDialog({ overlay, close: closeOverlay, width, height }) {
5881
6134
  errorLine
5882
6135
  ] });
5883
6136
  }
5884
- if (step.kind === "review")
5885
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: `Review ${theme2.glyph.sep} ${label2(step.id)} template`, width: dialogWidth, hints: [{ keys: "y enter", label: "claim + accept" }, { keys: "up down", label: "scroll" }, { keys: "n esc", label: "cancel" }], children: [
5886
- step.review ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LinesView, { lines: reviewLines(step.review.summary, inner), width: inner, height: fitLines(reviewLines(step.review.summary, inner).length), keyLayer: "overlay" }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading the template\u2026" }),
5887
- errorLine
5888
- ] });
5889
- if (step.kind === "files") {
5890
- let contents = detail?.template?.id === step.id ? detail.contents : null, rows = contents ? [
5891
- ...SEED_FILES.map((f) => ({ path: f.path, seed: f.key, size: Buffer.byteLength(contents.files[f.key] ?? "", "utf-8") })),
5892
- ...contents.extra.map((f) => ({ path: f.path, size: f.size }))
5893
- ] : [];
5894
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: `Files ${theme2.glyph.sep} ${label2(step.id)}`, width: dialogWidth, hints: [{ keys: "enter", label: "edit in $EDITOR" }, { keys: "d", label: "remove extra" }, { keys: "esc", label: "back" }], children: [
5895
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.muted, wrap: "wrap", children: "Starting content for a new agent's files. Extra files are copied into every new agent (add them in Studio)." }),
5896
- contents ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5897
- List,
5898
- {
5899
- items: rows,
5900
- getKey: (r) => r.path,
5901
- height: Math.max(3, Math.min(rows.length, bodyHeight - 2)),
5902
- width: inner,
5903
- keyLayer: "overlay",
5904
- selectedIndex: Math.min(fileIndex, rows.length - 1),
5905
- onSelectedIndexChange: setFileIndex,
5906
- onSubmit: (r) => {
5907
- r.seed ? edit(step.id, r.seed, step) : store.actions.toast(`${r.path} is an extra file: editing it is not supported here (d removes it)`, "info");
5908
- },
5909
- onKey: (input, _key, r) => input === "d" && r ? (r.seed ? store.actions.toast(`${r.path} is a seed file: clear its text instead`, "info") : removeExtra(step.id, r.path), !0) : !1,
5910
- renderItem: (r, { selected: selected2 }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { wrap: "truncate-end", inverse: theme2.mono && selected2, children: [
5911
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: selected2 ? theme2.color.accent : theme2.color.dim, children: [
5912
- selected2 ? theme2.glyph.pointer : " ",
5913
- " "
5914
- ] }),
5915
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { bold: selected2, color: selected2 ? theme2.color.accent : theme2.color.text, children: truncate(r.path, 30).padEnd(31) }),
5916
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.muted, children: (r.seed && r.size === 0 ? "empty" : formatSize(r.size)).padEnd(10) }),
5917
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, children: r.seed ? "seed file" : "extra file" })
5918
- ] })
5919
- }
5920
- ) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading files\u2026" }),
5921
- errorLine
5922
- ] });
5923
- }
5924
- if (step.kind === "detail") {
5925
- let t = byId(step.id), ready = detail && detail.template?.id === step.id, hints2 = [
5926
- { keys: "e", label: "instructions" },
5927
- { keys: "f", label: "files" },
5928
- { keys: "c", label: "config" },
5929
- { keys: "t", label: "notes" },
5930
- ...t && !t.reviewed ? [{ keys: "a", label: "review" }] : [{ keys: "s", label: "default" }],
5931
- { keys: "m", label: "rename" },
5932
- { keys: "u", label: "duplicate" },
5933
- ...t?.shipped ? [{ keys: "x", label: "reset" }] : [{ keys: "d", label: "delete" }],
5934
- { keys: "esc", label: props.id ? "close" : "back" }
5935
- ];
5936
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: `${t?.name ?? step.id} template${t && list?.defaultId === t.id ? ` ${theme2.glyph.sep} default` : ""}${t?.shipped ? ` ${theme2.glyph.sep} shipped` : ""}`, width: dialogWidth, hints: hints2, children: [
5937
- t && !t.reviewed ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: theme2.color.warn, wrap: "wrap", children: [
5938
- theme2.glyph.warn,
5939
- " Not reviewed: someone else's template. a reviews it; new agents cannot start from it until you accept."
5940
- ] }) : null,
5941
- !ready && !error ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading the template\u2026" }) : null,
5942
- ready ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LinesView, { lines: detailLines(detail, inner), width: inner, height: fitLines(detailLines(detail, inner).length), keyLayer: "overlay" }) : null,
5943
- errorLine
5944
- ] });
5945
- }
5946
- let selected = templates[Math.min(index, Math.max(0, templates.length - 1))], nameWidth = Math.min(26, Math.max(12, ...templates.map((t) => t.name.length + 1))), hints = [
5947
- { keys: "enter", label: "details" },
5948
- { keys: "n", label: "new" },
5949
- { keys: "u", label: "duplicate" },
5950
- { keys: "s", label: "default" },
5951
- ...selected && !selected.reviewed ? [{ keys: "a", label: "review" }] : [],
5952
- { keys: "d", label: "delete" },
5953
- { keys: "esc", label: "close" }
5954
- ];
5955
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: "Agent templates", width: dialogWidth, hints: loadError ? [...loadError.code === "identity_not_ready" ? [{ keys: "i", label: "owner identity" }] : [], { keys: "r", label: "retry" }, { keys: "esc", label: "close" }] : hints, children: [
5956
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.muted, wrap: "truncate-end", children: "Every agent you create starts from one of these. /new picks one." }),
5957
- loadError ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: loadError.code === "identity_not_ready" ? theme2.color.warn : theme2.color.error, wrap: "wrap", children: [
5958
- theme2.glyph.cross,
5959
- " ",
5960
- templateErrorText(loadError.code, loadError.message)
5961
- ] }) : null,
5962
- !list && !loadError ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading templates\u2026" }) : null,
5963
- list ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
5964
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5965
- List,
5966
- {
5967
- items: templates,
5968
- getKey: (t) => t.id,
5969
- height: Math.max(3, Math.min(templates.length || 1, height - (compact2 ? 11 : 16))),
5970
- width: inner,
5971
- keyLayer: "overlay",
5972
- selectedIndex: Math.min(index, Math.max(0, templates.length - 1)),
5973
- onSelectedIndexChange: setIndex,
5974
- onSubmit: (t) => {
5975
- setError(null), setStep({ kind: "detail", id: t.id });
5976
- },
5977
- filter: (t, q) => `${t.name} ${t.id} ${blurb(t)}`.toLowerCase().includes(q.toLowerCase()),
5978
- emptyText: "No templates in the folder: n creates one.",
5979
- onKey: (input, _key, t) => input === "n" ? (openName("new"), !0) : input === "r" ? (refresh2(), !0) : t ? templateKey(input, t, { kind: "list" }) : !1,
5980
- renderItem: (t, { selected: current }) => {
5981
- let tags = templateTags(t, list.defaultId);
5982
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { wrap: "truncate-end", inverse: theme2.mono && current, children: [
5983
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: current ? theme2.color.accent : theme2.color.dim, children: [
5984
- current ? theme2.glyph.pointer : " ",
5985
- " "
5986
- ] }),
5987
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { bold: current, color: current ? theme2.color.accent : theme2.color.text, children: truncate(t.name, nameWidth - 1).padEnd(nameWidth) }),
5988
- tags.map((tag, i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: tag.tone === "accent" ? theme2.color.accent : tag.tone === "warn" ? theme2.color.warn : tag.tone === "dim" ? theme2.color.dim : theme2.color.muted, children: `[${tag.text}] ` }, i)),
5989
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, children: blurb(t) })
5990
- ] });
5991
- }
5992
- }
5993
- ),
5994
- selected ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
5995
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { wrap: "wrap", color: theme2.color.text, children: blurb(selected) || /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, children: "No description." }) }),
5996
- selected.warning ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { wrap: "wrap", color: theme2.color.warn, children: [
5997
- theme2.glyph.warn,
5998
- " ",
5999
- selected.warning
6000
- ] }) : null,
6001
- compact2 ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { wrap: "truncate-end", color: theme2.color.dim, children: [selected.modelProvider, selected.modelId].filter(Boolean).join(" / ") || "no provider set: the default provider fills in" })
6002
- ] }) : null,
6003
- compact2 ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
6004
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, wrap: "truncate-end", children: INSTANTIATE_RULE }),
6005
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: theme2.color.dim, wrap: "truncate-end", children: [
6006
- "Folder: ",
6007
- list.folder
6008
- ] })
6009
- ] })
6010
- ] }) : null,
6011
- errorLine
6012
- ] });
6013
- }
6014
-
6015
- // src/main/tui/skills/SkillsDialog.tsx
6016
- var import_react23 = __toESM(require_react(), 1);
6017
-
6018
- // src/shared/utils/skills-panel.ts
6019
- var skills_panel_exports = {};
6020
- __export(skills_panel_exports, {
6021
- MAX_CATALOG_SOURCES: () => MAX_CATALOG_SOURCES,
6022
- MAX_REGISTRY_BYTES: () => MAX_REGISTRY_BYTES,
6023
- MAX_SKILLS: () => MAX_SKILLS,
6024
- MAX_SKILL_FILE_BYTES: () => MAX_SKILL_FILE_BYTES,
6025
- SKILLS_REGISTRY_PATH: () => SKILLS_REGISTRY_PATH,
6026
- SKILLS_STATE_PATH: () => SKILLS_STATE_PATH,
6027
- SKILL_MANIFEST: () => SKILL_MANIFEST,
6028
- addCatalogSource: () => addCatalogSource,
6029
- catalogSourceLabel: () => catalogSourceLabel,
6030
- estimateTokens: () => estimateTokens,
6031
- filterCatalogEntries: () => filterCatalogEntries,
6032
- installedSkillNames: () => installedSkillNames,
6033
- isCatalogUrl: () => isCatalogUrl,
6034
- mergeCatalogResults: () => mergeCatalogResults,
6035
- mergeDisabledList: () => mergeDisabledList,
6036
- normalizeCatalogSources: () => normalizeCatalogSources,
6037
- parseSkillsRegistry: () => parseSkillsRegistry,
6038
- sanitizeDisplayText: () => sanitizeDisplayText,
6039
- skillProblems: () => skillProblems,
6040
- unindexedHint: () => unindexedHint
6041
- });
6042
- var SKILLS_REGISTRY_PATH = "skills-registry.json", SKILLS_STATE_PATH = "skills-state.json", SKILL_MANIFEST = /^skills\/([^/]+)\/SKILL\.md$/, MAX_SKILLS = 48, MAX_REGISTRY_BYTES = 32 * 1024, MAX_SKILL_FILE_BYTES = 256 * 1024;
6043
- function parseSkillsRegistry(text) {
6044
- if (!text) return null;
6045
- try {
6046
- let doc = JSON.parse(text);
6047
- if (doc?.schema !== 1 || typeof doc.skills != "object" || doc.skills === null) return null;
6048
- let entries = [];
6049
- for (let value of Object.values(doc.skills)) {
6050
- let skill = value;
6051
- typeof skill?.name != "string" || typeof skill?.path != "string" || entries.push({
6052
- name: skill.name,
6053
- description: typeof skill.description == "string" ? skill.description : void 0,
6054
- path: skill.path,
6055
- enabled: skill.enabled !== !1
6056
- });
6057
- }
6058
- let rejected = [];
6059
- if (Array.isArray(doc.rejected))
6060
- for (let raw of doc.rejected) {
6061
- let row4 = raw;
6062
- typeof row4?.path != "string" || typeof row4?.reason != "string" || rejected.push({ path: row4.path, reason: row4.reason, name: SKILL_MANIFEST.exec(row4.path)?.[1] ?? null });
6063
- }
6064
- return { entries: entries.sort((a, b) => a.name.localeCompare(b.name)), rejected };
6065
- } catch {
6066
- return null;
6067
- }
6068
- }
6069
- function mergeDisabledList(existingText, name, enabled) {
6070
- let doc = {};
6071
- if (existingText)
6072
- try {
6073
- let parsed = JSON.parse(existingText);
6074
- parsed && typeof parsed == "object" && !Array.isArray(parsed) && (doc = parsed);
6075
- } catch {
6076
- }
6077
- let current = Array.isArray(doc.disabled) ? doc.disabled.filter((entry) => typeof entry == "string") : [], next = new Set(current);
6078
- return enabled ? next.delete(name) : next.add(name), JSON.stringify({ ...doc, schema: 1, disabled: [...next].sort() }, null, 2) + `
6079
- `;
6080
- }
6081
- var UNSAFE_DISPLAY_CHARS = /[\u0000-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
6082
- function sanitizeDisplayText(value) {
6083
- return value ? value.replace(UNSAFE_DISPLAY_CHARS, " ").replace(/\s+/g, " ").trim() : "";
6084
- }
6085
- function isCatalogUrl(value) {
6086
- let trimmed = value.trim();
6087
- if (!trimmed) return !1;
6088
- try {
6089
- return new URL(trimmed).protocol === "https:";
6090
- } catch {
6091
- return !1;
6092
- }
6093
- }
6094
- function estimateTokens(bytes) {
6095
- return Math.ceil(bytes / 4);
6096
- }
6097
- var MAX_CATALOG_SOURCES = 8;
6098
- function normalizeCatalogSources(stored) {
6099
- if (!Array.isArray(stored)) return [ADF_SKILLS_REGISTRY_URL];
6100
- let sources2 = [], seen = /* @__PURE__ */ new Set();
6101
- for (let raw of stored) {
6102
- if (typeof raw != "string") continue;
6103
- let url2 = raw.trim();
6104
- if (!(!isCatalogUrl(url2) || seen.has(url2)) && (seen.add(url2), sources2.push(url2), sources2.length >= MAX_CATALOG_SOURCES))
6105
- break;
6106
- }
6107
- return sources2;
6108
- }
6109
- function addCatalogSource(sources2, raw) {
6110
- let url2 = raw.trim();
6111
- return url2 ? isCatalogUrl(url2) ? sources2.includes(url2) ? { ok: !1, error: "That source is already listed." } : sources2.length >= MAX_CATALOG_SOURCES ? { ok: !1, error: `At most ${MAX_CATALOG_SOURCES} catalog sources.` } : { ok: !0, sources: [...sources2, url2] } : { ok: !1, error: "A catalog source must be an https:// URL." } : { ok: !1, error: "Enter a catalog URL." };
6112
- }
6113
- function catalogSourceLabel(url2, publisher) {
6114
- let named = sanitizeDisplayText(publisher);
6115
- if (named) return named;
6116
- try {
6117
- return new URL(url2).hostname;
6118
- } catch {
6119
- return url2;
6120
- }
6121
- }
6122
- function mergeCatalogResults(results) {
6123
- let merged = [], seen = /* @__PURE__ */ new Set();
6124
- for (let result of results) {
6125
- if (!result.ok) continue;
6126
- let sourceLabel = catalogSourceLabel(result.url, result.publisher);
6127
- for (let entry of result.entries)
6128
- seen.has(entry.name) || (seen.add(entry.name), merged.push({ ...entry, sourceUrl: result.url, sourceLabel }));
6129
- }
6130
- return merged.sort((a, b) => a.name.localeCompare(b.name));
6131
- }
6132
- function filterCatalogEntries(entries, query) {
6133
- let needle = query.trim().toLowerCase();
6134
- if (!needle) return entries;
6135
- let byName2 = [], byDescription = [];
6136
- for (let entry of entries)
6137
- entry.name.toLowerCase().includes(needle) ? byName2.push(entry) : entry.description.toLowerCase().includes(needle) && byDescription.push(entry);
6138
- return [...byName2, ...byDescription];
6139
- }
6140
- function installedSkillNames(files2) {
6141
- let names = /* @__PURE__ */ new Set();
6142
- for (let file of files2) {
6143
- let match = SKILL_MANIFEST.exec(file.path);
6144
- match && names.add(match[1]);
6137
+ if (step.kind === "review")
6138
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: `Review ${theme2.glyph.sep} ${label2(step.id)} template`, width: dialogWidth, hints: [{ keys: "y enter", label: "claim + accept" }, { keys: "up down", label: "scroll" }, { keys: "n esc", label: "cancel" }], children: [
6139
+ step.review?.summary ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LinesView, { lines: reviewLines(step.review.summary, inner), width: inner, height: fitLines(reviewLines(step.review.summary, inner).length), keyLayer: "overlay" }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading the template\u2026" }),
6140
+ errorLine
6141
+ ] });
6142
+ if (step.kind === "files") {
6143
+ let contents = detail?.template?.id === step.id ? detail.contents : null, rows = contents ? [
6144
+ ...SEED_FILES.map((f) => ({ path: f.path, seed: f.key, size: Buffer.byteLength(contents.files[f.key] ?? "", "utf-8") })),
6145
+ ...contents.extra.map((f) => ({ path: f.path, size: f.size }))
6146
+ ] : [];
6147
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: `Files ${theme2.glyph.sep} ${label2(step.id)}`, width: dialogWidth, hints: [{ keys: "enter", label: "edit in $EDITOR" }, { keys: "d", label: "remove extra" }, { keys: "esc", label: "back" }], children: [
6148
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.muted, wrap: "wrap", children: "Starting content for a new agent's files. Extra files are copied into every new agent (add them in Studio)." }),
6149
+ contents ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
6150
+ List,
6151
+ {
6152
+ items: rows,
6153
+ getKey: (r) => r.path,
6154
+ height: Math.max(3, Math.min(rows.length, bodyHeight - 2)),
6155
+ width: inner,
6156
+ keyLayer: "overlay",
6157
+ selectedIndex: Math.min(fileIndex, rows.length - 1),
6158
+ onSelectedIndexChange: setFileIndex,
6159
+ onSubmit: (r) => {
6160
+ r.seed ? edit(step.id, r.seed, step) : store.actions.toast(`${r.path} is an extra file: editing it is not supported here (d removes it)`, "info");
6161
+ },
6162
+ onKey: (input, _key, r) => input === "d" && r ? (r.seed ? store.actions.toast(`${r.path} is a seed file: clear its text instead`, "info") : removeExtra(step.id, r.path), !0) : !1,
6163
+ renderItem: (r, { selected: selected2 }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { wrap: "truncate-end", inverse: theme2.mono && selected2, children: [
6164
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: selected2 ? theme2.color.accent : theme2.color.dim, children: [
6165
+ selected2 ? theme2.glyph.pointer : " ",
6166
+ " "
6167
+ ] }),
6168
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { bold: selected2, color: selected2 ? theme2.color.accent : theme2.color.text, children: truncate(r.path, 30).padEnd(31) }),
6169
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.muted, children: (r.seed && r.size === 0 ? "empty" : formatSize(r.size)).padEnd(10) }),
6170
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, children: r.seed ? "seed file" : "extra file" })
6171
+ ] })
6172
+ }
6173
+ ) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading files\u2026" }),
6174
+ errorLine
6175
+ ] });
6145
6176
  }
6146
- return names;
6147
- }
6148
- function skillProblems(installedNames, registry) {
6149
- if (!registry) return [];
6150
- let indexed = new Set(registry.entries.map((s) => s.name)), rows = /* @__PURE__ */ new Map();
6151
- for (let name of installedNames)
6152
- indexed.has(name) || rows.set(name, { label: name, path: `skills/${name}/SKILL.md`, reason: null, isPackage: !0 });
6153
- for (let rejection of registry.rejected) {
6154
- let key = rejection.name ?? rejection.path;
6155
- rejection.name && indexed.has(rejection.name) || rows.set(key, {
6156
- label: rejection.name ?? rejection.path,
6157
- path: rejection.path,
6158
- reason: rejection.reason,
6159
- isPackage: rejection.name !== null
6160
- });
6177
+ if (step.kind === "detail") {
6178
+ let t = byId(step.id), ready = detail && detail.template?.id === step.id, hints2 = [
6179
+ { keys: "e", label: "instructions" },
6180
+ { keys: "f", label: "files" },
6181
+ { keys: "c", label: "config" },
6182
+ { keys: "t", label: "notes" },
6183
+ ...t && !t.reviewed ? [{ keys: "a", label: "review" }] : [{ keys: "s", label: "default" }],
6184
+ { keys: "m", label: "rename" },
6185
+ { keys: "u", label: "duplicate" },
6186
+ ...t?.shipped ? [{ keys: "x", label: "reset" }] : [{ keys: "d", label: "delete" }],
6187
+ { keys: "esc", label: props.id ? "close" : "back" }
6188
+ ];
6189
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: `${t?.name ?? step.id} template${t && list?.defaultId === t.id ? ` ${theme2.glyph.sep} default` : ""}${t?.shipped ? ` ${theme2.glyph.sep} shipped` : ""}`, width: dialogWidth, hints: hints2, children: [
6190
+ t && !t.reviewed ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: theme2.color.warn, wrap: "wrap", children: [
6191
+ theme2.glyph.warn,
6192
+ " Not reviewed: someone else's template. a reviews it; new agents cannot start from it until you accept."
6193
+ ] }) : null,
6194
+ !ready && !error ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading the template\u2026" }) : null,
6195
+ ready ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LinesView, { lines: detailLines(detail, inner), width: inner, height: fitLines(detailLines(detail, inner).length), keyLayer: "overlay" }) : null,
6196
+ errorLine
6197
+ ] });
6161
6198
  }
6162
- return [...rows.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, row4]) => ({ key, ...row4 }));
6163
- }
6164
- function unindexedHint() {
6165
- return `not indexed \u2014 the folder name must be lowercase kebab-case and match the frontmatter name, description must be one line under 500 characters, and the file must be under ${Math.round(MAX_SKILL_FILE_BYTES / 1024)} KB. The catalog also stops at ${MAX_SKILLS} skills or ${Math.round(MAX_REGISTRY_BYTES / 1024)} KB, whichever comes first.`;
6199
+ let selected = templates[Math.min(index, Math.max(0, templates.length - 1))], nameWidth = Math.min(26, Math.max(12, ...templates.map((t) => t.name.length + 1))), hints = [
6200
+ { keys: "enter", label: "details" },
6201
+ { keys: "n", label: "new" },
6202
+ { keys: "u", label: "duplicate" },
6203
+ { keys: "s", label: "default" },
6204
+ ...selected && !selected.reviewed ? [{ keys: "a", label: "review" }] : [],
6205
+ { keys: "d", label: "delete" },
6206
+ { keys: "esc", label: "close" }
6207
+ ];
6208
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Modal, { title: "Agent templates", width: dialogWidth, hints: loadError ? [...loadError.code === "identity_not_ready" ? [{ keys: "i", label: "owner identity" }] : [], { keys: "r", label: "retry" }, { keys: "esc", label: "close" }] : hints, children: [
6209
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.muted, wrap: "truncate-end", children: "Every agent you create starts from one of these. /new picks one." }),
6210
+ loadError ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: loadError.code === "identity_not_ready" ? theme2.color.warn : theme2.color.error, wrap: "wrap", children: [
6211
+ theme2.glyph.cross,
6212
+ " ",
6213
+ templateErrorText(loadError.code, loadError.message)
6214
+ ] }) : null,
6215
+ !list && !loadError ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner, { label: "reading templates\u2026" }) : null,
6216
+ list ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
6217
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
6218
+ List,
6219
+ {
6220
+ items: templates,
6221
+ getKey: (t) => t.id,
6222
+ height: Math.max(3, Math.min(templates.length || 1, height - (compact2 ? 11 : 16))),
6223
+ width: inner,
6224
+ keyLayer: "overlay",
6225
+ selectedIndex: Math.min(index, Math.max(0, templates.length - 1)),
6226
+ onSelectedIndexChange: setIndex,
6227
+ onSubmit: (t) => {
6228
+ setError(null), setStep({ kind: "detail", id: t.id });
6229
+ },
6230
+ filter: (t, q) => `${t.name} ${t.id} ${blurb(t)}`.toLowerCase().includes(q.toLowerCase()),
6231
+ emptyText: "No templates in the folder: n creates one.",
6232
+ onKey: (input, _key, t) => input === "n" ? (openName("new"), !0) : input === "r" ? (refresh2(), !0) : t ? templateKey(input, t, { kind: "list" }) : !1,
6233
+ renderItem: (t, { selected: current }) => {
6234
+ let tags = templateTags(t, list.defaultId);
6235
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { wrap: "truncate-end", inverse: theme2.mono && current, children: [
6236
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: current ? theme2.color.accent : theme2.color.dim, children: [
6237
+ current ? theme2.glyph.pointer : " ",
6238
+ " "
6239
+ ] }),
6240
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { bold: current, color: current ? theme2.color.accent : theme2.color.text, children: truncate(t.name, nameWidth - 1).padEnd(nameWidth) }),
6241
+ tags.map((tag, i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: tag.tone === "accent" ? theme2.color.accent : tag.tone === "warn" ? theme2.color.warn : tag.tone === "dim" ? theme2.color.dim : theme2.color.muted, children: `[${tag.text}] ` }, i)),
6242
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, children: blurb(t) })
6243
+ ] });
6244
+ }
6245
+ }
6246
+ ),
6247
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
6248
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { wrap: "wrap", color: theme2.color.text, children: blurb(selected) || /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, children: "No description." }) }),
6249
+ selected.warning ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { wrap: "wrap", color: theme2.color.warn, children: [
6250
+ theme2.glyph.warn,
6251
+ " ",
6252
+ selected.warning
6253
+ ] }) : null,
6254
+ compact2 ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { wrap: "truncate-end", color: theme2.color.dim, children: [selected.modelProvider, selected.modelId].filter(Boolean).join(" / ") || "no provider set: the default provider fills in" })
6255
+ ] }) : null,
6256
+ compact2 ? null : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Box_default, { marginTop: 1, flexDirection: "column", children: [
6257
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Text, { color: theme2.color.dim, wrap: "truncate-end", children: INSTANTIATE_RULE }),
6258
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Text, { color: theme2.color.dim, wrap: "truncate-end", children: [
6259
+ "Folder: ",
6260
+ list.folder
6261
+ ] })
6262
+ ] })
6263
+ ] }) : null,
6264
+ errorLine
6265
+ ] });
6166
6266
  }
6167
6267
 
6268
+ // src/main/tui/skills/SkillsDialog.tsx
6269
+ var import_react23 = __toESM(require_react(), 1);
6270
+
6168
6271
  // src/shared/utils/skill-preview.ts
6169
6272
  var skill_preview_exports = {};
6170
6273
  __export(skill_preview_exports, {
@@ -6484,20 +6587,62 @@ function describeDiff(before, after, preview = 6) {
6484
6587
  return diff.changes.length > preview && lines.push(` \u2026 ${diff.changes.length - preview} more changed lines`), { summary, lines };
6485
6588
  }
6486
6589
 
6590
+ // src/shared/types/resource-revision.types.ts
6591
+ var resource_revision_types_exports = {};
6592
+ __export(resource_revision_types_exports, {
6593
+ describeWriter: () => describeWriter
6594
+ });
6595
+ function describeWriter(writer2) {
6596
+ if (!writer2) return "someone else";
6597
+ switch (writer2.kind) {
6598
+ case "agent":
6599
+ return "the agent";
6600
+ case "cli":
6601
+ return "the CLI or another API client";
6602
+ case "client":
6603
+ return writer2.client === "studio" ? "another Studio window" : writer2.client === "tui" ? "the terminal app" : `another client (${writer2.client})`;
6604
+ }
6605
+ }
6606
+
6607
+ // src/main/tui/util/revisions.ts
6608
+ var { describeWriter: describeWriter2 } = cjs(resource_revision_types_exports);
6609
+ function revisionOf(result) {
6610
+ let revision = result?.revision;
6611
+ return typeof revision == "number" ? revision : void 0;
6612
+ }
6613
+ function staleRevisionOf(err) {
6614
+ if (!(err instanceof DaemonError) || err.status !== 409) return null;
6615
+ let body = err.body;
6616
+ return body?.code !== "stale_revision" || typeof body.revision != "number" ? null : { revision: body.revision, writer: body.writer ?? null };
6617
+ }
6618
+ function changedBy(stale) {
6619
+ return `changed by ${describeWriter2(stale.writer)}`;
6620
+ }
6621
+ async function writeOver(write2) {
6622
+ try {
6623
+ return { ok: await write2() };
6624
+ } catch (err) {
6625
+ let stale = staleRevisionOf(err);
6626
+ if (stale) return { stale };
6627
+ throw err;
6628
+ }
6629
+ }
6630
+
6487
6631
  // src/main/tui/views/files/io.ts
6488
6632
  async function loadTarget(client, agentId, target2) {
6489
6633
  if (target2.kind === "document" || target2.kind === "mind") {
6490
- let text2 = (target2.kind === "document" ? await client.document(agentId) : await client.mind(agentId)).content ?? "";
6491
- return { target: target2, text: text2, binary: !1, mime: "text/markdown", size: Buffer.byteLength(text2, "utf-8") };
6634
+ let result = target2.kind === "document" ? await client.document(agentId) : await client.mind(agentId), text2 = result.content ?? "";
6635
+ return { target: target2, text: text2, binary: !1, mime: "text/markdown", size: Buffer.byteLength(text2, "utf-8"), revision: revisionOf(result) };
6492
6636
  }
6493
- let file = await client.file(agentId, target2.path), base2 = { target: target2, mime: file.mime_type, size: file.size, protection: file.protection, authorized: file.authorized, updatedAt: file.updated_at };
6637
+ let file = await client.file(agentId, target2.path), base2 = { target: target2, mime: file.mime_type, size: file.size, protection: file.protection, authorized: file.authorized, updatedAt: file.updated_at, revision: revisionOf(file) };
6494
6638
  if (file.encoding === "base64")
6495
6639
  return { ...base2, binary: !0, bytes: Buffer.from(file.content_base64 ?? "", "base64") };
6496
6640
  let text = file.content ?? "";
6497
6641
  return looksBinary(text) ? { ...base2, binary: !0, bytes: Buffer.from(text, "utf-8") } : { ...base2, text, binary: !1 };
6498
6642
  }
6499
- async function writeTarget(client, agentId, target2, text) {
6500
- target2.kind === "document" ? await client.putDocument(agentId, text) : target2.kind === "mind" ? await client.putMind(agentId, text) : await client.writeFile(agentId, target2.path, { content: text });
6643
+ async function writeTarget(client, agentId, target2, text, baseRevision) {
6644
+ let base2 = baseRevision === "absent" ? { mustNotExist: !0 } : baseRevision !== void 0 ? { baseRevision } : {}, agent2 = `/agents/${encodeURIComponent(agentId)}`;
6645
+ return target2.kind === "document" ? revisionOf(await client.request("PUT", `${agent2}/document`, { body: { content: text, ...base2 } })) : target2.kind === "mind" ? revisionOf(await client.request("PUT", `${agent2}/mind`, { body: { content: text, ...base2 } })) : revisionOf(await client.request("PUT", `${agent2}/files/content`, { query: { path: target2.path }, body: { content: text, ...base2 } }));
6501
6646
  }
6502
6647
  var pathCache = /* @__PURE__ */ new Map();
6503
6648
  function cacheFiles(agentId, files2) {
@@ -6549,13 +6694,13 @@ async function editTarget(deps, target2, options = {}) {
6549
6694
  }
6550
6695
  if (original.charCodeAt(0) !== 65279 && (edited = stripBom2(edited)), edited === original && !options.create)
6551
6696
  return await cleanup(), actions4.toast(`No changes to ${label2}`, "info"), "unchanged";
6552
- let concurrent = null;
6697
+ let concurrent = null, baseRevision = options.create ? "absent" : void 0;
6553
6698
  if (options.create)
6554
6699
  target2.kind === "file" && (await client.files(agentId).catch(() => null))?.files.some((f) => f.path === target2.path) && (concurrent = "was created by someone else while you were editing");
6555
6700
  else
6556
6701
  try {
6557
6702
  let current = await loadTarget(client, agentId, target2);
6558
- current.text !== original && (concurrent = `changed while you were editing (their change: ${describeDiff(original, current.text ?? "", 0).summary})`);
6703
+ baseRevision = current.revision, current.text !== original && (concurrent = `changed while you were editing (their change: ${describeDiff(original, current.text ?? "", 0).summary})`);
6559
6704
  } catch (err) {
6560
6705
  concurrent = err.status === 404 ? "was deleted while you were editing" : `could not be re-checked (${err instanceof Error ? err.message : String(err)})`;
6561
6706
  }
@@ -6565,195 +6710,44 @@ async function editTarget(deps, target2, options = {}) {
6565
6710
  ...diff.lines
6566
6711
  ].filter((line) => line !== null).join(`
6567
6712
  `);
6568
- return await actions4.confirm({
6713
+ if (!await actions4.confirm({
6569
6714
  title: concurrent ? `Concurrent change: ${label2}` : `${options.create ? "Create" : "Save"} ${label2}`,
6570
6715
  message,
6571
6716
  confirmLabel: concurrent ? "Overwrite" : options.create ? "Create" : "Write",
6572
6717
  cancelLabel: "Discard",
6573
6718
  danger: !!concurrent
6574
- }) ? await actions4.run(`Write ${label2}`, (c) => writeTarget(c, agentId, target2, edited).then(() => !0)) ? (await cleanup(), actions4.toast(`${options.create ? "Created" : "Saved"} ${label2} (${diff.summary})`, "success"), "written") : (actions4.toast(`Write failed. Your edited copy is kept at ${file}`, "error", 12e3), "failed") : (actions4.toast(`Not written. Your edited copy is kept at ${file}`, "warn", 12e3), "discarded");
6575
- }
6576
-
6577
- // src/main/utils/guarded-fetch.ts
6578
- var guarded_fetch_exports = {};
6579
- __export(guarded_fetch_exports, {
6580
- DEFAULT_DAEMON_PORT: () => DEFAULT_DAEMON_PORT,
6581
- MAX_FETCH_REDIRECTS: () => MAX_FETCH_REDIRECTS,
6582
- daemonGuardPort: () => daemonGuardPort,
6583
- guardedFetch: () => guardedFetch,
6584
- isFetchFailure: () => isFetchFailure
6585
- });
6586
- var DEFAULT_DAEMON_PORT = 7385, MAX_FETCH_REDIRECTS = 3;
6587
- function daemonGuardPort() {
6588
- return Number(process.env.ADF_DAEMON_PORT) || DEFAULT_DAEMON_PORT;
6589
- }
6590
- function isFetchFailure(result) {
6591
- return "error" in result;
6719
+ }))
6720
+ return actions4.toast(`Not written. Your edited copy is kept at ${file}`, "warn", 12e3), "discarded";
6721
+ let written = await writeOverConflicts(actions4, label2, file, (base2) => actions4.run(`Write ${label2}`, (c) => writeOver(() => writeTarget(c, agentId, target2, edited, base2))), baseRevision);
6722
+ return written === "reloaded" ? "discarded" : written ? (await cleanup(), actions4.toast(`${options.create ? "Created" : "Saved"} ${label2} (${diff.summary})`, "success"), "written") : (actions4.toast(`Write failed. Your edited copy is kept at ${file}`, "error", 12e3), "failed");
6592
6723
  }
6593
- async function readCapped(response, maxBytes) {
6594
- let body = response.body;
6595
- if (!body) return Buffer.alloc(0);
6596
- if (typeof body.getReader != "function") {
6597
- let bytes = Buffer.from(new Uint8Array(await response.arrayBuffer()));
6598
- return bytes.length > maxBytes ? { error: `exceeds ${maxBytes} bytes` } : bytes;
6599
- }
6600
- let reader2 = body.getReader(), chunks = [], total = 0;
6724
+ async function writeOverConflicts(actions4, label2, file, write2, baseRevision) {
6725
+ let base2 = baseRevision;
6601
6726
  for (; ; ) {
6602
- let { done, value } = await reader2.read();
6603
- if (done) break;
6604
- if (value) {
6605
- if (total += value.byteLength, total > maxBytes) {
6606
- try {
6607
- await reader2.cancel();
6608
- } catch {
6609
- }
6610
- return { error: `exceeds ${maxBytes} bytes` };
6611
- }
6612
- chunks.push(Buffer.from(value));
6613
- }
6614
- }
6615
- return Buffer.concat(chunks);
6616
- }
6617
- async function guardedFetch(url2, options) {
6618
- let { maxBytes, timeoutMs } = options, maxRedirects = options.maxRedirects ?? MAX_FETCH_REDIRECTS, daemonPort = daemonGuardPort(), current;
6619
- try {
6620
- current = new URL(url2).toString();
6621
- } catch {
6622
- return { error: `invalid URL: ${String(url2).slice(0, 200)}` };
6623
- }
6624
- let controller = new AbortController(), timer = setTimeout(() => controller.abort(), timeoutMs);
6625
- try {
6626
- for (let hop = 0; ; hop++) {
6627
- let protocol = new URL(current).protocol;
6628
- if (protocol !== "https:")
6629
- return { error: `only https URLs may be fetched (got "${protocol}")` };
6630
- let blocked = await checkFetchTarget(current, { allowLocal: !1, daemonPort });
6631
- if (blocked) return { error: blocked };
6632
- let response = await fetch(current, { signal: controller.signal, redirect: "manual" });
6633
- if (response.status >= 300 && response.status <= 399) {
6634
- let location = response.headers.get("location");
6635
- if (!location) return { error: `redirect without a location header (HTTP ${response.status})` };
6636
- if (hop >= maxRedirects) return { error: `too many redirects (>${maxRedirects})` };
6637
- try {
6638
- await response.body?.cancel();
6639
- } catch {
6640
- }
6641
- try {
6642
- current = new URL(location, current).toString();
6643
- } catch {
6644
- return { error: `redirect to an unusable location: ${location.slice(0, 200)}` };
6645
- }
6646
- continue;
6647
- }
6648
- if (!response.ok) return { error: `HTTP ${response.status} ${response.statusText}`.trim() };
6649
- let lengthHeader = response.headers.get("content-length"), declared = lengthHeader === null ? NaN : Number(lengthHeader);
6650
- if (Number.isFinite(declared) && declared > maxBytes) {
6651
- try {
6652
- await response.body?.cancel();
6653
- } catch {
6654
- }
6655
- return { error: `exceeds ${maxBytes} bytes` };
6656
- }
6657
- let bytes = await readCapped(response, maxBytes);
6658
- return Buffer.isBuffer(bytes) ? { bytes, contentType: response.headers.get("content-type") ?? "" } : bytes;
6659
- }
6660
- } catch (err) {
6661
- let message = err instanceof Error ? err.message : String(err);
6662
- return { error: err instanceof Error && err.name === "AbortError" ? `request timed out after ${timeoutMs}ms` : message };
6663
- } finally {
6664
- clearTimeout(timer);
6665
- }
6666
- }
6667
-
6668
- // src/shared/schemas/skills-catalog.schema.ts
6669
- var skills_catalog_schema_exports = {};
6670
- __export(skills_catalog_schema_exports, {
6671
- MAX_CATALOG_BYTES: () => MAX_CATALOG_BYTES,
6672
- MAX_PACKAGE_FILES: () => MAX_PACKAGE_FILES,
6673
- MAX_SKILL_PACKAGE_BYTES: () => MAX_SKILL_PACKAGE_BYTES,
6674
- SKILL_NAME: () => SKILL_NAME,
6675
- SkillCatalogEntrySchema: () => SkillCatalogEntrySchema,
6676
- parseSkillsCatalogDocument: () => parseSkillsCatalogDocument,
6677
- resolvePackageFilePath: () => resolvePackageFilePath
6678
- });
6679
- var SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/, PATH_SEGMENT = /^[A-Za-z0-9._-]+$/, MAX_PACKAGE_FILES = 32, MAX_CATALOG_BYTES = 512 * 1024, MAX_SKILL_PACKAGE_BYTES = 256 * 1024, UNSAFE_TEXT = /[\u0000-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/, safeText = (value) => !UNSAFE_TEXT.test(value), UNSAFE_TEXT_MESSAGE = "must not contain control or bidirectional-override characters";
6680
- function resolvePackageFilePath(name, raw) {
6681
- if (typeof raw != "string") return null;
6682
- let directory = `skills/${name}/`, candidate = raw.trim().replace(/\\/g, "/");
6683
- if (!candidate) return null;
6684
- for (; candidate.startsWith("./"); ) candidate = candidate.slice(2);
6685
- if (candidate.startsWith("/")) return null;
6686
- let relative2 = candidate.startsWith(directory) ? candidate.slice(directory.length) : candidate;
6687
- return !relative2 || relative2.split("/").some((segment) => segment === "." || segment === ".." || !PATH_SEGMENT.test(segment)) || relative2 === "SKILL.md" ? null : relative2;
6688
- }
6689
- var SkillPackageFileSchema = external_exports.object({
6690
- path: external_exports.string().min(1).refine(safeText, UNSAFE_TEXT_MESSAGE),
6691
- raw_url: external_exports.string().url()
6692
- }), SkillCatalogEntrySchema = external_exports.object({
6693
- name: external_exports.string().regex(SKILL_NAME),
6694
- description: external_exports.string().min(1).max(500).refine(safeText, UNSAFE_TEXT_MESSAGE),
6695
- /** Path inside the publishing repo — display only. */
6696
- path: external_exports.string().min(1).refine(safeText, UNSAFE_TEXT_MESSAGE).optional(),
6697
- /** Where the SKILL.md body is fetched from on install. */
6698
- raw_url: external_exports.string().url(),
6699
- /** Resources installed beside SKILL.md, package-relative after parsing. */
6700
- files: external_exports.array(SkillPackageFileSchema).max(MAX_PACKAGE_FILES).optional()
6701
- }).superRefine((entry, ctx) => {
6702
- if (!entry.files) return;
6703
- let seen = /* @__PURE__ */ new Set();
6704
- entry.files.forEach((file, index) => {
6705
- let relative2 = resolvePackageFilePath(entry.name, file.path);
6706
- if (!relative2) {
6707
- ctx.addIssue({
6708
- code: external_exports.ZodIssueCode.custom,
6709
- path: ["files", index, "path"],
6710
- message: `must stay inside skills/${entry.name}/ and cannot be SKILL.md`
6711
- });
6712
- return;
6713
- }
6714
- if (seen.has(relative2)) {
6715
- ctx.addIssue({
6716
- code: external_exports.ZodIssueCode.custom,
6717
- path: ["files", index, "path"],
6718
- message: "duplicate package path"
6719
- });
6720
- return;
6721
- }
6722
- seen.add(relative2);
6723
- });
6724
- }).transform((entry) => entry.files ? {
6725
- ...entry,
6726
- files: entry.files.map((file) => ({
6727
- ...file,
6728
- path: resolvePackageFilePath(entry.name, file.path)
6729
- }))
6730
- } : entry);
6731
- function parseSkillsCatalogDocument(json2) {
6732
- if (typeof json2 != "object" || json2 === null || Array.isArray(json2)) return null;
6733
- let doc = json2;
6734
- if (doc.schema !== void 0 && doc.schema !== 1 || !Array.isArray(doc.skills)) return null;
6735
- let entries = [], seen = /* @__PURE__ */ new Set(), dropped = 0;
6736
- for (let raw of doc.skills) {
6737
- let result = SkillCatalogEntrySchema.safeParse(raw);
6738
- if (!result.success || seen.has(result.data.name)) {
6739
- dropped++;
6740
- continue;
6727
+ let result = await write2(base2);
6728
+ if (!result) return;
6729
+ if ("ok" in result) return !0;
6730
+ let copy = typeof file == "string" ? ` (your edited copy stays at ${file})` : file ? " (your edited version is kept in a file)" : "";
6731
+ if (!await actions4.confirm({
6732
+ title: `${label2} changed while you were editing`,
6733
+ message: `${label2} was ${changedBy(result.stale)} after you opened it.
6734
+ Overwrite replaces their version with yours. Reload keeps theirs${copy}.`,
6735
+ confirmLabel: "Overwrite",
6736
+ cancelLabel: "Reload theirs",
6737
+ danger: !0
6738
+ })) {
6739
+ let kept = typeof file == "function" ? await file().catch(() => null) : file;
6740
+ return actions4.toast(`${label2} not written: kept their version${kept ? `. Your edited copy is at ${kept}` : ""}`, "warn", 12e3), "reloaded";
6741
6741
  }
6742
- seen.add(result.data.name), entries.push(result.data);
6742
+ base2 = result.stale.revision;
6743
6743
  }
6744
- return {
6745
- entries,
6746
- publisher: typeof doc.publisher == "string" ? doc.publisher : void 0,
6747
- repository: typeof doc.repository == "string" ? doc.repository : void 0,
6748
- dropped
6749
- };
6750
6744
  }
6751
6745
 
6752
6746
  // src/main/tui/skills/catalog.ts
6753
- var { guardedFetch: guardedFetch2 } = cjs(guarded_fetch_exports), { parseSkillsCatalogDocument: parseSkillsCatalogDocument2, MAX_CATALOG_BYTES: MAX_CATALOG_BYTES2, MAX_SKILL_PACKAGE_BYTES: MAX_SKILL_PACKAGE_BYTES2 } = cjs(skills_catalog_schema_exports), { normalizeCatalogSources: normalizeCatalogSources2 } = cjs(skills_panel_exports), SKILLS_FETCH_TIMEOUT_MS = 1e4, CATALOG_SOURCES_SETTING = "skillCatalogSources";
6747
+ var { guardedFetch } = cjs(guarded_fetch_exports), { parseSkillsCatalogDocument, MAX_CATALOG_BYTES, MAX_SKILL_PACKAGE_BYTES } = cjs(skills_catalog_schema_exports), { normalizeCatalogSources: normalizeCatalogSources2 } = cjs(skills_panel_exports), SKILLS_FETCH_TIMEOUT_MS = 1e4, CATALOG_SOURCES_SETTING = "skillCatalogSources";
6754
6748
  async function fetchCatalogDocument(url2) {
6755
6749
  if (typeof url2 != "string" || !/^https:\/\//.test(url2)) return { ok: !1, error: "Catalog URL must be https" };
6756
- let body = await guardedFetch2(url2, { maxBytes: MAX_CATALOG_BYTES2, timeoutMs: SKILLS_FETCH_TIMEOUT_MS });
6750
+ let body = await guardedFetch(url2, { maxBytes: MAX_CATALOG_BYTES, timeoutMs: SKILLS_FETCH_TIMEOUT_MS });
6757
6751
  if ("error" in body) return { ok: !1, error: body.error };
6758
6752
  let json2;
6759
6753
  try {
@@ -6761,12 +6755,12 @@ async function fetchCatalogDocument(url2) {
6761
6755
  } catch {
6762
6756
  return { ok: !1, error: "Catalog is not valid JSON" };
6763
6757
  }
6764
- let parsed = parseSkillsCatalogDocument2(json2);
6758
+ let parsed = parseSkillsCatalogDocument(json2);
6765
6759
  return parsed ? { ok: !0, entries: parsed.entries, publisher: parsed.publisher, dropped: parsed.dropped } : { ok: !1, error: "Unrecognized catalog schema" };
6766
6760
  }
6767
6761
  async function fetchSkillText(url2) {
6768
6762
  if (typeof url2 != "string" || !/^https:\/\//.test(url2)) return { ok: !1, error: "Package URL must be https" };
6769
- let body = await guardedFetch2(url2, { maxBytes: MAX_SKILL_PACKAGE_BYTES2, timeoutMs: SKILLS_FETCH_TIMEOUT_MS });
6763
+ let body = await guardedFetch(url2, { maxBytes: MAX_SKILL_PACKAGE_BYTES, timeoutMs: SKILLS_FETCH_TIMEOUT_MS });
6770
6764
  return "error" in body ? { ok: !1, error: body.error } : body.bytes.subarray(0, 8192).includes(0) ? { ok: !1, error: "SKILL.md is not text" } : { ok: !0, content: body.bytes.toString("utf8") };
6771
6765
  }
6772
6766
  var DEFAULT_SEAMS = { catalog: fetchCatalogDocument, text: fetchSkillText }, seams2 = DEFAULT_SEAMS;
@@ -6798,9 +6792,9 @@ var {
6798
6792
  installedSkillNames: installedSkillNames2,
6799
6793
  isCatalogUrl: isCatalogUrl2,
6800
6794
  parseSkillsRegistry: parseSkillsRegistry2,
6801
- sanitizeDisplayText: sanitizeDisplayText2,
6795
+ sanitizeDisplayText: sanitizeDisplayText3,
6802
6796
  skillProblems: skillProblems2
6803
- } = cjs(skills_panel_exports), { splitSkillDocument: splitSkillDocument2 } = cjs(skill_preview_exports), { SKILL_NAME: SKILL_NAME2 } = cjs(skills_catalog_schema_exports), SKILLS_OVERLAY = "skills", looksLikePath = (value) => /^(\.{1,2}([\\/]|$)|[\\/]|~([\\/]|$)|[A-Za-z]:[\\/])/.test(value) || /[\\/]/.test(value) || /\.md$/i.test(value);
6797
+ } = cjs(skills_panel_exports), { splitSkillDocument: splitSkillDocument2 } = cjs(skill_preview_exports), { SKILL_NAME } = cjs(skills_catalog_schema_exports), SKILLS_OVERLAY = "skills", looksLikePath = (value) => /^(\.{1,2}([\\/]|$)|[\\/]|~([\\/]|$)|[A-Za-z]:[\\/])/.test(value) || /[\\/]/.test(value) || /\.md$/i.test(value);
6804
6798
  function parseAddTarget2(raw) {
6805
6799
  let value = (raw ?? "").trim().replace(/^(["'])(.*)\1$/, "$2");
6806
6800
  if (!value) return { kind: "catalog", query: "" };
@@ -6851,7 +6845,7 @@ function settleOverrides(overrides, registryText) {
6851
6845
  function tokenLabel(tokens) {
6852
6846
  return tokens == null ? "" : tokens >= 1e3 ? `~${(tokens / 1e3).toFixed(tokens >= 1e4 ? 0 : 1)}k tok` : `~${tokens} tok`;
6853
6847
  }
6854
- var safe = (value) => sanitizeDisplayText2(value), packageKey = (source) => source.type === "catalog" ? `catalog:${source.entry.sourceUrl}#${source.entry.name}` : source.type === "url" ? `url:${source.url}` : `disk:${source.path}`, frontmatterField = (manifest, key) => splitSkillDocument2(manifest).fields.find((f) => f.key === key)?.value, isSkillName = (value) => !!value && SKILL_NAME2.test(value);
6848
+ var safe = (value) => sanitizeDisplayText3(value), packageKey = (source) => source.type === "catalog" ? `catalog:${source.entry.sourceUrl}#${source.entry.name}` : source.type === "url" ? `url:${source.url}` : `disk:${source.path}`, frontmatterField = (manifest, key) => splitSkillDocument2(manifest).fields.find((f) => f.key === key)?.value, isSkillName = (value) => !!value && SKILL_NAME.test(value);
6855
6849
  function manifestProblem(manifest) {
6856
6850
  return Buffer.byteLength(manifest, "utf-8") > MAX_SKILL_FILE_BYTES2 ? `SKILL.md is over ${Math.round(MAX_SKILL_FILE_BYTES2 / 1024)} KB, the indexer would reject it` : manifest.slice(0, 8192).includes("\0") ? "SKILL.md is not text" : null;
6857
6851
  }
@@ -6919,7 +6913,7 @@ function exactEntry(entries, query) {
6919
6913
  import { promises as fs3 } from "node:fs";
6920
6914
  import { homedir as homedir2 } from "node:os";
6921
6915
  import { basename as basename2, dirname, join as join3, relative, resolve as resolve2, sep } from "node:path";
6922
- var { SKILLS_REGISTRY_PATH: SKILLS_REGISTRY_PATH3, SKILLS_STATE_PATH: SKILLS_STATE_PATH2, mergeDisabledList: mergeDisabledList2, parseSkillsRegistry: parseSkillsRegistry3 } = cjs(skills_panel_exports), { MAX_PACKAGE_FILES: MAX_PACKAGE_FILES2, MAX_SKILL_PACKAGE_BYTES: MAX_SKILL_PACKAGE_BYTES3, resolvePackageFilePath: resolvePackageFilePath2 } = cjs(skills_catalog_schema_exports), errorText2 = (err) => err instanceof Error ? err.message : String(err), isNotFound = (err) => err?.status === 404;
6916
+ var { SKILLS_REGISTRY_PATH: SKILLS_REGISTRY_PATH3, SKILLS_STATE_PATH: SKILLS_STATE_PATH2, mergeDisabledList: mergeDisabledList2, parseSkillsRegistry: parseSkillsRegistry3 } = cjs(skills_panel_exports), { MAX_PACKAGE_FILES, MAX_SKILL_PACKAGE_BYTES: MAX_SKILL_PACKAGE_BYTES2, resolvePackageFilePath } = cjs(skills_catalog_schema_exports), errorText2 = (err) => err instanceof Error ? err.message : String(err), isNotFound = (err) => err?.status === 404;
6923
6917
  async function must(result, what) {
6924
6918
  let outcome = await result;
6925
6919
  if (outcome && outcome.success === !1) throw new Error(`${what} was refused (protected file?)`);
@@ -6951,10 +6945,10 @@ async function readSkillFile(client, agentId, path) {
6951
6945
  }
6952
6946
  var stateQueues = /* @__PURE__ */ new Map();
6953
6947
  function enqueue(agentId, task) {
6954
- let run = (stateQueues.get(agentId) ?? Promise.resolve()).then(task, task);
6955
- return stateQueues.set(agentId, run.then(() => {
6948
+ let run2 = (stateQueues.get(agentId) ?? Promise.resolve()).then(task, task);
6949
+ return stateQueues.set(agentId, run2.then(() => {
6956
6950
  }, () => {
6957
- })), run;
6951
+ })), run2;
6958
6952
  }
6959
6953
  function setSkillMuted(client, agentId, name, enabled) {
6960
6954
  return enqueue(agentId, async () => {
@@ -7047,7 +7041,7 @@ async function readLocalPackage(input, cwd = process.cwd()) {
7047
7041
  try {
7048
7042
  let manifestStat = await fs3.stat(manifestPath);
7049
7043
  if (!manifestStat.isFile()) return { ok: !1, error: `${manifestPath} is not a file` };
7050
- if (manifestStat.size > MAX_SKILL_PACKAGE_BYTES3) return { ok: !1, error: `SKILL.md is over ${Math.round(MAX_SKILL_PACKAGE_BYTES3 / 1024)} KB` };
7044
+ if (manifestStat.size > MAX_SKILL_PACKAGE_BYTES2) return { ok: !1, error: `SKILL.md is over ${Math.round(MAX_SKILL_PACKAGE_BYTES2 / 1024)} KB` };
7051
7045
  manifestBytes = await fs3.readFile(manifestPath);
7052
7046
  } catch {
7053
7047
  return { ok: !1, error: stat.isDirectory() ? `No SKILL.md in ${target2}` : `Cannot read ${target2}` };
@@ -7079,24 +7073,24 @@ async function readLocalPackage(input, cwd = process.cwd()) {
7079
7073
  if (!entry.isFile()) continue;
7080
7074
  let rel = relative(target2, full).split(sep).join("/");
7081
7075
  if (rel === "SKILL.md") continue;
7082
- let path = resolvePackageFilePath2(identity.name, rel);
7076
+ let path = resolvePackageFilePath(identity.name, rel);
7083
7077
  if (!path) {
7084
7078
  warnings.push(`${rel}: skipped (not a package path)`);
7085
7079
  continue;
7086
7080
  }
7087
- if (resources.length >= MAX_PACKAGE_FILES2) {
7081
+ if (resources.length >= MAX_PACKAGE_FILES) {
7088
7082
  skippedOver++;
7089
7083
  continue;
7090
7084
  }
7091
- if ((await fs3.stat(full)).size > MAX_SKILL_PACKAGE_BYTES3) {
7092
- warnings.push(`${rel}: skipped (over ${Math.round(MAX_SKILL_PACKAGE_BYTES3 / 1024)} KB)`);
7085
+ if ((await fs3.stat(full)).size > MAX_SKILL_PACKAGE_BYTES2) {
7086
+ warnings.push(`${rel}: skipped (over ${Math.round(MAX_SKILL_PACKAGE_BYTES2 / 1024)} KB)`);
7093
7087
  continue;
7094
7088
  }
7095
7089
  let bytes = await fs3.readFile(full);
7096
7090
  resources.push(bytes.subarray(0, 8192).includes(0) ? { path, contentBase64: bytes.toString("base64") } : { path, content: bytes.toString("utf-8") });
7097
7091
  }
7098
7092
  };
7099
- await walk(target2, 0), skippedOver && warnings.push(`${skippedOver} more file${skippedOver === 1 ? "" : "s"} skipped (a package ships at most ${MAX_PACKAGE_FILES2} beside SKILL.md)`);
7093
+ await walk(target2, 0), skippedOver && warnings.push(`${skippedOver} more file${skippedOver === 1 ? "" : "s"} skipped (a package ships at most ${MAX_PACKAGE_FILES} beside SKILL.md)`);
7100
7094
  }
7101
7095
  return {
7102
7096
  ok: !0,
@@ -7209,7 +7203,7 @@ function SkillsDialog({ overlay, close, width, height }) {
7209
7203
  actions: store.actions,
7210
7204
  agentId,
7211
7205
  agentLabel: who,
7212
- suspend: (run) => suspendTerminal(run)
7206
+ suspend: (run2) => suspendTerminal(run2)
7213
7207
  }, { kind: "file", path }), bump();
7214
7208
  }, install = async (pkg2, key) => {
7215
7209
  if (read().installs[key]?.status === "installing") return;
@@ -7425,7 +7419,7 @@ function SkillDoc({ manifest, width, height }) {
7425
7419
  var import_react24 = __toESM(require_react(), 1);
7426
7420
 
7427
7421
  // src/main/tui/context/model.ts
7428
- var { resolveLoopThreshold } = cjs(context_breakdown_exports), CONTEXT_OVERLAY = "context";
7422
+ var { resolveLoopThreshold, calibrationNote } = cjs(context_breakdown_exports), CONTEXT_OVERLAY = "context";
7429
7423
  function openContext(store, props) {
7430
7424
  store.actions.pushOverlay({ kind: CONTEXT_OVERLAY, props: { ...props } });
7431
7425
  }
@@ -7448,6 +7442,9 @@ var SOURCE_TEXT = {
7448
7442
  function thresholdText(r, fmt) {
7449
7443
  return `auto-compacts at ${fmt(r.compactThreshold)} (${SOURCE_TEXT[r.compactThresholdSource]})`;
7450
7444
  }
7445
+ function calibrationText(cal, approx, fmt) {
7446
+ return calibrationNote(cal, fmt, approx);
7447
+ }
7451
7448
  function contextRows(categories, expanded) {
7452
7449
  let rows = [];
7453
7450
  for (let category of categories) {
@@ -7594,7 +7591,7 @@ function ContextDialog({ overlay, close: closeOverlay, width, height }) {
7594
7591
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Box_default, { marginTop: 1, flexDirection: "column", height: Math.min(rows.length, rowsHeight), children: shown.map((row4, i) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RowLine, { row: row4, selectedId: selected?.id, total, labelWidth, rowBar, theme: theme2 }, `${start + i}`)) }),
7595
7592
  rows.length > rowsHeight ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Text, { color: theme2.color.dim, children: `${start + 1}-${Math.min(rows.length, start + rowsHeight)} of ${rows.length} rows` }) : null,
7596
7593
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Text, { color: theme2.color.muted, wrap: "truncate-end", children: selected ? `${selected.label}: ${selected.note}` : "" }) }),
7597
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Text, { color: theme2.color.dim, wrap: "truncate-end", children: s.compacting ? `Compacting ${s.loop}${g.ellipsis}` : s.loading ? `Re-measuring${g.ellipsis}` : s.measuredAt ? `Measured ${formatClock(s.measuredAt)} ${g.sep} conversation estimated, the rest tokenized` : "" })
7594
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Text, { color: theme2.color.dim, wrap: "truncate-end", children: s.compacting ? `Compacting ${s.loop}${g.ellipsis}` : s.loading ? `Re-measuring${g.ellipsis}` : s.measuredAt ? `Measured ${formatClock(s.measuredAt)} ${g.sep} ${calibrationText(s.data?.breakdown?.calibration, g.approx, formatCount)}` : "" })
7598
7595
  ] });
7599
7596
  }
7600
7597
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Modal, { title: `Context ${g.sep} ${truncate(header2, inner - 12)}`, width: dialogWidth, hints, onClose: close, children: body });
@@ -7602,7 +7599,7 @@ function ContextDialog({ overlay, close: closeOverlay, width, height }) {
7602
7599
  function RowLine({ row: row4, selectedId, total, labelWidth, rowBar, theme: theme2 }) {
7603
7600
  let g = theme2.glyph, share = (tokens) => total > 0 ? `${Math.round(tokens / total * 100)}%` : "0%";
7604
7601
  if (row4.kind === "category") {
7605
- let c = row4.category, isSel = c.id === selectedId, label2 = `${row4.expandable ? row4.expanded ? g.expanded : g.collapsed : " "} ${c.label}${c.count !== void 0 ? ` (${c.count})` : ""}`, bar = barText(c.tokens, total, rowBar, theme2.ascii);
7602
+ let c = row4.category, isSel = c.id === selectedId, label2 = `${row4.expandable ? row4.expanded ? g.expanded : g.collapsed : " "} ${c.label}${c.count !== void 0 ? ` (${c.count})` : ""}${c.basis !== "measured" ? ` ${g.approx}` : ""}`, bar = barText(c.tokens, total, rowBar, theme2.ascii);
7606
7603
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Text, { wrap: "truncate-end", inverse: theme2.mono && isSel, bold: isSel, children: [
7607
7604
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Text, { color: isSel ? theme2.color.accent : theme2.color.dim, children: [
7608
7605
  isSel ? g.pointer : " ",
@@ -8013,7 +8010,7 @@ var RUNTIME_VIEW = "runtime", RUNTIME_TABS = [
8013
8010
  { id: "status", title: "Status", description: "Daemon health, version, uptime and every agent\u2019s loops" },
8014
8011
  { id: "folders", title: "Folders", description: "Tracked agent folders: Enter its agents (review, load, errors), a add, d stop tracking, r rescan" },
8015
8012
  { id: "identity", title: "Identity", description: "Owner identity: status; Enter to create, restore, unlock or lock" },
8016
- { id: "auth", title: "Sign-in", description: "Provider sign-ins (ChatGPT, Grok) and API-key providers; Enter to sign in" },
8013
+ { id: "auth", title: "Sign-in", description: "Provider sign-ins (ChatGPT, Grok, Claude) and API-key providers; Enter to sign in" },
8017
8014
  { id: "providers", title: "Providers", description: "LLM providers and which agents use them" },
8018
8015
  { id: "usage", title: "Usage", description: "Token usage across agents, by model" },
8019
8016
  { id: "network", title: "Network", description: "Mesh, LAN, WebSocket; m mesh on/off, s server start/stop" },
@@ -8488,14 +8485,14 @@ var REPORTS = {
8488
8485
  title: "Auth",
8489
8486
  async load(client) {
8490
8487
  let d = await client.authStatus(), lines = [heading("Subscription sign-ins")];
8491
- for (let [name, status] of [["chatgpt", d.chatgpt], ["grok", d.grok]]) {
8492
- let s = status ?? {};
8493
- lines.push(indent([{ text: name.padEnd(9), tone: "key" }, s.authenticated ? { text: "signed in", tone: "success" } : { text: "not signed in", tone: "muted" }, { text: s.email ? ` ${String(s.email)}` : "", tone: "muted" }]));
8488
+ for (let [name, status] of [["chatgpt", d.chatgpt], ["grok", d.grok], ["claude", d.claude]]) {
8489
+ let s = status ?? {}, state = s.authenticated ? { text: "signed in", tone: "success" } : s.installed === !1 ? { text: "Claude Code not installed", tone: "warn" } : { text: "not signed in", tone: "muted" }, extra = [s.email ? String(s.email) : "", s.plan ? `${String(s.plan)} plan` : "", s.installed === !1 && s.installUrl ? String(s.installUrl) : ""].filter(Boolean).join(" \xB7 ");
8490
+ lines.push(indent([{ text: name.padEnd(9), tone: "key" }, state, { text: extra ? ` ${extra}` : "", tone: "muted" }]));
8494
8491
  }
8495
8492
  return lines.push(blank(), heading("API-key providers")), lines.push(...tableLines(
8496
8493
  ["id", "type", "key", "stored in"],
8497
8494
  d.providers.map((p) => [p.id, p.type, p.hasApiKey ? "set" : "missing", p.credentialStorage])
8498
- ).map(indent)), lines.push(blank(), heading("Sign in or out")), lines.push(indent(plain("Enter opens the sign-in dialog \xB7 /login chatgpt \xB7 /login grok \xB7 /logout <chatgpt|grok>", "accent"))), lines.push(indent(plain("From a shell: adf auth login <chatgpt|grok>", "muted"))), lines.push(indent(plain("API keys: Studio settings, stored by the daemon (never shown here).", "muted"))), { data: redactSecrets(d), lines };
8495
+ ).map(indent)), lines.push(blank(), heading("Sign in or out")), lines.push(indent(plain("Enter opens the sign-in dialog \xB7 /login chatgpt \xB7 /login grok \xB7 /login claude \xB7 /logout <chatgpt|grok|claude>", "accent"))), lines.push(indent(plain("From a shell: adf auth login <chatgpt|grok|claude>", "muted"))), lines.push(indent(plain("API keys: Studio settings, stored by the daemon (never shown here).", "muted"))), { data: redactSecrets(d), lines };
8499
8496
  }
8500
8497
  },
8501
8498
  network: {
@@ -8902,15 +8899,14 @@ function folderAgentsSummary(agents) {
8902
8899
  }
8903
8900
  async function loadFolderAgent(store, agent2, options = {}) {
8904
8901
  try {
8905
- options.accept && await store.client.acceptReview(agent2.filePath);
8906
- let ref = await store.client.load(agent2.filePath, !0), name = ref.config?.handle || ref.config?.name || agent2.name, started = "";
8902
+ let accepted = options.accept ? await store.client.acceptReview(agent2.filePath) : null, ref = await store.client.load(agent2.filePath, !0), name = ref.config?.handle || ref.config?.name || agent2.name, started = "";
8907
8903
  if (agent2.autostart)
8908
8904
  try {
8909
8905
  await store.client.start(ref.id), started = " and started it";
8910
8906
  } catch (err) {
8911
8907
  started = ` (start failed: ${err instanceof Error ? err.message : String(err)})`;
8912
8908
  }
8913
- return store.actions.toast(`${options.accept ? "Reviewed and loaded" : "Loaded"} ${name}${started}`, started.startsWith(" (") ? "warn" : "success", 5e3), writeFolders(store, {}, !0), await store.actions.refreshAgents(), store.actions.refreshTracked(), { ok: !0, agentId: ref.id };
8909
+ return store.actions.toast(`${options.accept ? "Reviewed and loaded" : "Loaded"} ${name}${started}${lockedNote(accepted?.lockedFields)}`, started.startsWith(" (") ? "warn" : "success", 6e3), writeFolders(store, {}, !0), await store.actions.refreshAgents(), store.actions.refreshTracked(), { ok: !0, agentId: ref.id };
8914
8910
  } catch (err) {
8915
8911
  err instanceof DaemonError && err.unreachable && store.dispatch({ type: "daemon/reachable", reachable: !1 });
8916
8912
  let error = err instanceof DaemonError && err.status === 403 ? "It needs review first." : err instanceof Error ? err.message : String(err);
@@ -10109,7 +10105,10 @@ function ItemBody({ item, width, expanded, showThinking, queued }) {
10109
10105
  g.dot,
10110
10106
  " "
10111
10107
  ] }),
10112
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Box_default, { width: w - 2, flexDirection: "column", children: item.text ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Markdown, { text: item.text, color: theme2.color.assistant }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { children: " " }) })
10108
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Box_default, { width: w - 2, flexDirection: "column", children: [
10109
+ item.text ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Markdown, { text: item.text, color: theme2.color.assistant }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { children: " " }),
10110
+ item.interrupted ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { color: theme2.color.dim, children: "[interrupted]" }) : null
10111
+ ] })
10113
10112
  ] });
10114
10113
  case "thinking": {
10115
10114
  let open2 = expanded || showThinking, lines = lineCount(item.text);
@@ -10133,7 +10132,7 @@ function ItemBody({ item, width, expanded, showThinking, queued }) {
10133
10132
  case "tool":
10134
10133
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ToolBody, { item, w, expanded });
10135
10134
  case "hil": {
10136
- let tone = item.status === "pending" ? theme2.color.warn : item.status === "approved" ? theme2.color.success : theme2.color.error;
10135
+ let tone = item.status === "pending" ? theme2.color.warn : item.status === "approved" || item.status === "completed" ? theme2.color.success : theme2.color.error;
10137
10136
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Box_default, { flexDirection: "column", width: w, borderStyle: theme2.ascii ? "classic" : "single", borderLeft: !0, borderRight: !1, borderTop: !1, borderBottom: !1, borderColor: tone, paddingLeft: 1, children: [
10138
10137
  /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { wrap: "truncate-end", children: [
10139
10138
  /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { bold: !0, color: tone, children: [
@@ -10183,6 +10182,34 @@ function ItemBody({ item, width, expanded, showThinking, queued }) {
10183
10182
  item.text
10184
10183
  ] });
10185
10184
  case "context": {
10185
+ if (item.ownerCommand) {
10186
+ let command = item.ownerCommand, tone = command.status === "running" ? theme2.color.tool : command.status === "ok" ? theme2.color.success : theme2.color.error;
10187
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Box_default, { flexDirection: "column", width: w, children: [
10188
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { wrap: "truncate-end", children: [
10189
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { color: tone, children: command.status === "running" ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Spinner, { color: tone }) : command.status === "ok" ? g.check : g.cross }),
10190
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { bold: !0, color: theme2.color.tool, children: [
10191
+ " You ran ",
10192
+ command.command
10193
+ ] }),
10194
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { color: tone, children: [
10195
+ " [",
10196
+ command.status === "denied" ? "permission denied" : command.status,
10197
+ "]"
10198
+ ] }),
10199
+ command.authorized && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { color: theme2.color.warn, children: " [authorized]" }),
10200
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { color: theme2.color.dim, children: [
10201
+ " ",
10202
+ item.unrecorded ? "[not recorded]" : item.deferred ? "[recording after current step]" : "",
10203
+ " ",
10204
+ expanded ? g.expanded : `${g.collapsed} Enter`
10205
+ ] })
10206
+ ] }),
10207
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
10208
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { wrap: "wrap", color: theme2.color.muted, children: item.text || (command.status === "running" ? "Running\u2026" : "(empty)") }),
10209
+ command.status === "denied" && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Text, { wrap: "wrap", color: theme2.color.warn, children: "Add !authorize to allow restricted calls for one run." })
10210
+ ] })
10211
+ ] });
10212
+ }
10186
10213
  let label2 = item.category === "compaction" ? "compacted history summary" : `context ${g.sep} ${item.category}`;
10187
10214
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Box_default, { flexDirection: "column", width: w, children: [
10188
10215
  /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(Text, { wrap: "truncate-end", color: theme2.color.dim, children: [
@@ -10674,6 +10701,7 @@ function stepLoop(ctx, delta) {
10674
10701
  }
10675
10702
  var chatCommands = {
10676
10703
  commands: [
10704
+ agentCommand,
10677
10705
  {
10678
10706
  name: "interrupt",
10679
10707
  aliases: ["abort"],
@@ -11665,7 +11693,7 @@ var import_react38 = __toESM(require_react(), 1);
11665
11693
  import { promises as fs4, rmSync, statSync as statSync3 } from "node:fs";
11666
11694
  import { tmpdir as tmpdir3 } from "node:os";
11667
11695
  import { join as join4 } from "node:path";
11668
- var copies = /* @__PURE__ */ new Map(), dirs = /* @__PURE__ */ new Set(), exitHook = !1, copyKey = (agentId, target2) => `${agentId}\0${targetKey(target2)}`, errorText5 = (err) => err instanceof Error ? err.message : String(err), bytesOf = (loaded) => loaded.binary ? Buffer.from(loaded.bytes ?? new Uint8Array()) : Buffer.from(loaded.text ?? "", "utf-8");
11696
+ var { openBrowser: openBrowser2 } = cjs(auth_flow_exports), copies = /* @__PURE__ */ new Map(), dirs = /* @__PURE__ */ new Set(), exitHook = !1, copyKey = (agentId, target2) => `${agentId}\0${targetKey(target2)}`, errorText5 = (err) => err instanceof Error ? err.message : String(err), bytesOf = (loaded) => loaded.binary ? Buffer.from(loaded.bytes ?? new Uint8Array()) : Buffer.from(loaded.text ?? "", "utf-8");
11669
11697
  function externalCopy(agentId, target2) {
11670
11698
  return copies.get(copyKey(agentId, target2));
11671
11699
  }
@@ -11696,7 +11724,7 @@ async function openExternal(deps, target2) {
11696
11724
  }
11697
11725
  copies.set(copyKey(agentId, target2), { agentId, target: target2, file, original: bytes, binary: loaded.binary, mime: loaded.mime, mtimeMs: (await fs4.stat(file)).mtimeMs });
11698
11726
  }
11699
- return (deps.open ?? openBrowser)(file), actions4.toast(`Opened ${label2} in the default app \u2014 after saving there, press s to save it back (${file})`, "info", 8e3), !0;
11727
+ return (deps.open ?? openBrowser2)(file), actions4.toast(`Opened ${label2} in the default app \u2014 after saving there, press s to save it back (${file})`, "info", 8e3), !0;
11700
11728
  }
11701
11729
  async function saveBackExternal(deps, target2) {
11702
11730
  let { client, actions: actions4, agentId, agentLabel: agentLabel5 } = deps, label2 = targetLabel(target2), copy = externalCopy(agentId, target2);
@@ -11710,14 +11738,14 @@ async function saveBackExternal(deps, target2) {
11710
11738
  }
11711
11739
  if (edited.equals(copy.original))
11712
11740
  return copy.mtimeMs = (await fs4.stat(copy.file)).mtimeMs, actions4.toast(`No changes to ${label2}`, "info"), "unchanged";
11713
- let concurrent = null;
11741
+ let concurrent = null, baseRevision;
11714
11742
  try {
11715
11743
  let current = await loadTarget(client, agentId, target2);
11716
- bytesOf(current).equals(copy.original) || (concurrent = "changed in the agent since you opened it");
11744
+ baseRevision = current.revision, bytesOf(current).equals(copy.original) || (concurrent = "changed in the agent since you opened it");
11717
11745
  } catch (err) {
11718
11746
  concurrent = err.status === 404 ? "was deleted since you opened it" : `could not be re-checked (${errorText5(err)})`;
11719
11747
  }
11720
- return await actions4.confirm({
11748
+ if (!await actions4.confirm({
11721
11749
  title: concurrent ? `Concurrent change: ${label2}` : `Save ${label2}`,
11722
11750
  message: [
11723
11751
  concurrent ? `WARNING: ${label2} ${concurrent}. Writing replaces that version.` : null,
@@ -11727,7 +11755,9 @@ async function saveBackExternal(deps, target2) {
11727
11755
  confirmLabel: concurrent ? "Overwrite" : "Write",
11728
11756
  cancelLabel: "Cancel",
11729
11757
  danger: !!concurrent
11730
- }) ? await actions4.run(`Write ${label2}`, (c) => (copy.binary && target2.kind === "file" ? c.writeFile(agentId, target2.path, { contentBase64: edited.toString("base64"), ...copy.mime ? { mimeType: copy.mime } : {} }) : writeTarget(c, agentId, target2, edited.toString("utf-8"))).then(() => !0)) ? (copy.original = edited, copy.mtimeMs = (await fs4.stat(copy.file)).mtimeMs, actions4.toast(`Saved ${label2} from the app`, "success"), "written") : (actions4.toast(`Write failed. Your copy is still at ${copy.file}`, "error", 12e3), "failed") : "discarded";
11758
+ })) return "discarded";
11759
+ let written = copy.binary && target2.kind === "file" ? await actions4.run(`Write ${label2}`, (c) => c.writeFile(agentId, target2.path, { contentBase64: edited.toString("base64"), ...copy.mime ? { mimeType: copy.mime } : {} }).then(() => !0)) : await writeOverConflicts(actions4, label2, copy.file, (base2) => actions4.run(`Write ${label2}`, (c) => writeOver(() => writeTarget(c, agentId, target2, edited.toString("utf-8"), base2))), baseRevision);
11760
+ return written === "reloaded" ? "discarded" : written ? (copy.original = edited, copy.mtimeMs = (await fs4.stat(copy.file)).mtimeMs, actions4.toast(`Saved ${label2} from the app`, "success"), "written") : (actions4.toast(`Write failed. Your copy is still at ${copy.file}`, "error", 12e3), "failed");
11731
11761
  }
11732
11762
 
11733
11763
  // src/main/tui/views/files/FilesTab.tsx
@@ -11828,7 +11858,7 @@ function FilesTab(props) {
11828
11858
  actions: actions4,
11829
11859
  agentId,
11830
11860
  agentLabel: agentLabel5,
11831
- suspend: (run) => suspendTerminal(run)
11861
+ suspend: (run2) => suspendTerminal(run2)
11832
11862
  }, target2, { create }) === "written") {
11833
11863
  let key = targetKey(target2);
11834
11864
  invalidate(key), await reloadList(), reveal(key);
@@ -12485,8 +12515,8 @@ var { CronExpressionParser } = cjs(cronParserNs), DEFAULT_NEW_LOOP_TOOLS = ["loo
12485
12515
  function validateLoopName(name, existing = []) {
12486
12516
  return name ? name === MAIN_LOOP ? '"main" is the implicit host loop' : LOOP_NAME_PATTERN.test(name) ? existing.includes(name) ? `A loop named "${name}" already exists` : null : "1-32 chars: lowercase letters, digits, _ or -, starting with a letter or digit" : "Name is required";
12487
12517
  }
12488
- function validateGoal(goal) {
12489
- return goal.trim() ? goal.length > LOOP_GOAL_MAX_CHARS ? `Goal is ${goal.length} chars; the limit is ${LOOP_GOAL_MAX_CHARS}` : null : "Goal is required: it becomes the loop's whole system instruction";
12518
+ function validateGoal(goal, autostart = !1) {
12519
+ return !goal.trim() && autostart ? "Autostart needs a goal: add one or turn autostart off" : goal.length > LOOP_GOAL_MAX_CHARS ? `Goal is ${goal.length} chars; the limit is ${LOOP_GOAL_MAX_CHARS}` : null;
12490
12520
  }
12491
12521
  function hostLoopTools(config2) {
12492
12522
  if (!config2 || !Array.isArray(config2.tools)) return null;
@@ -13390,12 +13420,12 @@ function draftFromValues(v) {
13390
13420
  function LoopWizard({ overlay, close, width, height }) {
13391
13421
  let props = overlay.props ?? {}, theme2 = useTheme(), store = useStore(), actions4 = useActions(), agent2 = useAgent(props.agentId), loops2 = useLoops(props.agentId) ?? [], timers = useAgentTimers(props.agentId), { suspendTerminal } = use_app_default(), config2 = agent2?.config, host = hostLoopTools(config2), existing = props.mode === "edit" ? loops2.find((l) => l.info.name === props.name)?.info.config ?? void 0 : void 0, provider = config2?.model?.provider, models = useModelList(props.agentId, provider), takenNames = loops2.map((l) => l.info.name), initialTemplate = props.mode === "create" ? findTemplate(props.template) : void 0, [step, setStep] = (0, import_react43.useState)(props.mode === "create" && !initialTemplate ? "template" : "form"), [template, setTemplate] = (0, import_react43.useState)(initialTemplate), [values, setValues] = (0, import_react43.useState)(() => initialValues2(props, existing, initialTemplate, host, takenNames)), [touched, setTouched] = (0, import_react43.useState)(!1), [outcome, setOutcome] = (0, import_react43.useState)(null), dialogWidth = Math.max(40, Math.min(width - 4, 96)), inner = dialogWidth - 4, bodyRows = Math.max(6, height - 8), label2 = agent2 ? agentName3(store.getState(), props.agentId) : props.agentId, wanted = Array.isArray(values.tools) ? values.tools : [], options = toolOptions(host, [...wanted, ...existing?.tools ?? [], ...template?.tools ?? []]), draft = draftFromValues(values), loopName = props.mode === "edit" ? props.name ?? "" : String(values.name ?? ""), schedule = scheduleToTimer(draft, loopName), existingTimers = timers.data ? timersForLoop(timers.data, loopName) : [], errors = (0, import_react43.useMemo)(() => {
13392
13422
  let e = {};
13393
- props.mode === "create" && (e.name = validateLoopName(String(values.name ?? ""), takenNames) ?? void 0), e.goal = validateGoal(String(values.goal ?? "")) ?? void 0;
13423
+ props.mode === "create" && (e.name = validateLoopName(String(values.name ?? ""), takenNames) ?? void 0), e.goal = validateGoal(String(values.goal ?? ""), !!values.autostart) ?? void 0;
13394
13424
  let compact2 = String(values.compact ?? "").trim();
13395
13425
  return compact2 && !(Number.isInteger(Number(compact2)) && Number(compact2) > 0) && (e.compact = "Whole number of tokens, or empty to inherit"), String(values.model ?? "").trim() && !String(values.provider ?? "").trim() && (e.provider = "Provider id is required with a model"), schedule.error && (e.scheduleValue = schedule.error), e;
13396
13426
  }, [values, schedule.error, props.mode, existing]), hasErrors = Object.values(errors).some(Boolean), fields = [
13397
13427
  { kind: "text", key: "name", label: "Name", placeholder: "e.g. consolidator", readOnly: props.mode === "edit", hint: "lowercase letters, digits, _ -; cannot be renamed later" },
13398
- { kind: "text", key: "goal", label: "Goal", multiline: !0, rows: 5, editor: !0, placeholder: "What this loop works on. Becomes its whole system instruction.", hint: "Enter newline \xB7 Ctrl+O open in $EDITOR \xB7 \u2193 next field" },
13428
+ { kind: "text", key: "goal", label: "Goal", multiline: !0, rows: 5, editor: !0, placeholder: "Optional. What this loop works on; added to its instructions.", hint: "Enter newline \xB7 Ctrl+O open in $EDITOR \xB7 \u2193 next field" },
13399
13429
  { kind: "checklist", key: "tools", label: "Tools", rows: 7, options: options.map((o) => ({ value: o.name, note: o.unavailable ? "not granted by host" : void 0 })), hint: host ? "restricted to tools the host has enabled; loop_compact/loop_clear are added by the runtime" : "host tool list unknown: the daemon validates on save" },
13400
13430
  { kind: "combo", key: "model", label: "Model", options: models.data?.models ?? [], placeholder: `inherit (${config2?.model?.model_id ?? "host model"})`, hint: models.data?.error ? `models: ${models.data.error}` : `\u2190\u2192 pick a ${provider ?? ""} model, type any id, empty = inherit` },
13401
13431
  { kind: "text", key: "provider", label: "Provider", hidden: !String(values.model ?? "").trim(), hint: "provider id for the override (another configured provider is allowed)" },
@@ -13421,10 +13451,10 @@ function LoopWizard({ overlay, close, width, height }) {
13421
13451
  }
13422
13452
  setStep("review");
13423
13453
  }, createInput = () => {
13424
- let model2 = String(values.model ?? "").trim(), compact2 = String(values.compact ?? "").trim();
13454
+ let model2 = String(values.model ?? "").trim(), compact2 = String(values.compact ?? "").trim(), goal = String(values.goal ?? "").trim();
13425
13455
  return {
13426
13456
  name: String(values.name),
13427
- goal: String(values.goal).trim(),
13457
+ ...goal ? { goal } : {},
13428
13458
  tools: wanted,
13429
13459
  enabled: !!values.enabled,
13430
13460
  autostart: !!values.autostart,
@@ -13534,7 +13564,7 @@ function initialValues2(props, existing, template, host, taken) {
13534
13564
  if (props.mode === "edit" && existing)
13535
13565
  return {
13536
13566
  name: existing.name,
13537
- goal: existing.goal,
13567
+ goal: existing.goal ?? "",
13538
13568
  tools: [...existing.tools ?? []],
13539
13569
  model: existing.model?.model_id ?? "",
13540
13570
  provider: existing.model?.provider ?? "",
@@ -13588,7 +13618,7 @@ function TemplatePicker({ width, height, onPick, onCancel }) {
13588
13618
  );
13589
13619
  }
13590
13620
  function CreateReview({ input, schedulePreview, width, rows }) {
13591
- let theme2 = useTheme(), goalLines = input.goal.split(`
13621
+ let theme2 = useTheme(), goalLines = (input.goal || "(none)").split(`
13592
13622
  `), goalRows = Math.max(2, Math.min(goalLines.length, rows - 12)), line = (k, v) => /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(Text, { wrap: "truncate-end", children: [
13593
13623
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Text, { color: theme2.color.muted, children: k.padEnd(14) }),
13594
13624
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Text, { color: theme2.color.text, children: truncate(v, width - 14) })
@@ -14560,8 +14590,8 @@ var TOOL_GROUPS = [
14560
14590
  off: "nobody: no inbound messages, sends still allowed",
14561
14591
  directory: "only ancestor-directory agents on this runtime",
14562
14592
  localhost: "any agent on this machine",
14563
- lan: "any agent on the local network (binds 0.0.0.0)",
14564
- public: "any agent reachable over the public internet (binds 0.0.0.0)"
14593
+ lan: "any agent on an allowed network",
14594
+ public: "any agent that reaches this computer, also only on an allowed network"
14565
14595
  }, SEND_MODES = ["proactive", "respond_only", "listen_only"], SEND_MODE_HINTS = {
14566
14596
  proactive: "can send anytime",
14567
14597
  respond_only: "sends only when a message triggered the turn",
@@ -14573,8 +14603,6 @@ function triState(items, pred) {
14573
14603
  return n === 0 ? "none" : n === items.length ? "all" : "mixed";
14574
14604
  }
14575
14605
  function groupBlocked(config2, groupId) {
14576
- let triggers = config2.triggers;
14577
- if (groupId === "messaging" && !triggers?.on_inbox?.enabled) return "needs messaging (the on_inbox trigger)";
14578
14606
  if (groupId === "inbox" && config2.messaging?.inbox_mode !== !0) return "needs inbox mode (Settings \u203A Inbox mode)";
14579
14607
  }
14580
14608
  function mcpServers(config2) {
@@ -14731,6 +14759,26 @@ function setMainThreshold(config2, value) {
14731
14759
  let next = clone(config2), context = { ...next.context ?? {} };
14732
14760
  return value === null ? delete context.compact_threshold : context.compact_threshold = value, next.context = context, { ok: !0, config: next, message: value === null ? `main compacts at the default (${formatTokens2(mainThreshold(next).value)})` : `main compacts at ${formatTokens2(value)} tokens` };
14733
14761
  }
14762
+ function requestMediaLimitOf(config2) {
14763
+ let files2 = config2.context?.max_request_media_files, mb = config2.context?.max_request_media_mb;
14764
+ return {
14765
+ files: typeof files2 == "number" && files2 > 0 ? files2 : 20,
14766
+ mb: typeof mb == "number" && mb > 0 ? mb : 20,
14767
+ set: typeof files2 == "number" && files2 > 0 || typeof mb == "number" && mb > 0
14768
+ };
14769
+ }
14770
+ function setRequestMediaLimit(config2, value) {
14771
+ let next = clone(config2), context = { ...next.context ?? {} };
14772
+ delete context.max_request_media_files, delete context.max_request_media_mb, value && (context.max_request_media_files = value.files, context.max_request_media_mb = value.mb), next.context = context;
14773
+ let now = requestMediaLimitOf(next);
14774
+ return { ok: !0, config: next, message: `Requests carry at most ${now.files} files, ${now.mb} MB${value ? "" : " (default)"}` };
14775
+ }
14776
+ function parseMediaLimit(args) {
14777
+ if (args.length === 1 && args[0].toLowerCase() === "default") return { ok: !0, value: null };
14778
+ if (args.length !== 2) return { ok: !1, error: "Give files and MB, or default" };
14779
+ let [files2, mb] = args.map((a) => Number(a));
14780
+ return [files2, mb].every((n) => Number.isInteger(n) && n > 0) ? { ok: !0, value: { files: files2, mb } } : { ok: !1, error: "Files and MB must be positive whole numbers" };
14781
+ }
14734
14782
  function parseThreshold(text) {
14735
14783
  let raw = text.trim().toLowerCase().replace(/[,_\s]/g, "");
14736
14784
  if (!raw || raw === "default" || raw === "inherit" || raw === "0") return { ok: !0, value: null };
@@ -14837,6 +14885,15 @@ function settingRows(config2, extras = {}) {
14837
14885
  value: `main at ${formatTokens2(main.value)}${main.source === "default" ? " (default)" : ""}${typeof used == "number" ? ` \xB7 in use ${formatTokens2(used)} (${Math.round(used / main.value * 100)}%)` : ""}`,
14838
14886
  hint: (config2.loops ?? []).length ? `inner loops: ${(config2.loops ?? []).map((l) => `${l.name} ${l.compact_threshold != null ? formatTokens2(l.compact_threshold) : "inherit"}`).join(", ")}` : "History is summarized when the context reaches this size",
14839
14887
  ...lock(["context"])
14888
+ });
14889
+ let media2 = requestMediaLimitOf(config2);
14890
+ rows.push({
14891
+ id: "media",
14892
+ label: "Media",
14893
+ kind: "dialog",
14894
+ value: `${media2.files} files, ${media2.mb} MB per request${media2.set ? "" : " (default)"}`,
14895
+ hint: "Past either, the oldest files are left out until half remains \xB7 /media <files> <MB> | default",
14896
+ ...lock(["context"])
14840
14897
  }), rows.push({
14841
14898
  id: "autonomous",
14842
14899
  label: "Autonomous",
@@ -14872,8 +14929,8 @@ function settingRows(config2, extras = {}) {
14872
14929
 
14873
14930
  // src/main/tui/views/inspect/settings-ops.ts
14874
14931
  var queue = Promise.resolve();
14875
- function serial(run) {
14876
- let next = queue.then(run, run);
14932
+ function serial(run2) {
14933
+ let next = queue.then(run2, run2);
14877
14934
  return queue = next.catch(() => {
14878
14935
  }), next;
14879
14936
  }
@@ -14931,6 +14988,9 @@ async function actOnSetting(actions4, agentId, row4, config2) {
14931
14988
  case "compaction":
14932
14989
  actions4.pushOverlay({ kind: COMPACTION_OVERLAY, props: { agentId } });
14933
14990
  return;
14991
+ case "media":
14992
+ actions4.toast("Set with /media <files> <MB>, or /media default", "info", 3e3);
14993
+ return;
14934
14994
  case "visibility":
14935
14995
  case "send":
14936
14996
  actions4.pushOverlay({ kind: CHOICE_OVERLAY, props: { agentId, setting: row4.id } });
@@ -15304,6 +15364,24 @@ var inspect = {
15304
15364
  }
15305
15365
  await applyThreshold(ctx.store, ctx.agentId, ctx.loop, parsed.value);
15306
15366
  }
15367
+ }, media = {
15368
+ name: "media",
15369
+ args: "[<files> <MB>|default]",
15370
+ description: "Request media limit of the selected agent: most files and MB one request carries (default 20, 20). Past either, the oldest are left out until half remains",
15371
+ available: (scope) => !!scope.agentId,
15372
+ complete: (partial) => ["default"].filter((v) => v.startsWith(partial.trim())),
15373
+ run: async (ctx) => {
15374
+ if (!ctx.agentId) {
15375
+ ctx.print("No agent selected", "warn");
15376
+ return;
15377
+ }
15378
+ let parsed = parseMediaLimit(ctx.args);
15379
+ if (!parsed.ok) {
15380
+ ctx.print(`${parsed.error}. Usage: /media <files> <MB> | default`, "warn");
15381
+ return;
15382
+ }
15383
+ await applyChange(ctx.actions, ctx.agentId, (cfg) => setRequestMediaLimit(cfg, parsed.value), "Media");
15384
+ }
15307
15385
  }, TASK_FILTER_ARGS = TASK_FILTERS, tasks = {
15308
15386
  name: "tasks",
15309
15387
  args: `[${TASK_FILTERS.join("|")}]`,
@@ -15356,7 +15434,7 @@ var inspect = {
15356
15434
  patchInspectState(ctx.actions, ctx.state(), { tab: t.id }), ctx.actions.setView(INSPECT_VIEW);
15357
15435
  }
15358
15436
  }))
15359
- ], inspectCommands = { commands: [inspect, model, config, tasks, instructions, tools, compaction], actions: actions2 };
15437
+ ], inspectCommands = { commands: [inspect, model, config, tasks, instructions, tools, compaction, media], actions: actions2 };
15360
15438
 
15361
15439
  // src/main/tui/views/inspect/EventsTab.tsx
15362
15440
  var import_react51 = __toESM(require_react(), 1);
@@ -15615,6 +15693,9 @@ function typeColor(theme2, type) {
15615
15693
  // src/main/tui/views/inspect/ConfigTab.tsx
15616
15694
  var import_react52 = __toESM(require_react(), 1);
15617
15695
  var import_jsx_runtime64 = __toESM(require_jsx_runtime(), 1);
15696
+ import { promises as fs5 } from "node:fs";
15697
+ import { tmpdir as tmpdir4 } from "node:os";
15698
+ import { join as join5 } from "node:path";
15618
15699
  async function editConfigFlow(agentId, label2, actions4, suspend, edit = (text) => editText(text, { filename: `${label2}.config.json` })) {
15619
15700
  let original = await actions4.loadConfig(agentId);
15620
15701
  if (!original) return "failed";
@@ -15646,10 +15727,10 @@ Edit again? Discard drops your edit.`,
15646
15727
  }
15647
15728
  if (check.changedKeys.length === 0)
15648
15729
  return actions4.toast("Config unchanged (formatting only), nothing saved", "info"), "unchanged";
15649
- let fresh = await actions4.run("Re-check config", (c) => c.config(agentId).then((r) => r.config));
15650
- if (!fresh) return "failed";
15651
- let theirs = changedKeysBetween(original, fresh);
15652
- return theirs.length > 0 && !await actions4.confirm({
15730
+ let freshResult = await actions4.run("Re-check config", (c) => c.config(agentId));
15731
+ if (!freshResult) return "failed";
15732
+ let fresh = freshResult.config, baseRevision = revisionOf(freshResult), theirs = changedKeysBetween(original, fresh);
15733
+ if (theirs.length > 0 && !await actions4.confirm({
15653
15734
  title: `Config of ${label2} changed on the daemon`,
15654
15735
  message: `While you were editing, these keys changed on the daemon: ${theirs.join(", ")}.
15655
15736
  Saving your copy reverts them${theirs.includes("loops") ? " (a loop added meanwhile would be removed and archived)" : ""}.
@@ -15658,14 +15739,24 @@ Warning: ${check.warnings.join("; ")}` : ""}`,
15658
15739
  confirmLabel: "Overwrite",
15659
15740
  cancelLabel: "Discard",
15660
15741
  danger: !0
15661
- }) ? (actions4.toast("Config edit discarded (the daemon copy changed meanwhile)", "warn"), "discarded") : changedKeysBetween(fresh, check.config).length === 0 ? (actions4.toast("Config already matches the daemon, nothing saved", "info"), "unchanged") : theirs.length > 0 || await actions4.confirm({
15742
+ }))
15743
+ return actions4.toast("Config edit discarded (the daemon copy changed meanwhile)", "warn"), "discarded";
15744
+ if (changedKeysBetween(fresh, check.config).length === 0)
15745
+ return actions4.toast("Config already matches the daemon, nothing saved", "info"), "unchanged";
15746
+ if (!(theirs.length > 0 || await actions4.confirm({
15662
15747
  title: `Save config of ${label2}?`,
15663
15748
  message: `Changed: ${check.changedKeys.join(", ")}${check.warnings.length ? `
15664
15749
  Warning: ${check.warnings.join("; ")}` : ""}`,
15665
15750
  confirmLabel: "Save",
15666
15751
  cancelLabel: "Discard",
15667
15752
  danger: check.warnings.length > 0
15668
- }) ? await actions4.run("Save config", (c) => c.putConfig(agentId, check.config)) ? (actions4.toast(`Config of ${label2} saved (${check.changedKeys.join(", ")})`, "success"), await actions4.loadConfig(agentId), "saved") : "failed" : (actions4.toast("Config edit discarded", "warn"), "discarded");
15753
+ })))
15754
+ return actions4.toast("Config edit discarded", "warn"), "discarded";
15755
+ let keepCopy = async () => {
15756
+ let file = join5(tmpdir4(), `adf-tui-${label2.replace(/[^\w.-]+/g, "_")}.config-${Date.now()}.json`);
15757
+ return await fs5.writeFile(file, result.text ?? "", "utf-8"), file;
15758
+ }, saved = await writeOverConflicts(actions4, `Config of ${label2}`, keepCopy, (base2) => actions4.run("Save config", (c) => writeOver(() => c.request("PUT", `/agents/${encodeURIComponent(agentId)}/config`, { body: check.config, ...base2 !== void 0 ? { query: { baseRevision: base2 } } : {} }))), baseRevision);
15759
+ return saved === "reloaded" ? (await actions4.loadConfig(agentId), "discarded") : saved ? (actions4.toast(`Config of ${label2} saved (${check.changedKeys.join(", ")})`, "success"), await actions4.loadConfig(agentId), "saved") : "failed";
15669
15760
  }
15670
15761
  }
15671
15762
  function ConfigTab({ agentId, label: label2, width, height, focused }) {
@@ -16850,9 +16941,9 @@ function IdentityTab2({ width, height, focused }) {
16850
16941
  ] });
16851
16942
  }
16852
16943
  function NetworkTab(props) {
16853
- let store = useStore(), actions4 = useActions(), ask = async (title, message, run, done, reload) => {
16944
+ let store = useStore(), actions4 = useActions(), ask = async (title, message, run2, done, reload) => {
16854
16945
  if (!await actions4.confirm({ title, message, danger: !0 })) return;
16855
- await actions4.run(title, run) !== void 0 && actions4.toast(done, "success"), reload();
16946
+ await actions4.run(title, run2) !== void 0 && actions4.toast(done, "success"), reload();
16856
16947
  };
16857
16948
  return /* @__PURE__ */ (0, import_jsx_runtime72.jsx)(
16858
16949
  ReportTab,