nolo-cli 0.84.0-alpha.1 → 0.84.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 (168) hide show
  1. package/README.md +2 -2
  2. package/{_executeModel-IULR47HG.js → _executeModel-BPUR2QYH.js} +51 -50
  3. package/{action-D22U6CTH.js → action-P4TNFGAD.js} +11 -11
  4. package/{addContentAction-XAPUTGL4.js → addContentAction-MB2HHMDZ.js} +9 -9
  5. package/{addDialogAgentAction-M7VLLBZM.js → addDialogAgentAction-S2NRBCWW.js} +9 -9
  6. package/{agentDeleteCommand-2S4WCDQC.js → agentDeleteCommand-MNAA6MIG.js} +32 -30
  7. package/{agentEmailCommands-V3ADGHX7.js → agentEmailCommands-SK34WJKU.js} +70 -66
  8. package/{agentGrantCommands-R3VQEZJR.js → agentGrantCommands-M3XFO6EN.js} +2 -2
  9. package/{agentListCommands-EPK3S274.js → agentListCommands-I26MKI4T.js} +55 -36
  10. package/{agentMachineCommands-XMZWK7P3.js → agentMachineCommands-M6QDR6P3.js} +32 -30
  11. package/{agentProviderCommands-65ZC2WB4.js → agentProviderCommands-X3UNW77C.js} +87 -0
  12. package/{agentPullCommand-N7L2CT7L.js → agentPullCommand-O5MPVJ5E.js} +3 -3
  13. package/agentRecordCommands-HTHLO2MM.js +129 -0
  14. package/{agentRunCommand-VUWNWC7C.js → agentRunCommand-IAIRCMTH.js} +70 -65
  15. package/{agentRunControl-T44MESII.js → agentRunControl-AVJR4JW4.js} +2 -2
  16. package/{agentRuntimeLocal-YRONTDLM.js → agentRuntimeLocal-3WTYYIT2.js} +32 -26
  17. package/{agentSlice-2P7OJFES.js → agentSlice-TVP3HINW.js} +10 -10
  18. package/{appDeleteCommands-YS7UGMP3.js → appDeleteCommands-SB5TNLKY.js} +32 -30
  19. package/{appDeployCommands-XIE5Z4BE.js → appDeployCommands-2IPKZINV.js} +32 -30
  20. package/{appDeployStatusCommands-54UDVYET.js → appDeployStatusCommands-K63TFUID.js} +32 -30
  21. package/{appGetCommands-6XYLBCRN.js → appGetCommands-VU2SLNEH.js} +32 -30
  22. package/{appListCommands-VEGH7SF5.js → appListCommands-S3T4DJ36.js} +32 -30
  23. package/{authCommand-JIEOFHXW.js → authCommand-HGP2XTKC.js} +185 -28
  24. package/{authCommands-ILTBI6JV.js → authCommands-RGOGCPP6.js} +4 -5
  25. package/builtinSkillRegistry-Q2VVY3SC.js +33 -0
  26. package/{chatQueueReduxAdapter-GPYCH5Z2.js → chatQueueReduxAdapter-TWIYKVIZ.js} +25 -25
  27. package/{chromeCommands-VOIRNJBL.js → chromeCommands-FZYAU75D.js} +1 -1
  28. package/{chromeOpsCommands-M7DPCX2T.js → chromeOpsCommands-62P2Q2YB.js} +31 -1
  29. package/{chunk-C3FZR5EC.js → chunk-2F7RJC6L.js} +1 -1
  30. package/{chunk-BOYUESO7.js → chunk-3AMA42ZI.js} +1 -1
  31. package/{chunk-RZOJRTKR.js → chunk-3BGIKIPF.js} +444 -920
  32. package/{chunk-HQUORJTM.js → chunk-3BMYXEUW.js} +1 -1
  33. package/{chunk-EBKMQULC.js → chunk-3IUWIAHP.js} +13 -15
  34. package/{chunk-NTW5GWDM.js → chunk-3VNWWVKB.js} +2 -0
  35. package/{chunk-4SZ37KLL.js → chunk-3VUWPRFW.js} +50 -30
  36. package/{chunk-WL2RKIBF.js → chunk-3YIAR5UI.js} +1 -1
  37. package/{chunk-DWUCB2A3.js → chunk-42V6BK2E.js} +1 -1
  38. package/{chunk-AX7RMXSH.js → chunk-4MK3I7Y4.js} +38 -4
  39. package/{chunk-PF6O3GR3.js → chunk-52Q2IW2X.js} +1340 -1273
  40. package/{chunk-Y3NZYLPG.js → chunk-5MEYHVIB.js} +1 -1
  41. package/{chunk-5XTOKQLW.js → chunk-5UTFL4QJ.js} +6 -2
  42. package/chunk-5ZUL4CUO.js +10 -0
  43. package/{chunk-6YTOJY35.js → chunk-6D2JFXLY.js} +35 -0
  44. package/{chunk-E6RGHPDR.js → chunk-6D6VBVEW.js} +4 -6
  45. package/{chunk-FZGV4FGK.js → chunk-6GFOD3TK.js} +260 -140
  46. package/{chunk-TMPLPCVX.js → chunk-6OZ36FXI.js} +2 -2
  47. package/{chunk-PZM3EENV.js → chunk-6Z7MY3V7.js} +1 -1
  48. package/{chunk-BYEJ7K2Z.js → chunk-7S4YWN7P.js} +1 -1
  49. package/{chunk-SREASRV5.js → chunk-ABQYVJBJ.js} +1 -1
  50. package/chunk-ACTMNVHM.js +93 -0
  51. package/{chunk-ZZCJUXUI.js → chunk-AHAHBQMU.js} +2 -2
  52. package/{chunk-BJCWNHVU.js → chunk-BFDFRLLQ.js} +2 -2
  53. package/chunk-BMQ2MJUK.js +153 -0
  54. package/chunk-BNGW5ASC.js +344 -0
  55. package/{chunk-2KDR4CWE.js → chunk-BYMV6QZG.js} +24 -1
  56. package/{chunk-73BR2IQT.js → chunk-CWQINZEA.js} +127 -7
  57. package/{chunk-HTMVBPOF.js → chunk-DD6XE56O.js} +19 -72
  58. package/{chunk-CADAR6QU.js → chunk-EYAQT56M.js} +4 -3
  59. package/{chunk-CJCJF5XB.js → chunk-FDTR4QJ6.js} +4 -2
  60. package/{chunk-D6XYLSOS.js → chunk-FKNWP6SG.js} +1 -1
  61. package/{chunk-3VPDELAP.js → chunk-FP76KI2Y.js} +1500 -84
  62. package/chunk-FV3JEYR3.js +870 -0
  63. package/{chunk-GCJ3IS5M.js → chunk-GIVFXPWK.js} +177 -6
  64. package/chunk-GMZLP5DV.js +82 -0
  65. package/{chunk-LHDV2QUI.js → chunk-GNCCNNZD.js} +4 -2
  66. package/{chunk-XWNRAIUL.js → chunk-GP5LKBUZ.js} +6 -16
  67. package/{chunk-PZCTYKGU.js → chunk-GQBOR3G6.js} +2 -2
  68. package/{chunk-NFT2AOK5.js → chunk-H4AHY5BU.js} +13 -85
  69. package/{chunk-FNGAF7C4.js → chunk-HAFJ3NDE.js} +137 -12
  70. package/{chunk-ZVN6I33J.js → chunk-HCT7U36O.js} +9 -5
  71. package/{chunk-7QVPPOEC.js → chunk-HKNXJGEC.js} +43 -43
  72. package/{chunk-3JQ2NRYU.js → chunk-HVURBGF3.js} +4 -4
  73. package/{chunk-OZX2IPKM.js → chunk-HYJFRTLW.js} +2 -2
  74. package/{chunk-HH65VA5U.js → chunk-I3TQJXQ7.js} +109 -6
  75. package/{chunk-UDQWGU7F.js → chunk-J4MOO56S.js} +1 -1
  76. package/{chunk-KC5FAHVS.js → chunk-JEDZAO7L.js} +1 -1
  77. package/{chunk-6XIE5ZGI.js → chunk-JPGCXHNM.js} +1 -1
  78. package/{chunk-DUJWWUGV.js → chunk-K36IXSCQ.js} +1 -1
  79. package/{chunk-KRMWO2NN.js → chunk-KAUSRTO6.js} +3 -3
  80. package/{chunk-7FWPLK4M.js → chunk-LJ5VW2UJ.js} +1 -1
  81. package/{chunk-GHEJJGJI.js → chunk-LMREEBWT.js} +833 -109
  82. package/{chunk-RXX4GDIH.js → chunk-LS2ZANRJ.js} +1 -1
  83. package/chunk-LVIFM7VW.js +1625 -0
  84. package/{chunk-BJIRLHJR.js → chunk-M2EJHDJP.js} +4 -0
  85. package/{chunk-EVBFYPDC.js → chunk-MC4XLIJ7.js} +6 -2
  86. package/{chunk-KEZNSFGD.js → chunk-MZYBSTMH.js} +11 -173
  87. package/chunk-OCZKATRC.js +129 -0
  88. package/{chunk-MXUSPS2N.js → chunk-OIVSQH2O.js} +1510 -1592
  89. package/{chunk-E36HZARC.js → chunk-P5EVSOPA.js} +93 -17
  90. package/{chunk-SZLVDREM.js → chunk-PBA4UJBK.js} +3 -3
  91. package/{chunk-URWDSZRW.js → chunk-PMAIQX7A.js} +3 -3
  92. package/{chunk-AETZ7AD6.js → chunk-Q554LLSI.js} +1 -1
  93. package/{chunk-AJHEFRGJ.js → chunk-Q67DOFOO.js} +214 -5
  94. package/{chunk-UZ6XXCVR.js → chunk-QDSINZM3.js} +15 -2
  95. package/{chunk-NRALD3CC.js → chunk-QSHEEA5N.js} +54 -10
  96. package/chunk-QU7KDSZX.js +677 -0
  97. package/{chunk-T25L377S.js → chunk-RPJUFEGR.js} +10 -10
  98. package/{chunk-QCZX4FTW.js → chunk-SH43IESU.js} +173 -15
  99. package/{chunk-SPAQ4PVV.js → chunk-SV5IJK6K.js} +1 -1
  100. package/{chunk-BKJO7LDP.js → chunk-U4OKDZFH.js} +6 -6
  101. package/{chunk-DM4FBSWG.js → chunk-UDVH2P4A.js} +7 -7
  102. package/{chunk-4EJ5QYB5.js → chunk-UEVXFHQB.js} +2 -2
  103. package/{chunk-JYYZCK2Z.js → chunk-V7E4PXQD.js} +1 -1
  104. package/{chunk-NHRQCXIQ.js → chunk-VMFTFOWF.js} +13 -3
  105. package/{chunk-YFADVSZE.js → chunk-VRTTFDBM.js} +7 -6
  106. package/{chunk-U7YP7ECK.js → chunk-WP5UXDA5.js} +84 -13
  107. package/{chunk-5J7W2VWW.js → chunk-WV42OMV7.js} +6 -265
  108. package/{chunk-3QPYFHAH.js → chunk-XD2MJFUG.js} +1 -1
  109. package/{chunk-IDGYOD2O.js → chunk-XP3VVAPG.js} +3174 -223
  110. package/{chunk-ETLZZQJE.js → chunk-Z2PV6O2T.js} +121 -26
  111. package/{chunk-BZNGCVAW.js → chunk-Z5625XML.js} +4 -4
  112. package/{contentThunks-THSCADSK.js → contentThunks-JYWI7G7I.js} +26 -26
  113. package/{cooldownCommands-BSCH2W7P.js → cooldownCommands-REAWYVTY.js} +2 -2
  114. package/{createAgentAutomationAction-WGN3UQ5A.js → createAgentAutomationAction-Z4SUJ54Y.js} +26 -26
  115. package/{createDialogAction-3EN2NLH7.js → createDialogAction-RCHFLVJ2.js} +29 -29
  116. package/{createPageAction-4VK473QV.js → createPageAction-YK2253XQ.js} +28 -28
  117. package/{dbSlice-GKGGFNOB.js → dbSlice-A6C6H32X.js} +8 -8
  118. package/{deleteFile-LWSR674W.js → deleteFile-YG6QMJLU.js} +3 -3
  119. package/{dialogCommands-PS4NWYY7.js → dialogCommands-X4UOZQWX.js} +36 -32
  120. package/{dialogSlice-CLV7SVOZ.js → dialogSlice-IVTWLCE2.js} +31 -25
  121. package/{docCreateCommands-4KANZJR6.js → docCreateCommands-VD6TGHG4.js} +33 -31
  122. package/{docDeleteCommands-F2S2TRUF.js → docDeleteCommands-OT3R2G4W.js} +34 -32
  123. package/{docReadCommands-B2WDPZQE.js → docReadCommands-2G7BIXSH.js} +33 -31
  124. package/{docStore-TDLCHWPF.js → docStore-UENKCCDX.js} +9 -9
  125. package/{docUpdateCommands-E7TKHRIO.js → docUpdateCommands-45MVJKZA.js} +33 -31
  126. package/{fetchMessages-DRABNDPZ.js → fetchMessages-EIU7UFFK.js} +3 -4
  127. package/{getPricing-QVTUQQTM.js → getPricing-LUSEABA6.js} +4 -4
  128. package/{handleSendMessageAction-AHL3ZHE4.js → handleSendMessageAction-LM2J4HC3.js} +66 -74
  129. package/index.js +99 -86
  130. package/{localDialogRead-PUQKFI73.js → localDialogRead-BTFJ4V2I.js} +2 -2
  131. package/{localRuntimeDb-P37ZTX55.js → localRuntimeDb-SYQGUF4U.js} +1 -1
  132. package/{machineCommands-OTABVXOA.js → machineCommands-6N6MQIE6.js} +69 -65
  133. package/{machineWsRunDispatch-V4EVQSMZ.js → machineWsRunDispatch-7MFKVQCR.js} +65 -61
  134. package/{memberThunks-KATHH3F4.js → memberThunks-P2ECUJ56.js} +9 -9
  135. package/{models-GS72DVJS.js → models-T3SG6MJ5.js} +2 -2
  136. package/{offlineMarxistsAgentCommand-GEH6SNNA.js → offlineMarxistsAgentCommand-HGXZV4XJ.js} +1 -1
  137. package/package.json +1 -1
  138. package/pdf-U6KSUEZZ.js +26435 -0
  139. package/providerPresetApply-XI3WUPTI.js +214 -0
  140. package/providerRegistry-KEPWWGXD.js +31 -0
  141. package/{read-LC6HVYBN.js → read-COE3ZYR6.js} +5 -4
  142. package/{readDocTool-N3ERJHHF.js → readDocTool-5USN4A6V.js} +1 -1
  143. package/{readlineWorkspace-IIT3FQ6I.js → readlineWorkspace-JWBON5CX.js} +291 -135
  144. package/{removeDialogAgentAction-F5GPTALR.js → removeDialogAgentAction-W66BJ7CF.js} +12 -12
  145. package/{runAgentClientLoop-7TQM7IAR.js → runAgentClientLoop-TA4SSPHE.js} +49 -49
  146. package/{runtimeDoctorCommands-CROTMULY.js → runtimeDoctorCommands-X6WDBEQX.js} +2 -2
  147. package/{setDialogExtraReferencesAction-AY4ZCF3A.js → setDialogExtraReferencesAction-2ALKG3WA.js} +10 -10
  148. package/{setPrimaryDialogAgentAction-HPMG7V3R.js → setPrimaryDialogAgentAction-TQZB55F6.js} +12 -12
  149. package/{settingSlice-OEJA4NQO.js → settingSlice-P4ZW7K5X.js} +10 -10
  150. package/{spaceCommands-FSYH7AL4.js → spaceCommands-6NSAXB7D.js} +31 -29
  151. package/{streamAgentChatTurn-6EF6TVFK.js → streamAgentChatTurn-X5ENGUIS.js} +126 -65
  152. package/{switchDialogAgentAction-HMDZTZ4I.js → switchDialogAgentAction-3KI72HHS.js} +9 -9
  153. package/{tableCommands-SUNTHAYX.js → tableCommands-O6NWHFKV.js} +1 -1
  154. package/{toolApiClient-VGYUB4P5.js → toolApiClient-G3M73RTB.js} +3 -3
  155. package/{tools-R3QNWWTD.js → tools-XRQR7ZZR.js} +45 -45
  156. package/tuiSummaryLlmCaller-UFGFOIIW.js +128 -0
  157. package/{updateCommands-Y363MC3L.js → updateCommands-2M74UL7P.js} +2 -2
  158. package/{updateDialogTitleAction-F3EUOWOB.js → updateDialogTitleAction-ESUUD3GT.js} +46 -29
  159. package/agentRecordCommands-WFPWLWOF.js +0 -125
  160. package/chunk-FMODTASJ.js +0 -109
  161. package/chunk-I42VC5KR.js +0 -15
  162. package/chunk-NB5SIWC4.js +0 -611
  163. package/chunk-NG4UT4K4.js +0 -79
  164. package/codingSkills-7S6523WH.js +0 -48
  165. package/tuiSummaryLlmCaller-PWOXTCBF.js +0 -116
  166. package/{chunk-3RAG2NAD.js → chunk-UKWGR5SS.js} +3 -3
  167. package/{db-VQSAOWAO.js → db-WK5MDZVN.js} +1 -1
  168. package/{localRuntimeAuthority-R5UBODT3.js → localRuntimeAuthority-6IR6BBXE.js} +3 -3
@@ -5,21 +5,25 @@ import {
5
5
  finalizeResponsesToolCalls,
6
6
  flushChatCompletionStream,
7
7
  providerHttpFailure,
8
- readSseFrames,
9
- streamSseDataValues,
10
8
  throwIfChatCompletionStreamFailed
11
- } from "./chunk-NFT2AOK5.js";
9
+ } from "./chunk-H4AHY5BU.js";
12
10
  import {
13
- DEVIN_CONNECT_URL
14
- } from "./chunk-FMODTASJ.js";
11
+ DEVIN_CONNECT_URL,
12
+ parseSseDataLineJson,
13
+ parseSseDataLineObject
14
+ } from "./chunk-FV3JEYR3.js";
15
+ import {
16
+ readSseFrames
17
+ } from "./chunk-GMZLP5DV.js";
15
18
  import {
16
19
  clipToolText,
17
20
  hasImageInRuntimeMessages,
21
+ resolveAgentContextWindow,
18
22
  resolveAgentImageInputSupport,
19
23
  resolveHistoricalToolContentCap,
20
24
  resolveToolOutputProfile,
21
25
  stripImagePartsFromMessages
22
- } from "./chunk-AX7RMXSH.js";
26
+ } from "./chunk-4MK3I7Y4.js";
23
27
  import {
24
28
  downgradeUnparsableToolCalls,
25
29
  hasParsableObjectArguments,
@@ -34,16 +38,19 @@ import {
34
38
  finalizeAccumulatedToolCalls
35
39
  } from "./chunk-N7J63Q6C.js";
36
40
  import {
41
+ DELETE_SAFETY_RED_LINE,
37
42
  TOOL_GUIDED_SECTION_ORDER,
38
43
  buildIdentityBlock,
39
44
  buildRuntimeGuidanceBlocks,
45
+ detectDispatchIntent,
46
+ detectDispatchIntentFromMessages,
40
47
  estimateContextTokens,
41
48
  hashStablePrefixContent,
42
49
  normalizeChatCompletionsBodyForProvider,
43
50
  requiresBareImageUrl,
44
51
  resolveToolGuidedSections,
45
52
  toBareImageUrlMessages
46
- } from "./chunk-ETLZZQJE.js";
53
+ } from "./chunk-Z2PV6O2T.js";
47
54
  import {
48
55
  convertMessagesToResponsesInput,
49
56
  extractReasoningFromResponseOutput,
@@ -61,22 +68,18 @@ import {
61
68
  applyToolSurfaceConstraints,
62
69
  buildDialogReadEnvelope,
63
70
  buildNoloWorkspaceCommandArgs,
71
+ buildNoloWorkspaceOpenAiTools,
64
72
  createStreamingDialogReadProjector,
65
73
  noloPositiveIntegerString,
66
74
  noloStringArg,
67
75
  parseNoloWorkspaceToolArguments,
68
76
  readTextStreamUpToChars,
69
77
  resolveNoloDialogInput
70
- } from "./chunk-ZVN6I33J.js";
71
- import {
72
- getAntigravityUserAgent,
73
- readAntigravityProjectId,
74
- resolveAntigravityCloudCodeBaseUrl
75
- } from "./chunk-LHDV2QUI.js";
78
+ } from "./chunk-HCT7U36O.js";
76
79
  import {
77
80
  buildProviderAuthHeaders,
78
81
  buildProviderExecutionPlan
79
- } from "./chunk-TMPLPCVX.js";
82
+ } from "./chunk-6OZ36FXI.js";
80
83
  import {
81
84
  buildDialogFallbackTitleFromUserInput
82
85
  } from "./chunk-2MBTWINN.js";
@@ -90,7 +93,7 @@ import {
90
93
  resolveEmptyAssistantFallbackMessage,
91
94
  resolveEmptyAssistantOutcome,
92
95
  resolveTruncatedReasoningTailLog
93
- } from "./chunk-7QVPPOEC.js";
96
+ } from "./chunk-HKNXJGEC.js";
94
97
  import {
95
98
  COMPACTION_SUMMARY_SCHEMA_VERSION,
96
99
  COMPACTION_SUMMARY_SYSTEM_PROMPT,
@@ -109,7 +112,7 @@ import {
109
112
  resolveCompressionTriggerRatio,
110
113
  truncateToolOutputForContext,
111
114
  withProviderCallTimingFields
112
- } from "./chunk-E36HZARC.js";
115
+ } from "./chunk-P5EVSOPA.js";
113
116
  import {
114
117
  estimateTokenCount
115
118
  } from "./chunk-PSCNXBYA.js";
@@ -133,27 +136,25 @@ import {
133
136
  asRecordOrEmpty
134
137
  } from "./chunk-KX6YBIHG.js";
135
138
  import {
139
+ asTrimmedNonEmptyStringArray,
136
140
  dialogMessageKey,
137
141
  dialogMessageRange
138
- } from "./chunk-NHRQCXIQ.js";
142
+ } from "./chunk-VMFTFOWF.js";
139
143
  import {
140
144
  asOptionalFiniteNumber
141
145
  } from "./chunk-WWC7I6TA.js";
142
- import {
143
- asTrimmedNonEmptyStringArray
144
- } from "./chunk-I42VC5KR.js";
145
146
  import {
146
147
  asTrimmedLowercaseString
147
148
  } from "./chunk-QSJ3HFEF.js";
149
+ import {
150
+ isRecord
151
+ } from "./chunk-KUG52IAP.js";
148
152
  import {
149
153
  asTrimmedString
150
154
  } from "./chunk-3EUW3WEO.js";
151
155
  import {
152
156
  asOptionalPositiveFiniteNumber
153
157
  } from "./chunk-U6VC4MU4.js";
154
- import {
155
- isRecord
156
- } from "./chunk-KUG52IAP.js";
157
158
  import {
158
159
  normalizeServerOrigin
159
160
  } from "./chunk-CF7PE763.js";
@@ -11899,6 +11900,7 @@ var toQueue = /* @__PURE__ */ dual(2, (self, options) => toQueueArray(self.chann
11899
11900
  var runIntoQueue = /* @__PURE__ */ dual(2, (self, queue) => runIntoQueueArray(self.channel, queue));
11900
11901
 
11901
11902
  // packages/agent-runtime/abortableKernel.ts
11903
+ var IDLE_WATCHDOG_SLICES = 20;
11902
11904
  function runAbortableWithTimeout(args2) {
11903
11905
  const taskEffect = Effect_exports.callback(
11904
11906
  (resume) => {
@@ -11934,12 +11936,26 @@ function runAbortableWithTimeout(args2) {
11934
11936
  raced = Effect_exports.raceFirst(taskEffect, abortEffect);
11935
11937
  }
11936
11938
  if (typeof args2.timeoutMs === "number") {
11937
- const inner = raced;
11939
+ const outer = raced;
11938
11940
  const timeoutMs = args2.timeoutMs;
11939
- raced = Effect_exports.gen(function* () {
11940
- const outcome = yield* Effect_exports.timeoutOption(inner, Duration_exports.millis(timeoutMs));
11941
- return Option_exports.isNone(outcome) ? { kind: "timeout" } : outcome.value;
11941
+ const activityVersion = args2.activityVersion;
11942
+ const idleWatchdog = Effect_exports.gen(function* () {
11943
+ const sliceCount = activityVersion ? IDLE_WATCHDOG_SLICES : 1;
11944
+ const baseSliceMs = Math.max(1, Math.floor(timeoutMs / sliceCount));
11945
+ let idleMs = 0;
11946
+ for (; ; ) {
11947
+ const versionAtSliceStart = activityVersion?.();
11948
+ const sliceMs = Math.max(1, Math.min(timeoutMs - idleMs, baseSliceMs));
11949
+ yield* Effect_exports.sleep(Duration_exports.millis(sliceMs));
11950
+ idleMs += sliceMs;
11951
+ if (activityVersion && activityVersion() !== versionAtSliceStart) {
11952
+ idleMs = 0;
11953
+ continue;
11954
+ }
11955
+ if (idleMs >= timeoutMs) return { kind: "timeout" };
11956
+ }
11942
11957
  });
11958
+ raced = Effect_exports.raceFirst(outer, idleWatchdog);
11943
11959
  }
11944
11960
  return args2.runtime ? args2.runtime.runPromise(raced) : Effect_exports.runPromise(raced);
11945
11961
  }
@@ -12203,8 +12219,208 @@ function summarizeToolArguments(toolName, rawArgs) {
12203
12219
  );
12204
12220
  }
12205
12221
 
12222
+ // packages/agent-runtime/progressGuard.ts
12223
+ var DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS = 5;
12224
+ var DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS = 8;
12225
+ function sortJsonKeys(obj) {
12226
+ if (obj === null || typeof obj !== "object") return obj;
12227
+ if (Array.isArray(obj)) return obj.map(sortJsonKeys);
12228
+ const sorted = {};
12229
+ for (const key of Object.keys(obj).sort()) {
12230
+ sorted[key] = sortJsonKeys(obj[key]);
12231
+ }
12232
+ return sorted;
12233
+ }
12234
+ function normalizeToolArguments(rawArgs) {
12235
+ if (typeof rawArgs !== "string") {
12236
+ try {
12237
+ return JSON.stringify(sortJsonKeys(rawArgs ?? ""));
12238
+ } catch {
12239
+ return String(rawArgs ?? "");
12240
+ }
12241
+ }
12242
+ const trimmed = rawArgs.trim();
12243
+ if (!trimmed) return "";
12244
+ try {
12245
+ const parsed = JSON.parse(trimmed);
12246
+ return JSON.stringify(sortJsonKeys(parsed));
12247
+ } catch {
12248
+ return trimmed;
12249
+ }
12250
+ }
12251
+ function extractNormalizedContentText(content) {
12252
+ if (typeof content === "string") return content.trim();
12253
+ if (!Array.isArray(content)) return "";
12254
+ return content.map((part) => {
12255
+ if (part?.type === "text") return String(part.text ?? "").trim();
12256
+ if (part?.type === "image_url") return `[img:${part.image_url?.url ?? ""}]`;
12257
+ return "";
12258
+ }).filter(Boolean).join("\n");
12259
+ }
12260
+ function buildToolCallsSignature(toolCalls) {
12261
+ if (!toolCalls || toolCalls.length === 0) return "";
12262
+ return toolCalls.map(
12263
+ (tc) => `${tc.function?.name ?? ""}:${normalizeToolArguments(tc.function?.arguments)}`
12264
+ ).join(";;");
12265
+ }
12266
+ function buildAssistantActionFingerprint(result4) {
12267
+ const content = extractNormalizedContentText(result4.content);
12268
+ const reasoning = typeof result4.reasoning_content === "string" ? result4.reasoning_content.trim() : "";
12269
+ let toolCallsSig = buildToolCallsSignature(result4.tool_calls);
12270
+ let hasToolCalls = Array.isArray(result4.tool_calls) && result4.tool_calls.length > 0;
12271
+ if (!toolCallsSig && Array.isArray(result4.output) && result4.output.length > 0) {
12272
+ const blocksToolCalls = result4.output.filter(
12273
+ (b) => b.type === "toolCall"
12274
+ ).map((b) => b.toolCall);
12275
+ if (blocksToolCalls.length > 0) {
12276
+ toolCallsSig = buildToolCallsSignature(blocksToolCalls);
12277
+ hasToolCalls = true;
12278
+ }
12279
+ }
12280
+ const hasNonEmptyText = content.length > 0 || reasoning.length > 0;
12281
+ const fingerprint = `${content}##${reasoning}##${toolCallsSig}`;
12282
+ return {
12283
+ fingerprint,
12284
+ hasNonEmptyText,
12285
+ hasToolCalls,
12286
+ toolCallsSignature: toolCallsSig
12287
+ };
12288
+ }
12289
+ function buildToolResultsSignature(toolExecutions) {
12290
+ if (toolExecutions.length === 0) return "";
12291
+ return toolExecutions.map((exec) => {
12292
+ const contentStr = typeof exec.content === "string" ? exec.content.trim() : "";
12293
+ const metaStr = exec.metadata ? JSON.stringify(sortJsonKeys(exec.metadata)) : "";
12294
+ return `${exec.toolName}:${contentStr}:${metaStr}`;
12295
+ }).join(";;");
12296
+ }
12297
+ function resolveProgressGuardConfig(options, env = typeof process !== "undefined" ? process.env : {}) {
12298
+ const envDisabled = env.NOLO_LOOP_PROGRESS_GUARD_DISABLED === "true" || env.NOLO_LOOP_PROGRESS_GUARD_DISABLED === "1";
12299
+ const disabled = options?.disabled ?? envDisabled ?? false;
12300
+ const parsePositiveInt = (val, fallback) => {
12301
+ if (typeof val === "number" && Number.isFinite(val) && val >= 1) {
12302
+ return Math.floor(val);
12303
+ }
12304
+ if (typeof val === "string") {
12305
+ const parsed = parseInt(val, 10);
12306
+ if (Number.isFinite(parsed) && parsed >= 1) {
12307
+ return parsed;
12308
+ }
12309
+ }
12310
+ return fallback;
12311
+ };
12312
+ const maxConsecutiveIdenticalRounds = parsePositiveInt(
12313
+ options?.maxConsecutiveIdenticalRounds ?? env.NOLO_LOOP_MAX_IDENTICAL_ROUNDS,
12314
+ DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS
12315
+ );
12316
+ const maxConsecutiveStagnantToolRounds = parsePositiveInt(
12317
+ options?.maxConsecutiveStagnantToolRounds ?? env.NOLO_LOOP_MAX_STAGNANT_TOOL_ROUNDS,
12318
+ DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS
12319
+ );
12320
+ return {
12321
+ disabled,
12322
+ maxConsecutiveIdenticalRounds,
12323
+ maxConsecutiveStagnantToolRounds
12324
+ };
12325
+ }
12326
+ var LocalLoopProgressGuard = class {
12327
+ config;
12328
+ lastAssistantFingerprint = null;
12329
+ lastAssistantHasNonEmptyText = false;
12330
+ consecutiveIdenticalCount = 0;
12331
+ lastToolCallsSignature = null;
12332
+ lastToolResultsSignature = null;
12333
+ consecutiveStagnantToolCount = 0;
12334
+ constructor(config, env) {
12335
+ this.config = resolveProgressGuardConfig(config, env);
12336
+ }
12337
+ /**
12338
+ * 在 LLM 返回 assistant 响应后调用。
12339
+ * 仅在纯文本/无工具调用且实质文本相同达到阈值时直接熔断;
12340
+ * 若存在工具调用,则不在此处熔断,留待工具执行后结合工具结果综合判定。
12341
+ */
12342
+ observeAssistantResponse(result4) {
12343
+ if (this.config.disabled) return { action: "continue" };
12344
+ const { fingerprint, hasNonEmptyText, hasToolCalls } = buildAssistantActionFingerprint(result4);
12345
+ if (this.lastAssistantFingerprint !== null && this.lastAssistantFingerprint === fingerprint) {
12346
+ this.consecutiveIdenticalCount += 1;
12347
+ if (!hasToolCalls && hasNonEmptyText && this.consecutiveIdenticalCount >= this.config.maxConsecutiveIdenticalRounds) {
12348
+ return {
12349
+ action: "stall",
12350
+ reason: "repetition_loop",
12351
+ detail: `Consecutive identical text-only assistant response for ${this.consecutiveIdenticalCount} rounds.`,
12352
+ consecutiveRounds: this.consecutiveIdenticalCount
12353
+ };
12354
+ }
12355
+ } else {
12356
+ this.lastAssistantFingerprint = fingerprint;
12357
+ this.lastAssistantHasNonEmptyText = hasNonEmptyText;
12358
+ this.consecutiveIdenticalCount = 1;
12359
+ }
12360
+ return { action: "continue" };
12361
+ }
12362
+ /**
12363
+ * 在本轮所有工具执行完毕后调用,检查是否陷入停滞死循环。
12364
+ * 只有当工具返回内容与元数据完全无变化时才计入无进展 streak;
12365
+ * 一旦工具结果发生变化(如合法轮询状态推进),立即重置 identical streak,确保绝不误杀正常长任务。
12366
+ */
12367
+ observeToolExecution(toolCalls, toolResults) {
12368
+ if (this.config.disabled) return { action: "continue" };
12369
+ if (!toolCalls || toolCalls.length === 0) return { action: "continue" };
12370
+ const toolCallsSig = buildToolCallsSignature(toolCalls);
12371
+ const toolResultsSig = buildToolResultsSignature(toolResults);
12372
+ const toolCallsUnchanged = this.lastToolCallsSignature !== null && this.lastToolCallsSignature === toolCallsSig;
12373
+ const toolResultsUnchanged = this.lastToolResultsSignature !== null && this.lastToolResultsSignature === toolResultsSig;
12374
+ if (toolCallsUnchanged && toolResultsUnchanged) {
12375
+ this.consecutiveStagnantToolCount += 1;
12376
+ if (this.consecutiveIdenticalCount >= this.config.maxConsecutiveIdenticalRounds) {
12377
+ return {
12378
+ action: "stall",
12379
+ reason: "repetition_loop",
12380
+ detail: `Consecutive identical assistant response with unchanged tool results for ${this.consecutiveIdenticalCount} rounds.`,
12381
+ consecutiveRounds: this.consecutiveIdenticalCount
12382
+ };
12383
+ }
12384
+ if (this.consecutiveStagnantToolCount >= this.config.maxConsecutiveStagnantToolRounds) {
12385
+ return {
12386
+ action: "stall",
12387
+ reason: "stagnant_tool_calls",
12388
+ detail: `Consecutive stagnant tool calls with unchanged results for ${this.consecutiveStagnantToolCount} rounds.`,
12389
+ consecutiveRounds: this.consecutiveStagnantToolCount
12390
+ };
12391
+ }
12392
+ } else {
12393
+ this.lastToolCallsSignature = toolCallsSig;
12394
+ this.lastToolResultsSignature = toolResultsSig;
12395
+ this.consecutiveStagnantToolCount = 1;
12396
+ if (!toolResultsUnchanged) {
12397
+ this.consecutiveIdenticalCount = 1;
12398
+ }
12399
+ }
12400
+ return { action: "continue" };
12401
+ }
12402
+ };
12403
+ function createLocalLoopProgressGuard(config, env) {
12404
+ return new LocalLoopProgressGuard(config, env);
12405
+ }
12406
+
12407
+ // packages/agent-runtime/toolArgumentsFingerprint.ts
12408
+ var hashFingerprintInput = (value) => {
12409
+ let hash2 = 2166136261;
12410
+ for (let index = 0; index < value.length; index += 1) {
12411
+ hash2 ^= value.charCodeAt(index);
12412
+ hash2 = Math.imul(hash2, 16777619);
12413
+ }
12414
+ return (hash2 >>> 0).toString(16).padStart(8, "0");
12415
+ };
12416
+ function buildToolArgumentsFingerprint(rawArguments) {
12417
+ if (rawArguments === void 0 || rawArguments === null) return void 0;
12418
+ const normalized = normalizeToolArguments(rawArguments);
12419
+ return hashFingerprintInput(normalized);
12420
+ }
12421
+
12206
12422
  // packages/agent-runtime/leafFinalHandoff.ts
12207
- var LEAF_FINAL_HANDOFF_INSTRUCTIONS = `Leaf subtask final: return only the result, blockers/findings, and minimal evidence the parent needs. Do not narrate your process or restate the task. Keep success minimal (a one-line result is enough); on failure include the blocker, severity when relevant, and an evidence reference (file:line, test command, commit, artifact, or run/dialog). If the task explicitly requires a long-form deliverable (document, translation, dataset, code, or other artifact), provide that deliverable in full and keep only the handoff around it concise.`;
12423
+ var LEAF_FINAL_HANDOFF_INSTRUCTIONS = `Leaf subtask final: return only the result, blockers/findings, and minimal evidence the parent needs. Do not narrate your process or restate the task. Keep success minimal (a one-line result is enough); on failure include the blocker, severity when relevant, and an evidence reference (file:line, test command, commit, artifact, or run/dialog). If the task explicitly requires a long-form deliverable (document, translation, dataset, code, or other artifact), provide that deliverable in full and keep only the handoff around it concise. During execution, follow the brief as given: only when new evidence would materially change the goal, risk, irreversibility, or DoD should you surface it as a blocker/finding in the handoff \u2014 do not silently expand scope or re-litigate the settled direction.`;
12208
12424
 
12209
12425
  // packages/agent-runtime/contextBlockScope.ts
12210
12426
  var isContextBlockScope = (value) => {
@@ -12426,20 +12642,40 @@ function isColdResume(history, nowMs) {
12426
12642
  }
12427
12643
  async function maybeAutoCompactLocalHistory(args2) {
12428
12644
  const { adapter, dialogId, history } = args2;
12429
- const unchanged = () => ({
12645
+ const emitPhase = (phase) => {
12646
+ try {
12647
+ args2.onPhase?.(phase);
12648
+ } catch {
12649
+ }
12650
+ };
12651
+ emitPhase({ kind: "validating-context" });
12652
+ const unchanged = (decision) => ({
12430
12653
  history,
12431
12654
  compressed: false,
12432
- summaryGenerated: false
12655
+ summaryGenerated: false,
12656
+ decision
12433
12657
  });
12434
- if (!dialogId || history.length === 0 || typeof adapter.loadDialogSummary !== "function" || typeof adapter.saveDialogSummary !== "function") {
12435
- return unchanged();
12658
+ if (!dialogId) {
12659
+ return unchanged({ outcome: "skipped", skipReason: "no-dialog-id" });
12660
+ }
12661
+ if (history.length === 0) {
12662
+ return unchanged({ outcome: "skipped", skipReason: "empty-history" });
12663
+ }
12664
+ if (typeof adapter.loadDialogSummary !== "function" || typeof adapter.saveDialogSummary !== "function") {
12665
+ return unchanged({
12666
+ outcome: "skipped",
12667
+ skipReason: "adapter-missing-summary-methods"
12668
+ });
12436
12669
  }
12437
12670
  let stored = null;
12438
12671
  try {
12439
12672
  stored = await adapter.loadDialogSummary(dialogId);
12440
12673
  } catch (error) {
12441
12674
  console.warn("[localLoop] loadDialogSummary failed:", error);
12442
- return unchanged();
12675
+ return unchanged({
12676
+ outcome: "skipped",
12677
+ skipReason: "load-summary-failed"
12678
+ });
12443
12679
  }
12444
12680
  const bridged = toPlanCompressionMessages(history);
12445
12681
  const validation = validateStoredSummary({ history, stored: stored ?? {} });
@@ -12475,7 +12711,21 @@ async function maybeAutoCompactLocalHistory(args2) {
12475
12711
  ...args2.realContextUsagePercent !== void 0 ? { realContextUsagePercent: args2.realContextUsagePercent } : {}
12476
12712
  });
12477
12713
  const projectExisting = () => {
12478
- if (!existingSummary.trim()) return unchanged();
12714
+ const diag = plan.diagnostics;
12715
+ const diagSnapshot = diag ? {
12716
+ estimatedTokens: diag.totalUsed,
12717
+ historyBudget: diag.historyBudget,
12718
+ triggerRatio: diag.triggerRatio,
12719
+ ...diag.realUsageRatio !== void 0 ? { realUsageRatio: diag.realUsageRatio } : {}
12720
+ } : {};
12721
+ const skippedDecision = {
12722
+ outcome: "skipped",
12723
+ skipReason: diag?.emptyReason === "no-pending" ? "no-pending" : diag?.emptyReason === "below-min-compress-count" ? "below-min-compress-count" : "below-trigger",
12724
+ ...diagSnapshot,
12725
+ ...diag ? { pendingMsgCount: diag.pendingMsgCount } : {},
12726
+ ...diag?.compressCount !== void 0 ? { compressCount: diag.compressCount } : {}
12727
+ };
12728
+ if (!existingSummary.trim()) return unchanged(skippedDecision);
12479
12729
  return {
12480
12730
  history: projectHistoryWithSummary({
12481
12731
  history,
@@ -12483,14 +12733,26 @@ async function maybeAutoCompactLocalHistory(args2) {
12483
12733
  summarizedBeforeId
12484
12734
  }),
12485
12735
  compressed: true,
12486
- summaryGenerated: false
12736
+ summaryGenerated: false,
12737
+ decision: {
12738
+ outcome: "compressed",
12739
+ trigger: "existing-summary",
12740
+ ...diagSnapshot
12741
+ }
12487
12742
  };
12488
12743
  };
12489
12744
  if (!plan.shouldCompress) {
12490
12745
  return projectExisting();
12491
12746
  }
12747
+ emitPhase({ kind: "compaction-start" });
12492
12748
  try {
12749
+ if (args2.abortSignal?.aborted) {
12750
+ throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
12751
+ }
12493
12752
  const provider = await args2.resolveProvider();
12753
+ if (args2.abortSignal?.aborted) {
12754
+ throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
12755
+ }
12494
12756
  const msgsToCompress = plan.msgsToCompress;
12495
12757
  const messagesText = formatMessagesForSummaryWithTruncation(msgsToCompress);
12496
12758
  const fileOpsText = formatFileOperationsFromMessages(
@@ -12502,18 +12764,57 @@ async function maybeAutoCompactLocalHistory(args2) {
12502
12764
  messagesText,
12503
12765
  fileOpsText
12504
12766
  });
12505
- const result4 = await provider.complete([
12506
- { role: "system", content: COMPACTION_SUMMARY_SYSTEM_PROMPT },
12507
- { role: "user", content: promptContent }
12508
- ]);
12767
+ emitPhase({ kind: "waiting-provider" });
12768
+ const timeoutMs = typeof args2.timeoutMs === "number" && Number.isFinite(args2.timeoutMs) && args2.timeoutMs > 0 ? args2.timeoutMs : void 0;
12769
+ const requestController = new AbortController();
12770
+ let timeoutHandle;
12771
+ let abortListener;
12772
+ const request3 = provider.complete(
12773
+ [{ role: "system", content: COMPACTION_SUMMARY_SYSTEM_PROMPT }, { role: "user", content: promptContent }],
12774
+ { signal: requestController.signal, ...timeoutMs ? { timeoutMs } : {} }
12775
+ ).then(
12776
+ (value) => ({ kind: "done", value }),
12777
+ (error) => ({ kind: "provider-error", error })
12778
+ );
12779
+ const guards = [];
12780
+ if (timeoutMs) guards.push(new Promise((resolve5) => {
12781
+ timeoutHandle = setTimeout(() => {
12782
+ requestController.abort();
12783
+ resolve5({ kind: "timeout" });
12784
+ }, timeoutMs);
12785
+ }));
12786
+ if (args2.abortSignal) guards.push(new Promise((resolve5) => {
12787
+ abortListener = () => {
12788
+ requestController.abort(args2.abortSignal?.reason);
12789
+ resolve5({ kind: "aborted" });
12790
+ };
12791
+ args2.abortSignal?.addEventListener("abort", abortListener, { once: true });
12792
+ }));
12793
+ const outcome = guards.length ? await Promise.race([request3, ...guards]) : await request3;
12794
+ if (timeoutHandle) clearTimeout(timeoutHandle);
12795
+ if (abortListener) args2.abortSignal?.removeEventListener("abort", abortListener);
12796
+ if (outcome.kind !== "done") {
12797
+ const reason = outcome.kind === "timeout" ? "timeout" : outcome.kind === "aborted" ? "aborted" : "provider-error";
12798
+ const detail = outcome.kind === "provider-error" ? outcome.error instanceof Error ? outcome.error.message : String(outcome.error) : reason === "timeout" ? `auto-compaction timed out after ${timeoutMs}ms` : "auto-compaction aborted";
12799
+ throw Object.assign(new Error(detail), { compactionReason: reason });
12800
+ }
12801
+ const result4 = outcome.value;
12509
12802
  const newSummary = typeof result4.content === "string" ? result4.content.trim() : "";
12510
12803
  if (!newSummary) {
12511
12804
  console.warn(
12512
12805
  "[localLoop] auto-compaction produced empty summary; keeping prior projection"
12513
12806
  );
12807
+ const failureReason = "provider-error";
12808
+ emitPhase({ kind: "compaction-failed", reason: failureReason });
12514
12809
  return {
12515
12810
  ...projectExisting(),
12516
- failureMessage: "summary model returned empty content"
12811
+ failureMessage: "summary model returned empty content",
12812
+ failureReason,
12813
+ decision: {
12814
+ outcome: "failed",
12815
+ reason: failureReason,
12816
+ detail: "summary model returned empty content"
12817
+ }
12517
12818
  };
12518
12819
  }
12519
12820
  await adapter.saveDialogSummary({
@@ -12538,6 +12839,7 @@ async function maybeAutoCompactLocalHistory(args2) {
12538
12839
  summaryUsage: result4.usage
12539
12840
  });
12540
12841
  console.log(formatCompactionMetricsLog(metrics));
12842
+ emitPhase({ kind: "compaction-end" });
12541
12843
  return {
12542
12844
  history: projectHistoryWithSummary({
12543
12845
  history,
@@ -12550,223 +12852,55 @@ async function maybeAutoCompactLocalHistory(args2) {
12550
12852
  beforeTokens: metrics.previousSummaryTokens + metrics.compressedTokens,
12551
12853
  afterTokens: metrics.newSummaryTokens + metrics.retainedTokens,
12552
12854
  ...result4.usage ? { usage: result4.usage } : {},
12553
- metrics
12855
+ metrics,
12856
+ decision: {
12857
+ outcome: "compressed",
12858
+ trigger: invalidSummary ? "invalid-summary" : coldResume ? "cold-resume" : plan.diagnostics?.triggeredByRealUsage ? "real-usage" : "estimate",
12859
+ ...plan.diagnostics ? {
12860
+ estimatedTokens: plan.diagnostics.totalUsed,
12861
+ historyBudget: plan.diagnostics.historyBudget,
12862
+ triggerRatio: plan.diagnostics.triggerRatio,
12863
+ ...plan.diagnostics.realUsageRatio !== void 0 ? { realUsageRatio: plan.diagnostics.realUsageRatio } : {}
12864
+ } : {}
12865
+ }
12554
12866
  };
12555
12867
  } catch (error) {
12556
12868
  console.warn("[localLoop] auto-compaction failed:", error);
12557
12869
  const failureMessage = error instanceof Error && error.message ? error.message.slice(0, 200) : String(error).slice(0, 200);
12558
- return { ...projectExisting(), failureMessage };
12870
+ const failureReason = error instanceof Error && "compactionReason" in error ? error.compactionReason : "provider-error";
12871
+ emitPhase({ kind: "compaction-failed", reason: failureReason });
12872
+ return {
12873
+ ...projectExisting(),
12874
+ failureMessage,
12875
+ failureReason,
12876
+ decision: { outcome: "failed", reason: failureReason, detail: failureMessage }
12877
+ };
12559
12878
  }
12560
12879
  }
12561
12880
 
12562
- // packages/agent-runtime/progressGuard.ts
12563
- var DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS = 5;
12564
- var DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS = 8;
12565
- function sortJsonKeys(obj) {
12566
- if (obj === null || typeof obj !== "object") return obj;
12567
- if (Array.isArray(obj)) return obj.map(sortJsonKeys);
12568
- const sorted = {};
12569
- for (const key of Object.keys(obj).sort()) {
12570
- sorted[key] = sortJsonKeys(obj[key]);
12881
+ // packages/agent-runtime/localLoop.ts
12882
+ var LOOP_TIMING_ENABLED = typeof process !== "undefined" && process.env?.NOLO_LOOP_TIMING === "1";
12883
+ var LOOP_TIMING_FILE = typeof process !== "undefined" ? process.env?.NOLO_LOOP_TIMING_FILE : void 0;
12884
+ var loopTimingRows = [];
12885
+ var loopTimingLastMark;
12886
+ function loopTimingMark(phase, round) {
12887
+ if (!LOOP_TIMING_ENABLED) return;
12888
+ const now = performance.now();
12889
+ if (loopTimingLastMark !== void 0) {
12890
+ loopTimingRows.push({ phase, round, durationMs: now - loopTimingLastMark });
12571
12891
  }
12572
- return sorted;
12892
+ loopTimingLastMark = now;
12573
12893
  }
12574
- function normalizeToolArguments(rawArgs) {
12575
- if (typeof rawArgs !== "string") {
12894
+ async function loopTimingFlush() {
12895
+ if (!LOOP_TIMING_ENABLED) return;
12896
+ const lines = loopTimingRows.map((row) => JSON.stringify(row)).join("\n");
12897
+ loopTimingRows = [];
12898
+ loopTimingLastMark = void 0;
12899
+ if (!lines) return;
12900
+ if (LOOP_TIMING_FILE) {
12576
12901
  try {
12577
- return JSON.stringify(sortJsonKeys(rawArgs ?? ""));
12578
- } catch {
12579
- return String(rawArgs ?? "");
12580
- }
12581
- }
12582
- const trimmed = rawArgs.trim();
12583
- if (!trimmed) return "";
12584
- try {
12585
- const parsed = JSON.parse(trimmed);
12586
- return JSON.stringify(sortJsonKeys(parsed));
12587
- } catch {
12588
- return trimmed;
12589
- }
12590
- }
12591
- function extractNormalizedContentText(content) {
12592
- if (typeof content === "string") return content.trim();
12593
- if (!Array.isArray(content)) return "";
12594
- return content.map((part) => {
12595
- if (part?.type === "text") return String(part.text ?? "").trim();
12596
- if (part?.type === "image_url") return `[img:${part.image_url?.url ?? ""}]`;
12597
- return "";
12598
- }).filter(Boolean).join("\n");
12599
- }
12600
- function buildToolCallsSignature(toolCalls) {
12601
- if (!toolCalls || toolCalls.length === 0) return "";
12602
- return toolCalls.map(
12603
- (tc) => `${tc.function?.name ?? ""}:${normalizeToolArguments(tc.function?.arguments)}`
12604
- ).join(";;");
12605
- }
12606
- function buildAssistantActionFingerprint(result4) {
12607
- const content = extractNormalizedContentText(result4.content);
12608
- const reasoning = typeof result4.reasoning_content === "string" ? result4.reasoning_content.trim() : "";
12609
- let toolCallsSig = buildToolCallsSignature(result4.tool_calls);
12610
- let hasToolCalls = Array.isArray(result4.tool_calls) && result4.tool_calls.length > 0;
12611
- if (!toolCallsSig && Array.isArray(result4.output) && result4.output.length > 0) {
12612
- const blocksToolCalls = result4.output.filter(
12613
- (b) => b.type === "toolCall"
12614
- ).map((b) => b.toolCall);
12615
- if (blocksToolCalls.length > 0) {
12616
- toolCallsSig = buildToolCallsSignature(blocksToolCalls);
12617
- hasToolCalls = true;
12618
- }
12619
- }
12620
- const hasNonEmptyText = content.length > 0 || reasoning.length > 0;
12621
- const fingerprint = `${content}##${reasoning}##${toolCallsSig}`;
12622
- return {
12623
- fingerprint,
12624
- hasNonEmptyText,
12625
- hasToolCalls,
12626
- toolCallsSignature: toolCallsSig
12627
- };
12628
- }
12629
- function buildToolResultsSignature(toolExecutions) {
12630
- if (toolExecutions.length === 0) return "";
12631
- return toolExecutions.map((exec) => {
12632
- const contentStr = typeof exec.content === "string" ? exec.content.trim() : "";
12633
- const metaStr = exec.metadata ? JSON.stringify(sortJsonKeys(exec.metadata)) : "";
12634
- return `${exec.toolName}:${contentStr}:${metaStr}`;
12635
- }).join(";;");
12636
- }
12637
- function resolveProgressGuardConfig(options, env = typeof process !== "undefined" ? process.env : {}) {
12638
- const envDisabled = env.NOLO_LOOP_PROGRESS_GUARD_DISABLED === "true" || env.NOLO_LOOP_PROGRESS_GUARD_DISABLED === "1";
12639
- const disabled = options?.disabled ?? envDisabled ?? false;
12640
- const parsePositiveInt = (val, fallback) => {
12641
- if (typeof val === "number" && Number.isFinite(val) && val >= 1) {
12642
- return Math.floor(val);
12643
- }
12644
- if (typeof val === "string") {
12645
- const parsed = parseInt(val, 10);
12646
- if (Number.isFinite(parsed) && parsed >= 1) {
12647
- return parsed;
12648
- }
12649
- }
12650
- return fallback;
12651
- };
12652
- const maxConsecutiveIdenticalRounds = parsePositiveInt(
12653
- options?.maxConsecutiveIdenticalRounds ?? env.NOLO_LOOP_MAX_IDENTICAL_ROUNDS,
12654
- DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS
12655
- );
12656
- const maxConsecutiveStagnantToolRounds = parsePositiveInt(
12657
- options?.maxConsecutiveStagnantToolRounds ?? env.NOLO_LOOP_MAX_STAGNANT_TOOL_ROUNDS,
12658
- DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS
12659
- );
12660
- return {
12661
- disabled,
12662
- maxConsecutiveIdenticalRounds,
12663
- maxConsecutiveStagnantToolRounds
12664
- };
12665
- }
12666
- var LocalLoopProgressGuard = class {
12667
- config;
12668
- lastAssistantFingerprint = null;
12669
- lastAssistantHasNonEmptyText = false;
12670
- consecutiveIdenticalCount = 0;
12671
- lastToolCallsSignature = null;
12672
- lastToolResultsSignature = null;
12673
- consecutiveStagnantToolCount = 0;
12674
- constructor(config, env) {
12675
- this.config = resolveProgressGuardConfig(config, env);
12676
- }
12677
- /**
12678
- * 在 LLM 返回 assistant 响应后调用。
12679
- * 仅在纯文本/无工具调用且实质文本相同达到阈值时直接熔断;
12680
- * 若存在工具调用,则不在此处熔断,留待工具执行后结合工具结果综合判定。
12681
- */
12682
- observeAssistantResponse(result4) {
12683
- if (this.config.disabled) return { action: "continue" };
12684
- const { fingerprint, hasNonEmptyText, hasToolCalls } = buildAssistantActionFingerprint(result4);
12685
- if (this.lastAssistantFingerprint !== null && this.lastAssistantFingerprint === fingerprint) {
12686
- this.consecutiveIdenticalCount += 1;
12687
- if (!hasToolCalls && hasNonEmptyText && this.consecutiveIdenticalCount >= this.config.maxConsecutiveIdenticalRounds) {
12688
- return {
12689
- action: "stall",
12690
- reason: "repetition_loop",
12691
- detail: `Consecutive identical text-only assistant response for ${this.consecutiveIdenticalCount} rounds.`,
12692
- consecutiveRounds: this.consecutiveIdenticalCount
12693
- };
12694
- }
12695
- } else {
12696
- this.lastAssistantFingerprint = fingerprint;
12697
- this.lastAssistantHasNonEmptyText = hasNonEmptyText;
12698
- this.consecutiveIdenticalCount = 1;
12699
- }
12700
- return { action: "continue" };
12701
- }
12702
- /**
12703
- * 在本轮所有工具执行完毕后调用,检查是否陷入停滞死循环。
12704
- * 只有当工具返回内容与元数据完全无变化时才计入无进展 streak;
12705
- * 一旦工具结果发生变化(如合法轮询状态推进),立即重置 identical streak,确保绝不误杀正常长任务。
12706
- */
12707
- observeToolExecution(toolCalls, toolResults) {
12708
- if (this.config.disabled) return { action: "continue" };
12709
- if (!toolCalls || toolCalls.length === 0) return { action: "continue" };
12710
- const toolCallsSig = buildToolCallsSignature(toolCalls);
12711
- const toolResultsSig = buildToolResultsSignature(toolResults);
12712
- const toolCallsUnchanged = this.lastToolCallsSignature !== null && this.lastToolCallsSignature === toolCallsSig;
12713
- const toolResultsUnchanged = this.lastToolResultsSignature !== null && this.lastToolResultsSignature === toolResultsSig;
12714
- if (toolCallsUnchanged && toolResultsUnchanged) {
12715
- this.consecutiveStagnantToolCount += 1;
12716
- if (this.consecutiveIdenticalCount >= this.config.maxConsecutiveIdenticalRounds) {
12717
- return {
12718
- action: "stall",
12719
- reason: "repetition_loop",
12720
- detail: `Consecutive identical assistant response with unchanged tool results for ${this.consecutiveIdenticalCount} rounds.`,
12721
- consecutiveRounds: this.consecutiveIdenticalCount
12722
- };
12723
- }
12724
- if (this.consecutiveStagnantToolCount >= this.config.maxConsecutiveStagnantToolRounds) {
12725
- return {
12726
- action: "stall",
12727
- reason: "stagnant_tool_calls",
12728
- detail: `Consecutive stagnant tool calls with unchanged results for ${this.consecutiveStagnantToolCount} rounds.`,
12729
- consecutiveRounds: this.consecutiveStagnantToolCount
12730
- };
12731
- }
12732
- } else {
12733
- this.lastToolCallsSignature = toolCallsSig;
12734
- this.lastToolResultsSignature = toolResultsSig;
12735
- this.consecutiveStagnantToolCount = 1;
12736
- if (!toolResultsUnchanged) {
12737
- this.consecutiveIdenticalCount = 1;
12738
- }
12739
- }
12740
- return { action: "continue" };
12741
- }
12742
- };
12743
- function createLocalLoopProgressGuard(config, env) {
12744
- return new LocalLoopProgressGuard(config, env);
12745
- }
12746
-
12747
- // packages/agent-runtime/localLoop.ts
12748
- var LOOP_TIMING_ENABLED = typeof process !== "undefined" && process.env?.NOLO_LOOP_TIMING === "1";
12749
- var LOOP_TIMING_FILE = typeof process !== "undefined" ? process.env?.NOLO_LOOP_TIMING_FILE : void 0;
12750
- var loopTimingRows = [];
12751
- var loopTimingLastMark;
12752
- function loopTimingMark(phase, round) {
12753
- if (!LOOP_TIMING_ENABLED) return;
12754
- const now = performance.now();
12755
- if (loopTimingLastMark !== void 0) {
12756
- loopTimingRows.push({ phase, round, durationMs: now - loopTimingLastMark });
12757
- }
12758
- loopTimingLastMark = now;
12759
- }
12760
- async function loopTimingFlush() {
12761
- if (!LOOP_TIMING_ENABLED) return;
12762
- const lines = loopTimingRows.map((row) => JSON.stringify(row)).join("\n");
12763
- loopTimingRows = [];
12764
- loopTimingLastMark = void 0;
12765
- if (!lines) return;
12766
- if (LOOP_TIMING_FILE) {
12767
- try {
12768
- const { appendFileSync } = await import("node:fs");
12769
- appendFileSync(LOOP_TIMING_FILE, lines + "\n");
12902
+ const { appendFileSync } = await import("node:fs");
12903
+ appendFileSync(LOOP_TIMING_FILE, lines + "\n");
12770
12904
  } catch {
12771
12905
  }
12772
12906
  } else {
@@ -12796,6 +12930,8 @@ function emitLoopEvent(boundary, event, bridge) {
12796
12930
  boundary.emit(bridge ? { event, bridge } : { event });
12797
12931
  }
12798
12932
  var LLM_REQUEST_TIMEOUT = "LLM_REQUEST_TIMEOUT";
12933
+ var DEFAULT_LLM_REQUEST_TIMEOUT_MS = 10 * 60 * 1e3;
12934
+ var DEFAULT_COMPACTION_TIMEOUT_MS = 60 * 1e3;
12799
12935
  var LOCAL_TURN_ABORTED_CODE = "LOCAL_TURN_ABORTED";
12800
12936
  function buildAbortedError() {
12801
12937
  const error = new Error("local agent turn aborted by user");
@@ -12823,8 +12959,11 @@ async function runAbortableToolTask(input, task, pendingToolName) {
12823
12959
  }
12824
12960
  function resolveLlmRequestTimeoutMs(input) {
12825
12961
  const raw = input.llmRequestTimeoutMs ?? input.timeoutMs;
12826
- if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return void 0;
12827
- return raw;
12962
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_LLM_REQUEST_TIMEOUT_MS;
12963
+ }
12964
+ function resolveCompactionTimeoutMs(input) {
12965
+ const raw = input.compactionTimeoutMs;
12966
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_COMPACTION_TIMEOUT_MS;
12828
12967
  }
12829
12968
  async function runCompleteWithTimeout(args2) {
12830
12969
  const { provider, messages, options, timeoutMs, round, input, boundary, context: context3, providerName, model } = args2;
@@ -12847,6 +12986,7 @@ async function runCompleteWithTimeout(args2) {
12847
12986
  const outcome = await runAbortableWithTimeout({
12848
12987
  task: complete,
12849
12988
  timeoutMs: typeof timeoutMs === "number" ? timeoutMs : void 0,
12989
+ activityVersion: args2.activityVersion,
12850
12990
  abortSignal: input.abortSignal,
12851
12991
  runtime: input.effectRuntime
12852
12992
  });
@@ -12857,7 +12997,7 @@ async function runCompleteWithTimeout(args2) {
12857
12997
  }
12858
12998
  if (outcome.kind === "timeout") {
12859
12999
  const timeoutError = new Error(
12860
- `LLM request timed out after ${timeoutMs}ms (round ${round})`
13000
+ `LLM request timed out after ${timeoutMs}ms without new output (idle window, round ${round})`
12861
13001
  );
12862
13002
  timeoutError.code = LLM_REQUEST_TIMEOUT;
12863
13003
  throw timeoutError;
@@ -13049,10 +13189,10 @@ var TOOL_METADATA_KEYS = [
13049
13189
  "pasteId",
13050
13190
  "source"
13051
13191
  ];
13052
- function trimHistoryToContextBudget(history, model) {
13192
+ function trimHistoryToContextBudget(history, model, contextWindowOverride) {
13053
13193
  if (history.length === 0) return { history, droppedCount: 0 };
13054
13194
  const { rawMessageBudget } = planContextUsage({
13055
- contextWindow: getModelContextWindow(model ?? ""),
13195
+ contextWindow: contextWindowOverride ?? getModelContextWindow(model ?? ""),
13056
13196
  summaryTokens: 0,
13057
13197
  // localLoop 没有 web 端的负载分档器;medium 是中性默认值,
13058
13198
  // 不为了省几个 token 在这里复制一份分类逻辑。
@@ -13515,7 +13655,14 @@ async function runLocalAgentTurn(input) {
13515
13655
  agentConfig.exposedToolNames ?? agentConfig.toolNames ?? []
13516
13656
  );
13517
13657
  const guidanceBlocks = buildRuntimeGuidanceBlocks(agentTools);
13518
- const toolGuidedSections = resolveToolGuidedSections(agentTools);
13658
+ const toolGuidedSections = resolveToolGuidedSections(agentTools, {
13659
+ // 单调锁定:历史里任一 user 消息命中派发关键词、或已出现过
13660
+ // startAgentRun/controlAgentRun 的 tool_calls,后续每轮都保持 true,
13661
+ // 防止 session-scope 的 agentCollaboration 段逐轮翻转击穿前缀缓存。
13662
+ dispatchIntent: detectDispatchIntentFromMessages(history) || detectDispatchIntent(
13663
+ typeof input.input === "string" ? input.input : void 0
13664
+ )
13665
+ });
13519
13666
  const guidanceScopes = [
13520
13667
  ...input.userLanguage?.trim() ? [buildUserResponseLanguageContext({ language: input.userLanguage })] : [],
13521
13668
  guidanceBlocks.startupProtocol,
@@ -13531,6 +13678,9 @@ async function runLocalAgentTurn(input) {
13531
13678
  input.contextBlockScopes
13532
13679
  );
13533
13680
  const mergedContextBlockScopes = [
13681
+ // 删除安全红线:系统层最高优先级约束,位于所有块之前(含身份信息块),
13682
+ // owner 要求常驻「根本提示词」的最顶部(2026-09-26 批量误删事故)。
13683
+ { content: DELETE_SAFETY_RED_LINE, cacheScope: "session" },
13534
13684
  { content: identityBlock, cacheScope: "session" },
13535
13685
  ...guidanceScopes,
13536
13686
  ...callerScopes
@@ -13543,7 +13693,7 @@ async function runLocalAgentTurn(input) {
13543
13693
  }
13544
13694
  return resolvedProvider;
13545
13695
  };
13546
- const contextWindow = getModelContextWindow(agentConfig.model ?? "");
13696
+ const contextWindow = resolveAgentContextWindow(agentConfig);
13547
13697
  const compressionTriggerRatio = resolveCompressionTriggerRatio(contextWindow);
13548
13698
  let realContextUsagePercent;
13549
13699
  if (input.continueDialogId && typeof input.adapter.loadLastContextUsage === "function") {
@@ -13563,20 +13713,40 @@ async function runLocalAgentTurn(input) {
13563
13713
  }
13564
13714
  }
13565
13715
  let compactionUsage;
13716
+ const emitCompactionPhase = (phase, scope3) => {
13717
+ const atMs = Date.now();
13718
+ if (phase.kind === "validating-context") emitLoopEvent(observationBoundary, { kind: "turn-phase", phase: "validating-context", atMs, compactionScope: scope3 });
13719
+ else if (phase.kind === "compaction-start") {
13720
+ emitLoopEvent(observationBoundary, { kind: "turn-phase", phase: "compacting", atMs, compactionScope: scope3 });
13721
+ emitLoopEvent(observationBoundary, { kind: "compaction-start", atMs, scope: scope3 });
13722
+ } else if (phase.kind === "waiting-provider") emitLoopEvent(observationBoundary, { kind: "turn-phase", phase: "waiting-provider", atMs, compactionScope: scope3 });
13723
+ else if (phase.kind === "compaction-end") emitLoopEvent(observationBoundary, { kind: "compaction-end", atMs, scope: scope3 });
13724
+ else emitLoopEvent(observationBoundary, { kind: "compaction-failed", atMs, scope: scope3, reason: phase.reason });
13725
+ };
13566
13726
  const emitCompactionObservation = (compacted) => {
13567
- if (compacted.compressed || compacted.summaryGenerated || compacted.failureMessage) {
13568
- emitLoopEvent(observationBoundary, {
13569
- kind: "compaction",
13570
- atMs: Date.now(),
13571
- reason: compacted.reason ?? "context_budget",
13572
- summaryGenerated: compacted.summaryGenerated,
13573
- compressed: compacted.compressed,
13574
- ...compacted.failureMessage ? { failed: true, detail: compacted.failureMessage } : {},
13575
- ...compacted.beforeTokens !== void 0 ? { beforeTokens: compacted.beforeTokens } : {},
13576
- ...compacted.afterTokens !== void 0 ? { afterTokens: compacted.afterTokens } : {},
13577
- ...compacted.savedTokens !== void 0 ? { savedTokens: compacted.savedTokens } : {}
13578
- });
13727
+ const decision = compacted.decision;
13728
+ if (decision?.outcome === "skipped") {
13729
+ const isProtectionGap = decision.skipReason === "adapter-missing-summary-methods" || decision.skipReason === "load-summary-failed";
13730
+ const approachingBudget = decision.skipReason === "below-trigger" && typeof decision.estimatedTokens === "number" && typeof decision.historyBudget === "number" && decision.historyBudget > 0 && decision.estimatedTokens >= decision.historyBudget * 0.5;
13731
+ if (!isProtectionGap && !approachingBudget) return;
13579
13732
  }
13733
+ emitLoopEvent(observationBoundary, {
13734
+ kind: "compaction",
13735
+ atMs: Date.now(),
13736
+ ...compacted.reason ? { reason: compacted.reason } : {},
13737
+ summaryGenerated: compacted.summaryGenerated,
13738
+ compressed: compacted.compressed,
13739
+ ...compacted.failureMessage ? { failed: true, detail: compacted.failureMessage } : {},
13740
+ ...decision?.outcome === "skipped" ? { skipped: true, skipReason: decision.skipReason } : {},
13741
+ ...decision && "trigger" in decision && decision.trigger ? { trigger: decision.trigger } : {},
13742
+ ...decision && "estimatedTokens" in decision && decision.estimatedTokens !== void 0 ? { estimatedTokens: decision.estimatedTokens } : {},
13743
+ ...decision && "historyBudget" in decision && decision.historyBudget !== void 0 ? { historyBudget: decision.historyBudget } : {},
13744
+ ...decision && "realUsageRatio" in decision && decision.realUsageRatio !== void 0 ? { realUsageRatio: decision.realUsageRatio } : {},
13745
+ ...decision && "triggerRatio" in decision && decision.triggerRatio !== void 0 ? { triggerRatio: decision.triggerRatio } : {},
13746
+ ...compacted.beforeTokens !== void 0 ? { beforeTokens: compacted.beforeTokens } : {},
13747
+ ...compacted.afterTokens !== void 0 ? { afterTokens: compacted.afterTokens } : {},
13748
+ ...compacted.savedTokens !== void 0 ? { savedTokens: compacted.savedTokens } : {}
13749
+ });
13580
13750
  };
13581
13751
  try {
13582
13752
  const compacted = await maybeAutoCompactLocalHistory({
@@ -13585,7 +13755,11 @@ async function runLocalAgentTurn(input) {
13585
13755
  history,
13586
13756
  model: agentConfig.model,
13587
13757
  resolveProvider: resolveProviderOnce,
13588
- realContextUsagePercent
13758
+ contextWindow,
13759
+ realContextUsagePercent,
13760
+ abortSignal: input.abortSignal,
13761
+ timeoutMs: resolveCompactionTimeoutMs(input),
13762
+ onPhase: (phase) => emitCompactionPhase(phase, "initial")
13589
13763
  });
13590
13764
  history = compacted.history;
13591
13765
  compactionUsage = compacted.usage;
@@ -13594,7 +13768,7 @@ async function runLocalAgentTurn(input) {
13594
13768
  console.warn("[localLoop] auto-compaction unexpected error:", error);
13595
13769
  }
13596
13770
  loopTimingMark("maybeAutoCompactLocalHistory", 0);
13597
- const trimmedHistory = trimHistoryToContextBudget(history, agentConfig.model);
13771
+ const trimmedHistory = trimHistoryToContextBudget(history, agentConfig.model, contextWindow);
13598
13772
  if (trimmedHistory.droppedCount > 0) {
13599
13773
  history = trimmedHistory.history;
13600
13774
  }
@@ -13656,17 +13830,26 @@ async function runLocalAgentTurn(input) {
13656
13830
  let skipFinalAppend = false;
13657
13831
  let partialContent = "";
13658
13832
  const progressGuard = createLocalLoopProgressGuard(input.progressGuardConfig);
13833
+ let ephemeralSummary = null;
13834
+ const ephemeralDialogId = `ephemeral-${crypto.randomUUID()}`;
13835
+ const compactionAdapter = input.continueDialogId ? input.adapter : {
13836
+ ...input.adapter,
13837
+ loadDialogSummary: async () => ephemeralSummary,
13838
+ saveDialogSummary: async (summaryInput) => {
13839
+ ephemeralSummary = summaryInput;
13840
+ }
13841
+ };
13842
+ const compactionDialogId = input.continueDialogId ?? ephemeralDialogId;
13659
13843
  const maybeCompactInLoop = async () => {
13660
- if (!input.continueDialogId) return;
13661
- if (!contextUsage) return;
13662
- const inputTokens = normalizeUsage(contextUsage).input_tokens;
13663
- if (!inputTokens || inputTokens <= 0) return;
13664
- const ratio = Math.min(1, inputTokens / contextWindow);
13665
- if (ratio < compressionTriggerRatio) return;
13844
+ const inputTokens = contextUsage ? normalizeUsage(contextUsage).input_tokens : 0;
13845
+ const ratio = inputTokens > 0 ? Math.min(1, inputTokens / contextWindow) : void 0;
13846
+ if (ratio !== void 0 && ratio < compressionTriggerRatio * 0.85) {
13847
+ return;
13848
+ }
13666
13849
  try {
13667
13850
  const compacted = await maybeAutoCompactLocalHistory({
13668
- adapter: input.adapter,
13669
- dialogId: input.continueDialogId,
13851
+ adapter: compactionAdapter,
13852
+ dialogId: compactionDialogId,
13670
13853
  // canonical 坐标:store 全量历史 + 本轮消息的「持久化形态」
13671
13854
  // (applyPersistedTurnInput 会把首条 user 消息换成 paste 展开形态)。
13672
13855
  // 锚点与 sourceHash 都必须按持久化形态计算——否则下轮从 store
@@ -13681,7 +13864,11 @@ async function runLocalAgentTurn(input) {
13681
13864
  ],
13682
13865
  model: agentConfig.model,
13683
13866
  resolveProvider: resolveProviderOnce,
13684
- realContextUsagePercent: ratio
13867
+ contextWindow,
13868
+ ...ratio !== void 0 ? { realContextUsagePercent: ratio } : {},
13869
+ abortSignal: input.abortSignal,
13870
+ timeoutMs: resolveCompactionTimeoutMs(input),
13871
+ onPhase: (phase) => emitCompactionPhase(phase, "in-loop")
13685
13872
  });
13686
13873
  if (compacted.usage) {
13687
13874
  compactionUsage = addOutOfBandUsage(compactionUsage, compacted.usage);
@@ -13751,11 +13938,12 @@ async function runLocalAgentTurn(input) {
13751
13938
  const shouldPassToolEvents = Boolean(
13752
13939
  input.onToolEvent || input.onObservationEvent || input.observationBoundary
13753
13940
  );
13941
+ let llmStreamActivity = 0;
13754
13942
  result4 = await runCompleteWithTimeout({
13755
13943
  provider,
13756
13944
  messages: requestMessages,
13757
13945
  options: {
13758
- ...typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {},
13946
+ timeoutMs: resolveLlmRequestTimeoutMs(input),
13759
13947
  // 用户取消信号穿进 provider options:providerStreamRetry 据此在
13760
13948
  // 取消后跳过重试,provider 分支(如 antigravity/openai-compatible)
13761
13949
  // 也可把 fetch 绑定到同一信号,实现真正的传输层取消。
@@ -13764,6 +13952,7 @@ async function runLocalAgentTurn(input) {
13764
13952
  // 归到具体对话而不是 chat-proxy 兜底桶。新对话首轮 id 尚未分配。
13765
13953
  ...input.continueDialogId ? { dialogId: input.continueDialogId } : {},
13766
13954
  ...shouldStreamDeltas ? { onTextDelta: (chunk) => {
13955
+ llmStreamActivity += 1;
13767
13956
  partialContent += chunk;
13768
13957
  observationBoundary.emit({
13769
13958
  kind: "text-delta",
@@ -13773,6 +13962,7 @@ async function runLocalAgentTurn(input) {
13773
13962
  });
13774
13963
  } } : {},
13775
13964
  ...shouldStreamReasoning ? { onReasoningDelta: (chunk) => {
13965
+ llmStreamActivity += 1;
13776
13966
  observationBoundary.emit({
13777
13967
  kind: "reasoning-delta",
13778
13968
  chunk,
@@ -13781,6 +13971,7 @@ async function runLocalAgentTurn(input) {
13781
13971
  });
13782
13972
  } } : {},
13783
13973
  ...shouldPassToolEvents ? { onToolEvent: (event) => {
13974
+ llmStreamActivity += 1;
13784
13975
  observationBoundary.emit({
13785
13976
  kind: "tool-event",
13786
13977
  event,
@@ -13791,6 +13982,7 @@ async function runLocalAgentTurn(input) {
13791
13982
  ...shouldPassToolEvents ? { toolEventRound: round } : {}
13792
13983
  },
13793
13984
  timeoutMs: resolveLlmRequestTimeoutMs(input),
13985
+ activityVersion: () => llmStreamActivity,
13794
13986
  round,
13795
13987
  input,
13796
13988
  boundary: observationBoundary,
@@ -13949,6 +14141,7 @@ ${reasoningTailLog}
13949
14141
  const startedAt = Date.now();
13950
14142
  loopTimingMark("toolCallStart", round);
13951
14143
  const argumentsPreview = summarizeToolArguments(toolName, toolCall.function.arguments);
14144
+ const argumentsFingerprint = buildToolArgumentsFingerprint(toolCall.function.arguments);
13952
14145
  emitLoopEvent(
13953
14146
  observationBoundary,
13954
14147
  {
@@ -13957,7 +14150,8 @@ ${reasoningTailLog}
13957
14150
  toolCallId: toolCall.id,
13958
14151
  toolName,
13959
14152
  atMs: startedAt,
13960
- ...argumentsPreview ? { argumentsPreview } : {}
14153
+ ...argumentsPreview ? { argumentsPreview } : {},
14154
+ ...argumentsFingerprint ? { argumentsFingerprint } : {}
13961
14155
  },
13962
14156
  {
13963
14157
  type: "tool-call",
@@ -14270,23 +14464,6 @@ function kimiIdentityHeaders(url) {
14270
14464
  return isKimiEndpoint(url) ? { "User-Agent": KIMI_CODE_USER_AGENT } : {};
14271
14465
  }
14272
14466
 
14273
- // packages/agent-runtime/sseDataLine.ts
14274
- function parseSseDataLineJson(line) {
14275
- const trimmed = line.trim();
14276
- if (!trimmed.startsWith("data:")) return null;
14277
- const payload = trimmed.slice(5).trim();
14278
- if (!payload || payload === "[DONE]") return null;
14279
- try {
14280
- return JSON.parse(payload);
14281
- } catch {
14282
- return null;
14283
- }
14284
- }
14285
- function parseSseDataLineObject(line) {
14286
- const parsed = parseSseDataLineJson(line);
14287
- return parsed && typeof parsed === "object" ? parsed : null;
14288
- }
14289
-
14290
14467
  // packages/integrations/openai/responsesRequestBody.ts
14291
14468
  function buildResponsesRequestBody(body, model) {
14292
14469
  const tools = Array.isArray(body.tools) ? toResponsesTools(body.tools) : void 0;
@@ -15556,142 +15733,17 @@ async function evaluateCapabilityPolicy(capability, normalizedInput, ctx) {
15556
15733
  }
15557
15734
  }
15558
15735
 
15559
- // packages/agent-runtime/localToolPolicy.ts
15560
- var NEVER_LOCAL_TOOLS = /* @__PURE__ */ new Set([
15561
- "deleteSpaces",
15562
- "updateAgent",
15563
- "updateSelf",
15564
- "createAgent"
15565
- ]);
15566
- var DEFAULT_LOCAL_TOOLS = /* @__PURE__ */ new Set([
15567
- "readFile",
15568
- "writeFile",
15569
- "editFile",
15570
- "globFiles",
15571
- "execShell",
15572
- "launchProcess",
15573
- "listProcesses",
15574
- // ProcessTask follow-up tools (Phase 1). Same tier as listProcesses: they
15575
- // observe/stop tasks the agent itself already spawned, so their blast radius
15576
- // is strictly narrower than launchProcess (which is default-enabled).
15577
- "taskWait",
15578
- "taskLogs",
15579
- "taskStop",
15580
- // Scheduling tools: forking/observing sub-agents is a declared capability
15581
- // of capable agents, and the blast radius is strictly narrower than
15582
- // execShell (already default-enabled). Requiring an env allowlist for these
15583
- // while execShell is open by default inverted the risk model and forced every
15584
- // new local user to hand-edit .env files before sub-agent tasks worked.
15585
- "startAgentRun",
15586
- "controlAgentRun"
15587
- ]);
15588
- function parseToolAllowlist(value) {
15589
- return (value ?? "").split(",").flatMap((item) => {
15590
- const trimmed = item.trim();
15591
- return trimmed ? [trimmed] : [];
15592
- });
15593
- }
15594
- function normalizeLocalToolName(toolName) {
15595
- return canonicalizeToolName(
15596
- String(toolName ?? "").replace(/^functions\./, "").trim()
15597
- );
15598
- }
15599
- function isRestrictedLocalToolMode(env) {
15600
- return env.NOLO_LOCAL_TOOL_MODE === "restricted";
15601
- }
15602
- function resolveLocalToolPolicy(args2) {
15603
- const toolName = normalizeLocalToolName(args2.toolName);
15604
- const restrictedMode = isRestrictedLocalToolMode(args2.env);
15605
- if (!toolName) {
15606
- return {
15607
- allowed: false,
15608
- toolName,
15609
- reason: "Tool name is required."
15610
- };
15611
- }
15612
- if (NEVER_LOCAL_TOOLS.has(toolName)) {
15613
- return {
15614
- allowed: false,
15615
- toolName,
15616
- reason: `${toolName} is blocked by the local runtime safety policy.`
15617
- };
15618
- }
15619
- const allowedByEnv = new Set(parseToolAllowlist(args2.env.NOLO_LOCAL_ALLOWED_TOOLS));
15620
- const agentTools = new Set(
15621
- (args2.agentToolNames ?? []).flatMap((item) => {
15622
- const normalized = normalizeLocalToolName(item);
15623
- return normalized ? [normalized] : [];
15624
- })
15625
- );
15626
- if (!restrictedMode && agentTools.has(toolName)) {
15627
- return { allowed: true, toolName };
15628
- }
15629
- if (DEFAULT_LOCAL_TOOLS.has(toolName) && agentTools.has(toolName)) {
15630
- return { allowed: true, toolName };
15631
- }
15632
- if (allowedByEnv.has(toolName) && agentTools.has(toolName)) {
15633
- return { allowed: true, toolName };
15634
- }
15635
- return {
15636
- allowed: false,
15637
- toolName,
15638
- reason: `${toolName} is not enabled for local runtime runs. The agent must declare the tool in its manifest; in restricted mode (NOLO_LOCAL_TOOL_MODE=restricted) non-default tools also require adding to NOLO_LOCAL_ALLOWED_TOOLS (e.g. NOLO_LOCAL_ALLOWED_TOOLS=someCustomTool) and restarting the session so the env var loads.`
15639
- };
15640
- }
15641
- async function executeLocalToolWithPolicy(args2) {
15642
- if (args2.runToolNames !== void 0 && !args2.runToolNames.includes(normalizeLocalToolName(args2.call.name))) {
15643
- throw new Error(`${args2.call.name} is outside the final run tool surface.`);
15644
- }
15645
- const decision = resolveLocalToolPolicy({
15646
- env: args2.env,
15647
- agentToolNames: args2.agentToolNames,
15648
- toolName: args2.call.name
15649
- });
15650
- if (!decision.allowed) throw new Error(decision.reason);
15651
- const executor = args2.executors?.[decision.toolName];
15652
- if (!executor) {
15653
- throw new Error(`${decision.toolName} is allowed by policy but no local executor is registered.`);
15654
- }
15655
- const opts = {
15656
- ...args2.abortSignal ? { abortSignal: args2.abortSignal } : {},
15657
- ...args2.detachMs !== void 0 ? { detachMs: args2.detachMs } : {},
15658
- ...args2.confirmed !== void 0 ? { confirmed: args2.confirmed } : {},
15659
- ...args2.enableDestructiveShellGuard !== void 0 ? { enableDestructiveShellGuard: args2.enableDestructiveShellGuard } : {},
15660
- ...args2.confirmDestructiveAction ? { confirmDestructiveAction: args2.confirmDestructiveAction } : {},
15661
- ...args2.blockDestructiveWithoutConfirmation !== void 0 ? { blockDestructiveWithoutConfirmation: args2.blockDestructiveWithoutConfirmation } : {}
15662
- };
15663
- if (decision.toolName === "execShell") {
15664
- let parsedInput = args2.call.arguments;
15665
- if (typeof parsedInput === "string") {
15666
- try {
15667
- parsedInput = JSON.parse(parsedInput);
15668
- } catch {
15669
- parsedInput = { command: parsedInput };
15670
- }
15671
- }
15672
- await evaluateExecShellDestructiveGuard(parsedInput, opts);
15673
- opts.confirmed = true;
15674
- }
15675
- return executor(
15676
- {
15677
- ...args2.call,
15678
- name: decision.toolName
15679
- },
15680
- opts
15681
- );
15682
- }
15683
-
15684
- // packages/agent-runtime/localWorkspaceTools.ts
15685
- import { existsSync as existsSync3 } from "node:fs";
15686
- import { createHash as createHash3 } from "node:crypto";
15687
- import { lstat, mkdir, readdir as readdir2, readFile as readFile3, realpath, stat as stat3, writeFile } from "node:fs/promises";
15688
- import { homedir } from "node:os";
15689
- import { dirname, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve4, sep } from "node:path";
15690
- import { spawn as spawnChildProcess3 } from "node:child_process";
15691
-
15692
- // packages/core/readRangeLedger.ts
15693
- function filterRetainedLedgerRecords(records, retainedCap) {
15694
- return records.filter((record) => record.chars <= retainedCap);
15736
+ // packages/agent-runtime/localWorkspaceTools.ts
15737
+ import { existsSync as existsSync3 } from "node:fs";
15738
+ import { createHash as createHash3 } from "node:crypto";
15739
+ import { lstat, mkdir, readdir as readdir2, readFile as readFile3, realpath, stat as stat3, writeFile } from "node:fs/promises";
15740
+ import { homedir } from "node:os";
15741
+ import { dirname, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve4, sep } from "node:path";
15742
+ import { spawn as spawnChildProcess3 } from "node:child_process";
15743
+
15744
+ // packages/core/readRangeLedger.ts
15745
+ function filterRetainedLedgerRecords(records, retainedCap) {
15746
+ return records.filter((record) => record.chars <= retainedCap);
15695
15747
  }
15696
15748
  function isValidRange(range3) {
15697
15749
  return Number.isInteger(range3.startLine) && Number.isInteger(range3.endLine) && range3.endLine >= range3.startLine;
@@ -16479,29 +16531,6 @@ function getProcessRegistry() {
16479
16531
  return registry;
16480
16532
  }
16481
16533
 
16482
- // packages/agent-runtime/processOwnership.ts
16483
- var asNonEmptyString = (value) => {
16484
- if (typeof value !== "string") return null;
16485
- const trimmed = value.trim();
16486
- return trimmed ? trimmed : null;
16487
- };
16488
- function readProcessOwner(runtimeContext) {
16489
- if (!runtimeContext || typeof runtimeContext !== "object") return null;
16490
- const dialogId = asNonEmptyString(runtimeContext.dialogId) ?? asNonEmptyString(runtimeContext.parentThreadId);
16491
- if (!dialogId) return null;
16492
- const turnId = asNonEmptyString(runtimeContext.turnId);
16493
- const ownerId = asNonEmptyString(runtimeContext.ownerId);
16494
- return {
16495
- dialogId,
16496
- ...turnId ? { turnId } : {},
16497
- ...ownerId ? { ownerId } : {}
16498
- };
16499
- }
16500
-
16501
- // packages/agent-runtime/workspaceShell.ts
16502
- import { resolve as resolve3 } from "node:path";
16503
- import { spawn as spawnChildProcess2 } from "node:child_process";
16504
-
16505
16534
  // packages/agent-runtime/processTaskResult.ts
16506
16535
  var PROCESS_RESULT_CAPSULE_TAIL_CHARS = 4e3;
16507
16536
  var PROCESS_RESULT_CAPSULE_SPILL_THRESHOLD_CHARS = 2e4;
@@ -16521,11 +16550,17 @@ function buildProcessTaskResultCapsule(args2) {
16521
16550
  const exitCode = args2.exitCode;
16522
16551
  const captureError = args2.captureError?.trim() || void 0;
16523
16552
  const durationMs = typeof args2.startedAt === "number" && Number.isFinite(args2.startedAt) ? Math.max(0, (args2.endedAt ?? Date.now()) - args2.startedAt) : void 0;
16524
- const stdoutTail = tailText(stdout, tailChars);
16525
- const stderrTail = tailText(stderr, tailChars);
16553
+ const stdoutProvenance = args2.streamProvenance?.stdout;
16554
+ const stderrProvenance = args2.streamProvenance?.stderr;
16555
+ const ringEvicted = Boolean(stdoutProvenance?.evicted || stderrProvenance?.evicted);
16556
+ const stdoutTailSource = typeof args2.stdoutTail === "string" ? args2.stdoutTail : stdout;
16557
+ const stderrTailSource = typeof args2.stderrTail === "string" ? args2.stderrTail : stderr;
16558
+ const stdoutTail = tailText(stdoutTailSource, tailChars);
16559
+ const stderrTail = tailText(stderrTailSource, tailChars);
16526
16560
  const inlineSize = stdout.length + stderr.length;
16527
16561
  const inlineTruncated = stdoutTail.truncated || stderrTail.truncated;
16528
- const shouldSpill = inlineTruncated || inlineSize > spillThresholdChars;
16562
+ const truncated = inlineTruncated || ringEvicted || Boolean(captureError);
16563
+ const shouldSpill = truncated || inlineSize > spillThresholdChars;
16529
16564
  let spill;
16530
16565
  if (shouldSpill) {
16531
16566
  try {
@@ -16539,8 +16574,11 @@ function buildProcessTaskResultCapsule(args2) {
16539
16574
  });
16540
16575
  spill = {
16541
16576
  displayPath: result4.displayPath,
16542
- totalChars: result4.totalChars,
16543
- totalLines: result4.totalLines
16577
+ // Prefer the caller's live provenance counters: they count every
16578
+ // byte of the original stream, while the buffered content above may
16579
+ // be a capped prefix of it.
16580
+ totalChars: (stdoutProvenance?.totalChars ?? stdout.length) + (stderrProvenance?.totalChars ?? stderr.length),
16581
+ totalLines: (stdoutProvenance?.totalLines ?? countLines(stdout)) + (stderrProvenance?.totalLines ?? countLines(stderr))
16544
16582
  };
16545
16583
  } catch {
16546
16584
  }
@@ -16551,12 +16589,33 @@ function buildProcessTaskResultCapsule(args2) {
16551
16589
  exitCode,
16552
16590
  ...durationMs !== void 0 ? { durationMs } : {},
16553
16591
  ...captureError ? { captureError } : {},
16554
- truncated: inlineTruncated || Boolean(captureError),
16592
+ truncated,
16555
16593
  ...spill ? { spill } : {}
16556
16594
  };
16557
16595
  }
16558
16596
 
16597
+ // packages/agent-runtime/processOwnership.ts
16598
+ var asNonEmptyString = (value) => {
16599
+ if (typeof value !== "string") return null;
16600
+ const trimmed = value.trim();
16601
+ return trimmed ? trimmed : null;
16602
+ };
16603
+ function readProcessOwner(runtimeContext) {
16604
+ if (!runtimeContext || typeof runtimeContext !== "object") return null;
16605
+ const dialogId = asNonEmptyString(runtimeContext.dialogId) ?? asNonEmptyString(runtimeContext.parentThreadId);
16606
+ if (!dialogId) return null;
16607
+ const turnId = asNonEmptyString(runtimeContext.turnId);
16608
+ const ownerId = asNonEmptyString(runtimeContext.ownerId);
16609
+ return {
16610
+ dialogId,
16611
+ ...turnId ? { turnId } : {},
16612
+ ...ownerId ? { ownerId } : {}
16613
+ };
16614
+ }
16615
+
16559
16616
  // packages/agent-runtime/workspaceShell.ts
16617
+ import { resolve as resolve3 } from "node:path";
16618
+ import { spawn as spawnChildProcess2 } from "node:child_process";
16560
16619
  var EXEC_SHELL_TIMEOUT_ENV = "NOLO_EXEC_SHELL_TIMEOUT_MS";
16561
16620
  var EXEC_SHELL_DETACH_ENV = "NOLO_EXEC_SHELL_DETACH_MS";
16562
16621
  var DEFAULT_EXEC_SHELL_DETACH_MS = 12e4;
@@ -17425,13 +17484,169 @@ function runTasks(args2) {
17425
17484
  return { tasks, count: tasks.length };
17426
17485
  }
17427
17486
 
17487
+ // packages/agent-runtime/execShellCompletionWarnings.ts
17488
+ var BG_ESCAPE_WORD_PATTERN = /(?:^|[\s;|(&/\\])(nohup|disown|setsid|start-process)(?=$|[\s;|)&])/i;
17489
+ var SHELL_SPLIT_CHARS = /* @__PURE__ */ new Set([";", "|", "&", "(", ")", "<", ">", "\n", " ", " "]);
17490
+ function maskQuotedAndEscaped(command) {
17491
+ const out = Array.from(command);
17492
+ let quote = null;
17493
+ for (let i = 0; i < out.length; i += 1) {
17494
+ const ch = out[i];
17495
+ if (quote) {
17496
+ out[i] = " ";
17497
+ if (ch === "\\" && quote !== "'") {
17498
+ if (i + 1 < out.length) out[i + 1] = " ";
17499
+ i += 1;
17500
+ } else if (ch === quote) {
17501
+ quote = null;
17502
+ }
17503
+ continue;
17504
+ }
17505
+ if (ch === "\\") {
17506
+ out[i] = " ";
17507
+ if (i + 1 < out.length) out[i + 1] = " ";
17508
+ i += 1;
17509
+ continue;
17510
+ }
17511
+ if (ch === "'" || ch === '"' || ch === "`") {
17512
+ quote = ch;
17513
+ out[i] = " ";
17514
+ continue;
17515
+ }
17516
+ if (ch === "#" && (i === 0 || /[\s;]/.test(out[i - 1] ?? " "))) {
17517
+ while (i < out.length && out[i] !== "\n") {
17518
+ out[i] = " ";
17519
+ i += 1;
17520
+ }
17521
+ }
17522
+ }
17523
+ return out.join("");
17524
+ }
17525
+ function readRedirectTarget(original, start) {
17526
+ let i = start;
17527
+ while (i < original.length && /\s/.test(original[i] ?? "")) {
17528
+ i += 1;
17529
+ }
17530
+ if (i >= original.length) return void 0;
17531
+ let text = "";
17532
+ let j = i;
17533
+ while (j < original.length) {
17534
+ const o = original[j];
17535
+ if (o === "'" || o === '"' || o === "`") {
17536
+ const quote = o;
17537
+ j += 1;
17538
+ while (j < original.length && original[j] !== quote) {
17539
+ if (original[j] === "\\" && quote !== "'" && j + 1 < original.length) {
17540
+ text += original[j + 1];
17541
+ j += 2;
17542
+ continue;
17543
+ }
17544
+ text += original[j];
17545
+ j += 1;
17546
+ }
17547
+ j += 1;
17548
+ continue;
17549
+ }
17550
+ if (/\s/.test(o) || SHELL_SPLIT_CHARS.has(o)) break;
17551
+ text += o;
17552
+ j += 1;
17553
+ }
17554
+ if (!text || text === "$") return void 0;
17555
+ return { text, end: j };
17556
+ }
17557
+ function isIgnoredRedirectTarget(target) {
17558
+ return target === "/dev/null";
17559
+ }
17560
+ function detectExecShellSynchronousWarnings(command, shell) {
17561
+ const masked = maskQuotedAndEscaped(command);
17562
+ const isPowerShell = shell?.resolvedShell === "powershell";
17563
+ const redirectTargets = [];
17564
+ let backgrounded = false;
17565
+ let i = 0;
17566
+ while (i < masked.length) {
17567
+ const ch = masked[i];
17568
+ if (ch === "&") {
17569
+ const next = masked[i + 1];
17570
+ if (next === "&") {
17571
+ i += 2;
17572
+ continue;
17573
+ }
17574
+ if (next === ">") {
17575
+ let j = i + 2;
17576
+ if (masked[j] === ">") j += 1;
17577
+ const target = readRedirectTarget(command, j);
17578
+ if (target && !isIgnoredRedirectTarget(target.text)) redirectTargets.push(target.text);
17579
+ i = target ? target.end : j;
17580
+ continue;
17581
+ }
17582
+ const prev = masked[i - 1];
17583
+ if (prev === ">" || prev === "<") {
17584
+ i += 1;
17585
+ continue;
17586
+ }
17587
+ if (isPowerShell) {
17588
+ i += 1;
17589
+ continue;
17590
+ }
17591
+ backgrounded = true;
17592
+ i += 1;
17593
+ continue;
17594
+ }
17595
+ if (ch === ">") {
17596
+ const prev = masked[i - 1];
17597
+ if (prev === ">" || prev === "&" || prev === "<") {
17598
+ i += 1;
17599
+ continue;
17600
+ }
17601
+ let j = i + 1;
17602
+ if (masked[j] === ">") j += 1;
17603
+ if (masked[j] === "&") {
17604
+ const ampTarget = readRedirectTarget(command, j + 1);
17605
+ const isFdDuplication = ampTarget !== void 0 && (/^\d+$/.test(ampTarget.text) || ampTarget.text === "-");
17606
+ if (ampTarget && !isFdDuplication && !isIgnoredRedirectTarget(ampTarget.text)) {
17607
+ redirectTargets.push(ampTarget.text);
17608
+ }
17609
+ i = ampTarget ? ampTarget.end : j + 1;
17610
+ continue;
17611
+ }
17612
+ const target = readRedirectTarget(command, j);
17613
+ if (target && !isIgnoredRedirectTarget(target.text)) redirectTargets.push(target.text);
17614
+ i = target ? target.end : j;
17615
+ continue;
17616
+ }
17617
+ if (ch === "<") {
17618
+ i += masked[i + 1] === "<" || masked[i + 1] === "&" ? 2 : 1;
17619
+ continue;
17620
+ }
17621
+ i += 1;
17622
+ }
17623
+ const wordMatch = BG_ESCAPE_WORD_PATTERN.exec(masked);
17624
+ if (wordMatch) backgrounded = true;
17625
+ const warnings = [];
17626
+ if (redirectTargets.length > 0) {
17627
+ const target = redirectTargets[0];
17628
+ warnings.push({
17629
+ kind: "output-redirect",
17630
+ redirectTarget: target,
17631
+ message: `[wake-safety] This execShell call completed synchronously; its output was redirected to ${target}. That file is NOT delivered by any completion notification \u2014 no tracked background task was created. If you need the result, read that file in this turn.`
17632
+ });
17633
+ }
17634
+ if (backgrounded) {
17635
+ warnings.push({
17636
+ kind: "shell-backgrounding",
17637
+ message: `[wake-safety] Detected shell-internal backgrounding (lone \`&\`, nohup/disown/setsid/Start-Process or similar). Descendant processes are NOT tracked as platform tasks and will not produce completion notifications; text such as \`LAUNCHED\` printed by the command is plain stdout, not a platform receipt.`
17638
+ });
17639
+ }
17640
+ return warnings;
17641
+ }
17642
+
17428
17643
  // packages/agent-runtime/capabilities/execShellCapability.ts
17429
17644
  function buildExecShellToolDefinition(toolName = "execShell") {
17430
17645
  return {
17431
17646
  type: "function",
17432
17647
  function: {
17433
17648
  name: toolName,
17434
- description: `Execute a shell command from the workspace root. Prefer one compound command (e.g. 'git status && git diff --stat') to perform complete verification in one step instead of multiple small roundtrips. Do not cd into guessed paths; commands already run from the workspace root. Commands block until exit. Long-running commands (sleep over ${IMMEDIATE_DETACH_SLEEP_THRESHOLD_SECONDS}s, dev servers, watchers) automatically detach to background returning {detached: true, pid, label}; for persistent services, prefer launchProcess. On Windows the resolved shell may be Windows PowerShell 5.1 (metadata.shellKind="powershell5"): it does not support '&&'/'||' or $PSStyle \u2014 join commands with ';' or split into multiple calls. metadata.shellKind="pwsh" means PowerShell 7+ with full syntax. Read resolvedShell/shellKind from any execShell result before writing Windows-specific syntax.`,
17649
+ description: `Execute a shell command from the workspace root. Prefer one compound command (e.g. 'git status && git diff --stat') to perform complete verification in one step instead of multiple small roundtrips. Do not cd into guessed paths; commands already run from the workspace root. Commands block until exit. Long-running commands (sleep over ${IMMEDIATE_DETACH_SLEEP_THRESHOLD_SECONDS}s, dev servers, watchers) automatically detach to background returning {detached: true, pid, label}; for persistent services, prefer launchProcess. On Windows the resolved shell may be Windows PowerShell 5.1 (metadata.shellKind="powershell5"): it does not support '&&'/'||' or $PSStyle \u2014 join commands with ';' or split into multiple calls. metadata.shellKind="pwsh" means PowerShell 7+ with full syntax. Read resolvedShell/shellKind from any execShell result before writing Windows-specific syntax. LIFECYCLE CONTRACT (misreading this contract silenced a real dialog for 10 hours): (1) A normal command waits synchronously for at most 120 seconds; if it ends inside that window the call IS finished \u2014 a result containing exitCode means the command has already completed, never "still running". (2) Only a result with metadata.detached === true AND a taskId represents a tracked background task that may trigger completion wake-up (terminal completion turn carrying a bounded result capsule); that handle may be waited on with taskWait and will produce a completion notification. (3) Text the command itself prints (LAUNCHED, STARTED, BACKGROUND, ...) is plain stdout with zero lifecycle meaning \u2014 never read it as a platform receipt. (4) Shell-level backgrounding (\`&\`, nohup, disown, setsid, self-daemonizing scripts, PowerShell Start-Process) is NOT tracked: those descendants produce no completion notification, and the outer shell finishing tells you nothing about them. Use background: true to turn a finite job into a tracked background task immediately. When a synchronous result's core output was redirected to a file, read that file in the SAME turn \u2014 never end the turn promising a later automatic report without a taskId.`,
17435
17650
  parameters: {
17436
17651
  type: "object",
17437
17652
  properties: {
@@ -17442,6 +17657,10 @@ function buildExecShellToolDefinition(toolName = "execShell") {
17442
17657
  cmd: {
17443
17658
  type: "string",
17444
17659
  description: "Compatibility alias for command."
17660
+ },
17661
+ background: {
17662
+ type: "boolean",
17663
+ description: "Immediately detach this command into a tracked background task (no 120s synchronous wait): returns {detached: true, taskId}, and on completion the platform resumes this conversation with a bounded result capsule (exitCode, duration, stdout/stderr tail). Default false. Use for finite jobs (tests, builds, batch) whose result you need later; NOT for dev servers/watchers \u2014 use launchProcess for those."
17445
17664
  }
17446
17665
  }
17447
17666
  }
@@ -17482,11 +17701,13 @@ function normalizeExecShellInput(input) {
17482
17701
  throw new Error("execShell requires a non-empty command.");
17483
17702
  }
17484
17703
  const activity = extractActivity(record);
17704
+ const background = record.background === true || typeof record.background === "string" && record.background.trim().toLowerCase() === "true";
17485
17705
  return {
17486
17706
  command,
17487
17707
  ...record.shell !== void 0 ? { shell: record.shell } : {},
17488
17708
  ...activity ? { activity } : {},
17489
- ...record.input !== void 0 ? { input: record.input } : {}
17709
+ ...record.input !== void 0 ? { input: record.input } : {},
17710
+ ...background ? { background: true } : {}
17490
17711
  };
17491
17712
  }
17492
17713
  throw new Error("execShell requires a non-empty command.");
@@ -17524,6 +17745,7 @@ var execShellCapability = {
17524
17745
  command,
17525
17746
  shell: normalized.shell
17526
17747
  });
17748
+ const background = normalized.background === true;
17527
17749
  const result4 = await runWorkspaceCommand({
17528
17750
  workspaceRoot,
17529
17751
  command: shellPlan.argv,
@@ -17531,15 +17753,18 @@ var execShellCapability = {
17531
17753
  outputLimit: ctx.commandOutputLimit,
17532
17754
  commandPrefix: ctx.commandPrefix,
17533
17755
  abortSignal: ctx.abortSignal,
17534
- detachMs: isImmediateDetachShellCommand({ command }) ? 0 : ctx.detachMs,
17756
+ detachMs: background ? 0 : isImmediateDetachShellCommand({ command }) ? 0 : ctx.detachMs,
17535
17757
  stdin: typeof normalized.input === "string" ? normalized.input : void 0,
17536
17758
  // Ownership of the turn that invoked this command: an auto-detached
17537
17759
  // command keeps its parent dialog so its terminal can resume that
17538
17760
  // conversation (see processOwnership.ts). Absent ctx.owner = unowned.
17539
17761
  owner: ctx.owner ?? null
17540
17762
  });
17763
+ const warnings = result4.detached || result4.spawnFailed ? [] : detectExecShellSynchronousWarnings(command, {
17764
+ resolvedShell: shellPlan.resolvedShell
17765
+ });
17541
17766
  return {
17542
- content: result4.content,
17767
+ content: warnings.length > 0 ? [result4.content, ...warnings.map((warning) => warning.message)].join("\n\n") : result4.content,
17543
17768
  metadata: {
17544
17769
  command,
17545
17770
  exitCode: result4.exitCode,
@@ -17554,8 +17779,21 @@ var execShellCapability = {
17554
17779
  // Phase 0: stable taskId from the pre-registered Execution
17555
17780
  // Envelope (additive; existing detached fields untouched).
17556
17781
  taskId: result4.taskId,
17557
- status: "running"
17558
- } : {},
17782
+ status: "running",
17783
+ // P0 machine-readable execution state: an explicit positive
17784
+ // signal instead of requiring the model to infer "not detached"
17785
+ // from absence. Mirrors the real lifecycle contract.
17786
+ executionState: "detached",
17787
+ trackedBackgroundTask: true,
17788
+ completionNotification: "terminal-resume"
17789
+ } : {
17790
+ // P0 machine-readable execution state: the command already
17791
+ // finished inside the synchronous window; no background task
17792
+ // exists, so no completion notification will ever arrive.
17793
+ executionState: "synchronous-complete",
17794
+ trackedBackgroundTask: false,
17795
+ completionNotification: "none"
17796
+ },
17559
17797
  ...normalized.activity ? { activity: normalized.activity } : {}
17560
17798
  }
17561
17799
  };
@@ -17573,7 +17811,6 @@ var WORKSPACE_TOOL_NAMES = [
17573
17811
  "launchProcess",
17574
17812
  "listProcesses",
17575
17813
  "taskWait",
17576
- "taskLogs",
17577
17814
  "taskStop",
17578
17815
  "openDesktopPreview"
17579
17816
  ];
@@ -17582,7 +17819,6 @@ var SHELL_TOOL_NAMES = [
17582
17819
  "launchProcess",
17583
17820
  "listProcesses",
17584
17821
  "taskWait",
17585
- "taskLogs",
17586
17822
  "taskStop"
17587
17823
  ];
17588
17824
  var WORKSPACE_TOOL_NAME_SET = new Set(WORKSPACE_TOOL_NAMES);
@@ -17773,7 +18009,7 @@ function buildLaunchProcessTool() {
17773
18009
  type: "function",
17774
18010
  function: {
17775
18011
  name: "launchProcess",
17776
- description: "Start a long-running background process (dev server, watcher, REPL) and return immediately with {pid, label, status}. Use listProcesses to inspect or stop it.",
18012
+ description: "Start a long-running ambient background process (dev server, watcher, REPL) and return immediately with {pid, label, taskId, status}. Real boundaries: this registers a tracked background task, but completion does NOT automatically start a new turn \u2014 the process is intentionally notice-only (an exited dev server must not silently burn a model turn). When it reaches a terminal state the bounded result (exitCode, duration, stdout/stderr tail) is recorded and surfaced as a notice injected only on your next real turn in the same conversation. Use listProcesses to inspect, taskWait to block for its terminal state, or taskStop to stop it. For a finite job (tests, builds, batch) whose result must resume the conversation automatically, use execShell with background: true instead.",
17777
18013
  parameters: {
17778
18014
  type: "object",
17779
18015
  properties: {
@@ -17832,7 +18068,7 @@ function buildTaskWaitTool() {
17832
18068
  type: "function",
17833
18069
  function: {
17834
18070
  name: "taskWait",
17835
- description: 'Block until a background task (from launchProcess, or an execShell command that detached) reaches a terminal state, or until timeoutMs elapses. Returns {outcome, status, exitCode?, cursor, events}: outcome "terminal" with status exited|failed|stopped when it finished, outcome "timeout" with status "running" when it outlived the whole budget, outcome "not-found" for an unknown taskId, or outcome "evicted" when the event trail aged out (never wait again in that case). One call already waits the maximum budget and returns the moment the task ends, so a "timeout" means the task is genuinely longer than that \u2014 go do other work and come back, rather than re-waiting in a loop (each re-wait costs a full model round-trip). For a cheap liveness peek use taskLogs (non-blocking) or pass a small timeoutMs. `cursor` is the highest event seq covered by this response \u2014 pass it to taskLogs to read only newer events.',
18071
+ description: 'Block until a background task (from launchProcess, or an execShell command that detached) reaches a terminal state, or until timeoutMs elapses. Returns {outcome, status, exitCode?, cursor, events}: outcome "terminal" with status exited|failed|stopped when it finished, outcome "timeout" with status "running" when it outlived the whole budget, outcome "not-found" for an unknown taskId, or outcome "evicted" when the event trail aged out (never wait again in that case). One call already waits the maximum budget and returns the moment the task ends, so a "timeout" means the task is genuinely longer than that \u2014 go do other work and come back, rather than re-waiting in a loop (each re-wait costs a full model round-trip). Pass a small timeoutMs for a cheap liveness peek instead of the full result. `cursor` is the highest event seq covered by this response.',
17836
18072
  parameters: {
17837
18073
  type: "object",
17838
18074
  properties: {
@@ -17851,30 +18087,6 @@ function buildTaskWaitTool() {
17851
18087
  }
17852
18088
  };
17853
18089
  }
17854
- function buildTaskLogsTool() {
17855
- return {
17856
- type: "function",
17857
- function: {
17858
- name: "taskLogs",
17859
- description: "Read a background task's lifecycle events incrementally. Returns {outcome, status, cursor, events} where events are the started|promoted|exited|killed records with seq greater than the cursor you passed, and the returned `cursor` is the new high-water mark to pass next time (omit cursor to read from the beginning; repeating the same cursor is idempotent). Oversized output is spilled to a file and reported as `logRef`, which you can open with readFile.",
17860
- parameters: {
17861
- type: "object",
17862
- properties: {
17863
- taskId: {
17864
- type: "string",
17865
- minLength: 1,
17866
- description: "Task id returned by launchProcess or by a detached execShell result."
17867
- },
17868
- cursor: {
17869
- type: "number",
17870
- description: "Last event seq you already consumed; only events with a higher seq are returned. Defaults to 0 (all retained events)."
17871
- }
17872
- },
17873
- required: ["taskId"]
17874
- }
17875
- }
17876
- };
17877
- }
17878
18090
  function buildTaskStopTool() {
17879
18091
  return {
17880
18092
  type: "function",
@@ -17914,7 +18126,6 @@ function buildWorkspaceToolDefinition(toolName) {
17914
18126
  if (toolName === "openDesktopPreview") return buildOpenDesktopPreviewTool();
17915
18127
  if (toolName === "listProcesses") return buildListProcessesTool();
17916
18128
  if (toolName === "taskWait") return buildTaskWaitTool();
17917
- if (toolName === "taskLogs") return buildTaskLogsTool();
17918
18129
  if (toolName === "taskStop") return buildTaskStopTool();
17919
18130
  return null;
17920
18131
  }
@@ -18939,6 +19150,55 @@ async function execShellTool(args2) {
18939
19150
  }
18940
19151
  );
18941
19152
  }
19153
+ var LAUNCH_PROCESS_STREAM_RING_CHARS = 65536;
19154
+ var LAUNCH_PROCESS_STREAM_CAPTURE_MAX_CHARS = 4e6;
19155
+ function createLaunchProcessStreamCapture() {
19156
+ let ring = "";
19157
+ let full = null;
19158
+ let capturedChars = 0;
19159
+ let totalChars = 0;
19160
+ let totalNewlines = 0;
19161
+ return {
19162
+ push(chunk) {
19163
+ const str = typeof chunk === "string" ? chunk : chunk.toString("utf8");
19164
+ if (!str) return;
19165
+ totalChars += str.length;
19166
+ for (let i = 0; i < str.length; i += 1) {
19167
+ if (str.charCodeAt(i) === 10) totalNewlines += 1;
19168
+ }
19169
+ const combined = ring + str;
19170
+ if (combined.length <= LAUNCH_PROCESS_STREAM_RING_CHARS) {
19171
+ ring = combined;
19172
+ return;
19173
+ }
19174
+ if (full === null) {
19175
+ full = [combined];
19176
+ capturedChars = combined.length;
19177
+ } else if (capturedChars < LAUNCH_PROCESS_STREAM_CAPTURE_MAX_CHARS) {
19178
+ const room = LAUNCH_PROCESS_STREAM_CAPTURE_MAX_CHARS - capturedChars;
19179
+ const slice = str.length <= room ? str : str.slice(0, room);
19180
+ full.push(slice);
19181
+ capturedChars += slice.length;
19182
+ }
19183
+ ring = combined.slice(-LAUNCH_PROCESS_STREAM_RING_CHARS);
19184
+ },
19185
+ text() {
19186
+ return full === null ? ring : full.join("");
19187
+ },
19188
+ tailText() {
19189
+ return ring;
19190
+ },
19191
+ evicted() {
19192
+ return full !== null;
19193
+ },
19194
+ totalChars() {
19195
+ return totalChars;
19196
+ },
19197
+ totalLines() {
19198
+ return totalChars > 0 ? totalNewlines + 1 : 0;
19199
+ }
19200
+ };
19201
+ }
18942
19202
  async function launchProcessTool(args2) {
18943
19203
  const parsed = parseWorkspaceToolArguments(args2.call.arguments);
18944
19204
  const command = requireShellCommand(parsed, args2.call.name);
@@ -18966,6 +19226,14 @@ async function launchProcessTool(args2) {
18966
19226
  stdio: ["ignore", "pipe", "pipe"],
18967
19227
  detached
18968
19228
  });
19229
+ const stdoutCapture = createLaunchProcessStreamCapture();
19230
+ const stderrCapture = createLaunchProcessStreamCapture();
19231
+ proc.stdout?.on("data", (chunk) => stdoutCapture.push(chunk));
19232
+ proc.stderr?.on("data", (chunk) => stderrCapture.push(chunk));
19233
+ proc.stdout?.on("error", () => {
19234
+ });
19235
+ proc.stderr?.on("error", () => {
19236
+ });
18969
19237
  const pid = proc.pid;
18970
19238
  if (typeof pid !== "number") {
18971
19239
  return {
@@ -19006,7 +19274,38 @@ async function launchProcessTool(args2) {
19006
19274
  process.removeListener("SIGTERM", cleanupChildOnHostSignal);
19007
19275
  process.removeListener("SIGINT", cleanupChildOnHostSignal);
19008
19276
  }
19009
- registry2.markExited(pid, code ?? 1);
19277
+ const exitCode = code ?? 1;
19278
+ let capsule;
19279
+ try {
19280
+ capsule = buildProcessTaskResultCapsule({
19281
+ // The full captured output (bounded prefix when the process was
19282
+ // extremely chatty): the module tails it for the inline capsule and
19283
+ // spills it for recovery. Provenance carries the live original-stream
19284
+ // counters so truncated/spill metadata never derives from the ring.
19285
+ stdout: stdoutCapture.text(),
19286
+ stderr: stderrCapture.text(),
19287
+ stdoutTail: stdoutCapture.tailText(),
19288
+ stderrTail: stderrCapture.tailText(),
19289
+ exitCode,
19290
+ // Duration measured from the registry envelope's startedAt (spawn).
19291
+ startedAt: envelope.startedAt,
19292
+ workspaceRoot: args2.workspaceRoot,
19293
+ streamProvenance: {
19294
+ stdout: {
19295
+ evicted: stdoutCapture.evicted(),
19296
+ totalChars: stdoutCapture.totalChars(),
19297
+ totalLines: stdoutCapture.totalLines()
19298
+ },
19299
+ stderr: {
19300
+ evicted: stderrCapture.evicted(),
19301
+ totalChars: stderrCapture.totalChars(),
19302
+ totalLines: stderrCapture.totalLines()
19303
+ }
19304
+ }
19305
+ });
19306
+ } catch {
19307
+ }
19308
+ registry2.markExited(pid, exitCode, capsule);
19010
19309
  });
19011
19310
  const activity = extractActivity(parsed);
19012
19311
  const resultData = { pid, label, taskId: envelope.taskId, status: "running" };
@@ -19018,6 +19317,13 @@ async function launchProcessTool(args2) {
19018
19317
  label,
19019
19318
  status: "running",
19020
19319
  taskId: envelope.taskId,
19320
+ // P0 machine-readable execution state (consistent with the real wake
19321
+ // contract): a real background task from birth, but ambient — its
19322
+ // terminal result rides the next real turn's notice, not an automatic
19323
+ // completion turn.
19324
+ executionState: "background",
19325
+ trackedBackgroundTask: true,
19326
+ completionNotification: "next-turn-notice",
19021
19327
  processLaunch: resultData,
19022
19328
  ...activity ? { activity } : {}
19023
19329
  }
@@ -19242,11 +19548,295 @@ function createLocalWorkspaceToolExecutors(args2) {
19242
19548
  };
19243
19549
  }
19244
19550
 
19245
- // packages/cli/client/localDialogRecords.ts
19246
- var localDialogMessageRecordToRuntimeMessage = dialogMessageRecordToAgentRuntimeMessage;
19247
- function buildLocalDialogWritePlan(args2) {
19248
- return buildAgentRuntimeDialogWritePlan({
19249
- input: args2.input,
19551
+ // packages/agent-runtime/toolArgumentGate.ts
19552
+ var FIELD_ALIAS_GROUPS = [
19553
+ // localWorkspaceTools.ts:336 导出常量(readFile/writeFile/editFile/globFiles)
19554
+ PATH_FIELD_ALIASES,
19555
+ // localWorkspaceTools.ts:339-340 导出常量(editFile)
19556
+ OLD_TEXT_FIELD_ALIASES,
19557
+ NEW_TEXT_FIELD_ALIASES,
19558
+ // localWorkspaceTools.ts:733 `args.cmd || args.command`(launchProcess);
19559
+ // launchProcess 的 required ["command"] 见 localWorkspaceToolDefs.ts:283。
19560
+ // 注:execShell 的 schema 无 required(command 在 executor 层强制,
19561
+ // capabilities/execShellCapability.ts:42 声明 cmd 兼容字段),本组对它不产生拦截。
19562
+ ["command", "cmd"],
19563
+ // localWorkspaceTools.ts:698-699 `args.pattern ?? args.glob`(globFiles)
19564
+ ["pattern", "glob"],
19565
+ // noloWorkspaceTools.ts:492 `args.dialog ?? args.dialogId ?? args.id`(readDialog)
19566
+ ["dialog", "dialogId", "id"],
19567
+ // noloWorkspaceTools.ts:545 `args.agent ?? args.agentKey ?? args.id`(readAgent)
19568
+ ["agent", "agentKey", "id"],
19569
+ // noloWorkspaceTools.ts:552 `args.space ?? args.spaceId ?? args.id`(readSpace)
19570
+ ["space", "spaceId", "id"],
19571
+ // noloWorkspaceTools.ts:561/567 `args.doc ?? args.docKey ?? args.pageKey ?? args.key`
19572
+ // (readDoc / readSkillDoc)
19573
+ ["doc", "docKey", "pageKey", "key"],
19574
+ // noloWorkspaceTools.node.ts:238 `parsed.name ?? parsed.skillName ?? parsed.skill`(loadSkill)
19575
+ ["name", "skillName", "skill"],
19576
+ // noloWorkspaceTools.ts:593 `args.table ?? args.tableId ?? args.metaKey`(queryTableRows)
19577
+ ["table", "tableId", "metaKey"]
19578
+ ];
19579
+ var TYPE_MATCHERS = {
19580
+ string: (value) => typeof value === "string",
19581
+ // 数字字符串视为可接受:executor 普遍 Number()/clamp 宽松处理,拦截会误报。
19582
+ number: (value) => typeof value === "number" || typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)),
19583
+ integer: (value) => typeof value === "number" || typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)),
19584
+ boolean: (value) => typeof value === "boolean" || value === "true" || value === "false",
19585
+ object: (value) => typeof value === "object" && value !== null && !Array.isArray(value),
19586
+ array: (value) => Array.isArray(value)
19587
+ };
19588
+ function describeValueType(value) {
19589
+ if (value === null) return "null";
19590
+ if (Array.isArray(value)) return "array";
19591
+ return typeof value;
19592
+ }
19593
+ function isAliasSatisfied(args2, field) {
19594
+ for (const group of FIELD_ALIAS_GROUPS) {
19595
+ if (!group.includes(field)) continue;
19596
+ if (group.some((alias) => args2[alias] !== void 0)) return true;
19597
+ }
19598
+ return false;
19599
+ }
19600
+ function readRequiredFields(parameters) {
19601
+ const required = parameters.required;
19602
+ if (!Array.isArray(required)) return [];
19603
+ return required.filter((field) => typeof field === "string");
19604
+ }
19605
+ function readProperties(parameters) {
19606
+ const properties = parameters.properties;
19607
+ if (!properties || typeof properties !== "object" || Array.isArray(properties)) {
19608
+ return {};
19609
+ }
19610
+ return properties;
19611
+ }
19612
+ function validateToolArguments(args2) {
19613
+ const { toolName, rawArguments, parameters } = args2;
19614
+ if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) {
19615
+ return { ok: true, args: {} };
19616
+ }
19617
+ const schema = parameters;
19618
+ const requiredFields = readRequiredFields(schema);
19619
+ const properties = readProperties(schema);
19620
+ if (requiredFields.length === 0 && Object.keys(properties).length === 0) {
19621
+ return { ok: true, args: {} };
19622
+ }
19623
+ const raw = typeof rawArguments === "string" ? rawArguments.trim() : "";
19624
+ let record = {};
19625
+ if (raw !== "") {
19626
+ let parsed;
19627
+ try {
19628
+ parsed = JSON.parse(raw);
19629
+ } catch {
19630
+ return { ok: true, args: {} };
19631
+ }
19632
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
19633
+ return { ok: true, args: {} };
19634
+ }
19635
+ record = parsed;
19636
+ }
19637
+ for (const field of requiredFields) {
19638
+ if (record[field] !== void 0) continue;
19639
+ if (isAliasSatisfied(record, field)) continue;
19640
+ return {
19641
+ ok: false,
19642
+ message: `${toolName} \u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF1A\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570 "${field}"\u3002\u8BF7\u8865\u4E0A\u540E\u91CD\u65B0\u8C03\u7528 ${toolName}\u3002`
19643
+ };
19644
+ }
19645
+ for (const [field, spec] of Object.entries(properties)) {
19646
+ const value = record[field];
19647
+ if (value === void 0 || value === null) continue;
19648
+ if (!spec || typeof spec !== "object" || Array.isArray(spec)) continue;
19649
+ const declared = spec.type;
19650
+ if (typeof declared !== "string") continue;
19651
+ const matcher = TYPE_MATCHERS[declared];
19652
+ if (!matcher) continue;
19653
+ if (!matcher(value)) {
19654
+ return {
19655
+ ok: false,
19656
+ message: `${toolName} \u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF1A\u53C2\u6570 "${field}" \u5E94\u4E3A ${declared}\uFF0C\u5B9E\u9645\u6536\u5230 ${describeValueType(value)}\u3002\u8BF7\u4FEE\u6B63\u540E\u91CD\u65B0\u8C03\u7528 ${toolName}\u3002`
19657
+ };
19658
+ }
19659
+ }
19660
+ return { ok: true, args: record };
19661
+ }
19662
+ function buildToolParametersIndex(tools) {
19663
+ const index = /* @__PURE__ */ new Map();
19664
+ for (const tool of tools) {
19665
+ if (!tool || typeof tool !== "object") continue;
19666
+ const fn3 = tool.function;
19667
+ if (!fn3 || typeof fn3 !== "object") continue;
19668
+ const name = fn3.name;
19669
+ const parameters = fn3.parameters;
19670
+ if (typeof name === "string" && name && parameters !== void 0) {
19671
+ index.set(name, parameters);
19672
+ }
19673
+ }
19674
+ return index;
19675
+ }
19676
+ var cachedAgentRuntimeIndex = null;
19677
+ function getAgentRuntimeToolParametersIndex() {
19678
+ if (cachedAgentRuntimeIndex) return cachedAgentRuntimeIndex;
19679
+ const index = /* @__PURE__ */ new Map();
19680
+ try {
19681
+ const workspaceTools = buildLocalWorkspaceOpenAiTools({
19682
+ toolNames: [...WORKSPACE_TOOL_NAMES],
19683
+ exposeShellTools: true
19684
+ });
19685
+ for (const [name, parameters] of buildToolParametersIndex(workspaceTools)) {
19686
+ index.set(name, parameters);
19687
+ }
19688
+ const noloTools = buildNoloWorkspaceOpenAiTools({
19689
+ toolNames: [...NOLO_WORKSPACE_TOOL_NAMES]
19690
+ });
19691
+ for (const [name, parameters] of buildToolParametersIndex(noloTools)) {
19692
+ index.set(name, parameters);
19693
+ }
19694
+ } catch {
19695
+ }
19696
+ cachedAgentRuntimeIndex = index;
19697
+ return index;
19698
+ }
19699
+
19700
+ // packages/agent-runtime/localToolPolicy.ts
19701
+ var NEVER_LOCAL_TOOLS = /* @__PURE__ */ new Set([
19702
+ "deleteSpaces",
19703
+ "updateAgent",
19704
+ "updateSelf",
19705
+ "createAgent"
19706
+ ]);
19707
+ var DEFAULT_LOCAL_TOOLS = /* @__PURE__ */ new Set([
19708
+ "readFile",
19709
+ "writeFile",
19710
+ "editFile",
19711
+ "globFiles",
19712
+ "execShell",
19713
+ "launchProcess",
19714
+ "listProcesses",
19715
+ // ProcessTask follow-up tools. Same tier as listProcesses: they observe/stop
19716
+ // tasks the agent itself already spawned, so their blast radius is strictly
19717
+ // narrower than launchProcess (which is default-enabled). Model-facing
19718
+ // follow-ups are taskWait/taskStop only — lifecycle history (taskLogs) stays
19719
+ // runtime-internal.
19720
+ "taskWait",
19721
+ "taskStop",
19722
+ // Scheduling tools: forking/observing sub-agents is a declared capability
19723
+ // of capable agents, and the blast radius is strictly narrower than
19724
+ // execShell (already default-enabled). Requiring an env allowlist for these
19725
+ // while execShell is open by default inverted the risk model and forced every
19726
+ // new local user to hand-edit .env files before sub-agent tasks worked.
19727
+ "startAgentRun",
19728
+ "controlAgentRun"
19729
+ ]);
19730
+ function parseToolAllowlist(value) {
19731
+ return (value ?? "").split(",").flatMap((item) => {
19732
+ const trimmed = item.trim();
19733
+ return trimmed ? [trimmed] : [];
19734
+ });
19735
+ }
19736
+ function normalizeLocalToolName(toolName) {
19737
+ return canonicalizeToolName(
19738
+ String(toolName ?? "").replace(/^functions\./, "").trim()
19739
+ );
19740
+ }
19741
+ function isRestrictedLocalToolMode(env) {
19742
+ return env.NOLO_LOCAL_TOOL_MODE === "restricted";
19743
+ }
19744
+ function resolveLocalToolPolicy(args2) {
19745
+ const toolName = normalizeLocalToolName(args2.toolName);
19746
+ const restrictedMode = isRestrictedLocalToolMode(args2.env);
19747
+ if (!toolName) {
19748
+ return {
19749
+ allowed: false,
19750
+ toolName,
19751
+ reason: "Tool name is required."
19752
+ };
19753
+ }
19754
+ if (NEVER_LOCAL_TOOLS.has(toolName)) {
19755
+ return {
19756
+ allowed: false,
19757
+ toolName,
19758
+ reason: `${toolName} is blocked by the local runtime safety policy.`
19759
+ };
19760
+ }
19761
+ const allowedByEnv = new Set(parseToolAllowlist(args2.env.NOLO_LOCAL_ALLOWED_TOOLS));
19762
+ const agentTools = new Set(
19763
+ (args2.agentToolNames ?? []).flatMap((item) => {
19764
+ const normalized = normalizeLocalToolName(item);
19765
+ return normalized ? [normalized] : [];
19766
+ })
19767
+ );
19768
+ if (!restrictedMode && agentTools.has(toolName)) {
19769
+ return { allowed: true, toolName };
19770
+ }
19771
+ if (DEFAULT_LOCAL_TOOLS.has(toolName) && agentTools.has(toolName)) {
19772
+ return { allowed: true, toolName };
19773
+ }
19774
+ if (allowedByEnv.has(toolName) && agentTools.has(toolName)) {
19775
+ return { allowed: true, toolName };
19776
+ }
19777
+ return {
19778
+ allowed: false,
19779
+ toolName,
19780
+ reason: `${toolName} is not enabled for local runtime runs. The agent must declare the tool in its manifest; in restricted mode (NOLO_LOCAL_TOOL_MODE=restricted) non-default tools also require adding to NOLO_LOCAL_ALLOWED_TOOLS (e.g. NOLO_LOCAL_ALLOWED_TOOLS=someCustomTool) and restarting the session so the env var loads.`
19781
+ };
19782
+ }
19783
+ async function executeLocalToolWithPolicy(args2) {
19784
+ if (args2.runToolNames !== void 0 && !args2.runToolNames.includes(normalizeLocalToolName(args2.call.name))) {
19785
+ throw new Error(`${args2.call.name} is outside the final run tool surface.`);
19786
+ }
19787
+ const decision = resolveLocalToolPolicy({
19788
+ env: args2.env,
19789
+ agentToolNames: args2.agentToolNames,
19790
+ toolName: args2.call.name
19791
+ });
19792
+ if (!decision.allowed) throw new Error(decision.reason);
19793
+ const executor = args2.executors?.[decision.toolName];
19794
+ if (!executor) {
19795
+ throw new Error(`${decision.toolName} is allowed by policy but no local executor is registered.`);
19796
+ }
19797
+ const parametersSchema = args2.toolParametersIndex?.get(decision.toolName);
19798
+ if (parametersSchema !== void 0) {
19799
+ const gate = validateToolArguments({
19800
+ toolName: decision.toolName,
19801
+ rawArguments: args2.call.arguments,
19802
+ parameters: parametersSchema
19803
+ });
19804
+ if (!gate.ok) throw new Error(gate.message);
19805
+ }
19806
+ const opts = {
19807
+ ...args2.abortSignal ? { abortSignal: args2.abortSignal } : {},
19808
+ ...args2.detachMs !== void 0 ? { detachMs: args2.detachMs } : {},
19809
+ ...args2.confirmed !== void 0 ? { confirmed: args2.confirmed } : {},
19810
+ ...args2.enableDestructiveShellGuard !== void 0 ? { enableDestructiveShellGuard: args2.enableDestructiveShellGuard } : {},
19811
+ ...args2.confirmDestructiveAction ? { confirmDestructiveAction: args2.confirmDestructiveAction } : {},
19812
+ ...args2.blockDestructiveWithoutConfirmation !== void 0 ? { blockDestructiveWithoutConfirmation: args2.blockDestructiveWithoutConfirmation } : {}
19813
+ };
19814
+ if (decision.toolName === "execShell") {
19815
+ let parsedInput = args2.call.arguments;
19816
+ if (typeof parsedInput === "string") {
19817
+ try {
19818
+ parsedInput = JSON.parse(parsedInput);
19819
+ } catch {
19820
+ parsedInput = { command: parsedInput };
19821
+ }
19822
+ }
19823
+ await evaluateExecShellDestructiveGuard(parsedInput, opts);
19824
+ opts.confirmed = true;
19825
+ }
19826
+ return executor(
19827
+ {
19828
+ ...args2.call,
19829
+ name: decision.toolName
19830
+ },
19831
+ opts
19832
+ );
19833
+ }
19834
+
19835
+ // packages/cli/client/localDialogRecords.ts
19836
+ var localDialogMessageRecordToRuntimeMessage = dialogMessageRecordToAgentRuntimeMessage;
19837
+ function buildLocalDialogWritePlan(args2) {
19838
+ return buildAgentRuntimeDialogWritePlan({
19839
+ input: args2.input,
19250
19840
  userId: args2.userId,
19251
19841
  now: args2.now,
19252
19842
  createId: args2.createId,
@@ -19381,15 +19971,23 @@ function buildLoadSkillExecutor(args2) {
19381
19971
  const { resolveSkillByName } = await import("./skillDiscovery-CYUCJMAG.js");
19382
19972
  const resolved = resolveSkillByName(args2.cwd, name);
19383
19973
  if (!resolved) {
19384
- const { resolveCodingBuiltinSlug, buildCodingSkillContentBySlug } = await import("./codingSkills-7S6523WH.js");
19385
- const builtinSlug = resolveCodingBuiltinSlug(name);
19386
- if (builtinSlug) {
19387
- const body = buildCodingSkillContentBySlug(builtinSlug);
19974
+ const { resolveBuiltinSkillByName } = await import("./builtinSkillRegistry-Q2VVY3SC.js");
19975
+ const builtin = resolveBuiltinSkillByName(name);
19976
+ if (builtin) {
19388
19977
  return {
19389
19978
  content: `Skill "${name}" loaded inline. Follow its instructions.
19390
19979
 
19391
- ${body}`,
19392
- metadata: { loadSkill: true, resolved: true, name, requestedName: name, builtin: true }
19980
+ ${builtin.content}`,
19981
+ // toolNames 进 metadata:回退面已扩到全注册表,其中授工具型 skill
19982
+ // (search-* 等)的工具在本宿主未必接线,宿主可据此降级提示。
19983
+ metadata: {
19984
+ loadSkill: true,
19985
+ resolved: true,
19986
+ name,
19987
+ requestedName: name,
19988
+ builtin: true,
19989
+ ...builtin.config.toolNames?.length ? { toolNames: [...builtin.config.toolNames] } : {}
19990
+ }
19393
19991
  };
19394
19992
  }
19395
19993
  return {
@@ -19431,737 +20029,8 @@ No skills were discovered in this workspace.`;
19431
20029
  Available skills: ${available.join(", ")}`;
19432
20030
  }
19433
20031
 
19434
- // packages/core/chat/upstreamErrorBody.ts
19435
- function parseUpstreamErrorBody(errorText, statusText) {
19436
- if (errorText) {
19437
- try {
19438
- const parsed = JSON.parse(errorText);
19439
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19440
- const error = parsed.error;
19441
- if (error && typeof error === "object" && !Array.isArray(error)) {
19442
- return parsed;
19443
- }
19444
- }
19445
- } catch {
19446
- }
19447
- }
19448
- return { error: { message: errorText || statusText } };
19449
- }
19450
-
19451
- // packages/agent-runtime/antigravityCloudCodeProvider.ts
19452
- import { randomUUID } from "node:crypto";
19453
-
19454
- // packages/agent-runtime/antigravityWireModel.ts
19455
- var WIRE_PROFILES = {
19456
- // Antigravity discovery exposes 3.7 Flash as one tiered wire model.
19457
- // The UI labels its effort variants (Low/Medium/High), but the literal
19458
- // -medium/-high ids return 404 from streamGenerateContent.
19459
- "gemini-3.7-flash-tiered": { modelEnum: "MODEL_PLACEHOLDER_M301", maxOutputTokens: 65536 },
19460
- // Gemini 3.8 Flash 沿用 3.7 的 tiered 兼容策略:仓库内暂无 Antigravity discovery
19461
- // 证据确认 3.8 的 wire modelEnum,禁止虚构,先只声明同档输出上限;enum 待
19462
- // discovery 证据到位后补齐。
19463
- "gemini-3.8-flash-tiered": { maxOutputTokens: 65536 },
19464
- "gemini-3.6-flash-low": { maxOutputTokens: 65536 },
19465
- "gemini-3.6-flash-medium": { maxOutputTokens: 65536 },
19466
- "gemini-3.6-flash-high": { maxOutputTokens: 65536 },
19467
- // 旧模型 wire 映射保留:存量自定义 agent 仍可能引用(注册表已下线不影响此处)。
19468
- "gemini-3.5-flash-extra-low": { modelEnum: "MODEL_PLACEHOLDER_M187", maxOutputTokens: 65536 },
19469
- "gemini-3.5-flash-low": { modelEnum: "MODEL_PLACEHOLDER_M20", maxOutputTokens: 65536 },
19470
- "gemini-3-flash-agent": { modelEnum: "MODEL_PLACEHOLDER_M132", maxOutputTokens: 65536 },
19471
- "gemini-3.1-pro-low": { modelEnum: "MODEL_PLACEHOLDER_M36", maxOutputTokens: 65535 },
19472
- "gemini-pro-agent": { modelEnum: "MODEL_PLACEHOLDER_M16", maxOutputTokens: 65535 },
19473
- "claude-sonnet-5": { maxOutputTokens: 64e3 },
19474
- "claude-sonnet-4-6": { maxOutputTokens: 64e3 },
19475
- "claude-opus-4-6-thinking": { maxOutputTokens: 64e3 },
19476
- "gemini-2.5-flash": { maxOutputTokens: 65536 },
19477
- "gemini-2.5-pro": { maxOutputTokens: 65536 }
19478
- };
19479
- function resolveAntigravityWireModel(logicalModelId) {
19480
- const id = logicalModelId.trim();
19481
- const lower = id.toLowerCase();
19482
- if (lower === "gemini-3.5-flash") {
19483
- const wireModelId = "gemini-3.5-flash-low";
19484
- return { wireModelId, profile: WIRE_PROFILES[wireModelId] };
19485
- }
19486
- if (lower === "gemini-3.7-flash" || ["gemini-3.7-flash-low", "gemini-3.7-flash-medium", "gemini-3.7-flash-high"].includes(lower)) {
19487
- const wireModelId = "gemini-3.7-flash-tiered";
19488
- return { wireModelId, profile: WIRE_PROFILES[wireModelId] };
19489
- }
19490
- if (lower === "gemini-3.8-flash" || ["gemini-3.8-flash-low", "gemini-3.8-flash-medium", "gemini-3.8-flash-high"].includes(lower)) {
19491
- const wireModelId = "gemini-3.8-flash-tiered";
19492
- return { wireModelId, profile: WIRE_PROFILES[wireModelId] };
19493
- }
19494
- if (lower === "gemini-3.6-flash") {
19495
- const wireModelId = "gemini-3.6-flash-low";
19496
- return { wireModelId, profile: WIRE_PROFILES[wireModelId] };
19497
- }
19498
- if (lower === "gemini-3.1-pro" || lower === "gemini-3.1-pro-preview") {
19499
- const wireModelId = "gemini-3.1-pro-low";
19500
- return { wireModelId, profile: WIRE_PROFILES[wireModelId] };
19501
- }
19502
- if (WIRE_PROFILES[id]) {
19503
- return { wireModelId: id, profile: WIRE_PROFILES[id] };
19504
- }
19505
- return { wireModelId: id };
19506
- }
19507
-
19508
- // packages/agent-runtime/geminiNativeShared.ts
19509
- var SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
19510
- function resolveGeminiModelQuirks(modelId) {
19511
- const lower = modelId.toLowerCase();
19512
- const isGemini3 = lower.includes("gemini-3");
19513
- const isStrictSignatureModel = lower.includes("gemini-3.5") || lower.includes("flash-preview");
19514
- const isClaude = lower.includes("claude");
19515
- return {
19516
- requiresThoughtSignature: isGemini3,
19517
- allowsThoughtSignatureSentinel: isGemini3 && !isStrictSignatureModel,
19518
- isClaudeCrossModel: isClaude
19519
- };
19520
- }
19521
- function isGemini3Model(modelId) {
19522
- return resolveGeminiModelQuirks(modelId).requiresThoughtSignature;
19523
- }
19524
- function messageText(content) {
19525
- if (typeof content === "string") return content;
19526
- if (!Array.isArray(content)) return "";
19527
- return content.filter(
19528
- (part) => part?.type === "text"
19529
- ).map((part) => part.text).join("\n").trim();
19530
- }
19531
- function parseDataUrlToInlineData(url) {
19532
- const match7 = /^data:([A-Za-z0-9.+-]+\/[A-Za-z0-9.+-]+);base64,([A-Za-z0-9+/=]*)$/.exec(
19533
- url
19534
- );
19535
- if (!match7) return null;
19536
- return { mimeType: match7[1], data: match7[2] };
19537
- }
19538
- function extractInlineDataParts(content) {
19539
- if (!Array.isArray(content)) return [];
19540
- const parts2 = [];
19541
- for (const part of content) {
19542
- if (!part || typeof part !== "object") continue;
19543
- if (part.type !== "image_url") continue;
19544
- const url = part.image_url?.url;
19545
- if (typeof url !== "string" || !url.trim()) continue;
19546
- const inline = parseDataUrlToInlineData(url);
19547
- if (inline) parts2.push({ inlineData: inline });
19548
- }
19549
- return parts2;
19550
- }
19551
- function convertOpenAiMessagesToGemini(messages, options) {
19552
- const contents = [];
19553
- const systemTexts = [];
19554
- let toolCallIndex = 0;
19555
- let pendingFunctionCalls = [];
19556
- const pushOrMergeContent = (role, parts2) => {
19557
- if (parts2.length === 0) return;
19558
- const last = contents[contents.length - 1];
19559
- if (last && last.role === role) {
19560
- last.parts.push(...parts2);
19561
- } else {
19562
- contents.push({ role, parts: parts2 });
19563
- }
19564
- };
19565
- const flushPendingFunctionCalls = () => {
19566
- if (pendingFunctionCalls.length === 0) return;
19567
- const dummyResponses = pendingFunctionCalls.map(
19568
- ({ name, id }) => ({
19569
- functionResponse: {
19570
- name,
19571
- response: { output: "{}" },
19572
- ...id ? { id } : {}
19573
- }
19574
- })
19575
- );
19576
- pendingFunctionCalls = [];
19577
- pushOrMergeContent("user", dummyResponses);
19578
- };
19579
- for (const raw of messages) {
19580
- if (!raw || typeof raw !== "object" || !("role" in raw)) continue;
19581
- const role = String(raw.role);
19582
- const content = "content" in raw ? raw.content : null;
19583
- if (role === "system") {
19584
- const text = messageText(
19585
- content
19586
- );
19587
- if (text) systemTexts.push(text);
19588
- continue;
19589
- }
19590
- if (role === "user") {
19591
- flushPendingFunctionCalls();
19592
- const text = messageText(
19593
- content
19594
- );
19595
- const imageParts = extractInlineDataParts(
19596
- content
19597
- );
19598
- if (!text && imageParts.length === 0) continue;
19599
- const parts2 = [];
19600
- if (text) parts2.push({ text });
19601
- for (const img of imageParts) parts2.push(img);
19602
- pushOrMergeContent("user", parts2);
19603
- continue;
19604
- }
19605
- if (role === "assistant") {
19606
- flushPendingFunctionCalls();
19607
- const text = messageText(
19608
- content
19609
- );
19610
- const toolCalls = "tool_calls" in raw && Array.isArray(raw.tool_calls) ? raw.tool_calls ?? [] : [];
19611
- const parts2 = [];
19612
- if (text) {
19613
- parts2.push({ text });
19614
- }
19615
- for (const call of toolCalls) {
19616
- const name = call?.function?.name?.trim();
19617
- if (!name) continue;
19618
- const id = call.id?.trim() || `${name}_${toolCallIndex++}`;
19619
- pendingFunctionCalls.push({ name, id });
19620
- const realSignature = typeof call.thought_signature === "string" && call.thought_signature ? call.thought_signature : void 0;
19621
- const isFirstFunctionCallPart = !parts2.some(
19622
- (p) => "functionCall" in p
19623
- );
19624
- const signature = realSignature ?? (options.attachSkipThoughtSignature && isFirstFunctionCallPart ? SKIP_THOUGHT_SIGNATURE : void 0);
19625
- const part = {
19626
- functionCall: {
19627
- name,
19628
- args: parseToolArgs(call.function?.arguments),
19629
- ...id ? { id } : {}
19630
- },
19631
- ...signature ? { thoughtSignature: signature } : {}
19632
- };
19633
- parts2.push(part);
19634
- }
19635
- pushOrMergeContent("model", parts2);
19636
- continue;
19637
- }
19638
- if (role === "tool") {
19639
- const toolCallId = "tool_call_id" in raw && typeof raw.tool_call_id === "string" ? raw.tool_call_id : "";
19640
- const rawName = "name" in raw && typeof raw.name === "string" ? raw.name.trim() : "";
19641
- const pendingIndex = pendingFunctionCalls.findIndex(
19642
- (p) => toolCallId ? p.id === toolCallId : rawName ? p.name === rawName : true
19643
- );
19644
- if (pendingIndex === -1) continue;
19645
- const [{ name, id }] = pendingFunctionCalls.splice(pendingIndex, 1);
19646
- const output = messageText(content) || "{}";
19647
- pushOrMergeContent("user", [
19648
- {
19649
- functionResponse: {
19650
- name,
19651
- response: { output },
19652
- // 保留 OpenAI tool_call_id:antigravity Cloud Code Assist 网关把
19653
- // Gemini contents 转成 Claude messages 时需要 functionResponse →
19654
- // tool_result 的 tool_use_id;缺 id 时网关报
19655
- // "tool_result.tool_use_id: Field required"(HTTP 400)。
19656
- ...id ? { id } : {}
19657
- }
19658
- }
19659
- ]);
19660
- }
19661
- }
19662
- flushPendingFunctionCalls();
19663
- if (contents.length > 0 && contents[0]?.role === "model") {
19664
- contents.unshift({
19665
- role: "user",
19666
- parts: [{ text: "Continue the conversation." }]
19667
- });
19668
- }
19669
- return { contents, systemTexts };
19670
- }
19671
- function parseToolArgs(raw) {
19672
- if (typeof raw === "string") {
19673
- try {
19674
- const parsed = JSON.parse(raw);
19675
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19676
- return parsed;
19677
- }
19678
- } catch {
19679
- }
19680
- }
19681
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
19682
- return raw;
19683
- }
19684
- return {};
19685
- }
19686
- function sanitizeGeminiSchemaNode(node, active) {
19687
- const out = {};
19688
- if (!node || typeof node !== "object" || Array.isArray(node)) {
19689
- return { type: "string" };
19690
- }
19691
- if (active?.has(node)) {
19692
- return { type: "object", description: "(circular schema reference)" };
19693
- }
19694
- const seen = active ?? /* @__PURE__ */ new WeakSet();
19695
- seen.add(node);
19696
- try {
19697
- return sanitizeGeminiSchemaNodeInner(node, out, seen);
19698
- } finally {
19699
- seen.delete(node);
19700
- }
19701
- }
19702
- function sanitizeGeminiSchemaNodeInner(src, out, seen) {
19703
- if (Array.isArray(src.type)) {
19704
- const firstNonNull = src.type.find((t) => t !== "null");
19705
- if (typeof firstNonNull === "string") out.type = firstNonNull;
19706
- out.nullable = true;
19707
- } else if (typeof src.type === "string") {
19708
- out.type = src.type;
19709
- }
19710
- if (typeof src.description === "string") out.description = src.description;
19711
- if (typeof src.minimum === "number") out.minimum = src.minimum;
19712
- if (typeof src.maximum === "number") out.maximum = src.maximum;
19713
- if (Array.isArray(src.enum) && src.enum.length > 0) out.enum = src.enum;
19714
- if (src.nullable === true) out.nullable = true;
19715
- if ("const" in src && src.const !== void 0 && !out.enum) {
19716
- out.enum = [src.const];
19717
- }
19718
- const variantBranches = [
19719
- ...Array.isArray(src.oneOf) ? src.oneOf : [],
19720
- ...Array.isArray(src.anyOf) ? src.anyOf : []
19721
- ];
19722
- if (variantBranches.length > 0) {
19723
- const mergedProperties = {};
19724
- let requiredIntersection;
19725
- for (const branch of variantBranches) {
19726
- const sanitized = sanitizeGeminiSchemaNode(branch, seen);
19727
- const branchProperties = sanitized.properties ?? {};
19728
- for (const [key, value] of Object.entries(branchProperties)) {
19729
- const previous = mergedProperties[key];
19730
- const prevEnum = previous?.enum;
19731
- const nextEnum = value?.enum;
19732
- if (prevEnum && nextEnum) {
19733
- mergedProperties[key] = {
19734
- ...value,
19735
- enum: [.../* @__PURE__ */ new Set([...prevEnum, ...nextEnum])]
19736
- };
19737
- } else {
19738
- mergedProperties[key] = value;
19739
- }
19740
- }
19741
- const branchRequired = Array.isArray(sanitized.required) ? sanitized.required : [];
19742
- requiredIntersection = requiredIntersection === void 0 ? [...branchRequired] : requiredIntersection.filter((r) => branchRequired.includes(r));
19743
- }
19744
- out.properties = {
19745
- ...out.properties ?? {},
19746
- ...mergedProperties
19747
- };
19748
- if (requiredIntersection !== void 0 && requiredIntersection.length > 0) {
19749
- out.required = requiredIntersection;
19750
- }
19751
- if (typeof out.type !== "string") out.type = "object";
19752
- }
19753
- if (Array.isArray(src.allOf)) {
19754
- for (const branch of src.allOf) {
19755
- const sanitized = sanitizeGeminiSchemaNode(branch, seen);
19756
- out.properties = {
19757
- ...out.properties ?? {},
19758
- ...sanitized.properties ?? {}
19759
- };
19760
- if (Array.isArray(sanitized.required)) {
19761
- out.required = [
19762
- .../* @__PURE__ */ new Set([
19763
- ...out.required ?? [],
19764
- ...sanitized.required ?? []
19765
- ])
19766
- ];
19767
- }
19768
- }
19769
- if (typeof out.type !== "string") out.type = "object";
19770
- }
19771
- if (src.properties && typeof src.properties === "object") {
19772
- const properties = {};
19773
- for (const [key, value] of Object.entries(
19774
- src.properties
19775
- )) {
19776
- properties[key] = sanitizeGeminiSchemaNode(value, seen);
19777
- }
19778
- out.properties = properties;
19779
- if (typeof out.type !== "string") out.type = "object";
19780
- }
19781
- if (Array.isArray(src.required)) {
19782
- const properties = out.properties ?? {};
19783
- const required = [...new Set(src.required)].filter(
19784
- (r) => typeof r === "string" && r in properties
19785
- );
19786
- if (required.length > 0) out.required = required;
19787
- }
19788
- if (src.items !== void 0) {
19789
- out.items = sanitizeGeminiSchemaNode(src.items, seen);
19790
- if (typeof out.type !== "string") out.type = "array";
19791
- }
19792
- if (typeof out.type !== "string") out.type = "object";
19793
- return out;
19794
- }
19795
- function convertOpenAiToolsToGemini(tools) {
19796
- if (!Array.isArray(tools) || tools.length === 0) return void 0;
19797
- const declarations = [];
19798
- for (const tool of tools) {
19799
- if (!tool || typeof tool !== "object" || !("function" in tool)) continue;
19800
- const fn3 = tool.function;
19801
- const name = typeof fn3.name === "string" ? fn3.name : "";
19802
- if (!name) continue;
19803
- declarations.push({
19804
- name,
19805
- description: typeof fn3.description === "string" ? fn3.description : "",
19806
- parameters: sanitizeGeminiSchemaNode(
19807
- fn3.parameters ?? { type: "object", properties: {} }
19808
- )
19809
- });
19810
- }
19811
- if (declarations.length === 0) return void 0;
19812
- return [{ functionDeclarations: declarations }];
19813
- }
19814
- function createGeminiAccumulatorState() {
19815
- return {
19816
- text: "",
19817
- toolCalls: [],
19818
- reasoning: "",
19819
- events: []
19820
- };
19821
- }
19822
- function applyGeminiChunk(chunk, state, options) {
19823
- if (!chunk || typeof chunk !== "object") return;
19824
- const response = "response" in chunk && chunk.response && typeof chunk.response === "object" ? chunk.response : chunk;
19825
- if ("usageMetadata" in response && response.usageMetadata && typeof response.usageMetadata === "object") {
19826
- const meta = response.usageMetadata;
19827
- const prompt = typeof meta.promptTokenCount === "number" ? meta.promptTokenCount : 0;
19828
- const candidates2 = typeof meta.candidatesTokenCount === "number" ? meta.candidatesTokenCount : 0;
19829
- const total = typeof meta.totalTokenCount === "number" ? meta.totalTokenCount : prompt + candidates2;
19830
- const cached3 = typeof meta.cachedContentTokenCount === "number" && Number.isFinite(meta.cachedContentTokenCount) ? (
19831
- // cached ⊆ prompt;对畸形上游防御性钳位,避免 cache_read > input 破坏归一化与计费
19832
- Math.min(Math.max(0, Math.floor(meta.cachedContentTokenCount)), prompt)
19833
- ) : 0;
19834
- state.usage = {
19835
- prompt_tokens: prompt,
19836
- completion_tokens: candidates2,
19837
- total_tokens: total,
19838
- ...cached3 > 0 ? { cache_read_input_tokens: cached3 } : {}
19839
- };
19840
- state.events.push({ kind: "usage", usage: state.usage });
19841
- }
19842
- const candidates = Array.isArray(response.candidates) ? response.candidates : [];
19843
- for (const candidate of candidates) {
19844
- if (!candidate || typeof candidate !== "object") continue;
19845
- const candidateFinishReason = candidate.finishReason;
19846
- if (typeof candidateFinishReason === "string" && candidateFinishReason.trim()) {
19847
- state.finishReason = candidateFinishReason.trim();
19848
- state.events.push({ kind: "finish", reason: state.finishReason });
19849
- }
19850
- const parts2 = Array.isArray(
19851
- candidate.content?.parts
19852
- ) ? candidate.content.parts : [];
19853
- for (const part of parts2) {
19854
- if (!part || typeof part !== "object") continue;
19855
- const partSignature = part.thoughtSignature;
19856
- const isThought = Boolean(part.thought);
19857
- if ("text" in part && typeof part.text === "string") {
19858
- const piece = part.text;
19859
- if (!isThought) {
19860
- state.text += piece;
19861
- if (piece) {
19862
- state.events.push({ kind: "text", text: piece });
19863
- if (options?.onTextDelta) options.onTextDelta(piece);
19864
- }
19865
- } else {
19866
- state.reasoning += piece;
19867
- if (piece) {
19868
- state.events.push({ kind: "reasoning", text: piece });
19869
- if (options?.onReasoningDelta) options.onReasoningDelta(piece);
19870
- }
19871
- }
19872
- }
19873
- if (isThought && typeof partSignature === "string" && partSignature) {
19874
- state.pendingThoughtSignature = partSignature;
19875
- }
19876
- if ("functionCall" in part && part.functionCall) {
19877
- const call = part.functionCall;
19878
- const name = typeof call.name === "string" ? call.name : "tool";
19879
- const id = typeof call.id === "string" ? call.id : `${name}_${state.toolCalls.length}`;
19880
- const argsObj = call.args && typeof call.args === "object" ? call.args : {};
19881
- const resolvedSignature = typeof partSignature === "string" && partSignature ? partSignature : state.pendingThoughtSignature;
19882
- state.pendingThoughtSignature = void 0;
19883
- const toolCall = {
19884
- id,
19885
- type: "function",
19886
- function: { name, arguments: JSON.stringify(argsObj) },
19887
- ...typeof resolvedSignature === "string" && resolvedSignature ? { thought_signature: resolvedSignature } : {}
19888
- };
19889
- state.toolCalls.push(toolCall);
19890
- state.events.push({ kind: "tool_call", toolCall });
19891
- }
19892
- }
19893
- }
19894
- }
19895
- function accumulateGeminiChunks(chunks2, options) {
19896
- const state = createGeminiAccumulatorState();
19897
- for (const chunk of chunks2) {
19898
- applyGeminiChunk(chunk, state, options);
19899
- }
19900
- return {
19901
- text: state.text,
19902
- toolCalls: state.toolCalls,
19903
- usage: state.usage,
19904
- finishReason: state.finishReason,
19905
- reasoningContent: state.reasoning,
19906
- events: state.events
19907
- };
19908
- }
19909
- async function accumulateGeminiStream(stream, options) {
19910
- const state = createGeminiAccumulatorState();
19911
- for await (const chunk of stream) {
19912
- applyGeminiChunk(chunk, state, options);
19913
- }
19914
- return {
19915
- text: state.text,
19916
- toolCalls: state.toolCalls,
19917
- usage: state.usage,
19918
- finishReason: state.finishReason,
19919
- reasoningContent: state.reasoning,
19920
- events: state.events
19921
- };
19922
- }
19923
- function shouldUseGeminiNativeToolRoute(provider, model, tools, isImageModel) {
19924
- return provider === "google" && !isImageModel(model) && isGemini3Model(model) && Array.isArray(tools) && tools.length > 0;
19925
- }
19926
- function buildGeminiGenerateContentRequest(args2) {
19927
- const { contents, systemTexts } = convertOpenAiMessagesToGemini(
19928
- args2.messages,
19929
- { attachSkipThoughtSignature: args2.attachSkipThoughtSignature }
19930
- );
19931
- const request3 = { contents };
19932
- if (systemTexts.length > 0) {
19933
- request3.systemInstruction = {
19934
- role: "user",
19935
- parts: systemTexts.map((text) => ({ text }))
19936
- };
19937
- }
19938
- const toolsResult = convertOpenAiToolsToGemini(args2.tools);
19939
- if (toolsResult) {
19940
- request3.tools = toolsResult;
19941
- request3.toolConfig = { functionCallingConfig: { mode: "VALIDATED" } };
19942
- }
19943
- const generationConfig = {};
19944
- if (typeof args2.maxTokens === "number" && args2.maxTokens > 0) {
19945
- generationConfig.maxOutputTokens = args2.maxTokens;
19946
- }
19947
- if (typeof args2.temperature === "number") {
19948
- generationConfig.temperature = args2.temperature;
19949
- }
19950
- if (Object.keys(generationConfig).length > 0) {
19951
- request3.generationConfig = generationConfig;
19952
- }
19953
- return request3;
19954
- }
19955
-
19956
- // packages/agent-runtime/antigravityCloudCodeProvider.ts
19957
- var STREAM_PATH = "/v1internal:streamGenerateContent?alt=sse";
19958
- function buildCloudCodeAssistPayload(args2) {
19959
- const projectId = readAntigravityProjectId(args2.metadata);
19960
- if (!projectId) {
19961
- throw new Error(
19962
- "Antigravity OAuth credential is missing metadata.projectId. Re-run `nolo auth antigravity`."
19963
- );
19964
- }
19965
- const logicalModel = asOptionalTrimmedString(args2.openAiBody.model) ?? asOptionalTrimmedString(args2.agentConfig.model) ?? "gemini-3.1-pro";
19966
- const { wireModelId: model, profile } = resolveAntigravityWireModel(logicalModel);
19967
- const rawMessages = Array.isArray(args2.openAiBody.messages) ? args2.openAiBody.messages : [];
19968
- const quirks = resolveGeminiModelQuirks(model);
19969
- const isClaude = quirks.isClaudeCrossModel;
19970
- const { contents, systemTexts } = convertOpenAiMessagesToGemini(rawMessages, {
19971
- attachSkipThoughtSignature: quirks.allowsThoughtSignatureSentinel
19972
- });
19973
- if (contents.length === 0) {
19974
- throw new Error("Antigravity Cloud Code Assist request has no user/model contents.");
19975
- }
19976
- const prompt = args2.agentConfig.prompt?.trim();
19977
- if (prompt) systemTexts.unshift(prompt);
19978
- const request3 = { contents };
19979
- if (systemTexts.length > 0) {
19980
- request3.systemInstruction = {
19981
- role: "user",
19982
- parts: systemTexts.map((text) => ({ text }))
19983
- };
19984
- }
19985
- const tools = convertOpenAiToolsToGemini(
19986
- Array.isArray(args2.openAiBody.tools) ? args2.openAiBody.tools : void 0
19987
- );
19988
- if (tools) {
19989
- request3.tools = tools;
19990
- request3.toolConfig = { functionCallingConfig: { mode: "VALIDATED" } };
19991
- }
19992
- const generationConfig = {};
19993
- if (profile?.maxOutputTokens) {
19994
- generationConfig.maxOutputTokens = profile.maxOutputTokens;
19995
- } else if (typeof args2.agentConfig.max_tokens === "number" && args2.agentConfig.max_tokens > 0) {
19996
- generationConfig.maxOutputTokens = args2.agentConfig.max_tokens;
19997
- }
19998
- if (typeof args2.agentConfig.temperature === "number") {
19999
- generationConfig.temperature = args2.agentConfig.temperature;
20000
- }
20001
- if (Object.keys(generationConfig).length > 0) {
20002
- request3.generationConfig = generationConfig;
20003
- }
20004
- const agentId = randomUUID();
20005
- const trajectoryId = randomUUID();
20006
- const step = 2;
20007
- const requestId = `agent/${agentId}/${Date.now()}/${trajectoryId}/${step}`;
20008
- const labels = {
20009
- trajectory_id: trajectoryId,
20010
- last_step_index: String(step - 1),
20011
- used_claude: String(isClaude),
20012
- used_claude_conservative: String(isClaude)
20013
- };
20014
- if (profile?.modelEnum) {
20015
- labels.model_enum = profile.modelEnum;
20016
- }
20017
- request3.labels = labels;
20018
- request3.sessionId = `-${Math.floor(Math.random() * 9e15)}`;
20019
- return {
20020
- url: `${resolveAntigravityCloudCodeBaseUrl(args2.agentConfig.customProviderUrl)}${STREAM_PATH}`,
20021
- envelope: {
20022
- project: projectId,
20023
- model,
20024
- request: request3,
20025
- requestId,
20026
- requestType: "agent",
20027
- userAgent: "antigravity"
20028
- }
20029
- };
20030
- }
20031
- function resolveAntigravityFinishReason(upstream) {
20032
- const normalized = (upstream ?? "").trim().toUpperCase();
20033
- if (normalized === "MAX_TOKENS") return "length";
20034
- if (normalized === "SAFETY" || normalized === "RECITATION" || normalized === "PROHIBITED_CONTENT" || normalized === "BLOCKLIST") {
20035
- return "content_filter";
20036
- }
20037
- return "stop";
20038
- }
20039
- function normalizeAntigravityCompletion(decoded) {
20040
- const { text, toolCalls, usage, finishReason, reasoningContent, events } = decoded;
20041
- const normalizedFinishReason = (finishReason ?? "").trim().toUpperCase();
20042
- if (!text && !reasoningContent && toolCalls.length === 0 && !finishReason && !usage) {
20043
- const body = {
20044
- error: {
20045
- message: "antigravity upstream returned an empty stream (no content, finishReason or usage); channel degradation, not model emptiness"
20046
- },
20047
- provider_events: events
20048
- };
20049
- return {
20050
- status: 502,
20051
- text: "",
20052
- toolCalls: [],
20053
- providerEvents: events,
20054
- body
20055
- };
20056
- }
20057
- if (normalizedFinishReason === "MALFORMED_FUNCTION_CALL") {
20058
- const message2 = "antigravity upstream returned MALFORMED_FUNCTION_CALL (model generated an invalid function call or tool arguments were rejected by upstream)";
20059
- const providerFailure = {
20060
- kind: "provider_failure",
20061
- provider: "antigravity",
20062
- code: "MALFORMED_FUNCTION_CALL",
20063
- message: message2,
20064
- providerReason: "MALFORMED_FUNCTION_CALL",
20065
- retryable: false,
20066
- status: 502
20067
- };
20068
- return {
20069
- status: 502,
20070
- text: "",
20071
- toolCalls: [],
20072
- finishReason,
20073
- providerEvents: events,
20074
- providerFailure,
20075
- runtimeProviderFailure: {
20076
- message: providerFailure.message,
20077
- retryable: providerFailure.retryable
20078
- },
20079
- body: {
20080
- error: {
20081
- message: message2,
20082
- code: "MALFORMED_FUNCTION_CALL",
20083
- category: "malformed_function_call",
20084
- providerReason: "MALFORMED_FUNCTION_CALL",
20085
- retryable: false
20086
- },
20087
- provider_failure: providerFailure,
20088
- provider_events: events
20089
- }
20090
- };
20091
- }
20092
- const message = {
20093
- role: "assistant",
20094
- content: text || null
20095
- };
20096
- if (toolCalls.length > 0) message.tool_calls = toolCalls;
20097
- if (reasoningContent) message.reasoning_content = reasoningContent;
20098
- return {
20099
- status: 200,
20100
- text,
20101
- ...reasoningContent ? { reasoningContent } : {},
20102
- toolCalls,
20103
- ...usage ? { usage } : {},
20104
- ...finishReason ? { finishReason } : {},
20105
- providerEvents: events,
20106
- body: {
20107
- choices: [{
20108
- index: 0,
20109
- message,
20110
- finish_reason: toolCalls.length > 0 ? "tool_calls" : resolveAntigravityFinishReason(finishReason)
20111
- }],
20112
- ...usage ? { usage } : {},
20113
- provider_events: events
20114
- }
20115
- };
20116
- }
20117
- async function fetchAntigravityCloudCodeCompletion(args2) {
20118
- const fetchImpl = args2.fetchImpl ?? fetch;
20119
- const { url, envelope } = buildCloudCodeAssistPayload(args2);
20120
- const response = await fetchImpl(url, {
20121
- method: "POST",
20122
- headers: {
20123
- Authorization: `Bearer ${args2.accessToken}`,
20124
- "Content-Type": "application/json",
20125
- "User-Agent": getAntigravityUserAgent()
20126
- },
20127
- body: JSON.stringify(envelope),
20128
- signal: args2.signal
20129
- });
20130
- if (!response.ok) {
20131
- const errorText = await response.text();
20132
- return {
20133
- status: response.status,
20134
- text: "",
20135
- toolCalls: [],
20136
- providerEvents: [],
20137
- // 保留上游结构:Antigravity 的限流 body 同样携带结构化的重置信息,
20138
- // 压成字符串会让冷却退化成 5 分钟默认值。
20139
- body: parseUpstreamErrorBody(errorText, response.statusText)
20140
- };
20141
- }
20142
- const chunkStream = streamSseDataValues(response, parseSseDataLineJson);
20143
- const { text, toolCalls, usage, finishReason, reasoningContent, events } = await accumulateGeminiStream(chunkStream, {
20144
- onTextDelta: args2.onTextDelta,
20145
- onReasoningDelta: args2.onReasoningDelta
20146
- });
20147
- const normalized = normalizeAntigravityCompletion({
20148
- text,
20149
- toolCalls,
20150
- usage,
20151
- finishReason,
20152
- reasoningContent,
20153
- events
20154
- });
20155
- if (normalized.status !== 200 || !text && toolCalls.length === 0) {
20156
- console.warn(
20157
- `[antigravity] empty completion: finishReason=${JSON.stringify(finishReason)} reasoningLen=${reasoningContent?.length ?? 0} usage=${JSON.stringify(usage) ?? "none"} model=${String(envelope.model)} base=${new URL(url).host}`
20158
- );
20159
- }
20160
- return normalized;
20161
- }
20162
-
20163
20032
  // packages/agent-runtime/devin/devinProvider.ts
20164
- import { randomBytes, randomUUID as randomUUID2 } from "node:crypto";
20033
+ import { randomBytes, randomUUID } from "node:crypto";
20165
20034
  var CLIENT_NAME = "chisel";
20166
20035
  var CLIENT_VERSION = "2026.8.18";
20167
20036
  function writeVarint(fieldNum, value) {
@@ -20221,11 +20090,33 @@ function encodeDevinClientMetadata(token) {
20221
20090
  ]);
20222
20091
  }
20223
20092
  function encodeDevinChatMessage(msg) {
20224
- const source = msg.role === "assistant" ? 2 : 1;
20093
+ const source = msg.role === "assistant" ? 2 : msg.role === "tool" ? 4 : 1;
20225
20094
  return Buffer.concat([
20226
- writeStringField(1, randomUUID2()),
20095
+ writeStringField(1, randomUUID()),
20227
20096
  writeVarint(2, source),
20228
- writeStringField(3, msg.text)
20097
+ writeStringField(3, msg.text),
20098
+ ...(msg.toolCalls ?? []).map(
20099
+ (toolCall) => writeMessageField(
20100
+ 6,
20101
+ Buffer.concat([
20102
+ writeStringField(1, toolCall.id),
20103
+ writeStringField(2, toolCall.function.name),
20104
+ writeStringField(3, toolCall.function.arguments)
20105
+ ])
20106
+ )
20107
+ ),
20108
+ ...msg.toolCallId ? [writeStringField(7, msg.toolCallId)] : []
20109
+ ]);
20110
+ }
20111
+ function encodeDevinToolDefinition(tool) {
20112
+ const fn3 = tool.function ?? {};
20113
+ const name = typeof fn3.name === "string" ? fn3.name : "";
20114
+ const description = typeof fn3.description === "string" ? fn3.description : "";
20115
+ const parameters = fn3.parameters ?? {};
20116
+ return Buffer.concat([
20117
+ writeStringField(1, name),
20118
+ writeStringField(2, description.length > 6998 ? `${description.slice(0, 6995)}...` : description),
20119
+ writeStringField(3, JSON.stringify(parameters))
20229
20120
  ]);
20230
20121
  }
20231
20122
  function writeFixed64Field(fieldNum, val) {
@@ -20242,20 +20133,21 @@ function writeFixed64Field(fieldNum, val) {
20242
20133
  return Buffer.concat([Buffer.from(tagBytes), b]);
20243
20134
  }
20244
20135
  function buildCompletionConfig(params) {
20245
- const maxTokens = params?.maxTokens ?? 4096;
20246
- const temp = params?.temperature ?? 0.4;
20136
+ const maxTokens = params?.maxTokens ?? 128e3;
20137
+ const maxNewlines = params?.maxNewlines ?? 400;
20138
+ const temp = params?.temperature ?? 1;
20247
20139
  const topP = params?.topP ?? 0.95;
20248
20140
  return Buffer.concat([
20249
20141
  writeVarint(1, 1),
20250
20142
  writeVarint(2, maxTokens),
20251
- writeVarint(3, 128e3),
20143
+ writeVarint(3, maxNewlines),
20252
20144
  writeFixed64Field(5, temp),
20253
20145
  writeVarint(7, 40),
20254
20146
  writeFixed64Field(8, topP)
20255
20147
  ]);
20256
20148
  }
20257
20149
  function buildGetChatMessageRequest(params) {
20258
- const { token, messages, model } = params;
20150
+ const { token, messages, model, tools = [], temperature } = params;
20259
20151
  const metadata = encodeDevinClientMetadata(token);
20260
20152
  let systemPrompt = "";
20261
20153
  const filteredMessages = [];
@@ -20265,14 +20157,19 @@ function buildGetChatMessageRequest(params) {
20265
20157
  systemPrompt = systemPrompt ? `${systemPrompt}
20266
20158
 
20267
20159
  ${text}` : text;
20268
- } else if (m.role === "user" || m.role === "assistant") {
20269
- filteredMessages.push({ role: m.role, text });
20160
+ } else if (m.role === "user" || m.role === "assistant" || m.role === "tool") {
20161
+ filteredMessages.push({
20162
+ role: m.role,
20163
+ text,
20164
+ ...m.tool_call_id ? { toolCallId: m.tool_call_id } : {},
20165
+ ...m.tool_calls?.length ? { toolCalls: m.tool_calls } : {}
20166
+ });
20270
20167
  }
20271
20168
  }
20272
20169
  const coalesced = [];
20273
20170
  for (const item of filteredMessages) {
20274
20171
  const last = coalesced[coalesced.length - 1];
20275
- if (last && last.role === item.role) {
20172
+ if (last && last.role === item.role && item.role !== "tool" && !last.toolCalls?.length && !item.toolCalls?.length) {
20276
20173
  last.text = `${last.text}
20277
20174
 
20278
20175
  ${item.text}`;
@@ -20281,9 +20178,9 @@ ${item.text}`;
20281
20178
  }
20282
20179
  }
20283
20180
  const chatMessageBuffers = coalesced.map((m) => encodeDevinChatMessage(m));
20284
- const completionConfig = buildCompletionConfig();
20181
+ const completionConfig = buildCompletionConfig({ temperature });
20285
20182
  const modelConfig = Buffer.concat([
20286
- writeStringField(1, randomUUID2()),
20183
+ writeStringField(1, randomUUID()),
20287
20184
  writeVarint(2, 1),
20288
20185
  writeVarint(3, 4)
20289
20186
  ]);
@@ -20293,8 +20190,9 @@ ${item.text}`;
20293
20190
  ...chatMessageBuffers.map((b) => writeMessageField(3, b)),
20294
20191
  writeVarint(7, 5),
20295
20192
  writeMessageField(8, completionConfig),
20193
+ ...tools.map((tool) => writeMessageField(10, encodeDevinToolDefinition(tool))),
20296
20194
  writeMessageField(15, modelConfig),
20297
- writeStringField(16, randomUUID2()),
20195
+ writeStringField(16, randomUUID()),
20298
20196
  writeVarint(20, 1),
20299
20197
  writeStringField(21, model)
20300
20198
  ]);
@@ -20323,6 +20221,77 @@ function parseDevinConnectFrames(buffer3) {
20323
20221
  remainder: buffer3.subarray(offset)
20324
20222
  };
20325
20223
  }
20224
+ function parseDevinMetadataUsage(metaBytes) {
20225
+ let prompt = 0;
20226
+ let completion;
20227
+ let cacheRead = 0;
20228
+ let cacheWrite = 0;
20229
+ let sawAny = false;
20230
+ let offset = 0;
20231
+ while (offset < metaBytes.length) {
20232
+ let tag = 0;
20233
+ let shift = 0;
20234
+ while (offset < metaBytes.length) {
20235
+ const b = metaBytes[offset++];
20236
+ tag |= (b & 127) << shift;
20237
+ if (!(b & 128)) break;
20238
+ shift += 7;
20239
+ }
20240
+ const fieldNum = tag >>> 3;
20241
+ const wireType = tag & 7;
20242
+ if (wireType === 0) {
20243
+ let val = 0;
20244
+ let s = 0;
20245
+ while (offset < metaBytes.length) {
20246
+ const b = metaBytes[offset++];
20247
+ val |= (b & 127) << s;
20248
+ if (!(b & 128)) break;
20249
+ s += 7;
20250
+ }
20251
+ if (fieldNum === 2) {
20252
+ prompt = val;
20253
+ sawAny = true;
20254
+ } else if (fieldNum === 3) {
20255
+ completion = val;
20256
+ sawAny = true;
20257
+ } else if (fieldNum === 4) {
20258
+ cacheWrite = val;
20259
+ sawAny = true;
20260
+ } else if (fieldNum === 5) {
20261
+ cacheRead = val;
20262
+ sawAny = true;
20263
+ }
20264
+ } else if (wireType === 2) {
20265
+ let len = 0;
20266
+ let s = 0;
20267
+ while (offset < metaBytes.length) {
20268
+ const b = metaBytes[offset++];
20269
+ len |= (b & 127) << s;
20270
+ if (!(b & 128)) break;
20271
+ s += 7;
20272
+ }
20273
+ offset += len;
20274
+ } else {
20275
+ break;
20276
+ }
20277
+ }
20278
+ return sawAny ? { prompt, completion, cacheRead, cacheWrite } : null;
20279
+ }
20280
+ function attachDevinUpstreamFailure(error, failure) {
20281
+ if (failure) {
20282
+ error.devinUpstreamFailure = failure;
20283
+ }
20284
+ return error;
20285
+ }
20286
+ function readDevinUpstreamFailure(error) {
20287
+ if (!error || typeof error !== "object") return void 0;
20288
+ const failure = error.devinUpstreamFailure;
20289
+ if (!failure || typeof failure !== "object") return void 0;
20290
+ if (typeof failure.status !== "number" || !Number.isFinite(failure.status)) {
20291
+ return void 0;
20292
+ }
20293
+ return failure;
20294
+ }
20326
20295
  function parseDevinConnectTrailer(payload) {
20327
20296
  const text = payload.toString("utf8").trim();
20328
20297
  if (!text || text === "{}") return null;
@@ -20333,7 +20302,11 @@ function parseDevinConnectTrailer(payload) {
20333
20302
  }
20334
20303
  const code = typeof parsed.error.code === "string" ? parsed.error.code : "unknown";
20335
20304
  const message = typeof parsed.error.message === "string" ? parsed.error.message : "Unknown error";
20336
- return new Error(`Devin Connect upstream error ${code}: ${message}`);
20305
+ return attachDevinUpstreamFailure(
20306
+ new Error(`Devin Connect upstream error ${code}: ${message}`),
20307
+ // Connect/gRPC 的 resource_exhausted 与 HTTP 429 同义(限流/配额耗尽)。
20308
+ code === "resource_exhausted" ? { status: 429, body: parsed.error } : void 0
20309
+ );
20337
20310
  } catch {
20338
20311
  return new Error(`Devin Connect invalid terminal trailer: ${text}`);
20339
20312
  }
@@ -20343,6 +20316,7 @@ function decodeDevinResponsePayload(payload) {
20343
20316
  let content = "";
20344
20317
  let reasoning = "";
20345
20318
  let finishReason = null;
20319
+ let usage = null;
20346
20320
  const events = [];
20347
20321
  while (offset < payload.length) {
20348
20322
  let tag = 0;
@@ -20365,7 +20339,7 @@ function decodeDevinResponsePayload(payload) {
20365
20339
  s += 7;
20366
20340
  }
20367
20341
  if (fieldNum === 5) {
20368
- finishReason = val === 2 ? "stop" : "length";
20342
+ finishReason = val === 10 ? "tool_calls" : val === 1 || val === 3 ? "length" : "stop";
20369
20343
  }
20370
20344
  } else if (wireType === 2) {
20371
20345
  let len = 0;
@@ -20386,15 +20360,55 @@ function decodeDevinResponsePayload(payload) {
20386
20360
  const text = valBytes.toString("utf8");
20387
20361
  reasoning += text;
20388
20362
  events.push({ type: "reasoning", text });
20363
+ } else if (fieldNum === 6) {
20364
+ let nestedOffset = 0;
20365
+ let id;
20366
+ let name;
20367
+ let argsDelta;
20368
+ while (nestedOffset < valBytes.length) {
20369
+ let nestedTag = 0;
20370
+ let nestedShift = 0;
20371
+ while (nestedOffset < valBytes.length) {
20372
+ const b = valBytes[nestedOffset++];
20373
+ nestedTag |= (b & 127) << nestedShift;
20374
+ if (!(b & 128)) break;
20375
+ nestedShift += 7;
20376
+ }
20377
+ const nestedField = nestedTag >>> 3;
20378
+ const nestedWire = nestedTag & 7;
20379
+ if (nestedWire !== 2) break;
20380
+ let nestedLength = 0;
20381
+ let lengthShift = 0;
20382
+ while (nestedOffset < valBytes.length) {
20383
+ const b = valBytes[nestedOffset++];
20384
+ nestedLength |= (b & 127) << lengthShift;
20385
+ if (!(b & 128)) break;
20386
+ lengthShift += 7;
20387
+ }
20388
+ const nestedValue = valBytes.subarray(nestedOffset, nestedOffset + nestedLength);
20389
+ nestedOffset += nestedLength;
20390
+ const text = nestedValue.toString("utf8");
20391
+ if (nestedField === 1) id = text;
20392
+ else if (nestedField === 2) name = text;
20393
+ else if (nestedField === 3) argsDelta = (argsDelta ?? "") + text;
20394
+ }
20395
+ if (id !== void 0 && name !== void 0) {
20396
+ events.push({ type: "tool-call-start", id, name });
20397
+ }
20398
+ if (argsDelta !== void 0) {
20399
+ events.push({ type: "tool-call-args", argsDelta, ...id ? { id } : {} });
20400
+ }
20401
+ } else if (fieldNum === 7) {
20402
+ usage = parseDevinMetadataUsage(valBytes);
20389
20403
  }
20390
20404
  } else {
20391
20405
  break;
20392
20406
  }
20393
20407
  }
20394
- return { content, reasoning, finishReason, events };
20408
+ return { content, reasoning, finishReason, events, usage };
20395
20409
  }
20396
20410
  function createDevinProvider(options) {
20397
- const { token, model = "swe-2", fetchImpl = fetch } = options;
20411
+ const { token, model = "swe-2-max", fetchImpl = fetch, tools = [], temperature } = options;
20398
20412
  return {
20399
20413
  model,
20400
20414
  async complete(messages, opts) {
@@ -20402,7 +20416,9 @@ function createDevinProvider(options) {
20402
20416
  const requestPayload = buildGetChatMessageRequest({
20403
20417
  token,
20404
20418
  messages,
20405
- model: selectedModel
20419
+ model: selectedModel,
20420
+ tools,
20421
+ temperature
20406
20422
  });
20407
20423
  const frame = buildDevinConnectFrame(requestPayload);
20408
20424
  const response = await fetchImpl(DEVIN_CONNECT_URL, {
@@ -20417,8 +20433,11 @@ function createDevinProvider(options) {
20417
20433
  });
20418
20434
  if (!response.ok) {
20419
20435
  const errText = await response.text().catch(() => "");
20420
- throw new Error(
20421
- `Devin Connect upstream returned error ${response.status}: ${errText}`
20436
+ throw attachDevinUpstreamFailure(
20437
+ new Error(
20438
+ `Devin Connect upstream returned error ${response.status}: ${errText}`
20439
+ ),
20440
+ { status: response.status, body: errText }
20422
20441
  );
20423
20442
  }
20424
20443
  if (!response.body) {
@@ -20426,6 +20445,11 @@ function createDevinProvider(options) {
20426
20445
  }
20427
20446
  let text = "";
20428
20447
  let reasoning = "";
20448
+ let finishReason = null;
20449
+ let frameUsage = null;
20450
+ const toolCallOrder = [];
20451
+ const toolCallById = /* @__PURE__ */ new Map();
20452
+ let currentToolCallId = null;
20429
20453
  let buffer3 = Buffer.alloc(0);
20430
20454
  let sawTrailer = false;
20431
20455
  const reader = response.body.getReader();
@@ -20447,13 +20471,34 @@ function createDevinProvider(options) {
20447
20471
  continue;
20448
20472
  }
20449
20473
  const delta = decodeDevinResponsePayload(f.payload);
20474
+ if (delta.usage?.completion !== void 0) {
20475
+ frameUsage = delta.usage;
20476
+ }
20450
20477
  text += delta.content;
20451
20478
  reasoning += delta.reasoning;
20479
+ if (delta.finishReason) finishReason = delta.finishReason;
20452
20480
  for (const event of delta.events) {
20453
20481
  if (event.type === "reasoning") {
20454
20482
  opts?.onReasoningDelta?.(event.text);
20455
- } else {
20483
+ } else if (event.type === "content") {
20456
20484
  opts?.onTextDelta?.(event.text);
20485
+ } else if (event.type === "tool-call-start") {
20486
+ const existing = toolCallById.get(event.id);
20487
+ if (existing) {
20488
+ existing.name = event.name;
20489
+ } else {
20490
+ toolCallOrder.push(event.id);
20491
+ toolCallById.set(event.id, {
20492
+ id: event.id,
20493
+ name: event.name,
20494
+ argumentsText: ""
20495
+ });
20496
+ }
20497
+ currentToolCallId = event.id;
20498
+ } else {
20499
+ const targetId = event.id ?? currentToolCallId;
20500
+ const target = targetId ? toolCallById.get(targetId) : void 0;
20501
+ if (target) target.argumentsText += event.argsDelta;
20457
20502
  }
20458
20503
  }
20459
20504
  }
@@ -20467,14 +20512,42 @@ function createDevinProvider(options) {
20467
20512
  if (!sawTrailer) {
20468
20513
  throw new Error("Devin Connect upstream ended before the terminal trailer");
20469
20514
  }
20470
- if (!text && !reasoning) {
20471
- throw new Error("Devin Connect upstream completed without content");
20515
+ const toolCalls = toolCallOrder.map((id) => {
20516
+ const call = toolCallById.get(id);
20517
+ return {
20518
+ id: call.id,
20519
+ type: "function",
20520
+ function: {
20521
+ name: call.name,
20522
+ arguments: call.argumentsText || "{}"
20523
+ }
20524
+ };
20525
+ });
20526
+ if (!text && !reasoning && toolCalls.length === 0) {
20527
+ throw new Error("Devin Connect upstream completed without content or tool calls");
20472
20528
  }
20529
+ const usage = frameUsage ? {
20530
+ prompt_tokens: frameUsage.prompt + frameUsage.cacheRead,
20531
+ completion_tokens: frameUsage.completion ?? 0,
20532
+ total_tokens: frameUsage.prompt + frameUsage.cacheRead + (frameUsage.completion ?? 0) + frameUsage.cacheWrite,
20533
+ ...frameUsage.cacheRead > 0 ? { prompt_tokens_details: { cached_tokens: frameUsage.cacheRead } } : {},
20534
+ ...frameUsage.cacheWrite > 0 ? { cache_creation_input_tokens: frameUsage.cacheWrite } : {}
20535
+ } : void 0;
20473
20536
  return {
20474
20537
  content: text,
20475
20538
  model: selectedModel,
20476
20539
  provider: "devin",
20477
- reasoning_content: reasoning || void 0
20540
+ ...usage ? { usage } : {},
20541
+ ...reasoning ? { reasoning_content: reasoning } : {},
20542
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {},
20543
+ // The upstream finish event wins whenever it is present. A tool call
20544
+ // cut off by the length limit has to stay "length" instead of being
20545
+ // relabelled "tool_calls", so probes/telemetry and any downstream
20546
+ // consumer of the final finish_reason no longer see a truncated turn
20547
+ // mislabelled as a normal one. localLoop keeps its own truncation
20548
+ // gates and excludes tool-bearing turns from them.
20549
+ finish_reason: finishReason ?? (toolCalls.length > 0 ? "tool_calls" : "stop"),
20550
+ stream_complete: true
20478
20551
  };
20479
20552
  }
20480
20553
  };
@@ -20493,8 +20566,6 @@ export {
20493
20566
  LOCAL_AGENT_CONFIG_MISSING_CODE,
20494
20567
  LOCAL_TURN_ABORTED_CODE,
20495
20568
  runLocalAgentTurn,
20496
- parseSseDataLineJson,
20497
- parseSseDataLineObject,
20498
20569
  codexStreamFailureStatus,
20499
20570
  aggregateResponsesStream,
20500
20571
  buildOpenAiCompatibleChatCompletionRequest,
@@ -20510,8 +20581,6 @@ export {
20510
20581
  createHybridRecordStore,
20511
20582
  isDestructiveShellCommand,
20512
20583
  evaluateShellCommandPolicy,
20513
- resolveLocalToolPolicy,
20514
- executeLocalToolWithPolicy,
20515
20584
  filterRetainedLedgerRecords,
20516
20585
  isLineRangeCovered,
20517
20586
  getProcessRegistry,
@@ -20522,16 +20591,14 @@ export {
20522
20591
  internalSearchWorkspace,
20523
20592
  internalListWorkspaceEntries,
20524
20593
  createLocalWorkspaceToolExecutors,
20594
+ buildToolParametersIndex,
20595
+ getAgentRuntimeToolParametersIndex,
20596
+ resolveLocalToolPolicy,
20597
+ executeLocalToolWithPolicy,
20525
20598
  localDialogMessageRecordToRuntimeMessage,
20526
20599
  buildLocalDialogWritePlan,
20527
20600
  buildNoloWorkspaceCliToolExecutors,
20528
20601
  buildLoadSkillExecutor,
20529
- parseUpstreamErrorBody,
20530
- isGemini3Model,
20531
- accumulateGeminiChunks,
20532
- shouldUseGeminiNativeToolRoute,
20533
- buildGeminiGenerateContentRequest,
20534
- resolveAntigravityFinishReason,
20535
- fetchAntigravityCloudCodeCompletion,
20602
+ readDevinUpstreamFailure,
20536
20603
  createDevinProvider
20537
20604
  };