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.
- package/README.md +2 -2
- package/{_executeModel-IULR47HG.js → _executeModel-BPUR2QYH.js} +51 -50
- package/{action-D22U6CTH.js → action-P4TNFGAD.js} +11 -11
- package/{addContentAction-XAPUTGL4.js → addContentAction-MB2HHMDZ.js} +9 -9
- package/{addDialogAgentAction-M7VLLBZM.js → addDialogAgentAction-S2NRBCWW.js} +9 -9
- package/{agentDeleteCommand-2S4WCDQC.js → agentDeleteCommand-MNAA6MIG.js} +32 -30
- package/{agentEmailCommands-V3ADGHX7.js → agentEmailCommands-SK34WJKU.js} +70 -66
- package/{agentGrantCommands-R3VQEZJR.js → agentGrantCommands-M3XFO6EN.js} +2 -2
- package/{agentListCommands-EPK3S274.js → agentListCommands-I26MKI4T.js} +55 -36
- package/{agentMachineCommands-XMZWK7P3.js → agentMachineCommands-M6QDR6P3.js} +32 -30
- package/{agentProviderCommands-65ZC2WB4.js → agentProviderCommands-X3UNW77C.js} +87 -0
- package/{agentPullCommand-N7L2CT7L.js → agentPullCommand-O5MPVJ5E.js} +3 -3
- package/agentRecordCommands-HTHLO2MM.js +129 -0
- package/{agentRunCommand-VUWNWC7C.js → agentRunCommand-IAIRCMTH.js} +70 -65
- package/{agentRunControl-T44MESII.js → agentRunControl-AVJR4JW4.js} +2 -2
- package/{agentRuntimeLocal-YRONTDLM.js → agentRuntimeLocal-3WTYYIT2.js} +32 -26
- package/{agentSlice-2P7OJFES.js → agentSlice-TVP3HINW.js} +10 -10
- package/{appDeleteCommands-YS7UGMP3.js → appDeleteCommands-SB5TNLKY.js} +32 -30
- package/{appDeployCommands-XIE5Z4BE.js → appDeployCommands-2IPKZINV.js} +32 -30
- package/{appDeployStatusCommands-54UDVYET.js → appDeployStatusCommands-K63TFUID.js} +32 -30
- package/{appGetCommands-6XYLBCRN.js → appGetCommands-VU2SLNEH.js} +32 -30
- package/{appListCommands-VEGH7SF5.js → appListCommands-S3T4DJ36.js} +32 -30
- package/{authCommand-JIEOFHXW.js → authCommand-HGP2XTKC.js} +185 -28
- package/{authCommands-ILTBI6JV.js → authCommands-RGOGCPP6.js} +4 -5
- package/builtinSkillRegistry-Q2VVY3SC.js +33 -0
- package/{chatQueueReduxAdapter-GPYCH5Z2.js → chatQueueReduxAdapter-TWIYKVIZ.js} +25 -25
- package/{chromeCommands-VOIRNJBL.js → chromeCommands-FZYAU75D.js} +1 -1
- package/{chromeOpsCommands-M7DPCX2T.js → chromeOpsCommands-62P2Q2YB.js} +31 -1
- package/{chunk-C3FZR5EC.js → chunk-2F7RJC6L.js} +1 -1
- package/{chunk-BOYUESO7.js → chunk-3AMA42ZI.js} +1 -1
- package/{chunk-RZOJRTKR.js → chunk-3BGIKIPF.js} +444 -920
- package/{chunk-HQUORJTM.js → chunk-3BMYXEUW.js} +1 -1
- package/{chunk-EBKMQULC.js → chunk-3IUWIAHP.js} +13 -15
- package/{chunk-NTW5GWDM.js → chunk-3VNWWVKB.js} +2 -0
- package/{chunk-4SZ37KLL.js → chunk-3VUWPRFW.js} +50 -30
- package/{chunk-WL2RKIBF.js → chunk-3YIAR5UI.js} +1 -1
- package/{chunk-DWUCB2A3.js → chunk-42V6BK2E.js} +1 -1
- package/{chunk-AX7RMXSH.js → chunk-4MK3I7Y4.js} +38 -4
- package/{chunk-PF6O3GR3.js → chunk-52Q2IW2X.js} +1340 -1273
- package/{chunk-Y3NZYLPG.js → chunk-5MEYHVIB.js} +1 -1
- package/{chunk-5XTOKQLW.js → chunk-5UTFL4QJ.js} +6 -2
- package/chunk-5ZUL4CUO.js +10 -0
- package/{chunk-6YTOJY35.js → chunk-6D2JFXLY.js} +35 -0
- package/{chunk-E6RGHPDR.js → chunk-6D6VBVEW.js} +4 -6
- package/{chunk-FZGV4FGK.js → chunk-6GFOD3TK.js} +260 -140
- package/{chunk-TMPLPCVX.js → chunk-6OZ36FXI.js} +2 -2
- package/{chunk-PZM3EENV.js → chunk-6Z7MY3V7.js} +1 -1
- package/{chunk-BYEJ7K2Z.js → chunk-7S4YWN7P.js} +1 -1
- package/{chunk-SREASRV5.js → chunk-ABQYVJBJ.js} +1 -1
- package/chunk-ACTMNVHM.js +93 -0
- package/{chunk-ZZCJUXUI.js → chunk-AHAHBQMU.js} +2 -2
- package/{chunk-BJCWNHVU.js → chunk-BFDFRLLQ.js} +2 -2
- package/chunk-BMQ2MJUK.js +153 -0
- package/chunk-BNGW5ASC.js +344 -0
- package/{chunk-2KDR4CWE.js → chunk-BYMV6QZG.js} +24 -1
- package/{chunk-73BR2IQT.js → chunk-CWQINZEA.js} +127 -7
- package/{chunk-HTMVBPOF.js → chunk-DD6XE56O.js} +19 -72
- package/{chunk-CADAR6QU.js → chunk-EYAQT56M.js} +4 -3
- package/{chunk-CJCJF5XB.js → chunk-FDTR4QJ6.js} +4 -2
- package/{chunk-D6XYLSOS.js → chunk-FKNWP6SG.js} +1 -1
- package/{chunk-3VPDELAP.js → chunk-FP76KI2Y.js} +1500 -84
- package/chunk-FV3JEYR3.js +870 -0
- package/{chunk-GCJ3IS5M.js → chunk-GIVFXPWK.js} +177 -6
- package/chunk-GMZLP5DV.js +82 -0
- package/{chunk-LHDV2QUI.js → chunk-GNCCNNZD.js} +4 -2
- package/{chunk-XWNRAIUL.js → chunk-GP5LKBUZ.js} +6 -16
- package/{chunk-PZCTYKGU.js → chunk-GQBOR3G6.js} +2 -2
- package/{chunk-NFT2AOK5.js → chunk-H4AHY5BU.js} +13 -85
- package/{chunk-FNGAF7C4.js → chunk-HAFJ3NDE.js} +137 -12
- package/{chunk-ZVN6I33J.js → chunk-HCT7U36O.js} +9 -5
- package/{chunk-7QVPPOEC.js → chunk-HKNXJGEC.js} +43 -43
- package/{chunk-3JQ2NRYU.js → chunk-HVURBGF3.js} +4 -4
- package/{chunk-OZX2IPKM.js → chunk-HYJFRTLW.js} +2 -2
- package/{chunk-HH65VA5U.js → chunk-I3TQJXQ7.js} +109 -6
- package/{chunk-UDQWGU7F.js → chunk-J4MOO56S.js} +1 -1
- package/{chunk-KC5FAHVS.js → chunk-JEDZAO7L.js} +1 -1
- package/{chunk-6XIE5ZGI.js → chunk-JPGCXHNM.js} +1 -1
- package/{chunk-DUJWWUGV.js → chunk-K36IXSCQ.js} +1 -1
- package/{chunk-KRMWO2NN.js → chunk-KAUSRTO6.js} +3 -3
- package/{chunk-7FWPLK4M.js → chunk-LJ5VW2UJ.js} +1 -1
- package/{chunk-GHEJJGJI.js → chunk-LMREEBWT.js} +833 -109
- package/{chunk-RXX4GDIH.js → chunk-LS2ZANRJ.js} +1 -1
- package/chunk-LVIFM7VW.js +1625 -0
- package/{chunk-BJIRLHJR.js → chunk-M2EJHDJP.js} +4 -0
- package/{chunk-EVBFYPDC.js → chunk-MC4XLIJ7.js} +6 -2
- package/{chunk-KEZNSFGD.js → chunk-MZYBSTMH.js} +11 -173
- package/chunk-OCZKATRC.js +129 -0
- package/{chunk-MXUSPS2N.js → chunk-OIVSQH2O.js} +1510 -1592
- package/{chunk-E36HZARC.js → chunk-P5EVSOPA.js} +93 -17
- package/{chunk-SZLVDREM.js → chunk-PBA4UJBK.js} +3 -3
- package/{chunk-URWDSZRW.js → chunk-PMAIQX7A.js} +3 -3
- package/{chunk-AETZ7AD6.js → chunk-Q554LLSI.js} +1 -1
- package/{chunk-AJHEFRGJ.js → chunk-Q67DOFOO.js} +214 -5
- package/{chunk-UZ6XXCVR.js → chunk-QDSINZM3.js} +15 -2
- package/{chunk-NRALD3CC.js → chunk-QSHEEA5N.js} +54 -10
- package/chunk-QU7KDSZX.js +677 -0
- package/{chunk-T25L377S.js → chunk-RPJUFEGR.js} +10 -10
- package/{chunk-QCZX4FTW.js → chunk-SH43IESU.js} +173 -15
- package/{chunk-SPAQ4PVV.js → chunk-SV5IJK6K.js} +1 -1
- package/{chunk-BKJO7LDP.js → chunk-U4OKDZFH.js} +6 -6
- package/{chunk-DM4FBSWG.js → chunk-UDVH2P4A.js} +7 -7
- package/{chunk-4EJ5QYB5.js → chunk-UEVXFHQB.js} +2 -2
- package/{chunk-JYYZCK2Z.js → chunk-V7E4PXQD.js} +1 -1
- package/{chunk-NHRQCXIQ.js → chunk-VMFTFOWF.js} +13 -3
- package/{chunk-YFADVSZE.js → chunk-VRTTFDBM.js} +7 -6
- package/{chunk-U7YP7ECK.js → chunk-WP5UXDA5.js} +84 -13
- package/{chunk-5J7W2VWW.js → chunk-WV42OMV7.js} +6 -265
- package/{chunk-3QPYFHAH.js → chunk-XD2MJFUG.js} +1 -1
- package/{chunk-IDGYOD2O.js → chunk-XP3VVAPG.js} +3174 -223
- package/{chunk-ETLZZQJE.js → chunk-Z2PV6O2T.js} +121 -26
- package/{chunk-BZNGCVAW.js → chunk-Z5625XML.js} +4 -4
- package/{contentThunks-THSCADSK.js → contentThunks-JYWI7G7I.js} +26 -26
- package/{cooldownCommands-BSCH2W7P.js → cooldownCommands-REAWYVTY.js} +2 -2
- package/{createAgentAutomationAction-WGN3UQ5A.js → createAgentAutomationAction-Z4SUJ54Y.js} +26 -26
- package/{createDialogAction-3EN2NLH7.js → createDialogAction-RCHFLVJ2.js} +29 -29
- package/{createPageAction-4VK473QV.js → createPageAction-YK2253XQ.js} +28 -28
- package/{dbSlice-GKGGFNOB.js → dbSlice-A6C6H32X.js} +8 -8
- package/{deleteFile-LWSR674W.js → deleteFile-YG6QMJLU.js} +3 -3
- package/{dialogCommands-PS4NWYY7.js → dialogCommands-X4UOZQWX.js} +36 -32
- package/{dialogSlice-CLV7SVOZ.js → dialogSlice-IVTWLCE2.js} +31 -25
- package/{docCreateCommands-4KANZJR6.js → docCreateCommands-VD6TGHG4.js} +33 -31
- package/{docDeleteCommands-F2S2TRUF.js → docDeleteCommands-OT3R2G4W.js} +34 -32
- package/{docReadCommands-B2WDPZQE.js → docReadCommands-2G7BIXSH.js} +33 -31
- package/{docStore-TDLCHWPF.js → docStore-UENKCCDX.js} +9 -9
- package/{docUpdateCommands-E7TKHRIO.js → docUpdateCommands-45MVJKZA.js} +33 -31
- package/{fetchMessages-DRABNDPZ.js → fetchMessages-EIU7UFFK.js} +3 -4
- package/{getPricing-QVTUQQTM.js → getPricing-LUSEABA6.js} +4 -4
- package/{handleSendMessageAction-AHL3ZHE4.js → handleSendMessageAction-LM2J4HC3.js} +66 -74
- package/index.js +99 -86
- package/{localDialogRead-PUQKFI73.js → localDialogRead-BTFJ4V2I.js} +2 -2
- package/{localRuntimeDb-P37ZTX55.js → localRuntimeDb-SYQGUF4U.js} +1 -1
- package/{machineCommands-OTABVXOA.js → machineCommands-6N6MQIE6.js} +69 -65
- package/{machineWsRunDispatch-V4EVQSMZ.js → machineWsRunDispatch-7MFKVQCR.js} +65 -61
- package/{memberThunks-KATHH3F4.js → memberThunks-P2ECUJ56.js} +9 -9
- package/{models-GS72DVJS.js → models-T3SG6MJ5.js} +2 -2
- package/{offlineMarxistsAgentCommand-GEH6SNNA.js → offlineMarxistsAgentCommand-HGXZV4XJ.js} +1 -1
- package/package.json +1 -1
- package/pdf-U6KSUEZZ.js +26435 -0
- package/providerPresetApply-XI3WUPTI.js +214 -0
- package/providerRegistry-KEPWWGXD.js +31 -0
- package/{read-LC6HVYBN.js → read-COE3ZYR6.js} +5 -4
- package/{readDocTool-N3ERJHHF.js → readDocTool-5USN4A6V.js} +1 -1
- package/{readlineWorkspace-IIT3FQ6I.js → readlineWorkspace-JWBON5CX.js} +291 -135
- package/{removeDialogAgentAction-F5GPTALR.js → removeDialogAgentAction-W66BJ7CF.js} +12 -12
- package/{runAgentClientLoop-7TQM7IAR.js → runAgentClientLoop-TA4SSPHE.js} +49 -49
- package/{runtimeDoctorCommands-CROTMULY.js → runtimeDoctorCommands-X6WDBEQX.js} +2 -2
- package/{setDialogExtraReferencesAction-AY4ZCF3A.js → setDialogExtraReferencesAction-2ALKG3WA.js} +10 -10
- package/{setPrimaryDialogAgentAction-HPMG7V3R.js → setPrimaryDialogAgentAction-TQZB55F6.js} +12 -12
- package/{settingSlice-OEJA4NQO.js → settingSlice-P4ZW7K5X.js} +10 -10
- package/{spaceCommands-FSYH7AL4.js → spaceCommands-6NSAXB7D.js} +31 -29
- package/{streamAgentChatTurn-6EF6TVFK.js → streamAgentChatTurn-X5ENGUIS.js} +126 -65
- package/{switchDialogAgentAction-HMDZTZ4I.js → switchDialogAgentAction-3KI72HHS.js} +9 -9
- package/{tableCommands-SUNTHAYX.js → tableCommands-O6NWHFKV.js} +1 -1
- package/{toolApiClient-VGYUB4P5.js → toolApiClient-G3M73RTB.js} +3 -3
- package/{tools-R3QNWWTD.js → tools-XRQR7ZZR.js} +45 -45
- package/tuiSummaryLlmCaller-UFGFOIIW.js +128 -0
- package/{updateCommands-Y363MC3L.js → updateCommands-2M74UL7P.js} +2 -2
- package/{updateDialogTitleAction-F3EUOWOB.js → updateDialogTitleAction-ESUUD3GT.js} +46 -29
- package/agentRecordCommands-WFPWLWOF.js +0 -125
- package/chunk-FMODTASJ.js +0 -109
- package/chunk-I42VC5KR.js +0 -15
- package/chunk-NB5SIWC4.js +0 -611
- package/chunk-NG4UT4K4.js +0 -79
- package/codingSkills-7S6523WH.js +0 -48
- package/tuiSummaryLlmCaller-PWOXTCBF.js +0 -116
- package/{chunk-3RAG2NAD.js → chunk-UKWGR5SS.js} +3 -3
- package/{db-VQSAOWAO.js → db-WK5MDZVN.js} +1 -1
- 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-
|
|
9
|
+
} from "./chunk-H4AHY5BU.js";
|
|
12
10
|
import {
|
|
13
|
-
DEVIN_CONNECT_URL
|
|
14
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
11939
|
+
const outer = raced;
|
|
11938
11940
|
const timeoutMs = args2.timeoutMs;
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
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
|
-
|
|
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/
|
|
12563
|
-
var
|
|
12564
|
-
var
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
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
|
-
|
|
12892
|
+
loopTimingLastMark = now;
|
|
12573
12893
|
}
|
|
12574
|
-
function
|
|
12575
|
-
if (
|
|
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
|
-
|
|
12578
|
-
|
|
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
|
-
|
|
12827
|
-
|
|
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 =
|
|
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
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
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:
|
|
13669
|
-
dialogId:
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
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
|
|
16525
|
-
const
|
|
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
|
|
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
|
-
|
|
16543
|
-
|
|
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
|
|
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).
|
|
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
|
-
|
|
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/
|
|
19246
|
-
var
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
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 {
|
|
19385
|
-
const
|
|
19386
|
-
if (
|
|
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
|
-
${
|
|
19392
|
-
|
|
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
|
|
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,
|
|
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 ??
|
|
20246
|
-
const
|
|
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,
|
|
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({
|
|
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,
|
|
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,
|
|
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
|
|
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 ===
|
|
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
|
|
20421
|
-
|
|
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
|
-
|
|
20471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20530
|
-
isGemini3Model,
|
|
20531
|
-
accumulateGeminiChunks,
|
|
20532
|
-
shouldUseGeminiNativeToolRoute,
|
|
20533
|
-
buildGeminiGenerateContentRequest,
|
|
20534
|
-
resolveAntigravityFinishReason,
|
|
20535
|
-
fetchAntigravityCloudCodeCompletion,
|
|
20602
|
+
readDevinUpstreamFailure,
|
|
20536
20603
|
createDevinProvider
|
|
20537
20604
|
};
|