nolo-cli 0.34.0-alpha.1 → 0.35.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/{_executeModel-BUYCXZCQ.js → _executeModel-RV52PPE3.js} +11 -11
  2. package/{agentDeleteCommand-WSS2IJVM.js → agentDeleteCommand-FBBW3IIM.js} +2 -2
  3. package/{agentEmailCommands-SHTPJTSX.js → agentEmailCommands-7F4PCABZ.js} +12 -12
  4. package/{agentListCommands-GV6UYVAF.js → agentListCommands-ZQXPSB6U.js} +2 -2
  5. package/{agentMachineCommands-FLD7TAFH.js → agentMachineCommands-E3NB67GM.js} +3 -3
  6. package/{agentRecordCommands-57W6R7QW.js → agentRecordCommands-7PXWHCQM.js} +12 -12
  7. package/{agentRunCommand-HJGXSMDN.js → agentRunCommand-Z4654ONK.js} +13 -13
  8. package/{agentRunControl-GXOB47YI.js → agentRunControl-7MUPSYDQ.js} +3 -1
  9. package/{agentRuntimeLocal-G7ZV55I6.js → agentRuntimeLocal-OBKWKEKW.js} +2 -2
  10. package/{agentSlice-HFR7SP3B.js → agentSlice-B2IMSPH5.js} +1 -1
  11. package/{agentSuperviseCommands-Q64S5SPG.js → agentSuperviseCommands-MKWW3ICX.js} +3 -3
  12. package/{appDeleteCommands-SHHE56Y2.js → appDeleteCommands-72BE7DSA.js} +2 -2
  13. package/{appDeployCommands-WUMSO2VS.js → appDeployCommands-XJBLKCYO.js} +2 -2
  14. package/{appDeployStatusCommands-VZSVN6SR.js → appDeployStatusCommands-EC4HWCBQ.js} +2 -2
  15. package/{appGetCommands-ZJ2C3A5Y.js → appGetCommands-62BAATPL.js} +2 -2
  16. package/{appListCommands-W33L6U5X.js → appListCommands-5EZCONM7.js} +2 -2
  17. package/{chatQueueReduxAdapter-SYIX4ODY.js → chatQueueReduxAdapter-SSF3DNBX.js} +2 -2
  18. package/{chunk-AUH2LTIY.js → chunk-55C3UZSC.js} +3 -3
  19. package/{chunk-6KYCG5GW.js → chunk-5Q4DXT47.js} +9 -9
  20. package/{chunk-DX6BP4K7.js → chunk-7KY6U7MW.js} +8 -8
  21. package/{chunk-VSA6QAGV.js → chunk-E2OQ5N2F.js} +40 -10
  22. package/{chunk-LQ6PUHW6.js → chunk-EFN2AFWF.js} +252 -64
  23. package/{chunk-4HOXB6SY.js → chunk-IMHO5LC6.js} +100 -27
  24. package/{chunk-VD2SO4RG.js → chunk-INXNBCEY.js} +2 -2
  25. package/{chunk-HDT2RB7T.js → chunk-KPJYUEPE.js} +1 -1
  26. package/{chunk-3LWZNEGQ.js → chunk-LFOILL6R.js} +1 -1
  27. package/{chunk-LAUUAYLK.js → chunk-OCJNJUR4.js} +2 -2
  28. package/{chunk-VBD7W4Q6.js → chunk-OGPQ3AMC.js} +14 -10
  29. package/{chunk-FDKEDU7R.js → chunk-P62APAXL.js} +2 -2
  30. package/{chunk-MI2J67UU.js → chunk-P66M7LSQ.js} +16 -1
  31. package/{chunk-KCBK4MDT.js → chunk-RUVWTAK7.js} +3 -3
  32. package/{chunk-3XZUNRYF.js → chunk-SDAPQUSO.js} +7 -7
  33. package/{chunk-74IQ5RRO.js → chunk-Z7D7MBWR.js} +1 -1
  34. package/{chunk-YRP7LG4R.js → chunk-ZOEE4OVX.js} +7 -16
  35. package/{chunk-7YDKVLAE.js → chunk-ZZDWMZFU.js} +3 -3
  36. package/{createAgentAutomationAction-BH3D2L2H.js → createAgentAutomationAction-IDIY6K45.js} +3 -3
  37. package/{createDialogAction-U4LDV2RW.js → createDialogAction-5LB4YBPS.js} +4 -4
  38. package/{createPageAction-Q2ANG535.js → createPageAction-TJWVDPZD.js} +3 -3
  39. package/{dialogCommands-C556D6WD.js → dialogCommands-44NGBPJJ.js} +2 -2
  40. package/{dialogSlice-P2VSKG2X.js → dialogSlice-62JC4ZMX.js} +2 -2
  41. package/{docCreateCommands-AAVGLQWE.js → docCreateCommands-IOGYJ7F7.js} +2 -2
  42. package/{docDeleteCommands-PKMKSQD2.js → docDeleteCommands-RO4BZWFP.js} +2 -2
  43. package/{docReadCommands-2WUH7UXC.js → docReadCommands-F2KNOJFW.js} +2 -2
  44. package/{docStore-J5UWRBB4.js → docStore-ZRNKZZXU.js} +4 -4
  45. package/{docUpdateCommands-B2N4E5HQ.js → docUpdateCommands-2VG4AYOC.js} +2 -2
  46. package/{handleSendMessageAction-RRZ53YYH.js → handleSendMessageAction-YB64A32O.js} +2 -2
  47. package/index.js +46 -46
  48. package/{machineCommands-CWKMJG33.js → machineCommands-6WPICTON.js} +12 -12
  49. package/{machineWsRunDispatch-UYNVNX4A.js → machineWsRunDispatch-4OGEZTYG.js} +12 -12
  50. package/package.json +1 -1
  51. package/{readlineWorkspace-VYELP22P.js → readlineWorkspace-BZAZ5YXM.js} +127 -78
  52. package/{runAgentClientLoop-35GKZDOZ.js → runAgentClientLoop-SOZS3YTQ.js} +8 -8
  53. package/{spaceCommands-AAKQM2PM.js → spaceCommands-WEASK3IV.js} +2 -2
  54. package/{spaceSlice-NSNGR4Y2.js → spaceSlice-2QNUKRN6.js} +3 -3
  55. package/{streamAgentChatTurn-D5IDHXMV.js → streamAgentChatTurn-BYI7UXAA.js} +10 -10
  56. package/{tools-JQOETZP5.js → tools-E6FEUNZY.js} +6 -6
  57. package/{updateDialogTitleAction-WOTSDSFQ.js → updateDialogTitleAction-2A3BEXSP.js} +3 -3
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-AGGIDPCZ.js";
14
14
  import {
15
15
  runLlm
16
- } from "./chunk-AUH2LTIY.js";
16
+ } from "./chunk-55C3UZSC.js";
17
17
  import {
18
18
  cleanupCliSessionForDialog
19
19
  } from "./chunk-MGIHZ5CJ.js";
@@ -3519,7 +3519,7 @@ var messageSlice = createSliceWithThunks({
3519
3519
  runtimeOptions: args.runtimeOptions
3520
3520
  });
3521
3521
  if (agentKeyToUse) {
3522
- const { streamAgentChatTurn } = await import("./agentSlice-HFR7SP3B.js");
3522
+ const { streamAgentChatTurn } = await import("./agentSlice-B2IMSPH5.js");
3523
3523
  await dispatch(
3524
3524
  streamAgentChatTurn({
3525
3525
  agentKey: agentKeyToUse,
@@ -4742,7 +4742,7 @@ var deleteDialogThunk = async (payload, thunkApi) => {
4742
4742
  }
4743
4743
  if (isCurrentDialog) {
4744
4744
  await thunkApi.dispatch(resetMsgs());
4745
- const { clearPendingAttachments: clearPendingAttachments2 } = await import("./dialogSlice-P2VSKG2X.js");
4745
+ const { clearPendingAttachments: clearPendingAttachments2 } = await import("./dialogSlice-62JC4ZMX.js");
4746
4746
  dispatch(clearPendingAttachments2());
4747
4747
  }
4748
4748
  return { dialogKey, isCurrentDialog, attachmentPlan };
@@ -4750,15 +4750,15 @@ var deleteDialogThunk = async (payload, thunkApi) => {
4750
4750
 
4751
4751
  // packages/chat/dialog/dialogSlice.ts
4752
4752
  var runCreateDialogAction = async (args, thunkApi) => {
4753
- const { createDialogAction } = await import("./createDialogAction-U4LDV2RW.js");
4753
+ const { createDialogAction } = await import("./createDialogAction-5LB4YBPS.js");
4754
4754
  return createDialogAction(args, thunkApi);
4755
4755
  };
4756
4756
  var runCreateAgentAutomationAction = async (args, thunkApi) => {
4757
- const { createAgentAutomationAction } = await import("./createAgentAutomationAction-BH3D2L2H.js");
4757
+ const { createAgentAutomationAction } = await import("./createAgentAutomationAction-IDIY6K45.js");
4758
4758
  return createAgentAutomationAction(args, thunkApi);
4759
4759
  };
4760
4760
  var runUpdateDialogTitleAction = async (args, thunkApi) => {
4761
- const { updateDialogTitleAction } = await import("./updateDialogTitleAction-WOTSDSFQ.js");
4761
+ const { updateDialogTitleAction } = await import("./updateDialogTitleAction-2A3BEXSP.js");
4762
4762
  return updateDialogTitleAction(args, thunkApi);
4763
4763
  };
4764
4764
  var runAddDialogAgentAction = async (args, thunkApi) => {
@@ -4782,7 +4782,7 @@ var runSetDialogExtraReferencesAction = async (args, thunkApi) => {
4782
4782
  return setDialogExtraReferencesAction(args, thunkApi);
4783
4783
  };
4784
4784
  var runHandleSendMessageAction = async (args, thunkApi) => {
4785
- const { handleSendMessageAction } = await import("./handleSendMessageAction-RRZ53YYH.js");
4785
+ const { handleSendMessageAction } = await import("./handleSendMessageAction-YB64A32O.js");
4786
4786
  return handleSendMessageAction(args, thunkApi);
4787
4787
  };
4788
4788
  function clearDialogState() {
@@ -4795,7 +4795,7 @@ var createPageAndAddReference = createAsyncThunk(
4795
4795
  async (payload, { dispatch, getState, rejectWithValue }) => {
4796
4796
  const { slateData, jsonData, title, type, fileId, groupId, dialogKey } = payload;
4797
4797
  try {
4798
- const { createDocState } = await import("./docStore-J5UWRBB4.js");
4798
+ const { createDocState } = await import("./docStore-ZRNKZZXU.js");
4799
4799
  const pageKey = await createDocState(
4800
4800
  { slateData, title },
4801
4801
  { dispatch, getState }
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // packages/cli/agentRunControl.ts
10
10
  import { homedir as nodeHomedir } from "node:os";
11
- import { isAbsolute, join, resolve } from "node:path";
11
+ import { dirname, isAbsolute, join, resolve } from "node:path";
12
12
  import * as nodeFs from "node:fs";
13
13
  import { execFileSync as nodeExecFileSync, spawn as nodeSpawn } from "node:child_process";
14
14
  var RUN_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
@@ -380,16 +380,42 @@ function rewriteMsgFileArg(args, messagePath) {
380
380
  }
381
381
  return result;
382
382
  }
383
- function buildAgentRunChildCommand(options) {
383
+ function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs) {
384
+ if (cliEntrypointPath && fs.existsSync(cliEntrypointPath)) {
385
+ return cliEntrypointPath;
386
+ }
387
+ const fallback = resolveCliEntrypointPath();
388
+ if (fs.existsSync(fallback)) {
389
+ return fallback;
390
+ }
391
+ const candidates = [
392
+ join(dirname(fallback), "cli", "index.ts"),
393
+ join(dirname(fallback), "cli", "index.js"),
394
+ join(dirname(fallback), "..", "packages", "cli", "index.ts"),
395
+ join(dirname(fallback), "..", "packages", "cli", "index.js"),
396
+ join(dirname(fallback), "..", "index.js")
397
+ ];
398
+ for (const cand of candidates) {
399
+ if (fs.existsSync(cand)) {
400
+ return cand;
401
+ }
402
+ }
403
+ return fallback;
404
+ }
405
+ function buildAgentRunChildCommand(options, deps = {}) {
406
+ const fs = deps.fs ?? nodeFs;
384
407
  const execPath = process.execPath;
385
- const entrypoint = options.cliEntrypointPath || resolveCliEntrypointPath();
386
408
  const commandParts = options.commandPath ?? [];
387
409
  let strippedArgs = stripBackgroundFlag(options.rawArgs);
388
410
  if (options.messagePath) {
389
411
  strippedArgs = rewriteMsgFileArg(strippedArgs, options.messagePath);
390
412
  }
391
413
  strippedArgs = absolutizeSkillArgs(strippedArgs);
392
- if (isCompiledBinary() || entrypoint === execPath) {
414
+ if (isCompiledBinary() || options.cliEntrypointPath === execPath) {
415
+ return { execPath, childArgs: [...commandParts, ...strippedArgs] };
416
+ }
417
+ const entrypoint = resolveValidCliEntrypoint(options.cliEntrypointPath, fs);
418
+ if (entrypoint === execPath) {
393
419
  return { execPath, childArgs: [...commandParts, ...strippedArgs] };
394
420
  }
395
421
  return { execPath, childArgs: [entrypoint, ...commandParts, ...strippedArgs] };
@@ -453,12 +479,15 @@ async function spawnLocalBackgroundRun(input, deps = {}) {
453
479
  ...spawnHead ? { spawnHead } : {}
454
480
  };
455
481
  fs.writeFileSync(recordPath, JSON.stringify(record, null, 2));
456
- const { execPath, childArgs } = buildAgentRunChildCommand({
457
- rawArgs: input.rawArgs,
458
- commandPath: input.commandPath,
459
- cliEntrypointPath: input.cliEntrypointPath,
460
- messagePath
461
- });
482
+ const { execPath, childArgs } = buildAgentRunChildCommand(
483
+ {
484
+ rawArgs: input.rawArgs,
485
+ commandPath: input.commandPath,
486
+ cliEntrypointPath: input.cliEntrypointPath,
487
+ messagePath
488
+ },
489
+ deps
490
+ );
462
491
  const childEnv = {
463
492
  ...env,
464
493
  NOLO_AGENT_RUN_CHILD: "1",
@@ -1042,6 +1071,7 @@ export {
1042
1071
  stripBackgroundFlag,
1043
1072
  absolutizeSkillArgs,
1044
1073
  rewriteMsgFileArg,
1074
+ buildAgentRunChildCommand,
1045
1075
  spawnLocalBackgroundRun,
1046
1076
  ackRunRecord,
1047
1077
  DEFAULT_ACK_LEASE_TTL_MS,
@@ -23,10 +23,11 @@ import {
23
23
  } from "./chunk-T27IEBNQ.js";
24
24
  import {
25
25
  EMPTY_ASSISTANT_REPAIR_PROMPT,
26
+ formatLengthTruncatedReasoningTail,
26
27
  hasAssistantVisibleOutput,
27
28
  resolveEmptyAssistantFallbackMessage,
28
29
  resolveEmptyAssistantOutcome
29
- } from "./chunk-MI2J67UU.js";
30
+ } from "./chunk-P66M7LSQ.js";
30
31
  import {
31
32
  createThinkParserState,
32
33
  createToolCallAccumulator,
@@ -721,6 +722,191 @@ async function maybeAutoCompactLocalHistory(args) {
721
722
  }
722
723
  }
723
724
 
725
+ // packages/agent-runtime/progressGuard.ts
726
+ var DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS = 5;
727
+ var DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS = 8;
728
+ function sortJsonKeys(obj) {
729
+ if (obj === null || typeof obj !== "object") return obj;
730
+ if (Array.isArray(obj)) return obj.map(sortJsonKeys);
731
+ const sorted = {};
732
+ for (const key of Object.keys(obj).sort()) {
733
+ sorted[key] = sortJsonKeys(obj[key]);
734
+ }
735
+ return sorted;
736
+ }
737
+ function normalizeToolArguments(rawArgs) {
738
+ if (typeof rawArgs !== "string") {
739
+ try {
740
+ return JSON.stringify(sortJsonKeys(rawArgs ?? ""));
741
+ } catch {
742
+ return String(rawArgs ?? "");
743
+ }
744
+ }
745
+ const trimmed = rawArgs.trim();
746
+ if (!trimmed) return "";
747
+ try {
748
+ const parsed = JSON.parse(trimmed);
749
+ return JSON.stringify(sortJsonKeys(parsed));
750
+ } catch {
751
+ return trimmed;
752
+ }
753
+ }
754
+ function extractNormalizedContentText(content) {
755
+ if (typeof content === "string") return content.trim();
756
+ if (!Array.isArray(content)) return "";
757
+ return content.map((part) => {
758
+ if (part?.type === "text") return String(part.text ?? "").trim();
759
+ if (part?.type === "image_url") return `[img:${part.image_url?.url ?? ""}]`;
760
+ return "";
761
+ }).filter(Boolean).join("\n");
762
+ }
763
+ function buildToolCallsSignature(toolCalls) {
764
+ if (!toolCalls || toolCalls.length === 0) return "";
765
+ return toolCalls.map(
766
+ (tc) => `${tc.function?.name ?? ""}:${normalizeToolArguments(tc.function?.arguments)}`
767
+ ).join(";;");
768
+ }
769
+ function buildAssistantActionFingerprint(result) {
770
+ const content = extractNormalizedContentText(result.content);
771
+ const reasoning = typeof result.reasoning_content === "string" ? result.reasoning_content.trim() : "";
772
+ let toolCallsSig = buildToolCallsSignature(result.tool_calls);
773
+ let hasToolCalls = Array.isArray(result.tool_calls) && result.tool_calls.length > 0;
774
+ if (!toolCallsSig && Array.isArray(result.output) && result.output.length > 0) {
775
+ const blocksToolCalls = result.output.filter(
776
+ (b) => b.type === "toolCall"
777
+ ).map((b) => b.toolCall);
778
+ if (blocksToolCalls.length > 0) {
779
+ toolCallsSig = buildToolCallsSignature(blocksToolCalls);
780
+ hasToolCalls = true;
781
+ }
782
+ }
783
+ const hasNonEmptyText = content.length > 0 || reasoning.length > 0;
784
+ const fingerprint = `${content}##${reasoning}##${toolCallsSig}`;
785
+ return {
786
+ fingerprint,
787
+ hasNonEmptyText,
788
+ hasToolCalls,
789
+ toolCallsSignature: toolCallsSig
790
+ };
791
+ }
792
+ function buildToolResultsSignature(toolExecutions) {
793
+ if (toolExecutions.length === 0) return "";
794
+ return toolExecutions.map((exec) => {
795
+ const contentStr = typeof exec.content === "string" ? exec.content.trim() : "";
796
+ const metaStr = exec.metadata ? JSON.stringify(sortJsonKeys(exec.metadata)) : "";
797
+ return `${exec.toolName}:${contentStr}:${metaStr}`;
798
+ }).join(";;");
799
+ }
800
+ function resolveProgressGuardConfig(options, env = typeof process !== "undefined" ? process.env : {}) {
801
+ const envDisabled = env.NOLO_LOOP_PROGRESS_GUARD_DISABLED === "true" || env.NOLO_LOOP_PROGRESS_GUARD_DISABLED === "1";
802
+ const disabled = options?.disabled ?? envDisabled ?? false;
803
+ const parsePositiveInt = (val, fallback) => {
804
+ if (typeof val === "number" && Number.isFinite(val) && val >= 1) {
805
+ return Math.floor(val);
806
+ }
807
+ if (typeof val === "string") {
808
+ const parsed = parseInt(val, 10);
809
+ if (Number.isFinite(parsed) && parsed >= 1) {
810
+ return parsed;
811
+ }
812
+ }
813
+ return fallback;
814
+ };
815
+ const maxConsecutiveIdenticalRounds = parsePositiveInt(
816
+ options?.maxConsecutiveIdenticalRounds ?? env.NOLO_LOOP_MAX_IDENTICAL_ROUNDS,
817
+ DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS
818
+ );
819
+ const maxConsecutiveStagnantToolRounds = parsePositiveInt(
820
+ options?.maxConsecutiveStagnantToolRounds ?? env.NOLO_LOOP_MAX_STAGNANT_TOOL_ROUNDS,
821
+ DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS
822
+ );
823
+ return {
824
+ disabled,
825
+ maxConsecutiveIdenticalRounds,
826
+ maxConsecutiveStagnantToolRounds
827
+ };
828
+ }
829
+ var LocalLoopProgressGuard = class {
830
+ config;
831
+ lastAssistantFingerprint = null;
832
+ lastAssistantHasNonEmptyText = false;
833
+ consecutiveIdenticalCount = 0;
834
+ lastToolCallsSignature = null;
835
+ lastToolResultsSignature = null;
836
+ consecutiveStagnantToolCount = 0;
837
+ constructor(config, env) {
838
+ this.config = resolveProgressGuardConfig(config, env);
839
+ }
840
+ /**
841
+ * 在 LLM 返回 assistant 响应后调用。
842
+ * 仅在纯文本/无工具调用且实质文本相同达到阈值时直接熔断;
843
+ * 若存在工具调用,则不在此处熔断,留待工具执行后结合工具结果综合判定。
844
+ */
845
+ observeAssistantResponse(result) {
846
+ if (this.config.disabled) return { action: "continue" };
847
+ const { fingerprint, hasNonEmptyText, hasToolCalls } = buildAssistantActionFingerprint(result);
848
+ if (this.lastAssistantFingerprint !== null && this.lastAssistantFingerprint === fingerprint) {
849
+ this.consecutiveIdenticalCount += 1;
850
+ if (!hasToolCalls && hasNonEmptyText && this.consecutiveIdenticalCount >= this.config.maxConsecutiveIdenticalRounds) {
851
+ return {
852
+ action: "stall",
853
+ reason: "repetition_loop",
854
+ detail: `Consecutive identical text-only assistant response for ${this.consecutiveIdenticalCount} rounds.`,
855
+ consecutiveRounds: this.consecutiveIdenticalCount
856
+ };
857
+ }
858
+ } else {
859
+ this.lastAssistantFingerprint = fingerprint;
860
+ this.lastAssistantHasNonEmptyText = hasNonEmptyText;
861
+ this.consecutiveIdenticalCount = 1;
862
+ }
863
+ return { action: "continue" };
864
+ }
865
+ /**
866
+ * 在本轮所有工具执行完毕后调用,检查是否陷入停滞死循环。
867
+ * 只有当工具返回内容与元数据完全无变化时才计入无进展 streak;
868
+ * 一旦工具结果发生变化(如合法轮询状态推进),立即重置 identical streak,确保绝不误杀正常长任务。
869
+ */
870
+ observeToolExecution(toolCalls, toolResults) {
871
+ if (this.config.disabled) return { action: "continue" };
872
+ if (!toolCalls || toolCalls.length === 0) return { action: "continue" };
873
+ const toolCallsSig = buildToolCallsSignature(toolCalls);
874
+ const toolResultsSig = buildToolResultsSignature(toolResults);
875
+ const toolCallsUnchanged = this.lastToolCallsSignature !== null && this.lastToolCallsSignature === toolCallsSig;
876
+ const toolResultsUnchanged = this.lastToolResultsSignature !== null && this.lastToolResultsSignature === toolResultsSig;
877
+ if (toolCallsUnchanged && toolResultsUnchanged) {
878
+ this.consecutiveStagnantToolCount += 1;
879
+ if (this.consecutiveIdenticalCount >= this.config.maxConsecutiveIdenticalRounds) {
880
+ return {
881
+ action: "stall",
882
+ reason: "repetition_loop",
883
+ detail: `Consecutive identical assistant response with unchanged tool results for ${this.consecutiveIdenticalCount} rounds.`,
884
+ consecutiveRounds: this.consecutiveIdenticalCount
885
+ };
886
+ }
887
+ if (this.consecutiveStagnantToolCount >= this.config.maxConsecutiveStagnantToolRounds) {
888
+ return {
889
+ action: "stall",
890
+ reason: "stagnant_tool_calls",
891
+ detail: `Consecutive stagnant tool calls with unchanged results for ${this.consecutiveStagnantToolCount} rounds.`,
892
+ consecutiveRounds: this.consecutiveStagnantToolCount
893
+ };
894
+ }
895
+ } else {
896
+ this.lastToolCallsSignature = toolCallsSig;
897
+ this.lastToolResultsSignature = toolResultsSig;
898
+ this.consecutiveStagnantToolCount = 1;
899
+ if (!toolResultsUnchanged) {
900
+ this.consecutiveIdenticalCount = 1;
901
+ }
902
+ }
903
+ return { action: "continue" };
904
+ }
905
+ };
906
+ function createLocalLoopProgressGuard(config, env) {
907
+ return new LocalLoopProgressGuard(config, env);
908
+ }
909
+
724
910
  // packages/agent-runtime/localLoop.ts
725
911
  var LOCAL_AGENT_CONFIG_MISSING_CODE = "LOCAL_AGENT_CONFIG_MISSING";
726
912
  function formatToolExecutionError(args) {
@@ -1489,6 +1675,7 @@ async function runLocalAgentTurn(input) {
1489
1675
  let reasoningEmptyRepairCount = 0;
1490
1676
  let skipFinalAppend = false;
1491
1677
  let partialContent = "";
1678
+ const progressGuard = createLocalLoopProgressGuard(input.progressGuardConfig);
1492
1679
  try {
1493
1680
  const provider = await resolveProviderOnce();
1494
1681
  while (true) {
@@ -1548,6 +1735,23 @@ async function runLocalAgentTurn(input) {
1548
1735
  ...result.provider || agentConfig.provider ? { provider: result.provider || agentConfig.provider } : {}
1549
1736
  });
1550
1737
  }
1738
+ const assistantGuardVerdict = progressGuard.observeAssistantResponse(result);
1739
+ if (assistantGuardVerdict.action === "stall") {
1740
+ emitLoopEvent(input, {
1741
+ kind: "loop-stalled",
1742
+ round,
1743
+ reason: assistantGuardVerdict.reason,
1744
+ detail: assistantGuardVerdict.detail,
1745
+ consecutiveRounds: assistantGuardVerdict.consecutiveRounds,
1746
+ atMs: Date.now()
1747
+ });
1748
+ result = {
1749
+ ...result,
1750
+ content: resolveEmptyAssistantFallbackMessage(assistantGuardVerdict.reason),
1751
+ emptyAssistantFallbackReason: assistantGuardVerdict.reason
1752
+ };
1753
+ break;
1754
+ }
1551
1755
  const toolCalls = result.tool_calls ?? [];
1552
1756
  const rawToolCallsCount = (result.tool_calls?.length ?? 0) || (Array.isArray(result.raw_tool_calls) ? result.raw_tool_calls.length : 0);
1553
1757
  if (toolCalls.length === 0 && rawToolCallsCount === 0) {
@@ -1567,9 +1771,21 @@ async function runLocalAgentTurn(input) {
1567
1771
  continue;
1568
1772
  }
1569
1773
  if (outcome.kind === "fallback") {
1774
+ if (outcome.reason === "length_truncated" && result.reasoning_content) {
1775
+ const reasoningTailLog = formatLengthTruncatedReasoningTail(result.reasoning_content);
1776
+ if (reasoningTailLog) {
1777
+ console.warn(`
1778
+ ${reasoningTailLog}
1779
+ `);
1780
+ }
1781
+ }
1570
1782
  result = {
1571
1783
  ...result,
1572
- content: resolveEmptyAssistantFallbackMessage(outcome.reason)
1784
+ content: resolveEmptyAssistantFallbackMessage(outcome.reason),
1785
+ // 只打标记不抛错:交互侧仍照常显示诊断文案(零行为变化)。
1786
+ // 上层(后台 run 编排者)据 emptyAssistantFallbackReason 判断
1787
+ // 是否把本轮结算为 failed。见 LocalAgentTurnResult 注释。
1788
+ emptyAssistantFallbackReason: outcome.reason
1573
1789
  };
1574
1790
  break;
1575
1791
  }
@@ -1605,6 +1821,7 @@ async function runLocalAgentTurn(input) {
1605
1821
  ...result.reasoning_content ? { reasoning_content: result.reasoning_content } : {},
1606
1822
  tool_calls: toolCalls
1607
1823
  });
1824
+ const executedToolResults = [];
1608
1825
  for (const toolCall of toolCalls) {
1609
1826
  throwIfAborted(input);
1610
1827
  const toolName = toolCall.function.name;
@@ -1717,6 +1934,11 @@ async function runLocalAgentTurn(input) {
1717
1934
  }
1718
1935
  };
1719
1936
  }
1937
+ executedToolResults.push({
1938
+ toolName,
1939
+ content: toolResult.content,
1940
+ metadata: toolResult.metadata
1941
+ });
1720
1942
  messages.push({
1721
1943
  role: "tool",
1722
1944
  content: formatToolMessageContent({
@@ -1729,6 +1951,26 @@ async function runLocalAgentTurn(input) {
1729
1951
  ...toolResult.metadata ? { tool_result_metadata: toolResult.metadata } : {}
1730
1952
  });
1731
1953
  }
1954
+ const toolExecutionGuardVerdict = progressGuard.observeToolExecution(
1955
+ toolCalls,
1956
+ executedToolResults
1957
+ );
1958
+ if (toolExecutionGuardVerdict.action === "stall") {
1959
+ emitLoopEvent(input, {
1960
+ kind: "loop-stalled",
1961
+ round,
1962
+ reason: toolExecutionGuardVerdict.reason,
1963
+ detail: toolExecutionGuardVerdict.detail,
1964
+ consecutiveRounds: toolExecutionGuardVerdict.consecutiveRounds,
1965
+ atMs: Date.now()
1966
+ });
1967
+ result = {
1968
+ ...result,
1969
+ content: resolveEmptyAssistantFallbackMessage(toolExecutionGuardVerdict.reason),
1970
+ emptyAssistantFallbackReason: toolExecutionGuardVerdict.reason
1971
+ };
1972
+ break;
1973
+ }
1732
1974
  round += 1;
1733
1975
  }
1734
1976
  } catch (error) {
@@ -3044,14 +3286,6 @@ var NEVER_LOCAL_TOOLS = /* @__PURE__ */ new Set([
3044
3286
  "updateSelf",
3045
3287
  "createAgent"
3046
3288
  ]);
3047
- var REMOVED_LOCAL_TOOLS = /* @__PURE__ */ new Set([
3048
- "gitStatus",
3049
- "gitDiff",
3050
- "gitCreateBranch",
3051
- "gitAdd",
3052
- "gitCommit",
3053
- "commitWorkspace"
3054
- ]);
3055
3289
  var DEFAULT_LOCAL_TOOLS = /* @__PURE__ */ new Set([
3056
3290
  "readFile",
3057
3291
  "writeFile",
@@ -3099,13 +3333,6 @@ function resolveLocalToolPolicy(args) {
3099
3333
  reason: `${toolName} is blocked by the local runtime safety policy.`
3100
3334
  };
3101
3335
  }
3102
- if (REMOVED_LOCAL_TOOLS.has(toolName)) {
3103
- return {
3104
- allowed: false,
3105
- toolName,
3106
- reason: `${toolName} has been removed from the local runtime tool surface. Use execShell in shell-enabled workspace runs when shell is needed.`
3107
- };
3108
- }
3109
3336
  const allowedByEnv = new Set(parseToolAllowlist(args.env.NOLO_LOCAL_ALLOWED_TOOLS));
3110
3337
  const agentTools = new Set(
3111
3338
  (args.agentToolNames ?? []).flatMap((item) => {
@@ -4076,14 +4303,6 @@ var WORKSPACE_TOOL_NAMES = [
4076
4303
  ];
4077
4304
  var SHELL_TOOL_NAMES = ["execShell", "launchProcess", "listProcesses"];
4078
4305
  var WORKSPACE_TOOL_NAME_SET = new Set(WORKSPACE_TOOL_NAMES);
4079
- var REMOVED_WORKSPACE_TOOL_NAMES = /* @__PURE__ */ new Set([
4080
- "gitStatus",
4081
- "gitDiff",
4082
- "gitCreateBranch",
4083
- "gitAdd",
4084
- "gitCommit",
4085
- "commitWorkspace"
4086
- ]);
4087
4306
  function buildWorkspacePathProperty() {
4088
4307
  return {
4089
4308
  type: "string",
@@ -4327,7 +4546,7 @@ function buildWorkspaceToolDefinition(toolName) {
4327
4546
  }
4328
4547
  function filterDeclaredWorkspaceToolNames(args) {
4329
4548
  return (args.toolNames ?? []).filter(
4330
- (toolName) => WORKSPACE_TOOL_NAME_SET.has(toolName) && !REMOVED_WORKSPACE_TOOL_NAMES.has(toolName) && (args.exposeShellTools || !SHELL_TOOL_NAMES.includes(toolName))
4549
+ (toolName) => WORKSPACE_TOOL_NAME_SET.has(toolName) && (args.exposeShellTools || !SHELL_TOOL_NAMES.includes(toolName))
4331
4550
  );
4332
4551
  }
4333
4552
 
@@ -4418,7 +4637,7 @@ function buildLocalWorkspacePolicyToolNames(args) {
4418
4637
  toolNames: args.declaredToolNames,
4419
4638
  exposeShellTools: args.exposeShellTools === true
4420
4639
  })
4421
- ])].filter((toolName) => !REMOVED_WORKSPACE_TOOL_NAMES.has(toolName));
4640
+ ])];
4422
4641
  }
4423
4642
  function buildLocalWorkspaceOpenAiTools(args) {
4424
4643
  const declaredTools = new Set(args.toolNames ?? []);
@@ -4489,7 +4708,6 @@ function readExpectedReplacementCount(args) {
4489
4708
  return value;
4490
4709
  }
4491
4710
  var READ_FILE_SLICE_FALLBACK_LINES = 200;
4492
- var LEGACY_SLICE_ARG_NAMES = ["startLine", "endLine", "maxLines", "tailLines"];
4493
4711
  function isAbsentArg(value) {
4494
4712
  return value === void 0 || value === null;
4495
4713
  }
@@ -4559,46 +4777,16 @@ function parseLinesArg(value) {
4559
4777
  }
4560
4778
  function readFileSliceArgs(args) {
4561
4779
  const warnings = [];
4562
- const legacyUsed = LEGACY_SLICE_ARG_NAMES.filter((name) => !isAbsentArg(args[name]));
4563
- const withFallback = (slice) => {
4780
+ const withFallback = (slice2) => {
4564
4781
  warnings.push(
4565
4782
  `No usable line range remained; returned the first ${READ_FILE_SLICE_FALLBACK_LINES} lines instead of the whole file.`
4566
4783
  );
4567
- return { ...slice, maxLines: READ_FILE_SLICE_FALLBACK_LINES, warnings };
4784
+ return { ...slice2, maxLines: READ_FILE_SLICE_FALLBACK_LINES, warnings };
4568
4785
  };
4569
- if (!isAbsentArg(args.lines)) {
4570
- if (legacyUsed.length > 0) {
4571
- warnings.push(`Ignored ${legacyUsed.join(", ")}: superseded by lines.`);
4572
- }
4573
- const { slice, warning } = parseLinesArg(args.lines);
4574
- if (warning) warnings.push(warning);
4575
- return slice ? { ...slice, warnings } : withFallback({});
4576
- }
4577
- if (legacyUsed.length === 0) return { warnings };
4578
- warnings.push(
4579
- `${legacyUsed.join(", ")} ${legacyUsed.length > 1 ? "are" : "is"} deprecated; use lines instead (${LINES_ARG_SYNTAX}).`
4580
- );
4581
- const take = (name, max) => collectIntegerArg({ value: args[name], name, max, warnings });
4582
- const resolved = {
4583
- startLine: take("startLine"),
4584
- endLine: take("endLine"),
4585
- maxLines: take("maxLines", 2e3),
4586
- tailLines: take("tailLines", 2e3)
4587
- };
4588
- if (resolved.endLine !== void 0 && resolved.startLine !== void 0 && resolved.endLine < resolved.startLine) {
4589
- warnings.push(
4590
- `Ignored endLine (${resolved.endLine}): endLine must be greater than or equal to startLine (${resolved.startLine}).`
4591
- );
4592
- resolved.endLine = void 0;
4593
- }
4594
- if (resolved.tailLines !== void 0 && (resolved.startLine !== void 0 || resolved.endLine !== void 0)) {
4595
- warnings.push(
4596
- `Ignored tailLines (${resolved.tailLines}): tailLines cannot be combined with startLine or endLine.`
4597
- );
4598
- resolved.tailLines = void 0;
4599
- }
4600
- const nothingUsable = LEGACY_SLICE_ARG_NAMES.every((name) => resolved[name] === void 0);
4601
- return nothingUsable ? withFallback(resolved) : { ...resolved, warnings };
4786
+ if (isAbsentArg(args.lines)) return { warnings };
4787
+ const { slice, warning } = parseLinesArg(args.lines);
4788
+ if (warning) warnings.push(warning);
4789
+ return slice ? { ...slice, warnings } : withFallback({});
4602
4790
  }
4603
4791
  function buildReadFileSliceHint(totalLines) {
4604
4792
  return `File has ${totalLines} lines. Use lines: ${LINES_ARG_SYNTAX}, or omit it for the whole file.`;