@nexrall/code-core 1.4.29 → 1.4.30

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.
@@ -35,6 +35,20 @@ export interface AgentType {
35
35
  * user — ~/.nexrall/agent-memory/<name>.md (follows you everywhere)
36
36
  */
37
37
  memory?: AgentMemoryScope;
38
+ /**
39
+ * Skip the project's nexrall.md for this agent, keeping its prompt lean.
40
+ *
41
+ * Claude Code does the same for its two research builtins (Explore and Plan skip
42
+ * CLAUDE.md and git status to stay fast and cheap), and the reasoning transfers: a
43
+ * search-and-report agent re-reads the entire project instruction file on EVERY
44
+ * invocation, on the tier chosen for being cheap, to produce output the main agent
45
+ * then interprets WITH that context anyway. The rules that must reach it belong in the
46
+ * delegation prompt instead.
47
+ *
48
+ * Only for agents that neither write files nor make judgement calls about conventions
49
+ * — a reviewer or test-writer needs the project's rules, an explorer does not.
50
+ */
51
+ lightPrompt?: boolean;
38
52
  }
39
53
  /** Where a sub-agent's own persistent notes live. */
40
54
  export type AgentMemoryScope = 'project' | 'user' | 'local';
@@ -1 +1 @@
1
- {"version":3,"file":"agentTypes.d.ts","sourceRoot":"","sources":["../../src/agent/agentTypes.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAwS5D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAyKD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAqB5G;AAED,yFAAyF;AACzF,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,IAAI,SAAS,EAAE,CAE3C;AACD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,CAY1D;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAIjG"}
1
+ {"version":3,"file":"agentTypes.d.ts","sourceRoot":"","sources":["../../src/agent/agentTypes.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AA4U5D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAyKD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAqB5G;AAED,yFAAyF;AACzF,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,IAAI,SAAS,EAAE,CAE3C;AACD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,CAY1D;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAIjG"}
@@ -115,6 +115,37 @@ const WRITE_TOOLS = [
115
115
  'notebook_edit',
116
116
  ];
117
117
  const BUILTIN_AGENTS = [
118
+ {
119
+ // The catch-all, matching Claude Code's `general-purpose`.
120
+ //
121
+ // This capability already existed — omitting `subagent_type` gives an unrestricted
122
+ // sub-agent — but it had no NAME, and that had two consequences worth fixing:
123
+ //
124
+ // 1. `permissions.deny: ["task(...)"]` matches on the agent name, so the ONE
125
+ // sub-agent that can write files and run bash was the one variant a project
126
+ // could not disable individually. Only a blanket `deny: ["task"]` reached it.
127
+ // 2. The model had to infer that leaving the field blank was even an option, so
128
+ // it would sometimes pick a specialist that fitted badly (an unrestricted
129
+ // explorer) rather than the general worker it actually wanted.
130
+ //
131
+ // `tools` is deliberately UNDEFINED, which means "no allowlist" — full access,
132
+ // inheriting whatever the session permits. That is the same power an unnamed
133
+ // sub-task always had; naming it changes only who can see and deny it. The safety
134
+ // properties elsewhere still apply: plan mode is inherited, the user's permission
135
+ // gate still runs on every call, and it cannot spawn further sub-agents.
136
+ name: 'general-purpose',
137
+ description: 'General-purpose worker for a multi-step task that needs BOTH exploration and changes ' +
138
+ '(edit files, run commands) and that no specialist above fits. Inherits the session model ' +
139
+ 'and full tool access, so prefer a narrower agent when one matches.',
140
+ prompt: 'You are a general-purpose engineering sub-agent. Work the task end to end: explore what you ' +
141
+ 'need, make the changes, and verify them with the project\'s own build/test commands.\n\n' +
142
+ 'Rules:\n' +
143
+ '- Mirror existing conventions; make the smallest correct change.\n' +
144
+ '- Verify before you claim success. If you could not verify, say so explicitly.\n' +
145
+ '- Your FINAL MESSAGE is the only thing that reaches the main agent: state what you changed ' +
146
+ '(with file paths), what you ran and its outcome, and anything you deliberately left undone.',
147
+ source: 'builtin',
148
+ },
118
149
  {
119
150
  name: 'reviewer',
120
151
  description: 'Read-only code reviewer — finds correctness bugs, edge cases, and security issues in a diff or file set. Cannot modify files.',
@@ -177,6 +208,9 @@ const BUILTIN_AGENTS = [
177
208
  // frontier model, and this is the agent most likely to be spawned in bulk.
178
209
  {
179
210
  name: 'explorer',
211
+ // Lean prompt: this agent exists to keep bulk searching cheap, and it reports
212
+ // findings for the MAIN agent to interpret with full project context.
213
+ lightPrompt: true,
180
214
  description: 'Fast read-only codebase explorer — locates files, symbols, and call sites and reports concise findings. Use to keep bulk searching out of the main context. Cannot modify files.',
181
215
  tools: READ_ONLY_TOOLS,
182
216
  model: 'turbo',
@@ -91,6 +91,18 @@ export declare function resolveMaxIterations(optionValue: number | undefined, se
91
91
  * dozen lines.
92
92
  */
93
93
  export declare function createLimiter(max: number): <T>(fn: () => Promise<T>) => Promise<T>;
94
+ /**
95
+ * How long a sub-agent may make NO progress before it is stopped.
96
+ *
97
+ * Precedence matches every other tunable in this file
98
+ * (env → settings.json → default) — it used to be env-ONLY, which meant a project that
99
+ * legitimately needed longer sub-tasks had no way to say so in the file where every
100
+ * other such preference lives, and the limit was invisible to anyone reading settings.
101
+ *
102
+ * Exported for tests: the behaviour that matters (a long-but-productive sub-agent is
103
+ * NOT killed) takes minutes of wall clock to exercise through the real loop.
104
+ */
105
+ export declare function resolveSubtaskTimeoutMs(settingsRaw: Record<string, unknown>): number;
94
106
  /**
95
107
  * Thrown by a sub-agent's permission gate when the AGENT DEFINITION forbids a
96
108
  * tool — as opposed to the user declining it.
@@ -135,6 +147,16 @@ export declare function capSubTaskText(text: string, max?: number): string;
135
147
  * had already been billed in full.
136
148
  */
137
149
  export declare function summariseSubTaskProgress(messages: Message[]): string;
150
+ /**
151
+ * The last N tool results from a transcript, newest last, each truncated.
152
+ *
153
+ * Pure + exported so the salvage rules are testable without a real sub-agent.
154
+ *
155
+ * Truncation keeps the HEAD of each result: tool output is overwhelmingly
156
+ * front-loaded (a file starts with its imports, a failing command starts with its
157
+ * error), and a head slice is the half that identifies what was found.
158
+ */
159
+ export declare function lastToolResults(messages: Message[], count: number, maxChars: number): string;
138
160
  /** The context window (tokens) for a model alias — the same lookup runAgentLoop uses internally. */
139
161
  export declare function contextWindowFor(model?: 'turbo' | 'pro' | 'ultra'): number;
140
162
  /** Auto-prune / auto-compact thresholds as fractions of the context window — for UI display (e.g. `/context`). */
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
@@ -39,9 +39,11 @@ exports.executeAgentMemoryWrite = executeAgentMemoryWrite;
39
39
  exports.stopReasonNotice = stopReasonNotice;
40
40
  exports.resolveMaxIterations = resolveMaxIterations;
41
41
  exports.createLimiter = createLimiter;
42
+ exports.resolveSubtaskTimeoutMs = resolveSubtaskTimeoutMs;
42
43
  exports.extractSubTaskText = extractSubTaskText;
43
44
  exports.capSubTaskText = capSubTaskText;
44
45
  exports.summariseSubTaskProgress = summariseSubTaskProgress;
46
+ exports.lastToolResults = lastToolResults;
45
47
  exports.contextWindowFor = contextWindowFor;
46
48
  exports.compactionThresholds = compactionThresholds;
47
49
  exports.estimateBodyBytes = estimateBodyBytes;
@@ -539,9 +541,28 @@ let _subTaskCounter = 0; // unique per-process id → per-sub-agent todo scope
539
541
  // pointing at it specifically. Give every sub-task an explicit wall-clock cap:
540
542
  // if it hasn't finished by then, fail it clearly instead of hanging the parent
541
543
  // turn indefinitely. Overridable via env for slow CI machines / huge sub-tasks.
542
- const SUBTASK_TIMEOUT_MS = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) > 0
543
- ? Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS)
544
- : 10 * 60 * 1000; // 10 minutes, matching the general industry norm for a stalled-agent cutoff
544
+ const DEFAULT_SUBTASK_TIMEOUT_MS = 10 * 60 * 1000; // 10 min of NO PROGRESS (see the watchdog)
545
+ /**
546
+ * How long a sub-agent may make NO progress before it is stopped.
547
+ *
548
+ * Precedence matches every other tunable in this file
549
+ * (env → settings.json → default) — it used to be env-ONLY, which meant a project that
550
+ * legitimately needed longer sub-tasks had no way to say so in the file where every
551
+ * other such preference lives, and the limit was invisible to anyone reading settings.
552
+ *
553
+ * Exported for tests: the behaviour that matters (a long-but-productive sub-agent is
554
+ * NOT killed) takes minutes of wall clock to exercise through the real loop.
555
+ */
556
+ function resolveSubtaskTimeoutMs(settingsRaw) {
557
+ const fromEnv = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS);
558
+ if (Number.isFinite(fromEnv) && fromEnv > 0)
559
+ return Math.floor(fromEnv);
560
+ const raw = settingsRaw.subtaskTimeoutMs;
561
+ const fromSettings = Number(raw);
562
+ if (Number.isFinite(fromSettings) && fromSettings > 0)
563
+ return Math.floor(fromSettings);
564
+ return DEFAULT_SUBTASK_TIMEOUT_MS;
565
+ }
545
566
  /** Cap on the text a sub-task hands back, so one verbose sub-agent can't blow up
546
567
  * the PARENT's context in a single tool_result. */
547
568
  const SUBTASK_MAX = 48000; // chars (~12k tokens)
@@ -648,6 +669,14 @@ function summariseSubTaskProgress(messages) {
648
669
  }
649
670
  if (toolNames.length === 0)
650
671
  return '';
672
+ // The FINDINGS, not just the activity log.
673
+ //
674
+ // An inventory of tool names ("read_file ×9, bash ×14") tells the parent that work
675
+ // happened but nothing about what was learned, so it re-derives everything. A stalled
676
+ // research agent's value is almost entirely in what its last few tool calls RETURNED
677
+ // — the file it had just read, the command output it was about to interpret — because
678
+ // its own prose summary is exactly the thing it never got to write.
679
+ const recentFindings = lastToolResults(messages, SALVAGE_RESULT_COUNT, SALVAGE_RESULT_CHARS);
651
680
  // Collapse to "name ×N" so a 40-call run reads as a short inventory rather
652
681
  // than forty repeated lines of the same tool name.
653
682
  const counts = new Map();
@@ -657,7 +686,71 @@ function summariseSubTaskProgress(messages) {
657
686
  .sort((a, b) => b[1] - a[1])
658
687
  .map(([name, n]) => (n > 1 ? `${name} ×${n}` : name))
659
688
  .join(', ');
660
- return `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
689
+ const header = `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
690
+ return recentFindings
691
+ ? `${header}\n\nWhat its most recent tool calls actually returned (use this instead of repeating them):\n${recentFindings}`
692
+ : header;
693
+ }
694
+ /** How many trailing tool results to salvage, and how much of each. */
695
+ const SALVAGE_RESULT_COUNT = 4;
696
+ const SALVAGE_RESULT_CHARS = 2000;
697
+ /**
698
+ * The last N tool results from a transcript, newest last, each truncated.
699
+ *
700
+ * Pure + exported so the salvage rules are testable without a real sub-agent.
701
+ *
702
+ * Truncation keeps the HEAD of each result: tool output is overwhelmingly
703
+ * front-loaded (a file starts with its imports, a failing command starts with its
704
+ * error), and a head slice is the half that identifies what was found.
705
+ */
706
+ function lastToolResults(messages, count, maxChars) {
707
+ // Map tool_use id → tool name, so a salvaged result can say WHICH tool produced it.
708
+ const nameById = new Map();
709
+ for (const m of messages) {
710
+ if (m.role !== 'assistant' || !Array.isArray(m.content))
711
+ continue;
712
+ for (const b of m.content) {
713
+ if (b?.type === 'tool_use' && b.id && typeof b.name === 'string')
714
+ nameById.set(b.id, b.name);
715
+ }
716
+ }
717
+ const out = [];
718
+ // Walk backwards and stop early: only the most recent results are worth the tokens,
719
+ // and a long research run may hold hundreds.
720
+ for (let i = messages.length - 1; i >= 0 && out.length < count; i--) {
721
+ const m = messages[i];
722
+ if (m.role !== 'user' || !Array.isArray(m.content))
723
+ continue;
724
+ for (const b of [...m.content].reverse()) {
725
+ if (out.length >= count)
726
+ break;
727
+ if (b?.type !== 'tool_result')
728
+ continue;
729
+ const text = toolResultText(b);
730
+ if (!text)
731
+ continue;
732
+ const name = nameById.get(String(b.tool_use_id ?? '')) ?? 'tool';
733
+ const body = text.length > maxChars
734
+ ? `${sliceSafeEnd(text, maxChars)}\n… [truncated]`
735
+ : text;
736
+ out.push(`• ${name}:\n${body}`);
737
+ }
738
+ }
739
+ return out.reverse().join('\n\n');
740
+ }
741
+ /** Extract readable text from a tool_result block, whose content may be string or blocks. */
742
+ function toolResultText(block) {
743
+ const c = block.content;
744
+ if (typeof c === 'string')
745
+ return c.trim();
746
+ if (Array.isArray(c)) {
747
+ return c
748
+ .filter((x) => x?.type === 'text' && typeof x.text === 'string')
749
+ .map((x) => x.text)
750
+ .join('\n')
751
+ .trim();
752
+ }
753
+ return '';
661
754
  }
662
755
  async function runSubTask(input, options, agentTypes) {
663
756
  const prompt = typeof input.prompt === 'string' ? input.prompt.trim() : '';
@@ -755,10 +848,14 @@ async function runSubTask(input, options, agentTypes) {
755
848
  const agentMemoryNotes = agent && memoryScope
756
849
  ? (0, memory_1.agentMemoryPreamble)(agent.name, memoryScope, options.workDir)
757
850
  : '';
851
+ // `lightPrompt` agents skip the project's nexrall.md — see AgentType.lightPrompt for
852
+ // why. The role prompt and any private notes still apply; only the (potentially very
853
+ // large) project instruction file is dropped.
854
+ const inheritedMd = agent?.lightPrompt ? '' : (options.nexrallMd ?? '');
758
855
  const subNexrallMd = agent
759
856
  ? `# Sub-agent role: ${agent.name}\n${agent.prompt}` +
760
857
  (agentMemoryNotes ? `\n\n---\n\n${agentMemoryNotes}` : '') +
761
- (options.nexrallMd ? `\n\n---\n\n${options.nexrallMd}` : '')
858
+ (inheritedMd ? `\n\n---\n\n${inheritedMd}` : '')
762
859
  : options.nexrallMd;
763
860
  // Optional tool allowlist — deny anything outside it for this sub-agent.
764
861
  //
@@ -809,7 +906,34 @@ async function runSubTask(input, options, agentTypes) {
809
906
  // runAgentLoop already checks abortSignal.aborted at every iteration boundary,
810
907
  // so this is enough to make it stop promptly without a forceful kill.
811
908
  const subAbort = { aborted: false };
812
- const timer = setTimeout(() => { subAbort.aborted = true; }, SUBTASK_TIMEOUT_MS);
909
+ const subtaskTimeoutMs = resolveSubtaskTimeoutMs((0, rules_1.loadSettings)(options.workDir).raw);
910
+ // ── Stall watchdog, NOT a total-runtime deadline ─────────────────────────────
911
+ //
912
+ // This used to be a single setTimeout armed once and never refreshed. Its own
913
+ // comment called it a "stalled-agent cutoff", but it measured TOTAL LIFETIME: a
914
+ // sub-agent working hard and calling a tool every few seconds was killed at ten
915
+ // minutes exactly like one that had hung. That is not a hypothetical — auditing a
916
+ // handful of 600-2800 line files legitimately exceeds it, and when it fired the
917
+ // parent got back a fragment ("I'll start by reading the files…") after paying for
918
+ // 23 tool calls, then typically re-ran the whole thing.
919
+ //
920
+ // The main loop already draws this distinction correctly (client.ts's heartbeat vs
921
+ // progress watchdogs, where only real events bump lastProgressAt). Sub-agents now do
922
+ // too: the clock resets on every completed tool round, so the cap means "no progress
923
+ // for N minutes" — which is what catches a genuine hang — while useful work can run
924
+ // as long as it keeps being useful.
925
+ //
926
+ // `stalled` is tracked separately from `subAbort.aborted` because the parent's Ctrl+C
927
+ // also sets the latter, and the two must be reported differently.
928
+ let lastProgressAt = Date.now();
929
+ let stalled = false;
930
+ const bumpProgress = () => { lastProgressAt = Date.now(); };
931
+ const stallWatchdog = setInterval(() => {
932
+ if (Date.now() - lastProgressAt > subtaskTimeoutMs) {
933
+ stalled = true;
934
+ subAbort.aborted = true;
935
+ }
936
+ }, 1000);
813
937
  // Mirror the parent's own abort into the sub-agent's signal so Ctrl+C during
814
938
  // a running sub-task still stops it (previously this worked implicitly by
815
939
  // sharing the same object via `...options` — now that we own a distinct
@@ -823,11 +947,27 @@ async function runSubTask(input, options, agentTypes) {
823
947
  ...options,
824
948
  _depth: depth + 1,
825
949
  _agentScope: `sub_${++_subTaskCounter}`, // isolated todo store per sub-agent
826
- // Only set when the agent opted in, so the tool is inert for everyone else.
827
- ...(agent && memoryScope ? { _agentMemory: { agentName: agent.name, scope: memoryScope } } : {}),
950
+ // ── Assigned UNCONDITIONALLY, never by conditional spread ────────────────
951
+ //
952
+ // These two were previously spread in only when set:
953
+ //
954
+ // ...(agent && memoryScope ? { _agentMemory: … } : {}),
955
+ //
956
+ // which does NOT clear the key — it leaves whatever `...options` already had.
957
+ // So a child WITHOUT its own `memory:` inherited its PARENT's binding and would
958
+ // have appended to another agent's private notes; likewise an agent with no
959
+ // `tools:` line inherited the parent's allowlist, making the prompt's capability
960
+ // claim disagree with its real one.
961
+ //
962
+ // MAX_TASK_DEPTH === 1 means no nested spawn can reach this today, so it is
963
+ // latent rather than live — but the limiter comment below explicitly contemplates
964
+ // raising that depth, and this is exactly the kind of leak that would come back
965
+ // as a security bug rather than a visible error. Explicit undefined makes the
966
+ // child's identity independent of the parent's by construction.
967
+ _agentMemory: agent && memoryScope ? { agentName: agent.name, scope: memoryScope } : undefined,
828
968
  // The same set `gatedPermission` enforces above, so prompt and permission agree
829
969
  // by construction instead of by two people remembering to update both.
830
- ...(allowed ? { _allowedTools: allowed } : {}),
970
+ _allowedTools: allowed ?? undefined,
831
971
  editorContext: null, // fresh isolated context for sub-agent
832
972
  model: agent?.model ?? options.model,
833
973
  // Plan mode is inherited, never relaxed. If the main agent could spawn a
@@ -856,13 +996,20 @@ async function runSubTask(input, options, agentTypes) {
856
996
  // Forward tool events with isSubTask=true so the UI can render a badge
857
997
  // instead of prepending "[sub-task]" to the tool name (which caused double-prefix
858
998
  // when the name was already labelled, and mixed display concerns into the data layer).
859
- onToolUse: (n, i) => options.onToolUse(n, i, true),
860
- onToolResult: (n, r) => options.onToolResult(n, r, true),
999
+ // Every tool event is PROGRESS: it proves the sub-agent is still doing work, which
1000
+ // is what the stall watchdog above measures. Bumping on both use and result means a
1001
+ // single very slow tool (a long test run) resets the clock when it starts AND when
1002
+ // it finishes, so it cannot be mistaken for a hang.
1003
+ onToolUse: (n, i) => { bumpProgress(); options.onToolUse(n, i, true); },
1004
+ onToolResult: (n, r) => { bumpProgress(); options.onToolResult(n, r, true); },
861
1005
  onToolStreamChunk: (n, c) => options.onToolStreamChunk?.(n, c, true),
862
1006
  // Forward thinking so the UI shows the indicator while sub-agent reasons
863
- onThinking: (text) => options.onThinking?.(text),
864
- onThinkingDelta: (text) => options.onThinkingDelta?.(text),
865
- onThinkingProgress: (tok) => options.onThinkingProgress?.(tok),
1007
+ // Thinking is progress too — a model reasoning for minutes on a hard problem is
1008
+ // working, not stalled. Without this, deep reasoning on an expensive tier would
1009
+ // trip the watchdog precisely when the sub-agent was most valuable.
1010
+ onThinking: (text) => { bumpProgress(); options.onThinking?.(text); },
1011
+ onThinkingDelta: (text) => { bumpProgress(); options.onThinkingDelta?.(text); },
1012
+ onThinkingProgress: (tok) => { bumpProgress(); options.onThinkingProgress?.(tok); },
866
1013
  });
867
1014
  // ── Stall timeout: SALVAGE, don't discard ────────────────────────────────
868
1015
  //
@@ -877,15 +1024,29 @@ async function runSubTask(input, options, agentTypes) {
877
1024
  // whatever was actually accomplished, not instead of it. `preferLast: false`
878
1025
  // because a killed sub-agent rarely has a closing summary — its useful output
879
1026
  // is spread across the assistant turns it did manage to produce.
880
- if (subAbort.aborted && !options.abortSignal?.aborted) {
881
- const mins = Math.round(SUBTASK_TIMEOUT_MS / 60000);
1027
+ if (stalled && !options.abortSignal?.aborted) {
1028
+ const mins = Math.round(subtaskTimeoutMs / 60000);
882
1029
  const partial = capSubTaskText(extractSubTaskText(result, false));
883
1030
  const progress = summariseSubTaskProgress(result);
1031
+ // Keep the transcript so the parent can CONTINUE this run instead of redoing it.
1032
+ //
1033
+ // Previously only a cleanly-finished sub-agent was remembered, on the reasoning
1034
+ // that a transcript ending mid-thought is unsafe to build on. The reasoning is
1035
+ // sound; the conclusion was too strong. Refusing to store it meant a stalled
1036
+ // sub-agent's entire body of work — dozens of tool calls, already billed — was
1037
+ // unreachable, so the parent's only option was the very thing we tell it not to
1038
+ // do: run the whole task again. Resuming is now POSSIBLE but never implied to be
1039
+ // safe: the text below states plainly that the work is unverified, and resumption
1040
+ // re-authorises against current permissions exactly as it does for a clean run.
1041
+ const partialId = (0, agentRegistry_1.rememberAgent)(agent?.name ?? null, (typeof input.description === 'string' && input.description.trim()) || prompt.slice(0, 80), result);
884
1042
  const sections = [
885
- `Sub-task STOPPED after ${mins} minutes without completing treat the following as PARTIAL, unverified work, not a finished answer.`,
1043
+ `Sub-task STOPPED after ${mins} minutes with NO PROGRESS (it was not making tool calls or ` +
1044
+ 'producing output) — treat everything below as PARTIAL, unverified work, not a finished answer.',
886
1045
  progress,
887
1046
  partial ? `Partial output before it was stopped:\n\n${partial}` : '',
888
- 'Do NOT simply re-run the same sub-task: build on what is above, or split the remaining work into smaller, more focused sub-tasks.',
1047
+ `Do NOT re-run the same sub-task from scratch. Either build on what is above, or continue THIS ` +
1048
+ `run with resume_agent_id="${partialId}" (it still has everything it read), or split the ` +
1049
+ 'remaining work into smaller, more focused sub-tasks.',
889
1050
  ].filter(Boolean);
890
1051
  // Returned as `error` (not `output`) on purpose: the loop's ledger counts an
891
1052
  // errored call as a non-effect, which is right — nothing here is verified —
@@ -934,7 +1095,7 @@ async function runSubTask(input, options, agentTypes) {
934
1095
  return { error: `Sub-task failed: ${err.message}` };
935
1096
  }
936
1097
  finally {
937
- clearTimeout(timer);
1098
+ clearInterval(stallWatchdog);
938
1099
  clearInterval(parentAbortPoll);
939
1100
  }
940
1101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexrall/code-core",
3
- "version": "1.4.29",
3
+ "version": "1.4.30",
4
4
  "description": "Core agent loop, tools, and extension primitives for Nexrall Code — embed an AI coding agent in any Node.js application.",
5
5
  "license": "MIT",
6
6
  "author": "Nexrall <support@nexrall.com> (https://nexrall.com)",