nolo-cli 0.212.0-alpha.1 → 0.213.0

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 (124) hide show
  1. package/{_executeModel-KE6MBNUJ.js → _executeModel-CUJLCFWR.js} +35 -35
  2. package/{addContentAction-N2YC5IL3.js → addContentAction-WERAONNY.js} +5 -5
  3. package/{addDialogAgentAction-3NKVQCEC.js → addDialogAgentAction-7VI7YRML.js} +4 -4
  4. package/{agentDeleteCommand-NWK7VU4N.js → agentDeleteCommand-XJORXPGU.js} +42 -42
  5. package/{agentEmailCommands-UZ4DHEX2.js → agentEmailCommands-XTYYFR6Z.js} +43 -43
  6. package/{agentListCommands-HE7L2A6L.js → agentListCommands-OBLRBLHS.js} +60 -51
  7. package/{agentMachineCommands-6TE4MBMP.js → agentMachineCommands-5FMT3OT5.js} +13 -12
  8. package/{agentRecordCommands-L3QQ7HZ4.js → agentRecordCommands-ZQ7SVYGG.js} +42 -42
  9. package/{agentRunCommand-2IFGQHCO.js → agentRunCommand-WS7IGO2W.js} +43 -43
  10. package/{agentRunControl-OI3LOENH.js → agentRunControl-TAMHJQSR.js} +3 -3
  11. package/{agentRuntimeLocal-HL526DNQ.js → agentRuntimeLocal-SOO2OPJG.js} +10 -10
  12. package/{agentSlice-TMYP2UEA.js → agentSlice-CHR6KOZN.js} +5 -5
  13. package/{appDeleteCommands-ZERW4AXY.js → appDeleteCommands-GUP6P5YE.js} +15 -14
  14. package/{appDeployCommands-DUWSMYHL.js → appDeployCommands-MH2FX4O5.js} +15 -14
  15. package/{appDeployStatusCommands-N2KIE4BF.js → appDeployStatusCommands-SUHN3HPJ.js} +15 -14
  16. package/{appGetCommands-YVR4NPVY.js → appGetCommands-YWDEX6Z4.js} +15 -14
  17. package/{appListCommands-JS6HOHZS.js → appListCommands-ZQVMLPAY.js} +15 -14
  18. package/{authCommand-FE2C3ZUM.js → authCommand-PWX2ZNUP.js} +2 -2
  19. package/{builtinSkillRegistry-3YOIGEF6.js → builtinSkillRegistry-2QETTQPM.js} +2 -2
  20. package/{chromeOpsCommands-HEN5AX2J.js → chromeOpsCommands-DIVM2K3T.js} +1 -1
  21. package/{chunk-YUXQAVRD.js → chunk-3UKL3HPD.js} +251 -14
  22. package/{chunk-PDGKOTO7.js → chunk-4T2WNOY7.js} +4 -4
  23. package/{chunk-NOMIGOST.js → chunk-5MQRBSMW.js} +35 -41
  24. package/{chunk-5RYBGBQH.js → chunk-6DWMYIUY.js} +3 -3
  25. package/{chunk-KSTEXKL6.js → chunk-75XIDFBW.js} +4 -3
  26. package/{chunk-CZM5YTZ7.js → chunk-7BT37FK3.js} +193 -305
  27. package/{chunk-GPO7FCEZ.js → chunk-7QTZCMKL.js} +159 -106
  28. package/{chunk-NJR3WKVP.js → chunk-7Y4YTDQF.js} +6 -6
  29. package/{chunk-CKAHAVMK.js → chunk-A7TFUNZO.js} +35 -25
  30. package/{chunk-PJNFNEJE.js → chunk-ALQ3657A.js} +28 -2
  31. package/{chunk-YJMRHHGY.js → chunk-BBJ22CLB.js} +5 -5
  32. package/{chunk-AVTVGMDY.js → chunk-COFEN36Z.js} +1 -1
  33. package/{chunk-DM6UMOEL.js → chunk-CRHOO2ZR.js} +1 -1
  34. package/{chunk-EYUBHHFB.js → chunk-EHGEOAHI.js} +2 -2
  35. package/{chunk-Y2WUYX4I.js → chunk-EHSZD5OI.js} +1 -1
  36. package/{chunk-QT6PKHYJ.js → chunk-FUGMREBO.js} +2 -2
  37. package/{chunk-BFM34N54.js → chunk-FW6YHHLI.js} +4 -4
  38. package/{chunk-3FOQHGHN.js → chunk-G2BQWAVT.js} +2 -2
  39. package/{chunk-VPAZOH2D.js → chunk-GPVRUGGY.js} +1 -1
  40. package/{chunk-JNKSDZFS.js → chunk-H4UOTUOX.js} +162 -48
  41. package/{chunk-UTV2AYEG.js → chunk-H5HF7TJA.js} +1 -1
  42. package/{chunk-IJXYFTV7.js → chunk-H7PPIYWP.js} +1 -1
  43. package/{chunk-NRFKLKRU.js → chunk-HSVBJRF4.js} +1 -1
  44. package/{chunk-GNDJKC4B.js → chunk-HUBHAQK2.js} +8 -8
  45. package/{chunk-LKDF3ATR.js → chunk-INUFILG6.js} +1 -1
  46. package/{chunk-OHNELDTO.js → chunk-IXNGOHV5.js} +2 -2
  47. package/{chunk-WXYLV624.js → chunk-J2NHJ7WX.js} +4 -4
  48. package/{chunk-YPD3VJZW.js → chunk-KFLT5KAD.js} +4 -4
  49. package/{chunk-OOF4UPC5.js → chunk-LLRPJECG.js} +4 -4
  50. package/{chunk-B7NJ3T4A.js → chunk-MDB67IWP.js} +4 -4
  51. package/{chunk-IF45XG7K.js → chunk-MOIJOHA5.js} +9 -4
  52. package/{chunk-IIQ6JGRI.js → chunk-NJLL224W.js} +1 -1
  53. package/{chunk-YOIW77S2.js → chunk-NOKUNJON.js} +2 -2
  54. package/{chunk-HIRC32DV.js → chunk-NSAPDIN2.js} +8 -4
  55. package/{chunk-JIT6FSXJ.js → chunk-O2TZF4AI.js} +1 -1
  56. package/{chunk-4KH4J3G5.js → chunk-OQPKUBPB.js} +1 -1
  57. package/{chunk-OXME3PEE.js → chunk-PCLE46CE.js} +1 -1
  58. package/{chunk-CJRONOYL.js → chunk-QG4AOSON.js} +8 -45
  59. package/{chunk-6OQUPIQF.js → chunk-QHPRUL4O.js} +3 -3
  60. package/{chunk-MMNYUVLW.js → chunk-R5O4QAWG.js} +1 -1
  61. package/{chunk-IAPCBNEE.js → chunk-RLBNLZVC.js} +1 -1
  62. package/{chunk-FHU44LN3.js → chunk-RSHTSUEG.js} +246 -77
  63. package/{chunk-25JZEIXN.js → chunk-RZOV7OMV.js} +11 -11
  64. package/{chunk-D2MDMY2M.js → chunk-SVYO6AO7.js} +7 -1
  65. package/{chunk-INSBKSBB.js → chunk-TAKB25RM.js} +1 -1
  66. package/{chunk-RU4SD66C.js → chunk-TX5EYMGT.js} +5 -5
  67. package/{chunk-D5RCRX4A.js → chunk-UYD5HQ6T.js} +746 -177
  68. package/{chunk-JTDORD5P.js → chunk-V5Y3R6BT.js} +110 -2
  69. package/{chunk-3RQVYR4B.js → chunk-VNYYOX3V.js} +59 -20
  70. package/{chunk-3AKFVZ2I.js → chunk-VO4WSAVP.js} +3 -3
  71. package/{chunk-6NSSLV67.js → chunk-WMTBPBMP.js} +6 -6
  72. package/{chunk-EZFQ6XRV.js → chunk-WRYQ35LQ.js} +4 -4
  73. package/{chunk-SOHTHFV2.js → chunk-WYDI6TGM.js} +14 -14
  74. package/{chunk-M6RL5JCD.js → chunk-X7SDOJUM.js} +3 -3
  75. package/{chunk-CQ46IRNN.js → chunk-XHQC4PXX.js} +48 -34
  76. package/{chunk-2WZXXAQD.js → chunk-YB3YZK56.js} +67 -1
  77. package/{chunk-22CIEBYO.js → chunk-YJAFKXMU.js} +11 -11
  78. package/{chunk-GSQNH63I.js → chunk-YTF4WH7J.js} +3 -19
  79. package/{chunk-5AQR3DGV.js → chunk-ZNWI6OVT.js} +2 -2
  80. package/{chunk-VZKUKCA6.js → chunk-ZXOUUDCG.js} +49 -40
  81. package/{commandRegistry-VVMTPFAX.js → commandRegistry-W63CYRZJ.js} +64 -64
  82. package/{contentThunks-N52O67MI.js → contentThunks-U7PL5MQD.js} +12 -12
  83. package/{cooldownCommands-YXLWOXWR.js → cooldownCommands-TIGUHXTD.js} +3 -3
  84. package/{createAgentAutomationAction-ZC57YRSV.js → createAgentAutomationAction-OLBQDD6R.js} +12 -12
  85. package/{createDialogAction-VQW5WDYX.js → createDialogAction-SVH6B3SB.js} +14 -14
  86. package/{createPageAction-M5VDA7SC.js → createPageAction-F4DIWZ2Z.js} +14 -14
  87. package/{dbSlice-HP7G4LX3.js → dbSlice-BVH2YZPQ.js} +4 -4
  88. package/{deleteFile-7VQGLK2V.js → deleteFile-5YOEKV4U.js} +2 -2
  89. package/{dialogCommands-JHMR2N2Y.js → dialogCommands-KHIEPY3M.js} +18 -18
  90. package/{dialogSlice-GRSVGDTF.js → dialogSlice-ZCTDV2PZ.js} +11 -11
  91. package/{docCreateCommands-F2K3UPSQ.js → docCreateCommands-DPMZI7G5.js} +14 -13
  92. package/{docDeleteCommands-XGXB5RFT.js → docDeleteCommands-IRQQLUBT.js} +16 -16
  93. package/{docReadCommands-7HWZZV6J.js → docReadCommands-LK37XH6L.js} +14 -13
  94. package/{docStore-KNZK6Y2G.js → docStore-V2KECILA.js} +5 -5
  95. package/{docUpdateCommands-OZZ7NQII.js → docUpdateCommands-O6T3HJHH.js} +14 -13
  96. package/{fetchMessages-M3QU7ZCQ.js → fetchMessages-NR4KNQLO.js} +2 -2
  97. package/{handleSendMessageAction-SHIH74GJ.js → handleSendMessageAction-2PE25WM3.js} +25 -25
  98. package/{hostCredentialCreate-X64WK4JD.js → hostCredentialCreate-S233UMAP.js} +42 -42
  99. package/index.js +7 -7
  100. package/{localDialogRead-XRLAH4CZ.js → localDialogRead-7NNDVVF7.js} +1 -1
  101. package/{localRuntimeAdapter-A7K3ABA6.js → localRuntimeAdapter-KY6WXNBR.js} +41 -41
  102. package/{machineCommands-DE3MR7CJ.js → machineCommands-Q6HA3IXF.js} +42 -42
  103. package/{machineWsRunDispatch-R26P5V36.js → machineWsRunDispatch-WWJQ6WR3.js} +42 -42
  104. package/{memberThunks-7WJAY5V6.js → memberThunks-7EDJADWU.js} +5 -5
  105. package/package.json +1 -1
  106. package/{projectArchive-GGL5HLMD.js → projectArchive-DRTDIMEV.js} +14 -14
  107. package/{read-4VZQODSN.js → read-4V2RMG7N.js} +2 -2
  108. package/{readDocTool-AVD32RUJ.js → readDocTool-BH6ATGQO.js} +1 -1
  109. package/{readlineWorkspace-XKYKNL2I.js → readlineWorkspace-2W4WRCOU.js} +322 -159
  110. package/{removeDialogAgentAction-FKL4BYMR.js → removeDialogAgentAction-LYAZGNKW.js} +5 -5
  111. package/{runAgentClientLoop-V7E2WBVM.js → runAgentClientLoop-J2VQBS2Z.js} +33 -33
  112. package/{secretCommands-4QS3HCA2.js → secretCommands-LXDWOLZ6.js} +1 -1
  113. package/{setDialogExtraReferencesAction-TLIOLWWI.js → setDialogExtraReferencesAction-OCRPGPYH.js} +5 -5
  114. package/{setPrimaryDialogAgentAction-2OLWRFYR.js → setPrimaryDialogAgentAction-HFJDQIBU.js} +5 -5
  115. package/{settingThunks-UGBPCNYF.js → settingThunks-RAMHMTGM.js} +7 -7
  116. package/{spaceCommands-4KSLKVNF.js → spaceCommands-KVJZVKVO.js} +14 -14
  117. package/{streamAgentChatTurn-7FR7X2NU.js → streamAgentChatTurn-GHBMJJ6S.js} +37 -37
  118. package/{switchDialogAgentAction-UZSAG2N5.js → switchDialogAgentAction-EBRMW3RA.js} +4 -4
  119. package/{tools-ZLKCC25W.js → tools-EWKCPKMS.js} +28 -28
  120. package/{tuiSummaryLlmCaller-WC54MVGP.js → tuiSummaryLlmCaller-ALQYUUWB.js} +41 -12
  121. package/{updateCommands-4744PL3P.js → updateCommands-ZQYU6SCI.js} +3 -3
  122. package/{updateDialogTitleAction-U53ZT7PJ.js → updateDialogTitleAction-5GGMBTDF.js} +12 -12
  123. package/{windowsSelfUpdate-XSQVF2GK.js → windowsSelfUpdate-257DV2T6.js} +2 -2
  124. package/{workloadLifecycle-4BEQHMSD.js → workloadLifecycle-4C42SVBD.js} +1 -0
@@ -4,16 +4,14 @@ import {
4
4
  import {
5
5
  clearCredentialAvailability,
6
6
  markCredentialUnavailable,
7
- readCredentialEntry,
8
- readEffectiveCredentialEntry,
9
- recordCredentialProbe
10
- } from "./chunk-GSQNH63I.js";
7
+ readCredentialEntry
8
+ } from "./chunk-YTF4WH7J.js";
11
9
  import {
12
10
  CREDENTIAL_STORE_SESSION_FALLBACK_WARNING,
13
11
  OAUTH_PROVIDER_REFS,
14
12
  buildLocalAgentLookupKeys,
15
13
  getSharedSessionCredentialBroker
16
- } from "./chunk-IF45XG7K.js";
14
+ } from "./chunk-MOIJOHA5.js";
17
15
  import {
18
16
  customNativeAuthHeaders,
19
17
  detectDeclaredCustomNativeWire,
@@ -73,7 +71,7 @@ import {
73
71
  import {
74
72
  inlineImageUrlsForCustomProvider,
75
73
  prepareTools
76
- } from "./chunk-3FOQHGHN.js";
74
+ } from "./chunk-G2BQWAVT.js";
77
75
  import {
78
76
  getServerProviderSecret
79
77
  } from "./chunk-HS7MLR74.js";
@@ -95,7 +93,7 @@ import {
95
93
  resolveCliLocalRuntimeKeyCapability,
96
94
  resolveCliLocalSpawnDispatch,
97
95
  resolveStartAgentRunCwd
98
- } from "./chunk-CQ46IRNN.js";
96
+ } from "./chunk-XHQC4PXX.js";
99
97
  import {
100
98
  fetchWithTransientRetry,
101
99
  isTransientFetchError
@@ -103,22 +101,22 @@ import {
103
101
  import {
104
102
  deriveCredentialGroup,
105
103
  deriveCustomProviderCredentialGroup
106
- } from "./chunk-CJRONOYL.js";
104
+ } from "./chunk-QG4AOSON.js";
107
105
  import {
108
106
  inferCaptureIntent,
109
107
  resolveUserSkillRuntime
110
- } from "./chunk-5AQR3DGV.js";
108
+ } from "./chunk-ZNWI6OVT.js";
111
109
  import {
112
110
  resolveAgentRequiredPackIds,
113
111
  resolveUserEnabledSkills
114
- } from "./chunk-MMNYUVLW.js";
112
+ } from "./chunk-R5O4QAWG.js";
115
113
  import {
116
114
  applyBuiltinAgentRuntimeOverride,
117
115
  prepareTokenUsageData,
118
116
  resolveBuiltinPlatformAgentConfig,
119
117
  runKeyed,
120
118
  validateAskChoiceInput
121
- } from "./chunk-VZKUKCA6.js";
119
+ } from "./chunk-ZXOUUDCG.js";
122
120
  import {
123
121
  applyTokenUsageToDayStats,
124
122
  require_format
@@ -129,13 +127,13 @@ import {
129
127
  } from "./chunk-523DITKR.js";
130
128
  import {
131
129
  fetchServerSyncedCredential
132
- } from "./chunk-YPD3VJZW.js";
130
+ } from "./chunk-KFLT5KAD.js";
133
131
  import {
134
132
  getDefaultCliLocalRuntimeDb
135
133
  } from "./chunk-AZMYUSVF.js";
136
134
  import {
137
135
  isTombstoneRecord
138
- } from "./chunk-D2MDMY2M.js";
136
+ } from "./chunk-SVYO6AO7.js";
139
137
  import {
140
138
  FORCED_TOOLS,
141
139
  addDefaultLightWebToolsForConfiguredAgents,
@@ -145,7 +143,7 @@ import {
145
143
  applySystemBuiltinSkillFilter,
146
144
  expandEnabledPacks,
147
145
  resolveEffectiveEnabledPacks
148
- } from "./chunk-EYUBHHFB.js";
146
+ } from "./chunk-EHGEOAHI.js";
149
147
  import {
150
148
  DEFAULT_HYBRID_READ_TIMEOUT_MS,
151
149
  Duration_exports,
@@ -191,7 +189,7 @@ import {
191
189
  runExecShellGates,
192
190
  validateConfiguredRootExecutionLease,
193
191
  verifyOwnerRouteAck
194
- } from "./chunk-FHU44LN3.js";
192
+ } from "./chunk-RSHTSUEG.js";
195
193
  import {
196
194
  PlatformStreamInterruptedError,
197
195
  buildPlatformChatCompletionRequest,
@@ -202,7 +200,7 @@ import {
202
200
  readPlatformChatSseCompletion,
203
201
  resolvePlatformChatProviderConfig,
204
202
  shouldUsePlatformChatProvider
205
- } from "./chunk-4KH4J3G5.js";
203
+ } from "./chunk-OQPKUBPB.js";
206
204
  import {
207
205
  accumulateGeminiChunks,
208
206
  buildGeminiGenerateContentRequest,
@@ -228,7 +226,7 @@ import {
228
226
  findAgentStatePairingIssues,
229
227
  stablePromptCacheKey,
230
228
  toResponsesTools
231
- } from "./chunk-UTV2AYEG.js";
229
+ } from "./chunk-H5HF7TJA.js";
232
230
  import {
233
231
  EMPTY_TOOL_RESULT_TEXT,
234
232
  createToolImageBudget,
@@ -241,21 +239,22 @@ import {
241
239
  import {
242
240
  buildNoloWorkspaceOpenAiTools,
243
241
  filterClientNoloWorkspaceToolNames,
244
- parseNoloWorkspaceToolArguments
245
- } from "./chunk-25JZEIXN.js";
242
+ parseNoloWorkspaceToolArguments,
243
+ resolveDispatchedSubtaskBudget
244
+ } from "./chunk-RZOV7OMV.js";
246
245
  import {
246
+ extractQuotaSnapshot,
247
247
  isCapacitySaturatedRateLimit,
248
248
  mergeAvailabilityDeadline,
249
249
  resolveAvailabilityAction,
250
- resolveCooldownGate,
251
250
  resolveCredentialKeyWithFallback
252
- } from "./chunk-YUXQAVRD.js";
251
+ } from "./chunk-3UKL3HPD.js";
253
252
  import {
254
253
  resolveRequestToolSurface
255
254
  } from "./chunk-KC4XDTT3.js";
256
255
  import {
257
256
  resolveHistoricalToolContentCap
258
- } from "./chunk-2WZXXAQD.js";
257
+ } from "./chunk-YB3YZK56.js";
259
258
  import {
260
259
  resolveToolImagePolicy
261
260
  } from "./chunk-YWSEPJ7C.js";
@@ -264,8 +263,16 @@ import {
264
263
  normalizeDialogTitle
265
264
  } from "./chunk-2MBTWINN.js";
266
265
  import {
267
- normalizeUsage
268
- } from "./chunk-JTDORD5P.js";
266
+ CODEX_OAUTH_CATALOG_RETRY_FLOOR_MS,
267
+ CODEX_OAUTH_CATALOG_TTL_MS,
268
+ codexOAuthCatalogAccountKey,
269
+ codexOAuthCatalogSnapshot,
270
+ commitCodexOAuthCatalogWindows,
271
+ isCodexOAuthAgent,
272
+ markCodexOAuthCatalogFailure,
273
+ normalizeUsage,
274
+ resolveCodexOAuthEffectiveModel
275
+ } from "./chunk-V5Y3R6BT.js";
269
276
  import {
270
277
  createProviderCallTimingTracker,
271
278
  finalizeProviderCallTiming,
@@ -326,7 +333,7 @@ import {
326
333
  terminateRunProcess,
327
334
  transitionRunToTerminal,
328
335
  withQueueLock
329
- } from "./chunk-HIRC32DV.js";
336
+ } from "./chunk-NSAPDIN2.js";
330
337
  import {
331
338
  remoteDialogSyncTimeout,
332
339
  resolveRuntimeAuthToken,
@@ -334,6 +341,7 @@ import {
334
341
  } from "./chunk-HRMJUN5J.js";
335
342
  import {
336
343
  TASK_PREVIEW_MAX,
344
+ formatControlRunMessageCard,
337
345
  formatListRunsCard,
338
346
  formatNotFoundRunCard,
339
347
  formatStartRunCard,
@@ -344,7 +352,7 @@ import {
344
352
  normalizeRunTitle,
345
353
  resolveRunLabel,
346
354
  shortRunId
347
- } from "./chunk-PJNFNEJE.js";
355
+ } from "./chunk-ALQ3657A.js";
348
356
  import {
349
357
  estimateTokenCount
350
358
  } from "./chunk-PSCNXBYA.js";
@@ -394,7 +402,7 @@ import {
394
402
  import {
395
403
  agentRunCardLabels,
396
404
  t
397
- } from "./chunk-3RQVYR4B.js";
405
+ } from "./chunk-VNYYOX3V.js";
398
406
  import {
399
407
  createOAuthTokenStore,
400
408
  isTokenExpired,
@@ -1060,7 +1068,7 @@ async function fetchAnthropicMessagesCompletion(args) {
1060
1068
  const errorBody = await response.json().catch(async () => ({
1061
1069
  error: { message: await response.text().catch(() => response.statusText) }
1062
1070
  }));
1063
- if (adoptRequiredClaudeCodeVersion(errorBody)) {
1071
+ if (!args.noRetries && adoptRequiredClaudeCodeVersion(errorBody)) {
1064
1072
  response = await (args.fetchImpl ?? fetch)(ANTHROPIC_MESSAGES_URL, {
1065
1073
  method: "POST",
1066
1074
  headers: buildHeaders(),
@@ -1527,6 +1535,8 @@ var resolveAnthropicOAuthTransport = async (ctx) => {
1527
1535
  const result = await fetchAnthropicMessagesCompletion({
1528
1536
  agentConfig,
1529
1537
  accessToken,
1538
+ signal: options?.signal,
1539
+ ...options?.noRetries ? { noRetries: true } : {},
1530
1540
  openAiBody: {
1531
1541
  model: agentConfig.model || "claude-sonnet-5",
1532
1542
  messages,
@@ -1535,7 +1545,8 @@ var resolveAnthropicOAuthTransport = async (ctx) => {
1535
1545
  },
1536
1546
  fetchImpl: (url, init) => fetchWithTransientRetry(fetchImpl, url, init, {
1537
1547
  sleep: deps.sleep,
1538
- loopbackRequest
1548
+ loopbackRequest,
1549
+ ...options?.noRetries ? { maxAttempts: 1, coreDrainingMaxAttempts: 1 } : {}
1539
1550
  })
1540
1551
  });
1541
1552
  await recordLocalAvailability(result.status, result.body);
@@ -1926,7 +1937,7 @@ var resolveAntigravityTransport = async (ctx) => {
1926
1937
  agentConfig,
1927
1938
  resolveCredential: resolveLocalCredential,
1928
1939
  // 非 OAuth(api-key ref)不重试:metered key 没有 refresh 语义。
1929
- supportsAuthRetry: !isApiKeyRef,
1940
+ supportsAuthRetry: !isApiKeyRef && !options?.noRetries,
1930
1941
  openAiBody,
1931
1942
  ...options?.signal ? { signal: options.signal } : {},
1932
1943
  // whole-invocation timeout 透传:seam 最外层 timeoutOption 统一 interrupt
@@ -1936,7 +1947,8 @@ var resolveAntigravityTransport = async (ctx) => {
1936
1947
  onReasoningDelta: options?.onReasoningDelta,
1937
1948
  fetchImpl: (url, init) => fetchWithTransientRetry(fetchImpl, url, init, {
1938
1949
  sleep: deps.sleep,
1939
- loopbackRequest
1950
+ loopbackRequest,
1951
+ ...options?.noRetries ? { maxAttempts: 1, coreDrainingMaxAttempts: 1 } : {}
1940
1952
  })
1941
1953
  });
1942
1954
  } catch (error) {
@@ -2257,6 +2269,7 @@ function collectCliProviderImageInputs(messages) {
2257
2269
  var resolveCliProviderTransport = async (ctx) => {
2258
2270
  const { agentConfig, deps, workspaceRoot } = ctx;
2259
2271
  if (isCliProviderAgent(agentConfig)) {
2272
+ if (ctx.textOnly) throw new Error("local CLI subprocess does not support isolated text-only mode");
2260
2273
  const provider = resolveCliProviderName(agentConfig);
2261
2274
  logLocalRuntimeDiagnostic("provider.selected", {
2262
2275
  agentKey: agentConfig.key,
@@ -2319,41 +2332,6 @@ var resolveCliProviderTransport = async (ctx) => {
2319
2332
  // packages/agent-runtime/codexResponsesProvider.ts
2320
2333
  import { createHash } from "node:crypto";
2321
2334
  import { randomUUID as randomUUID2 } from "node:crypto";
2322
-
2323
- // packages/agent-runtime/codexOAuthModelAliases.ts
2324
- var CODEX_OAUTH_MODEL_ALIASES = {
2325
- // sol 家族最新:gpt-6.1-sol
2326
- "gpt-6-sol": {
2327
- aliasOf: "gpt-6.1-sol",
2328
- since: "2026-11-01",
2329
- reason: "\u540C\u5BB6\u65CF\u6362\u4EE3\uFF0Cowner \u51B3\u5B9A\u900F\u660E\u91CD\u5B9A\u5411\uFF08sol \u5BB6\u65CF\u6700\u65B0\u4E3A gpt-6.1-sol\uFF09\u3002"
2330
- },
2331
- "gpt-5.6-sol": {
2332
- aliasOf: "gpt-6.1-sol",
2333
- since: "2026-11-01",
2334
- reason: "\u540C\u5BB6\u65CF\u6362\u4EE3\uFF0Cowner \u51B3\u5B9A\u900F\u660E\u91CD\u5B9A\u5411\uFF08sol \u5BB6\u65CF\u6700\u65B0\u4E3A gpt-6.1-sol\uFF09\u3002"
2335
- },
2336
- // terra 家族最新:gpt-6.1-terra
2337
- "gpt-5.6-terra": {
2338
- aliasOf: "gpt-6.1-terra",
2339
- since: "2026-11-01",
2340
- reason: "\u540C\u5BB6\u65CF\u6362\u4EE3\uFF0Cowner \u51B3\u5B9A\u900F\u660E\u91CD\u5B9A\u5411\uFF08terra \u5BB6\u65CF\u6700\u65B0\u4E3A gpt-6.1-terra\uFF09\u3002"
2341
- },
2342
- // luna 家族最新:gpt-6-luna(上游无 6.1-luna,luna 家族最新即为 gpt-6-luna)
2343
- "gpt-5.6-luna": {
2344
- aliasOf: "gpt-6-luna",
2345
- since: "2026-11-01",
2346
- reason: "\u540C\u5BB6\u65CF\u6362\u4EE3\uFF0Cowner \u51B3\u5B9A\u900F\u660E\u91CD\u5B9A\u5411\uFF08luna \u5BB6\u65CF\u6700\u65B0\u4E3A gpt-6-luna\uFF09\u3002"
2347
- }
2348
- };
2349
- function resolveCodexOAuthEffectiveModel(model) {
2350
- const raw = asOptionalTrimmedString(model);
2351
- if (!raw) return void 0;
2352
- const normalized = raw.toLowerCase();
2353
- return CODEX_OAUTH_MODEL_ALIASES[normalized]?.aliasOf ?? raw;
2354
- }
2355
-
2356
- // packages/agent-runtime/codexResponsesProvider.ts
2357
2335
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
2358
2336
  var CODEX_CLIENT_VERSION = "0.162.1";
2359
2337
  var CODEX_ORIGINATOR = "codex_cli_rs";
@@ -2504,6 +2482,8 @@ function buildCodexRequestBody(args, identity = createCodexRequestIdentity(), se
2504
2482
  prompt_cache_key: sessionKey && isStableSessionIdentityEnabled() ? identity.sessionId : stablePromptCacheKey([model, stableInstructions, tools ?? []], "nolo-codex"),
2505
2483
  client_metadata: identity.clientMetadata
2506
2484
  };
2485
+ const reasoningEffort = args.openAiBody.reasoning_effort;
2486
+ if (reasoningEffort === "low") body.reasoning = { effort: reasoningEffort };
2507
2487
  if (instructions) body.instructions = instructions;
2508
2488
  if (tools && tools.length > 0) {
2509
2489
  body.tools = tools;
@@ -2679,10 +2659,96 @@ async function fetchCodexResponsesCompletion(args) {
2679
2659
  }
2680
2660
  return result;
2681
2661
  }
2682
- function isCodexOAuthAgent(agentConfig) {
2683
- if (!agentConfig) return false;
2684
- const apiKeyRef = asTrimmedLowercaseString(agentConfig.apiKeyRef);
2685
- return apiKeyRef === "chatgpt";
2662
+
2663
+ // packages/agent-runtime/codexOAuthModelCatalog.ts
2664
+ var CODEX_OAUTH_MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
2665
+ var CODEX_OAUTH_CATALOG_REQUEST_TIMEOUT_MS = 4e3;
2666
+ var inFlightCatalogFetches = /* @__PURE__ */ new Map();
2667
+ var catalogRequestUrl = () => `${CODEX_OAUTH_MODELS_URL}?client_version=${encodeURIComponent(CODEX_CLIENT_VERSION)}`;
2668
+ var parseCodexOAuthModelCatalog = (payload) => {
2669
+ const models = payload?.models;
2670
+ if (!Array.isArray(models)) return null;
2671
+ const windows = {};
2672
+ for (const entry of models) {
2673
+ if (!entry || typeof entry !== "object") continue;
2674
+ const record = entry;
2675
+ const slug = typeof record.slug === "string" ? record.slug.trim().toLowerCase() : "";
2676
+ if (!slug) continue;
2677
+ const contextWindow = record.context_window;
2678
+ if (typeof contextWindow !== "number" || !Number.isInteger(contextWindow) || contextWindow <= 0) {
2679
+ continue;
2680
+ }
2681
+ windows[slug] = contextWindow;
2682
+ }
2683
+ return windows;
2684
+ };
2685
+ var reportCodexOAuthCatalogFailure = (stage, accountId) => {
2686
+ try {
2687
+ const account = accountId ? accountId.slice(0, 6) : null;
2688
+ console.debug(
2689
+ `[codexOAuthModelCatalog] ${stage}${account ? ` account=${account}\u2026` : ""}`
2690
+ );
2691
+ } catch {
2692
+ }
2693
+ };
2694
+ async function ensureCodexOAuthModelCatalog(args) {
2695
+ const accessToken = args.accessToken?.trim();
2696
+ if (!accessToken) return;
2697
+ const accountId = args.accountId?.trim() || void 0;
2698
+ const nowMs = () => args.now ? args.now() : Date.now();
2699
+ const ttlMs = args.ttlMs ?? CODEX_OAUTH_CATALOG_TTL_MS;
2700
+ const accountKey = codexOAuthCatalogAccountKey(accountId);
2701
+ const snapshot = codexOAuthCatalogSnapshot(accountId);
2702
+ const now = nowMs();
2703
+ if (snapshot.fetchedAt !== null && now - snapshot.fetchedAt < ttlMs) return;
2704
+ if (snapshot.lastFailureAt !== null && now - snapshot.lastFailureAt < CODEX_OAUTH_CATALOG_RETRY_FLOOR_MS) {
2705
+ return;
2706
+ }
2707
+ const pending = inFlightCatalogFetches.get(accountKey);
2708
+ if (pending) {
2709
+ await pending;
2710
+ return;
2711
+ }
2712
+ const fetchImpl = args.fetchImpl ?? fetch;
2713
+ const timeoutMs = args.timeoutMs ?? CODEX_OAUTH_CATALOG_REQUEST_TIMEOUT_MS;
2714
+ const job = (async () => {
2715
+ try {
2716
+ const headers = {
2717
+ Authorization: `Bearer ${accessToken}`,
2718
+ originator: CODEX_ORIGINATOR,
2719
+ version: CODEX_CLIENT_VERSION,
2720
+ Accept: "application/json"
2721
+ };
2722
+ if (accountId) headers["chatgpt-account-id"] = accountId;
2723
+ const response = await fetchImpl(catalogRequestUrl(), {
2724
+ method: "GET",
2725
+ headers,
2726
+ signal: typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0
2727
+ });
2728
+ if (!response.ok) {
2729
+ markCodexOAuthCatalogFailure({ accountId, now: nowMs() });
2730
+ reportCodexOAuthCatalogFailure(`http ${response.status}`, accountId);
2731
+ return;
2732
+ }
2733
+ const payload = await response.json();
2734
+ const windows = parseCodexOAuthModelCatalog(payload);
2735
+ if (!windows) {
2736
+ markCodexOAuthCatalogFailure({ accountId, now: nowMs() });
2737
+ reportCodexOAuthCatalogFailure("malformed payload", accountId);
2738
+ return;
2739
+ }
2740
+ commitCodexOAuthCatalogWindows({ accountId, models: windows, now: nowMs() });
2741
+ } catch {
2742
+ markCodexOAuthCatalogFailure({ accountId, now: nowMs() });
2743
+ reportCodexOAuthCatalogFailure("request failed", accountId);
2744
+ }
2745
+ })();
2746
+ inFlightCatalogFetches.set(accountKey, job);
2747
+ try {
2748
+ await job;
2749
+ } finally {
2750
+ inFlightCatalogFetches.delete(accountKey);
2751
+ }
2686
2752
  }
2687
2753
 
2688
2754
  // packages/cli/client/providerResolution/codexOAuthTransport.ts
@@ -2787,7 +2853,12 @@ var resolveCodexOAuthTransport = async (ctx) => {
2787
2853
  trace.log("headers", { attempt, status: res.status });
2788
2854
  return res;
2789
2855
  };
2856
+ if (!options?.noRetries) void ensureCodexOAuthModelCatalog({
2857
+ accessToken,
2858
+ ...accountId ? { accountId } : {}
2859
+ });
2790
2860
  const result = await fetchCodexResponsesCompletion({
2861
+ ...options?.noRetries ? { maxAttempts: 1 } : {},
2791
2862
  agentConfig,
2792
2863
  accessToken,
2793
2864
  ...accountId ? { accountId } : {},
@@ -2795,7 +2866,8 @@ var resolveCodexOAuthTransport = async (ctx) => {
2795
2866
  model: agentConfig.model || "gpt-5.6-sol",
2796
2867
  messages,
2797
2868
  stream: false,
2798
- ...tools.length > 0 ? { tools } : {}
2869
+ ...tools.length > 0 ? { tools } : {},
2870
+ ...options?.reasoning_effort ? { reasoning_effort: options.reasoning_effort } : {}
2799
2871
  },
2800
2872
  sessionKey: resolveSessionKey({ dialogId: options?.dialogId }),
2801
2873
  signal: options?.signal,
@@ -2804,7 +2876,8 @@ var resolveCodexOAuthTransport = async (ctx) => {
2804
2876
  fetchImpl: (url, init) => fetchWithTransientRetry(observedFetch, url, init, {
2805
2877
  // 内层退避也要能被本次请求的 signal 打断(见 abortableSleep 注释)。
2806
2878
  sleep: (ms) => abortableSleep(deps.sleep, ms, init?.signal ?? options?.signal),
2807
- loopbackRequest
2879
+ loopbackRequest,
2880
+ ...options?.noRetries ? { maxAttempts: 1, coreDrainingMaxAttempts: 1 } : {}
2808
2881
  })
2809
2882
  });
2810
2883
  await recordLocalAvailability(result.status, result.body);
@@ -3191,7 +3264,11 @@ var resolveCursorTransport = async (ctx) => {
3191
3264
  }
3192
3265
  };
3193
3266
  };
3194
- const confirmExternalFileAccess = deps.confirmDestructiveAction;
3267
+ const hostConfirm = deps.confirmDestructiveAction;
3268
+ const confirmExternalFileAccess = hostConfirm ? async (request) => {
3269
+ const decision = await hostConfirm(request);
3270
+ return typeof decision === "boolean" ? decision : decision.approved === true;
3271
+ } : void 0;
3195
3272
  const cursorSearchWorkspace = (args) => internalSearchWorkspace({
3196
3273
  workspaceRoot,
3197
3274
  query: args.query,
@@ -3213,9 +3290,11 @@ var resolveCursorTransport = async (ctx) => {
3213
3290
  model: agentConfig.model || "cursor-default",
3214
3291
  systemPrompt: agentConfig.prompt?.trim() || void 0,
3215
3292
  tools,
3216
- executeTool: cursorExecuteTool,
3217
- searchWorkspace: cursorSearchWorkspace,
3218
- listWorkspaceEntries: cursorListWorkspaceEntries
3293
+ ...ctx.textOnly ? {} : {
3294
+ executeTool: cursorExecuteTool,
3295
+ searchWorkspace: cursorSearchWorkspace,
3296
+ listWorkspaceEntries: cursorListWorkspaceEntries
3297
+ }
3219
3298
  });
3220
3299
  return {
3221
3300
  model: agentConfig.model || "cursor-default",
@@ -3402,13 +3481,15 @@ var resolveDirectOpenAiCompatibleTransport = async (ctx) => {
3402
3481
  tools,
3403
3482
  fetchImpl: (url, init) => fetchWithTransientRetry(fetchImpl, url, init, {
3404
3483
  sleep: deps.sleep,
3405
- loopbackRequest
3484
+ loopbackRequest,
3485
+ ...options?.noRetries ? { maxAttempts: 1, coreDrainingMaxAttempts: 1 } : {}
3406
3486
  }),
3407
3487
  stream,
3408
3488
  sessionId: asOptionalTrimmedString(options?.dialogId) ?? asOptionalTrimmedString(agentConfig.key),
3409
3489
  onTextDelta: options?.onTextDelta,
3410
3490
  onReasoningDelta: options?.onReasoningDelta,
3411
3491
  signal: options?.signal,
3492
+ ...options?.noRetries ? { noRetries: true } : {},
3412
3493
  outboundDiagnostics: options?.requestId ? { callId: options.requestId } : void 0,
3413
3494
  onHttpResult: ({ status, body }) => recordLocalAvailability(status, body),
3414
3495
  ...resolveRequestApiKey ? { resolveApiKey: resolveRequestApiKey } : {}
@@ -3738,14 +3819,8 @@ var resolveOwnerOAuthProxyTransport = async (ctx) => {
3738
3819
  retryNetworkErrors: false,
3739
3820
  // 内层预算按「剩余总额度」裁剪:普通可重试状态码 ≤ 3 次、503
3740
3821
  // core_draining 排空窗口 ≤ 30 次,且都不超过剩余总额度。
3741
- maxAttempts: Math.min(
3742
- OWNER_PROXY_GENERIC_MAX_ATTEMPTS,
3743
- remaining
3744
- ),
3745
- coreDrainingMaxAttempts: Math.min(
3746
- OWNER_PROXY_CORE_DRAINING_MAX_ATTEMPTS,
3747
- remaining
3748
- ),
3822
+ maxAttempts: options?.noRetries ? 1 : Math.min(OWNER_PROXY_GENERIC_MAX_ATTEMPTS, remaining),
3823
+ coreDrainingMaxAttempts: options?.noRetries ? 1 : Math.min(OWNER_PROXY_CORE_DRAINING_MAX_ATTEMPTS, remaining),
3749
3824
  // 注意:计数必须**无条件**挂在 onRetry 上——若只在有 activityReporter
3750
3825
  // 时才挂,innerRetries / usedAttempts 会漏计,重试预算就形同虚设。
3751
3826
  onRetry: ({ attempt, maxAttempts, delayMs }) => {
@@ -3760,7 +3835,7 @@ var resolveOwnerOAuthProxyTransport = async (ctx) => {
3760
3835
  usedAttempts += 1;
3761
3836
  return { response, innerRetried: innerRetries > 0 };
3762
3837
  };
3763
- const res = await sendWithOwnerOriginMismatchRetry(
3838
+ const res = options?.noRetries ? (await send()).response : await sendWithOwnerOriginMismatchRetry(
3764
3839
  send,
3765
3840
  init.signal ?? void 0,
3766
3841
  deps.activityReporter ? ({ attempt, maxAttempts, delayMs }) => {
@@ -3963,6 +4038,7 @@ var resolvePlatformProxyTransport = async (ctx) => {
3963
4038
  {
3964
4039
  sleep: deps.sleep,
3965
4040
  loopbackRequest,
4041
+ ...options?.noRetries ? { maxAttempts: 1, coreDrainingMaxAttempts: 1 } : {},
3966
4042
  // A chat POST is not safe to replay after an ambiguous network or
3967
4043
  // gateway failure: the server/provider may already have accepted
3968
4044
  // it. The client only waits through the server's explicit pre-
@@ -4113,6 +4189,27 @@ async function resolveLocalProvider(ctx) {
4113
4189
  );
4114
4190
  }
4115
4191
 
4192
+ // packages/cli/client/providerResolveOverrides.ts
4193
+ function resolveProviderResolveOverrides(agentConfig, options) {
4194
+ if (!options) return agentConfig;
4195
+ const { toolNames, prompt } = options;
4196
+ if (toolNames === void 0 && prompt === void 0) return agentConfig;
4197
+ const current = agentConfig;
4198
+ return {
4199
+ ...agentConfig,
4200
+ ...toolNames !== void 0 ? {
4201
+ toolNames,
4202
+ exposedToolNames: toolNames,
4203
+ toolSurface: { ...current.toolSurface, finalToolNames: toolNames },
4204
+ runScopedToolSurface: {
4205
+ ...current.runScopedToolSurface,
4206
+ finalToolNames: toolNames
4207
+ }
4208
+ } : {},
4209
+ ...prompt !== void 0 ? { prompt } : {}
4210
+ };
4211
+ }
4212
+
4116
4213
  // packages/cli/client/localRuntimePaths.ts
4117
4214
  import { dirname, extname, join } from "node:path";
4118
4215
  import { fileURLToPath } from "node:url";
@@ -4401,7 +4498,7 @@ function clipLocalWakeEvidence(value, max = 1200) {
4401
4498
  function buildLocalWakeDisplayText(args) {
4402
4499
  const failed = args.terminalStatus !== "done";
4403
4500
  const duration = typeof args.durationMs === "number" && args.durationMs >= 0 ? ` \xB7 ${Math.floor(args.durationMs / 6e4)}m${Math.floor(args.durationMs / 1e3) % 60}s` : "";
4404
- return `1 \u6761\u540E\u53F0 run \u5DF2\u5B8C\u6210 \xB7 ${failed ? "\u2717" : "\u2713"} ${args.label || "\u540E\u53F0 run"}${failed ? ` ${args.terminalStatus}` : ""}${duration}`;
4501
+ return `1 \u6761\u540E\u53F0 run \u5DF2\u7ED3\u675F \xB7 ${failed ? "\u2717" : "\u2713"} ${args.label || "\u540E\u53F0 run"} [${args.terminalStatus}]${duration}`;
4405
4502
  }
4406
4503
  function buildLocalParentWakeMessage(args) {
4407
4504
  return [
@@ -5898,21 +5995,27 @@ var INSTABOX_DEFAULT_BASE_URL = "https://api.instabox.sh/v1";
5898
5995
  var INSTABOX_MAX_EXEC_SECONDS = 600;
5899
5996
  var INSTABOX_MAX_STDIN_BYTES = 1024 * 1024;
5900
5997
  var SAFE_MESSAGE = {
5998
+ instabox_upload_failed: "Instabox file upload failed",
5999
+ instabox_recovery_failed: "Instabox recovery persistence or authority validation failed",
6000
+ instabox_recovery_unknown: "Instabox machine identity is unknown; manual reconciliation required",
5901
6001
  "instabox_invalid_config": "Rejected: instabox backend needs an apiKey and a templateId",
5902
6002
  "instabox_invalid_ticket": "Rejected: execution ticket cannot be satisfied by the instabox backend",
5903
6003
  "instabox_unsupported_request": "Request is not supported by the instabox backend",
5904
6004
  "instabox_lease_not_found": "Unknown execution lease",
5905
6005
  "instabox_lease_mismatch": "Lease does not match this backend or ticket",
5906
6006
  "instabox_lease_released": "Lease was already released",
6007
+ "instabox_lease_stopping": "Lease stop was requested; execution is fenced",
5907
6008
  "instabox_create_failed": "Instabox machine create failed",
5908
6009
  "instabox_create_unconfirmed": "Instabox machine create could not be confirmed",
5909
6010
  "instabox_exec_failed": "Instabox exec could not be completed",
6011
+ "instabox_delete_forbidden": "Instabox machine deletion is forbidden; machine was not cleaned up",
5910
6012
  "instabox_cleanup_pending": "Instabox machine delete could not be confirmed; cleanup is pending"
5911
6013
  };
5912
6014
  var InstaboxBackendError = class extends Error {
5913
- constructor(code, detail, cleanupPending = false, machineId, upstreamErrorCode) {
6015
+ constructor(code, detail, cleanupPending = false, machineId, upstreamErrorCode, recovery) {
5914
6016
  super(detail ? `${SAFE_MESSAGE[code]} (${detail})` : SAFE_MESSAGE[code]);
5915
6017
  this.machineId = machineId;
6018
+ this.recovery = recovery;
5916
6019
  this.name = "InstaboxBackendError";
5917
6020
  this.code = code;
5918
6021
  this.cleanupPending = cleanupPending;
@@ -5923,6 +6026,7 @@ var InstaboxBackendError = class extends Error {
5923
6026
  cleanupPending;
5924
6027
  upstreamErrorCode;
5925
6028
  };
6029
+ var INSTABOX_MAX_UPLOAD_BYTES = 5 * 1024 ** 3;
5926
6030
  var DEFAULT_OPERATION_WAIT_SECONDS = 20;
5927
6031
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
5928
6032
  var DEFAULT_MAX_POLL_ATTEMPTS = 30;
@@ -5999,6 +6103,7 @@ var InstaboxBackend = class {
5999
6103
  #machineTimeoutSeconds;
6000
6104
  #createTimeoutMs;
6001
6105
  #deleteTimeoutMs;
6106
+ #stopTimeoutMs;
6002
6107
  #operationWaitSeconds;
6003
6108
  #pollIntervalMs;
6004
6109
  #maxPollAttempts;
@@ -6007,9 +6112,23 @@ var InstaboxBackend = class {
6007
6112
  #creationPlan;
6008
6113
  #freeOrgObservation;
6009
6114
  #billingPolicy;
6115
+ #deletionPolicy;
6010
6116
  #deleteMachineImpl;
6117
+ #recovery;
6011
6118
  #leases = /* @__PURE__ */ new Map();
6119
+ #recoveryWrites = /* @__PURE__ */ new WeakMap();
6012
6120
  constructor(options) {
6121
+ if (options.deletionPolicy !== void 0 && options.deletionPolicy !== "legacy" && options.deletionPolicy !== "forbid") {
6122
+ throw new InstaboxBackendError("instabox_invalid_config", "invalid deletion policy");
6123
+ }
6124
+ this.#deletionPolicy = options.deletionPolicy ?? "legacy";
6125
+ if (options.recovery) {
6126
+ const a = options.recovery.authority;
6127
+ if (this.#deletionPolicy !== "forbid" || !options.creationPlan || !options.recovery.accountId?.trim() || !a?.ownerUserId?.trim() || !a.ticketId?.trim() || !a.grantId?.trim() || !Number.isSafeInteger(a.generation) || a.generation < 0) {
6128
+ throw new InstaboxBackendError("instabox_invalid_config", "invalid recovery binding");
6129
+ }
6130
+ this.#recovery = { ...options.recovery, authority: structuredClone(a) };
6131
+ }
6013
6132
  const apiKey = options.apiKey?.trim() ?? "";
6014
6133
  const templateId = options.templateId?.trim() ?? "";
6015
6134
  if (!apiKey || !templateId) {
@@ -6035,11 +6154,21 @@ var InstaboxBackend = class {
6035
6154
  ""
6036
6155
  );
6037
6156
  this.#now = options.now ?? Date.now;
6038
- this.#fetch = options.fetchImpl ?? ((url, init) => globalThis.fetch(url, init));
6157
+ this.#fetch = options.fetchImpl ?? ((url, init) => {
6158
+ const { rawBody, ...request } = init;
6159
+ return globalThis.fetch(url, {
6160
+ ...request,
6161
+ ...rawBody === void 0 ? {} : { body: rawBody }
6162
+ });
6163
+ });
6039
6164
  this.#sleep = options.sleepImpl ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
6040
6165
  this.#machineTimeoutSeconds = options.machineTimeoutSeconds ?? 1800;
6041
6166
  this.#createTimeoutMs = options.createTimeoutMs ?? 18e4;
6042
6167
  this.#deleteTimeoutMs = options.deleteTimeoutMs ?? 12e4;
6168
+ this.#stopTimeoutMs = options.stopTimeoutMs ?? 12e4;
6169
+ if (!Number.isFinite(this.#stopTimeoutMs) || this.#stopTimeoutMs <= 0) {
6170
+ throw new InstaboxBackendError("instabox_invalid_config");
6171
+ }
6043
6172
  this.#operationWaitSeconds = options.operationWaitSeconds ?? DEFAULT_OPERATION_WAIT_SECONDS;
6044
6173
  this.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
6045
6174
  this.#maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
@@ -6061,6 +6190,15 @@ var InstaboxBackend = class {
6061
6190
  * 成功时刻(不采信 202 响应时间,那是「已受理」不是「可用」)。
6062
6191
  */
6063
6192
  async prepare(ticket) {
6193
+ const context = {};
6194
+ try {
6195
+ return await this.#prepare(ticket, context);
6196
+ } catch (error) {
6197
+ if (context.record) throw this.#withRecovery(error, context.record);
6198
+ throw error;
6199
+ }
6200
+ }
6201
+ async #prepare(ticket, context) {
6064
6202
  if (ticket.version !== 1 || !ticket.ticketId || !ticket.grantId) {
6065
6203
  throw new InstaboxBackendError("instabox_invalid_ticket");
6066
6204
  }
@@ -6074,23 +6212,61 @@ var InstaboxBackend = class {
6074
6212
  }
6075
6213
  }
6076
6214
  const machineName = plan?.name ?? buildInstaboxMachineName(ticket.ticketId, ticket.agentKey);
6215
+ const createBody = plan ? {
6216
+ name: plan.name,
6217
+ templateId: plan.templateId,
6218
+ orgId: plan.orgId,
6219
+ vcpus: plan.vcpus,
6220
+ memMiB: plan.memMiB
6221
+ } : {
6222
+ name: machineName,
6223
+ templateId: this.#templateId,
6224
+ timeoutSeconds: this.#machineTimeoutSeconds
6225
+ };
6226
+ const createKey2 = buildInstaboxCreateIdempotencyKey(ticket.ticketId);
6227
+ if (this.#recovery && plan) {
6228
+ const authority = this.#recovery.authority;
6229
+ if (ticket.ticketId !== authority.ticketId || ticket.grantId !== authority.grantId || ticket.callerUserId !== authority.ownerUserId) {
6230
+ throw new InstaboxBackendError("instabox_recovery_failed");
6231
+ }
6232
+ const expectedSpec = this.#freeOrgObservation.allowedSpecs.find(
6233
+ (s) => s.vcpus === plan.vcpus && s.memMiB === plan.memMiB
6234
+ );
6235
+ context.record = {
6236
+ version: 1,
6237
+ recordId: crypto.randomUUID(),
6238
+ revision: 0,
6239
+ ...authority,
6240
+ accountId: this.#recovery.accountId,
6241
+ provider: "instabox",
6242
+ baseUrl: this.#baseUrl,
6243
+ plan: structuredClone(plan),
6244
+ billingPolicy: this.#billingPolicy,
6245
+ persistentDiskGiB: expectedSpec.persistentDiskGiB,
6246
+ create: {
6247
+ method: "POST",
6248
+ path: "/machines",
6249
+ body: JSON.stringify(createBody),
6250
+ idempotencyKey: createKey2,
6251
+ requestedAt,
6252
+ state: "submitted-unknown"
6253
+ },
6254
+ stop: { intent: false, accepted: false }
6255
+ };
6256
+ await this.#saveRecovery(context.record);
6257
+ }
6077
6258
  const created = await this.#call({
6078
6259
  method: "POST",
6079
6260
  path: "/machines",
6080
- idempotencyKey: buildInstaboxCreateIdempotencyKey(ticket.ticketId),
6081
- body: plan ? {
6082
- name: plan.name,
6083
- templateId: plan.templateId,
6084
- orgId: plan.orgId,
6085
- vcpus: plan.vcpus,
6086
- memMiB: plan.memMiB
6087
- // Ordinary API keys cannot change free maxRunMinutes. Do not send it.
6088
- } : {
6089
- name: machineName,
6090
- templateId: this.#templateId,
6091
- timeoutSeconds: this.#machineTimeoutSeconds
6092
- }
6261
+ idempotencyKey: createKey2,
6262
+ body: createBody
6093
6263
  });
6264
+ if (context.record) {
6265
+ context.record.machineId = readString2(asRecord(created.payload)?.machineId) ?? void 0;
6266
+ context.record.createOperationId = readString2(asRecord(created.payload)?.id) ?? void 0;
6267
+ if (isSuccess(created.status)) context.record.create.state = "responded";
6268
+ await this.#saveRecovery(context.record);
6269
+ }
6094
6270
  if (!isSuccess(created.status)) {
6095
6271
  const upstreamCode = extractInstaboxErrorCode(created.payload);
6096
6272
  let detail = `create HTTP ${created.status}`;
@@ -6163,6 +6339,7 @@ var InstaboxBackend = class {
6163
6339
  if (this.#billingPolicy === "free-only" && machine.maxRunMinutes !== 480) mismatches.push("maxRunMinutes mismatch");
6164
6340
  if (mismatches.length > 0) {
6165
6341
  const reason = mismatches.join("; ");
6342
+ this.#assertDeletionAllowed(machineId, reason);
6166
6343
  try {
6167
6344
  if (this.#deleteMachineImpl) {
6168
6345
  await this.#deleteMachineImpl(machineId);
@@ -6208,6 +6385,7 @@ var InstaboxBackend = class {
6208
6385
  const usageBaselineRunningMs = baseline === null ? null : Math.round(baseline.runningSeconds * 1e3);
6209
6386
  const leaseId = `${machineId}:${ticket.ticketId}`;
6210
6387
  this.#leases.set(leaseId, {
6388
+ recovery: context.record,
6211
6389
  machineId,
6212
6390
  ticketId: ticket.ticketId,
6213
6391
  grantId: ticket.grantId,
@@ -6216,7 +6394,9 @@ var InstaboxBackend = class {
6216
6394
  createOperationId: operationId,
6217
6395
  usageBaselineRunningMs,
6218
6396
  releasedAt: null,
6219
- releaseOutcome: null
6397
+ releaseOutcome: null,
6398
+ stopState: null,
6399
+ stopAccepted: false
6220
6400
  });
6221
6401
  return {
6222
6402
  leaseId,
@@ -6227,6 +6407,7 @@ var InstaboxBackend = class {
6227
6407
  readableRoots: [...ticket.filesystem.readableRoots],
6228
6408
  writableRoots: [...ticket.filesystem.writableRoots],
6229
6409
  metadata: {
6410
+ ...context.record ? { recoveryRecordId: context.record.recordId } : {},
6230
6411
  machineId,
6231
6412
  machineName,
6232
6413
  templateId: this.#templateId,
@@ -6237,11 +6418,92 @@ var InstaboxBackend = class {
6237
6418
  }
6238
6419
  };
6239
6420
  }
6421
+ /** Official OpenAPI: upload-url is on the API's own origin, then PUT raw
6422
+ * bytes with the signed token and NO API credential. No redirect is followed.
6423
+ * Caller retains ownership of bytes and must not mutate them until completion.
6424
+ * Provider follows target symlinks: lexical /workspace validation is not an OS
6425
+ * containment guarantee. Deliver source before executing untrusted code. */
6426
+ async uploadFile(lease, input) {
6427
+ const entry = this.#requireEntry(lease);
6428
+ const checkLease = () => {
6429
+ if (entry.releasedAt !== null) throw new InstaboxBackendError("instabox_lease_released");
6430
+ if (entry.stopState !== null) throw new InstaboxBackendError("instabox_lease_stopping");
6431
+ };
6432
+ const fail5 = () => new InstaboxBackendError("instabox_upload_failed");
6433
+ checkLease();
6434
+ if (!input || typeof input.path !== "string" || !input.path.startsWith("/workspace/") || input.path.endsWith("/") || input.path.split("/").some((p, i) => i > 0 && (!p || p === "." || p === "..")) || /[\\\x00-\x1f\x7f]/.test(input.path) || !(input.bytes instanceof Uint8Array) || input.bytes.byteLength > INSTABOX_MAX_UPLOAD_BYTES || !input.signal || !Number.isFinite(input.deadlineAt)) throw fail5();
6435
+ let base;
6436
+ try {
6437
+ base = new URL(this.#baseUrl);
6438
+ } catch {
6439
+ throw fail5();
6440
+ }
6441
+ if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash) throw fail5();
6442
+ const controller = new AbortController();
6443
+ const remaining = input.deadlineAt - this.#now();
6444
+ if (input.signal.aborted || remaining <= 0) throw fail5();
6445
+ const started = performance.now();
6446
+ const timeLeft = () => Math.min(input.deadlineAt - this.#now(), remaining - (performance.now() - started));
6447
+ let timer;
6448
+ let onAbort;
6449
+ const interrupted = new Promise((_resolve, reject) => {
6450
+ onAbort = () => {
6451
+ controller.abort();
6452
+ reject(fail5());
6453
+ };
6454
+ input.signal.addEventListener("abort", onAbort, { once: true });
6455
+ const tick = () => {
6456
+ const left = timeLeft();
6457
+ if (left <= 0) onAbort();
6458
+ else timer = setTimeout(tick, Math.min(left, 2147483647));
6459
+ };
6460
+ tick();
6461
+ });
6462
+ const check = () => {
6463
+ checkLease();
6464
+ if (input.signal.aborted || controller.signal.aborted || timeLeft() <= 0) throw fail5();
6465
+ };
6466
+ try {
6467
+ check();
6468
+ const response = await Promise.race([this.#call({
6469
+ method: "POST",
6470
+ path: `/machines/${encodeURIComponent(entry.machineId)}/files/upload-url`,
6471
+ body: { path: input.path, parents: true },
6472
+ signal: controller.signal,
6473
+ redirect: "error"
6474
+ }), interrupted]);
6475
+ check();
6476
+ const payload = asRecord(response.payload);
6477
+ if (response.status !== 201 || !payload || payload.method !== "PUT" || payload.path !== input.path || typeof payload.url !== "string" || typeof payload.expiresAt !== "string" || !Number.isSafeInteger(payload.maxBytes) || payload.maxBytes < input.bytes.byteLength || payload.maxBytes > INSTABOX_MAX_UPLOAD_BYTES || !Number.isFinite(Date.parse(payload.expiresAt)) || Date.parse(payload.expiresAt) <= this.#now()) throw fail5();
6478
+ const url = new URL(payload.url);
6479
+ if (url.protocol !== "https:" || url.origin !== base.origin || url.username || url.password || url.hash || url.pathname !== `${base.pathname}/machines/${encodeURIComponent(entry.machineId)}/files` || !url.searchParams.get("token")) throw fail5();
6480
+ check();
6481
+ const uploaded = await Promise.race([this.#fetch(url.href, {
6482
+ method: "PUT",
6483
+ headers: { "Content-Type": "application/octet-stream" },
6484
+ rawBody: input.bytes,
6485
+ signal: controller.signal,
6486
+ redirect: "error"
6487
+ }), interrupted]);
6488
+ check();
6489
+ if (uploaded.redirected || !isSuccess(uploaded.status)) throw fail5();
6490
+ } catch (error) {
6491
+ checkLease();
6492
+ throw fail5();
6493
+ } finally {
6494
+ if (timer !== void 0) clearTimeout(timer);
6495
+ if (onAbort) input.signal.removeEventListener("abort", onAbort);
6496
+ controller.abort();
6497
+ }
6498
+ }
6240
6499
  async execute(lease, request, onEvent) {
6241
6500
  const entry = this.#requireEntry(lease);
6242
6501
  if (entry.releasedAt !== null) {
6243
6502
  throw new InstaboxBackendError("instabox_lease_released");
6244
6503
  }
6504
+ if (entry.stopState !== null) {
6505
+ throw entry.recovery ? this.#withRecovery(new InstaboxBackendError("instabox_lease_stopping"), entry.recovery) : new InstaboxBackendError("instabox_lease_stopping");
6506
+ }
6245
6507
  if (request.kind !== "command") {
6246
6508
  throw new InstaboxBackendError(
6247
6509
  "instabox_unsupported_request",
@@ -6260,11 +6522,17 @@ var InstaboxBackend = class {
6260
6522
  const executionId = `${entry.machineId}:${this.#now().toString(36)}`;
6261
6523
  const startedAt = this.#now();
6262
6524
  onEvent?.({ type: "started", executionId, at: startedAt });
6525
+ if (entry.stopState !== null) {
6526
+ throw entry.recovery ? this.#withRecovery(new InstaboxBackendError("instabox_lease_stopping"), entry.recovery) : new InstaboxBackendError("instabox_lease_stopping");
6527
+ }
6263
6528
  const response = await this.#call({
6264
6529
  method: "POST",
6265
6530
  path: `/machines/${encodeURIComponent(entry.machineId)}/exec`,
6266
6531
  body: { cmd, timeoutSeconds, ...stdin === void 0 ? {} : { stdin } }
6267
6532
  });
6533
+ if (entry.stopState !== null) {
6534
+ throw entry.recovery ? this.#withRecovery(new InstaboxBackendError("instabox_lease_stopping"), entry.recovery) : new InstaboxBackendError("instabox_lease_stopping");
6535
+ }
6268
6536
  if (!isSuccess(response.status)) {
6269
6537
  throw new InstaboxBackendError(
6270
6538
  "instabox_exec_failed",
@@ -6299,6 +6567,9 @@ var InstaboxBackend = class {
6299
6567
  usage: { wallMs: Math.max(0, this.#now() - startedAt) }
6300
6568
  });
6301
6569
  onEvent?.({ type: "exited", executionId, exitCode });
6570
+ if (entry.stopState !== null) {
6571
+ throw entry.recovery ? this.#withRecovery(new InstaboxBackendError("instabox_lease_stopping"), entry.recovery) : new InstaboxBackendError("instabox_lease_stopping");
6572
+ }
6302
6573
  return {
6303
6574
  executionId,
6304
6575
  ok: !timedOut && exitCode === 0,
@@ -6311,6 +6582,114 @@ var InstaboxBackend = class {
6311
6582
  }
6312
6583
  };
6313
6584
  }
6585
+ /**
6586
+ * Official contract verified from https://api.instabox.sh/v1/openapi.json:
6587
+ * POST /v1/machines/{id}/stop -> 202 Operation (not a Machine).
6588
+ * GET /v1/machines/{id} -> Machine; only state=stopped confirms shutdown.
6589
+ * No release/delete, and no inference from operation success, 404 or desiredState.
6590
+ * State is process-local. Restart recovery requires a durable lease/machine binding,
6591
+ * stop intent, idempotency key and reconciliation API owned by the caller.
6592
+ * A caller publishing an already-resolved execute result must also fence its
6593
+ * own run generation/cancellation state: this backend cannot revoke that result.
6594
+ * The request deadline bounds observation, not transport cancellation; an HTTP
6595
+ * request may settle late, so retries keep the same stop idempotency key.
6596
+ */
6597
+ async stop(lease) {
6598
+ const entry = this.#requireEntry(lease);
6599
+ if (entry.releasedAt !== null) {
6600
+ throw new InstaboxBackendError("instabox_lease_released");
6601
+ }
6602
+ if (entry.stopState === "stopped") {
6603
+ return { ok: true, state: "stopped", machineId: entry.machineId };
6604
+ }
6605
+ if (entry.stopTask) return entry.stopTask;
6606
+ entry.stopState = "unknown";
6607
+ const task = this.#stopEntry(entry);
6608
+ entry.stopTask = task;
6609
+ try {
6610
+ return await task;
6611
+ } catch (error) {
6612
+ if (entry.recovery) throw this.#withRecovery(error, entry.recovery);
6613
+ throw error;
6614
+ } finally {
6615
+ entry.stopTask = void 0;
6616
+ }
6617
+ }
6618
+ async #stopEntry(entry) {
6619
+ const startedAt = this.#now();
6620
+ const wallStartedAt = Date.now();
6621
+ const remaining = () => this.#stopTimeoutMs - Math.max(
6622
+ this.#now() - startedAt,
6623
+ Date.now() - wallStartedAt
6624
+ );
6625
+ if (entry.recovery) {
6626
+ entry.recovery.stop.intent = true;
6627
+ entry.recovery.stop.idempotencyKey ??= `nolo-stop-${entry.machineId}`;
6628
+ await this.#saveRecovery(entry.recovery);
6629
+ }
6630
+ const result = async (state2) => {
6631
+ if (entry.recovery) {
6632
+ entry.recovery.stop.lastObservedState = state2;
6633
+ await this.#saveRecovery(entry.recovery);
6634
+ }
6635
+ entry.stopState = state2;
6636
+ return { ok: state2 === "stopped", state: state2, machineId: entry.machineId };
6637
+ };
6638
+ const path = `/machines/${encodeURIComponent(entry.machineId)}`;
6639
+ let state = "unknown";
6640
+ for (let attempt = 0; attempt < this.#maxPollAttempts && remaining() > 0; attempt += 1) {
6641
+ const observed = await this.#call({ method: "GET", path, timeoutMs: remaining() });
6642
+ const machine = observed.status === 200 ? asRecord(observed.payload) : null;
6643
+ if (machine?.id !== entry.machineId || typeof machine.state !== "string") {
6644
+ return result("unknown");
6645
+ }
6646
+ if (machine.state === "stopped") return result("stopped");
6647
+ if (machine.state !== "running" && machine.state !== "stopping") {
6648
+ return result("unknown");
6649
+ }
6650
+ state = entry.stopAccepted || machine.state === "stopping" ? "pending" : "unknown";
6651
+ if (!entry.stopAccepted && machine.state === "running" && remaining() > 0) {
6652
+ const accepted = await this.#call({
6653
+ method: "POST",
6654
+ path: `${path}/stop`,
6655
+ idempotencyKey: entry.recovery?.stop.idempotencyKey ?? `nolo-stop-${entry.machineId}`,
6656
+ timeoutMs: remaining(),
6657
+ // Runs even when the HTTP response arrives after the observation deadline.
6658
+ onResult: entry.recovery ? async (response) => {
6659
+ if (response.status === 202) {
6660
+ entry.stopAccepted = true;
6661
+ entry.recovery.stop.accepted = true;
6662
+ const operation2 = asRecord(response.payload);
6663
+ if (this.#isStopOperation(operation2, entry)) {
6664
+ entry.recovery.stop.operationId = readString2(operation2?.id);
6665
+ }
6666
+ await this.#saveRecovery(entry.recovery);
6667
+ }
6668
+ } : void 0
6669
+ });
6670
+ if (accepted.status === 202) entry.stopAccepted = true;
6671
+ const operation = asRecord(accepted.payload);
6672
+ if (accepted.status !== 202 || !this.#isStopOperation(operation, entry)) {
6673
+ return result("unknown");
6674
+ }
6675
+ state = "pending";
6676
+ }
6677
+ if (attempt + 1 < this.#maxPollAttempts && remaining() > 0 && this.#pollIntervalMs > 0) {
6678
+ let timer;
6679
+ try {
6680
+ await Promise.race([
6681
+ this.#sleep(Math.min(this.#pollIntervalMs, remaining())),
6682
+ new Promise((resolve5) => {
6683
+ timer = setTimeout(resolve5, remaining());
6684
+ })
6685
+ ]);
6686
+ } finally {
6687
+ if (timer !== void 0) clearTimeout(timer);
6688
+ }
6689
+ }
6690
+ }
6691
+ return result(state);
6692
+ }
6314
6693
  /**
6315
6694
  * DELETE 只是「已受理」;必须轮询 delete operation 到 `succeeded` 才算清理完成。
6316
6695
  * 未确认一律抛 `instabox_cleanup_pending`(`cleanupPending: true`),lease 保持可重试。
@@ -6318,6 +6697,12 @@ var InstaboxBackend = class {
6318
6697
  */
6319
6698
  async release(lease, reason) {
6320
6699
  const entry = this.#requireEntry(lease);
6700
+ try {
6701
+ this.#assertDeletionAllowed(entry.machineId);
6702
+ } catch (error) {
6703
+ if (entry.recovery) throw this.#withRecovery(error, entry.recovery);
6704
+ throw error;
6705
+ }
6321
6706
  if (entry.releasedAt !== null) {
6322
6707
  return entry.releaseOutcome ?? { ok: true };
6323
6708
  }
@@ -6366,6 +6751,145 @@ var InstaboxBackend = class {
6366
6751
  });
6367
6752
  return this.#confirmRelease(entry, providerUsageMs);
6368
6753
  }
6754
+ #assertDeletionAllowed(machineId, detail) {
6755
+ if (this.#deletionPolicy === "forbid") {
6756
+ throw new InstaboxBackendError("instabox_delete_forbidden", detail, true, machineId);
6757
+ }
6758
+ }
6759
+ #isStopOperation(operation, entry) {
6760
+ const id = readString2(operation?.id);
6761
+ const knownId = entry.recovery?.stop.operationId;
6762
+ return id !== null && operation?.machineId === entry.machineId && (!entry.recovery || operation.type === "stop") && ["queued", "running", "succeeded", "failed"].includes(String(operation.state)) && (knownId === void 0 || knownId === id);
6763
+ }
6764
+ #withRecovery(error, record) {
6765
+ const known = error instanceof InstaboxBackendError ? error : void 0;
6766
+ return new InstaboxBackendError(
6767
+ known?.code ?? "instabox_recovery_failed",
6768
+ void 0,
6769
+ true,
6770
+ record.machineId,
6771
+ known?.upstreamErrorCode,
6772
+ {
6773
+ recordId: record.recordId,
6774
+ machineId: record.machineId,
6775
+ createOperationId: record.createOperationId,
6776
+ stopOperationId: record.stop.operationId,
6777
+ machineState: record.machineId ? "known" : "unknown"
6778
+ }
6779
+ );
6780
+ }
6781
+ async #saveRecovery(record) {
6782
+ const previous = this.#recoveryWrites.get(record) ?? Promise.resolve();
6783
+ const task = previous.then(async () => {
6784
+ const revision = record.revision;
6785
+ const next = structuredClone({ ...record, revision: revision + 1 });
6786
+ try {
6787
+ await this.#recovery.store.save(next, revision);
6788
+ record.revision = next.revision;
6789
+ } catch {
6790
+ throw this.#withRecovery(new InstaboxBackendError("instabox_recovery_failed"), record);
6791
+ }
6792
+ });
6793
+ this.#recoveryWrites.set(record, task);
6794
+ await task;
6795
+ }
6796
+ /** Authority must come from authenticated host context, never from a request body. */
6797
+ async restoreForStop(recordId, authority) {
6798
+ if (typeof recordId !== "string" || !/^[a-zA-Z0-9_-]{1,128}$/.test(recordId)) {
6799
+ throw new InstaboxBackendError("instabox_recovery_failed");
6800
+ }
6801
+ try {
6802
+ return await this.#restoreForStop(recordId, authority);
6803
+ } catch (error) {
6804
+ if (error instanceof InstaboxBackendError && error.recovery) throw error;
6805
+ throw new InstaboxBackendError(
6806
+ "instabox_recovery_failed",
6807
+ void 0,
6808
+ true,
6809
+ void 0,
6810
+ void 0,
6811
+ { recordId, machineState: "unknown" }
6812
+ );
6813
+ }
6814
+ }
6815
+ async #restoreForStop(recordId, authority) {
6816
+ const binding = this.#recovery;
6817
+ if (!binding || !/^[a-zA-Z0-9_-]{1,128}$/.test(recordId)) {
6818
+ throw new InstaboxBackendError("instabox_recovery_failed");
6819
+ }
6820
+ let record;
6821
+ try {
6822
+ record = await binding.store.load(recordId);
6823
+ } catch {
6824
+ throw new InstaboxBackendError("instabox_recovery_failed");
6825
+ }
6826
+ if (!record || record.version !== 1 || record.recordId !== recordId || !Number.isSafeInteger(record.revision) || record.revision < 1 || record.provider !== "instabox" || record.accountId !== binding.accountId || record.baseUrl !== this.#baseUrl || ![authority, binding.authority].every((a) => a.ticketId === record.ticketId && a.grantId === record.grantId && a.generation === record.generation && a.ownerUserId === record.ownerUserId) || !record.plan || !this.#creationPlan || ![
6827
+ "orgId",
6828
+ "name",
6829
+ "templateId",
6830
+ "vcpus",
6831
+ "memMiB",
6832
+ "maxRunMinutes",
6833
+ "snapshotId",
6834
+ "persistentDiskGiB",
6835
+ "rootDiskGiB"
6836
+ ].every(
6837
+ (key) => record.plan[key] === this.#creationPlan[key]
6838
+ ) || record.billingPolicy !== this.#billingPolicy || !Number.isSafeInteger(record.persistentDiskGiB) || record.persistentDiskGiB < 0 || record.create?.method !== "POST" || record.create.path !== "/machines" || record.create.idempotencyKey !== buildInstaboxCreateIdempotencyKey(record.ticketId) || !Number.isFinite(record.create.requestedAt) || !["submitted-unknown", "responded"].includes(record.create.state) || typeof record.stop?.intent !== "boolean" || typeof record.stop.accepted !== "boolean" || record.stop.accepted && !record.stop.intent || record.stop.intent && record.stop.idempotencyKey !== `nolo-stop-${record.machineId}`) {
6839
+ throw new InstaboxBackendError("instabox_recovery_failed");
6840
+ }
6841
+ const plan = this.#creationPlan;
6842
+ const expectedSpec = this.#freeOrgObservation?.allowedSpecs.filter(
6843
+ (spec2) => spec2.vcpus === plan.vcpus && spec2.memMiB === plan.memMiB
6844
+ );
6845
+ if (expectedSpec?.length !== 1 || expectedSpec[0].persistentDiskGiB !== record.persistentDiskGiB) {
6846
+ throw new InstaboxBackendError("instabox_recovery_failed");
6847
+ }
6848
+ const body = JSON.stringify({
6849
+ name: plan.name,
6850
+ templateId: plan.templateId,
6851
+ orgId: plan.orgId,
6852
+ vcpus: plan.vcpus,
6853
+ memMiB: plan.memMiB
6854
+ });
6855
+ if (record.create.body !== body) throw new InstaboxBackendError("instabox_recovery_failed");
6856
+ if (!readString2(record.machineId)) {
6857
+ throw this.#withRecovery(new InstaboxBackendError("instabox_recovery_unknown"), record);
6858
+ }
6859
+ const machineId = record.machineId;
6860
+ const response = await this.#call({ method: "GET", path: `/machines/${encodeURIComponent(machineId)}` });
6861
+ const machine = asRecord(response.payload);
6862
+ const spec = asRecord(machine?.spec);
6863
+ if (response.status !== 200 || machine?.id !== machineId || machine.orgId !== plan.orgId || machine.name !== plan.name || asRecord(machine.template)?.id !== plan.templateId || spec?.vcpus !== plan.vcpus || spec?.memMiB !== plan.memMiB || spec?.persistentDiskGiB !== record.persistentDiskGiB || typeof machine.free !== "boolean" || record.billingPolicy === "free-only" && (machine.free !== true || machine.maxRunMinutes !== plan.maxRunMinutes)) {
6864
+ throw this.#withRecovery(new InstaboxBackendError("instabox_recovery_failed"), record);
6865
+ }
6866
+ const leaseId = `${machineId}:${record.ticketId}`;
6867
+ if (this.#leases.has(leaseId)) throw this.#withRecovery(new InstaboxBackendError("instabox_lease_mismatch"), record);
6868
+ this.#leases.set(leaseId, {
6869
+ recovery: structuredClone(record),
6870
+ machineId,
6871
+ ticketId: record.ticketId,
6872
+ grantId: record.grantId,
6873
+ requestedAt: record.create.requestedAt,
6874
+ readyAt: this.#now(),
6875
+ createOperationId: record.createOperationId ?? "",
6876
+ usageBaselineRunningMs: null,
6877
+ releasedAt: null,
6878
+ releaseOutcome: null,
6879
+ stopState: "unknown",
6880
+ stopAccepted: record.stop.accepted
6881
+ });
6882
+ return {
6883
+ leaseId,
6884
+ backendId: INSTABOX_BACKEND_ID,
6885
+ ticketId: record.ticketId,
6886
+ grantId: record.grantId,
6887
+ createdAt: this.#now(),
6888
+ readableRoots: [],
6889
+ writableRoots: [],
6890
+ metadata: { machineId, recoveryRecordId: recordId, stopOnly: true }
6891
+ };
6892
+ }
6369
6893
  #confirmRelease(entry, providerUsageMs) {
6370
6894
  const outcome = providerUsageMs === void 0 ? { ok: true } : { ok: true, providerUsageMs };
6371
6895
  entry.releasedAt = this.#now();
@@ -6452,6 +6976,25 @@ var InstaboxBackend = class {
6452
6976
  return segments.join(" && ");
6453
6977
  }
6454
6978
  async #call(args) {
6979
+ if (args.timeoutMs !== void 0) {
6980
+ if (args.timeoutMs <= 0) return { status: 0, payload: null, transportError: "Timeout" };
6981
+ let timer;
6982
+ try {
6983
+ return await Promise.race([
6984
+ this.#call({ ...args, timeoutMs: void 0 }),
6985
+ new Promise((resolve5) => {
6986
+ timer = setTimeout(() => resolve5({ status: 0, payload: null, transportError: "Timeout" }), args.timeoutMs);
6987
+ })
6988
+ ]);
6989
+ } finally {
6990
+ if (timer !== void 0) clearTimeout(timer);
6991
+ }
6992
+ }
6993
+ if (args.onResult) {
6994
+ const response = await this.#call({ ...args, onResult: void 0 });
6995
+ await args.onResult(response);
6996
+ return response;
6997
+ }
6455
6998
  const headers = {
6456
6999
  "X-API-Key": this.#apiKey,
6457
7000
  Accept: "application/json"
@@ -6462,8 +7005,11 @@ var InstaboxBackend = class {
6462
7005
  const response = await this.#fetch(`${this.#baseUrl}${args.path}`, {
6463
7006
  method: args.method,
6464
7007
  headers,
7008
+ ...args.signal ? { signal: args.signal } : {},
7009
+ ...args.redirect ? { redirect: args.redirect } : {},
6465
7010
  ...args.body !== void 0 ? { body: JSON.stringify(args.body) } : {}
6466
7011
  });
7012
+ if (args.redirect === "error" && response.redirected) return { status: 0, payload: null };
6467
7013
  let payload = null;
6468
7014
  if (typeof response?.json === "function") {
6469
7015
  try {
@@ -7392,6 +7938,11 @@ function createCliStartAgentRunExecutor(deps = {}) {
7392
7938
  allowCredentialConcurrency: effectiveAllowCredentialConcurrency
7393
7939
  });
7394
7940
  const dodCommands = Array.isArray(args.dodCommands) ? args.dodCommands.filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()) : typeof args.dod === "string" && args.dod.trim().length > 0 ? [args.dod.trim()] : Array.isArray(args.dod) ? args.dod.filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()) : void 0;
7941
+ const dispatchedBudget = resolveDispatchedSubtaskBudget({
7942
+ ...typeof args.maxToolCalls === "number" ? { maxToolCalls: args.maxToolCalls } : {},
7943
+ ...typeof args.maxWallClockMs === "number" ? { maxWallClockMs: args.maxWallClockMs } : {},
7944
+ env: deps.env
7945
+ });
7395
7946
  const { runId, batchId: resolvedBatchId } = await spawnLocalBackgroundRun(
7396
7947
  {
7397
7948
  rawArgs,
@@ -7409,6 +7960,7 @@ function createCliStartAgentRunExecutor(deps = {}) {
7409
7960
  ...dodCommands && dodCommands.length > 0 ? { dodCommands } : {},
7410
7961
  cwd: requestedCwd ?? deps.cwd ?? process.cwd(),
7411
7962
  message,
7963
+ subtaskBudgetLimits: dispatchedBudget.limits,
7412
7964
  ...args.ephemeral === true ? { ephemeral: true } : {},
7413
7965
  output: noopOutput
7414
7966
  },
@@ -7469,7 +8021,11 @@ function createCliStartAgentRunExecutor(deps = {}) {
7469
8021
  };
7470
8022
  };
7471
8023
  }
7472
- async function spawnContinuationRun(reconciled, userInput, deps) {
8024
+ function controlMessageLabels(queuedCount) {
8025
+ const labels = agentRunCardLabels().controlMessage;
8026
+ return { ...labels, queued: queuedCount === void 0 ? labels.queued : labels.enqueued(queuedCount) };
8027
+ }
8028
+ async function spawnContinuationRun(reconciled, message, userInput, deps) {
7473
8029
  if (!reconciled.ephemeral && !reconciled.dialogId) {
7474
8030
  throw new Error(t("agentRunTool.noDialogToContinue"));
7475
8031
  }
@@ -7504,19 +8060,13 @@ async function spawnContinuationRun(reconciled, userInput, deps) {
7504
8060
  // 续跑继承原 run 的凭证组(同一 agent),供并发扇出守卫判定。
7505
8061
  ...reconciled.credentialGroup ? { credentialGroup: reconciled.credentialGroup } : {},
7506
8062
  cwd: reconciled.cwd ?? deps.cwd ?? process.cwd(),
7507
- message: userInput,
8063
+ message,
7508
8064
  ...reconciled.ephemeral ? { ephemeral: true } : {},
7509
8065
  continuedFrom,
7510
8066
  output: noopOutput
7511
8067
  },
7512
8068
  { ...deps, shellApprovalHost: deps.shellApprovalHostForAgent ? await deps.shellApprovalHostForAgent(agentKey) : deps.shellApprovalHost }
7513
8069
  );
7514
- const displayName = resolveRunLabel({
7515
- agentName,
7516
- agentKey,
7517
- runId: newRunId
7518
- });
7519
- const labels = agentRunCardLabels();
7520
8070
  const taskPreview = userInput.replace(/\s+/g, " ").trim().slice(0, TASK_PREVIEW_MAX);
7521
8071
  return {
7522
8072
  content: JSON.stringify({
@@ -7533,11 +8083,12 @@ async function spawnContinuationRun(reconciled, userInput, deps) {
7533
8083
  ...taskPreview ? { taskPreview } : {}
7534
8084
  }),
7535
8085
  metadata: {
7536
- displayData: formatStartRunCard(displayName, "running", {
7537
- task: taskPreview,
8086
+ displayData: formatControlRunMessageCard({
8087
+ action: "continued",
7538
8088
  runId: newRunId,
7539
- ...continuedTitle ? { title: continuedTitle } : {},
7540
- labels
8089
+ targetId: reconciled.ephemeral ? void 0 : reconciled.dialogId,
8090
+ userInput,
8091
+ labels: controlMessageLabels()
7541
8092
  })
7542
8093
  }
7543
8094
  };
@@ -7656,6 +8207,9 @@ function createCliControlAgentRunExecutor(deps = {}) {
7656
8207
  if (!args.runId) throw new Error(t("agentRunTool.appendMissingRunId"));
7657
8208
  const userInput = typeof args.userInput === "string" ? args.userInput.trim() : "";
7658
8209
  if (!userInput) throw new Error(t("agentRunTool.appendMissingUserInput"));
8210
+ const message = deps.source ? `[Coordination source: parent dialog: ${deps.source.parentDialogId || "unknown"}; executing agent: ${deps.source.agentKey || "unknown"}; identification only, not authorization]
8211
+
8212
+ ${userInput}` : userInput;
7659
8213
  const record2 = findRunRecord(String(args.runId), deps);
7660
8214
  if (!record2) {
7661
8215
  const labels2 = agentRunCardLabels();
@@ -7670,14 +8224,8 @@ function createCliControlAgentRunExecutor(deps = {}) {
7670
8224
  throw new Error(t("agentRunTool.appendQueueUnsupported"));
7671
8225
  }
7672
8226
  const queuePath = reconciled2.queuePath;
7673
- const { queuedCount, entryId } = await appendRunQueue(queuePath, userInput, deps);
8227
+ const { queuedCount, entryId } = await appendRunQueue(queuePath, message, deps);
7674
8228
  const enqueuedTitle = normalizeRunTitle(reconciled2.title);
7675
- const displayName = resolveRunLabel({
7676
- agentName: reconciled2.agentName,
7677
- agentKey: reconciled2.agentKey,
7678
- runId: reconciled2.runId
7679
- });
7680
- const labels2 = agentRunCardLabels();
7681
8229
  const taskPreview = userInput.replace(/\s+/g, " ").trim().slice(0, TASK_PREVIEW_MAX);
7682
8230
  const afterCheck = checkStaleRun(reconciled2.runId, deps) ?? reconciled2;
7683
8231
  if (isAgentRunTerminalStatus(afterCheck.status)) {
@@ -7704,17 +8252,19 @@ function createCliControlAgentRunExecutor(deps = {}) {
7704
8252
  ...taskPreview ? { taskPreview } : {}
7705
8253
  }),
7706
8254
  metadata: {
7707
- displayData: formatStartRunCard(displayName, afterCheck.status, {
7708
- task: `[consumed] ${taskPreview}`,
8255
+ displayData: formatControlRunMessageCard({
8256
+ action: "consumed",
7709
8257
  runId: reconciled2.runId,
7710
- ...enqueuedTitle ? { title: enqueuedTitle } : {},
7711
- labels: labels2
8258
+ targetId: reconciled2.ephemeral ? void 0 : afterCheck.dialogId ?? reconciled2.dialogId,
8259
+ userInput,
8260
+ labels: controlMessageLabels()
7712
8261
  })
7713
8262
  }
7714
8263
  };
7715
8264
  }
7716
- const mergedMessage = remainingEntries.map((e) => e.text).join("\n\n");
7717
- return await spawnContinuationRun(afterCheck, mergedMessage, deps);
8265
+ const mergedMessage = remainingEntries.length === 1 ? remainingEntries[0].text : remainingEntries.map((entry, index) => `--- queued message ${index + 1} of ${remainingEntries.length} (${entry.text.length} characters) ---
8266
+ ${entry.text}`).join("\n\n");
8267
+ return await spawnContinuationRun(afterCheck, mergedMessage, userInput, deps);
7718
8268
  }
7719
8269
  return {
7720
8270
  content: JSON.stringify({
@@ -7729,16 +8279,17 @@ function createCliControlAgentRunExecutor(deps = {}) {
7729
8279
  ...taskPreview ? { taskPreview } : {}
7730
8280
  }),
7731
8281
  metadata: {
7732
- displayData: formatStartRunCard(displayName, "running", {
7733
- task: `[enqueued ${queuedCount}] ${taskPreview}`,
8282
+ displayData: formatControlRunMessageCard({
8283
+ action: "queued",
7734
8284
  runId: reconciled2.runId,
7735
- ...enqueuedTitle ? { title: enqueuedTitle } : {},
7736
- labels: labels2
8285
+ targetId: reconciled2.ephemeral ? void 0 : reconciled2.dialogId,
8286
+ userInput,
8287
+ labels: controlMessageLabels(queuedCount)
7737
8288
  })
7738
8289
  }
7739
8290
  };
7740
8291
  }
7741
- return await spawnContinuationRun(reconciled2, userInput, deps);
8292
+ return await spawnContinuationRun(reconciled2, message, userInput, deps);
7742
8293
  }
7743
8294
  if (action !== "status" && action !== "stop" && action !== "wait") {
7744
8295
  throw new Error(t("agentRunTool.unknownAction", String(action)));
@@ -8421,6 +8972,7 @@ function buildLocalToolExecutors(args) {
8421
8972
  currentAgentKey: args.agentKey
8422
8973
  }),
8423
8974
  controlAgentRun: createCliControlAgentRunExecutor({
8975
+ source: { parentDialogId: args.sourceParentDialogId, agentKey: args.agentKey },
8424
8976
  shellApprovalHostForAgent: args.shellApprovalHostForAgent,
8425
8977
  cwd: args.workspaceRoot,
8426
8978
  cliEntrypoint: args.cliEntrypoint,
@@ -10018,7 +10570,7 @@ async function createLocalDockerWorkload(args) {
10018
10570
  }
10019
10571
  const createLifecycle = args.createLifecycle ?? (async () => {
10020
10572
  const [{ DockerWorkloadLifecycle, createDockerBindingAuthorizer }, accounts] = await Promise.all([
10021
- import("./workloadLifecycle-4BEQHMSD.js"),
10573
+ import("./workloadLifecycle-4C42SVBD.js"),
10022
10574
  import("./accountResolver-4LKIHEKL.js")
10023
10575
  ]);
10024
10576
  const resolveAccount = args.resolveAccount ?? accounts.createDockerAccountResolver({
@@ -12467,7 +13019,6 @@ async function recordLocalAvailabilityForAgent(args) {
12467
13019
  return typeof entry?.nextAvailableAt === "number" ? entry.nextAvailableAt : action.nextAvailableAt;
12468
13020
  }
12469
13021
  const record = current;
12470
- if (action.kind === "clear" && !("nextAvailableAt" in record)) return void 0;
12471
13022
  const next = { ...record };
12472
13023
  let effectiveNextAvailableAt;
12473
13024
  if (action.kind === "mark") {
@@ -12478,7 +13029,30 @@ async function recordLocalAvailabilityForAgent(args) {
12478
13029
  next.nextAvailableAt = merged;
12479
13030
  effectiveNextAvailableAt = merged;
12480
13031
  } else {
13032
+ const hadCooldown = "nextAvailableAt" in record;
12481
13033
  delete next.nextAvailableAt;
13034
+ const observedAt = now();
13035
+ const snapshot = extractQuotaSnapshot(
13036
+ typeof agentConfig?.provider === "string" ? agentConfig.provider : void 0,
13037
+ status,
13038
+ void 0,
13039
+ args.body,
13040
+ observedAt
13041
+ );
13042
+ const previous = record.quota && typeof record.quota === "object" ? record.quota : void 0;
13043
+ const hasFreshSnapshot = Boolean(snapshot) && (!previous?.observedAt || previous.observedAt <= observedAt);
13044
+ if (!hadCooldown && !hasFreshSnapshot) return void 0;
13045
+ if (hasFreshSnapshot) {
13046
+ const windows = new Map(
13047
+ (Array.isArray(previous?.windows) ? previous.windows : []).map((window) => [window.scope, window])
13048
+ );
13049
+ for (const window of snapshot?.windows ?? []) windows.set(window.scope, window);
13050
+ next.quota = {
13051
+ ...previous,
13052
+ windows: [...windows.values()],
13053
+ observedAt
13054
+ };
13055
+ }
12482
13056
  }
12483
13057
  await store.write(key, next).catch(() => void 0);
12484
13058
  if (action.kind !== "mark") return void 0;
@@ -12672,7 +13246,7 @@ function createCliLocalRuntimeAdapter(deps) {
12672
13246
  emitRunIsolationNotice(fact, { runId });
12673
13247
  try {
12674
13248
  const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync4 } = await import("node:fs");
12675
- const { resolveRunsDir: resolveRunsDir2 } = await import("./agentRunControl-OI3LOENH.js");
13249
+ const { resolveRunsDir: resolveRunsDir2 } = await import("./agentRunControl-TAMHJQSR.js");
12676
13250
  recordLocalRunExecutionIsolation({
12677
13251
  runId,
12678
13252
  runsDir: resolveRunsDir2(deps.env),
@@ -12697,7 +13271,7 @@ function createCliLocalRuntimeAdapter(deps) {
12697
13271
  dockerWorkloadReleased = true;
12698
13272
  dockerWorkloadLoad = null;
12699
13273
  if (loaded) {
12700
- const { resolveRunsDir: resolveRunsDir2 } = await import("./agentRunControl-OI3LOENH.js");
13274
+ const { resolveRunsDir: resolveRunsDir2 } = await import("./agentRunControl-TAMHJQSR.js");
12701
13275
  const exportRunDiff = deps.exportDockerRunDiffArtifact ?? exportDockerRunDiffArtifact;
12702
13276
  const warn = (message) => process.stderr.write(`[nolo] ${message}
12703
13277
  `);
@@ -12780,6 +13354,7 @@ function createCliLocalRuntimeAdapter(deps) {
12780
13354
  let runtimeToolExecutionLimits = {};
12781
13355
  const buildCurrentToolExecutors = (agentKey) => buildLocalToolExecutors({
12782
13356
  workspaceRoot,
13357
+ sourceParentDialogId: deps.parentDialogId,
12783
13358
  env: deps.env,
12784
13359
  fetchImpl,
12785
13360
  ...deps.chromeConnectorClient ? { chromeConnectorClient: deps.chromeConnectorClient } : {},
@@ -12844,6 +13419,8 @@ function createCliLocalRuntimeAdapter(deps) {
12844
13419
  );
12845
13420
  }
12846
13421
  });
13422
+ let nextSettingsRemoteAttempt = 0;
13423
+ let settingsDiagnosticEmitted = false;
12847
13424
  const adapterBase = {
12848
13425
  host: "cli",
12849
13426
  capabilities: [
@@ -12859,10 +13436,23 @@ function createCliLocalRuntimeAdapter(deps) {
12859
13436
  const sharedStore = await getOrCreateSharedStore(deps);
12860
13437
  if (userId) {
12861
13438
  try {
12862
- const settingsRecord = await sharedStore.read(
12863
- createUserKey.settings(userId),
12864
- { remote: false }
12865
- );
13439
+ const settingsKey = createUserKey.settings(userId);
13440
+ let settingsRecord = await sharedStore.read(settingsKey, { remote: false });
13441
+ if (!settingsRecord && Date.now() >= nextSettingsRemoteAttempt) {
13442
+ nextSettingsRemoteAttempt = Date.now() + 3e4;
13443
+ let unavailable = false;
13444
+ settingsRecord = await sharedStore.read(settingsKey, {
13445
+ onRemoteUnavailable: () => {
13446
+ unavailable = true;
13447
+ }
13448
+ });
13449
+ if (!settingsRecord && !settingsDiagnosticEmitted) {
13450
+ process.stderr.write(`[nolo] Account skill settings ${unavailable ? "unavailable" : "not found"}; preferences unknown, retrying on a later load.
13451
+ `);
13452
+ settingsDiagnosticEmitted = true;
13453
+ }
13454
+ }
13455
+ if (settingsRecord) settingsDiagnosticEmitted = false;
12866
13456
  if (settingsRecord && typeof settingsRecord === "object") {
12867
13457
  skillPreferences = {
12868
13458
  ...Object.prototype.hasOwnProperty.call(settingsRecord, "enabledSkills") ? { enabledSkills: settingsRecord.enabledSkills } : {},
@@ -12871,6 +13461,10 @@ function createCliLocalRuntimeAdapter(deps) {
12871
13461
  }
12872
13462
  systemBuiltinSkills = settingsRecord && typeof settingsRecord === "object" ? settingsRecord.systemBuiltinSkills ?? null : null;
12873
13463
  } catch {
13464
+ if (!settingsDiagnosticEmitted) {
13465
+ process.stderr.write("[nolo] Account skill settings unavailable; preferences unknown, retrying on a later load.\n");
13466
+ settingsDiagnosticEmitted = true;
13467
+ }
12874
13468
  }
12875
13469
  }
12876
13470
  const enabledSkills = resolveUserEnabledSkills(skillPreferences).sort();
@@ -13037,8 +13631,7 @@ function createCliLocalRuntimeAdapter(deps) {
13037
13631
  loopbackRequest
13038
13632
  })
13039
13633
  }),
13040
- resolveProviderBase: async (agentConfig) => {
13041
- const nowMs = now();
13634
+ resolveProviderBase: async (agentConfig, options) => {
13042
13635
  const serverUrl = asOptionalTrimmedString(deps.env.NOLO_SERVER) ?? "https://us.nolo.chat";
13043
13636
  const authToken = asOptionalTrimmedString(deps.env.AUTH_TOKEN);
13044
13637
  let ownerOriginExecution;
@@ -13134,18 +13727,6 @@ function createCliLocalRuntimeAdapter(deps) {
13134
13727
  let recordLocalAvailability = async () => ownerRouteLeakGuard("availability write");
13135
13728
  let apiKeyRefResolver = () => ownerRouteLeakGuard("apiKeyRefResolver read");
13136
13729
  if (!ownerOriginExecution) {
13137
- const credentialKeyForGate = resolveCredentialKeyWithFallback(
13138
- agentConfig
13139
- );
13140
- const gateEntry = credentialKeyForGate ? await readEffectiveCredentialEntry(credentialKeyForGate, deps.env, nowMs).catch(
13141
- () => void 0
13142
- ) : void 0;
13143
- const credentialCooldownAt = gateEntry?.nextAvailableAt;
13144
- const agentLevelNextAvailableAt = agentConfig.nextAvailableAt;
13145
- const effectiveNextAvailableAt = typeof credentialCooldownAt === "number" ? mergeAvailabilityDeadline(
13146
- agentLevelNextAvailableAt,
13147
- credentialCooldownAt
13148
- ) : agentLevelNextAvailableAt;
13149
13730
  recordLocalAvailability = (status, body) => recordLocalAvailabilityForAgent({
13150
13731
  deps,
13151
13732
  agentConfig,
@@ -13153,24 +13734,6 @@ function createCliLocalRuntimeAdapter(deps) {
13153
13734
  body,
13154
13735
  now
13155
13736
  }).then(() => void 0);
13156
- const gateDecision = resolveCooldownGate(
13157
- {
13158
- nextAvailableAt: effectiveNextAvailableAt,
13159
- lastProbeAt: gateEntry?.lastProbeAt
13160
- },
13161
- nowMs
13162
- );
13163
- if (gateDecision === "blocked") {
13164
- throw new Error(
13165
- `agent temporarily unavailable until ${new Date(Number(effectiveNextAvailableAt)).toISOString()}`
13166
- );
13167
- }
13168
- const activeKeyForGate = gateEntry?.matchedKey || credentialKeyForGate;
13169
- if (gateDecision === "probe" && activeKeyForGate) {
13170
- await recordCredentialProbe(activeKeyForGate, deps.env, nowMs).catch(
13171
- () => void 0
13172
- );
13173
- }
13174
13737
  const legacyCredentialMigration = {
13175
13738
  enableLegacyMigration: true
13176
13739
  };
@@ -13200,14 +13763,18 @@ function createCliLocalRuntimeAdapter(deps) {
13200
13763
  return null;
13201
13764
  }
13202
13765
  } : void 0;
13766
+ const resolvedAgentConfig = resolveProviderResolveOverrides(agentConfig, options?.textOnly ? { ...options, toolNames: [] } : options);
13203
13767
  return resolveLocalProvider({
13204
- agentConfig,
13768
+ agentConfig: resolvedAgentConfig,
13205
13769
  deps,
13206
13770
  fetchImpl,
13207
13771
  loopbackRequest,
13208
13772
  now,
13209
13773
  workspaceRoot,
13210
- additionalToolNames,
13774
+ ...options?.textOnly ? { textOnly: true } : {},
13775
+ // 请求空工具面时连 host 追加项(readPastedText)也一并去掉,
13776
+ // 否则 `toolNames: []` 的契约会被 additionalToolNames 刺破。
13777
+ additionalToolNames: options?.textOnly || options?.toolNames !== void 0 ? [] : additionalToolNames,
13211
13778
  activeAgentToolNames,
13212
13779
  localToolExecutors,
13213
13780
  buildProviderOpenAiTools,
@@ -13434,10 +14001,12 @@ function createCliLocalRuntimeAdapter(deps) {
13434
14001
  get compactionSummaryFallback() {
13435
14002
  return cliAutoCompactionSummaryFallback;
13436
14003
  },
13437
- resolveProvider: async (agentConfig) => {
14004
+ resolveProvider: async (agentConfig, options) => {
13438
14005
  const provider = await adapterBase.resolveProviderBase(
13439
- agentConfig
14006
+ agentConfig,
14007
+ options
13440
14008
  );
14009
+ if (options?.textOnly) return { ...provider, textOnly: true };
13441
14010
  return shouldUsePlatformChatProvider(deps.env, agentConfig) ? withProviderStreamRetry(provider, {
13442
14011
  ...deps,
13443
14012
  retryOnly: isBusyProviderStreamError