@nexrall/code-core 1.4.66 → 1.4.68

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 (91) hide show
  1. package/dist/agent/agentRegistry.d.ts +27 -0
  2. package/dist/agent/agentRegistry.d.ts.map +1 -1
  3. package/dist/agent/agentRegistry.js +45 -0
  4. package/dist/agent/agentTypes.d.ts +6 -2
  5. package/dist/agent/agentTypes.d.ts.map +1 -1
  6. package/dist/agent/agentTypes.js +13 -4
  7. package/dist/agent/askOnce.d.ts +12 -0
  8. package/dist/agent/askOnce.d.ts.map +1 -0
  9. package/dist/agent/askOnce.js +41 -0
  10. package/dist/agent/compaction.d.ts +244 -0
  11. package/dist/agent/compaction.d.ts.map +1 -0
  12. package/dist/agent/compaction.js +976 -0
  13. package/dist/agent/fileLocks.d.ts +34 -0
  14. package/dist/agent/fileLocks.d.ts.map +1 -0
  15. package/dist/agent/fileLocks.js +114 -0
  16. package/dist/agent/hooks.d.ts +331 -0
  17. package/dist/agent/hooks.d.ts.map +1 -0
  18. package/dist/agent/hooks.js +1239 -0
  19. package/dist/agent/iterationPolicy.d.ts +121 -0
  20. package/dist/agent/iterationPolicy.d.ts.map +1 -0
  21. package/dist/agent/iterationPolicy.js +297 -0
  22. package/dist/agent/lifecycleHost.d.ts +55 -0
  23. package/dist/agent/lifecycleHost.d.ts.map +1 -0
  24. package/dist/agent/lifecycleHost.js +294 -0
  25. package/dist/agent/loop.d.ts +39 -491
  26. package/dist/agent/loop.d.ts.map +1 -1
  27. package/dist/agent/loop.js +538 -3069
  28. package/dist/agent/planMode.d.ts.map +1 -1
  29. package/dist/agent/planMode.js +1 -0
  30. package/dist/agent/sharedTasks.d.ts +7 -0
  31. package/dist/agent/sharedTasks.d.ts.map +1 -1
  32. package/dist/agent/sharedTasks.js +16 -0
  33. package/dist/agent/subAgentBudget.d.ts +65 -0
  34. package/dist/agent/subAgentBudget.d.ts.map +1 -0
  35. package/dist/agent/subAgentBudget.js +269 -0
  36. package/dist/agent/subTask.d.ts +15 -0
  37. package/dist/agent/subTask.d.ts.map +1 -0
  38. package/dist/agent/subTask.js +730 -0
  39. package/dist/agent/subTaskSupport.d.ts +167 -0
  40. package/dist/agent/subTaskSupport.d.ts.map +1 -0
  41. package/dist/agent/subTaskSupport.js +425 -0
  42. package/dist/agent/toolDescriptions.d.ts +3 -0
  43. package/dist/agent/toolDescriptions.d.ts.map +1 -0
  44. package/dist/agent/toolDescriptions.js +116 -0
  45. package/dist/api/client.d.ts +41 -0
  46. package/dist/api/client.d.ts.map +1 -1
  47. package/dist/api/client.js +173 -1
  48. package/dist/index.d.ts +5 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +5 -0
  51. package/dist/mcp/client.d.ts +104 -0
  52. package/dist/mcp/client.d.ts.map +1 -1
  53. package/dist/mcp/client.js +136 -2
  54. package/dist/mcp/httpClient.d.ts +17 -1
  55. package/dist/mcp/httpClient.d.ts.map +1 -1
  56. package/dist/mcp/httpClient.js +120 -19
  57. package/dist/mcp/manager.d.ts +77 -2
  58. package/dist/mcp/manager.d.ts.map +1 -1
  59. package/dist/mcp/manager.js +275 -9
  60. package/dist/mcp/server.d.ts +39 -0
  61. package/dist/mcp/server.d.ts.map +1 -0
  62. package/dist/mcp/server.js +189 -0
  63. package/dist/mcp/sseClient.d.ts +7 -1
  64. package/dist/mcp/sseClient.d.ts.map +1 -1
  65. package/dist/mcp/sseClient.js +45 -1
  66. package/dist/mcp/stats.d.ts +41 -0
  67. package/dist/mcp/stats.d.ts.map +1 -0
  68. package/dist/mcp/stats.js +108 -0
  69. package/dist/permissions/destructive.d.ts +2 -0
  70. package/dist/permissions/destructive.d.ts.map +1 -1
  71. package/dist/permissions/destructive.js +6 -2
  72. package/dist/permissions/destructiveTokens.d.ts +5 -0
  73. package/dist/permissions/destructiveTokens.d.ts.map +1 -1
  74. package/dist/permissions/destructiveTokens.js +9 -3
  75. package/dist/permissions/modePolicy.d.ts.map +1 -1
  76. package/dist/permissions/modePolicy.js +5 -2
  77. package/dist/permissions/rules.d.ts +4 -1
  78. package/dist/permissions/rules.d.ts.map +1 -1
  79. package/dist/permissions/rules.js +29 -0
  80. package/dist/plugins/data.d.ts +23 -0
  81. package/dist/plugins/data.d.ts.map +1 -0
  82. package/dist/plugins/data.js +141 -0
  83. package/dist/plugins/index.d.ts +14 -2
  84. package/dist/plugins/index.d.ts.map +1 -1
  85. package/dist/plugins/index.js +45 -4
  86. package/dist/plugins/installer.d.ts +83 -0
  87. package/dist/plugins/installer.d.ts.map +1 -1
  88. package/dist/plugins/installer.js +207 -1
  89. package/dist/types.d.ts +45 -1
  90. package/dist/types.d.ts.map +1 -1
  91. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"planMode.d.ts","sourceRoot":"","sources":["../../src/agent/planMode.ts"],"names":[],"mappings":"AAgJA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAAC;IAC/C,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;CACjB;AAqFD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA8D1D;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,eAAe,GAAG,IAAI,CAoCxB;AAED,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,QAqBvB,CAAC"}
1
+ {"version":3,"file":"planMode.d.ts","sourceRoot":"","sources":["../../src/agent/planMode.ts"],"names":[],"mappings":"AAiJA,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAAC;IAC/C,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;CACjB;AAqFD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA8D1D;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,eAAe,GAAG,IAAI,CAoCxB;AAED,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,QAqBvB,CAAC"}
@@ -36,6 +36,7 @@ const READ_ONLY_TOOLS = new Set([
36
36
  // list, not the repo, so it stays available — a plan mode that cannot draft
37
37
  // a checklist is missing the point of plan mode.
38
38
  'todo_write', 'todo_read',
39
+ 'cron_list', 'mcp_list_resources', 'mcp_read_resource',
39
40
  // Reading memory is fine; memory_write is NOT here on purpose. Memory is
40
41
  // durable state that survives the session, so writing it is a real mutation
41
42
  // even though no file in the repo changes.
@@ -59,5 +59,12 @@ export declare const _sharedTasksInternal: {
59
59
  CLAIM_STALE_MS: number;
60
60
  MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS: number;
61
61
  };
62
+ /**
63
+ * Remove a task outright — the veto path for a `TaskCreated` hook. Claude Code creates the
64
+ * task, and when a hook refuses it the task is DELETED and the reason goes back to the
65
+ * model as the tool's error; so "no task remains" is the observable contract of a veto,
66
+ * and this is the only place that needs to make it true.
67
+ */
68
+ export declare function deleteSharedTask(workDir: string, taskId: string): boolean;
62
69
  export {};
63
70
  //# sourceMappingURL=sharedTasks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sharedTasks.d.ts","sourceRoot":"","sources":["../../src/agent/sharedTasks.ts"],"names":[],"mappings":"AA6DA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzC;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzD;AA4BD,0HAA0H;AAC1H,iBAAS,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAUnD;AAED,4GAA4G;AAC5G,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAG/D;AAED,0IAA0I;AAC1I,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAW7D;AAED,sJAAsJ;AACtJ,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC/C,UAAU,CAUZ;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAA;CAAE,CAAC;AAEvF;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BxG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrF,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED,eAAO,MAAM,oBAAoB;;;;;;;;CAAqH,CAAC"}
1
+ {"version":3,"file":"sharedTasks.d.ts","sourceRoot":"","sources":["../../src/agent/sharedTasks.ts"],"names":[],"mappings":"AA6DA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzC;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzD;AA4BD,0HAA0H;AAC1H,iBAAS,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAUnD;AAED,4GAA4G;AAC5G,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAG/D;AAED,0IAA0I;AAC1I,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAW7D;AAED,sJAAsJ;AACtJ,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC/C,UAAU,CAUZ;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAA;CAAE,CAAC;AAEvF;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BxG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrF,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED,eAAO,MAAM,oBAAoB;;;;;;;;CAAqH,CAAC;AAEvJ;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzE"}
@@ -38,6 +38,7 @@ exports.listSharedTasks = listSharedTasks;
38
38
  exports.createSharedTask = createSharedTask;
39
39
  exports.claimTask = claimTask;
40
40
  exports.updateSharedTaskStatus = updateSharedTaskStatus;
41
+ exports.deleteSharedTask = deleteSharedTask;
41
42
  /**
42
43
  * Shared task list — lets MULTIPLE independent sessions (two CLI windows, or
43
44
  * CLI + VS Code + Desktop, all working the same project) coordinate who is
@@ -281,4 +282,19 @@ async function updateSharedTaskStatus(workDir, taskId, status, opts = {}) {
281
282
  }, workDir);
282
283
  }
283
284
  exports._sharedTasksInternal = { tasksRoot, scopeDir, taskPath, isClaimAbandoned, isUnblocked, CLAIM_STALE_MS, MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS };
285
+ /**
286
+ * Remove a task outright — the veto path for a `TaskCreated` hook. Claude Code creates the
287
+ * task, and when a hook refuses it the task is DELETED and the reason goes back to the
288
+ * model as the tool's error; so "no task remains" is the observable contract of a veto,
289
+ * and this is the only place that needs to make it true.
290
+ */
291
+ function deleteSharedTask(workDir, taskId) {
292
+ try {
293
+ fs.unlinkSync(taskPath(workDir, taskId));
294
+ return true;
295
+ }
296
+ catch {
297
+ return false;
298
+ }
299
+ }
284
300
  //# sourceMappingURL=sharedTasks.js.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Resolve the fan-out limit: env → settings.json → default.
3
+ *
4
+ * Was env-ONLY, the same gap as the sub-task timeout: a project on a small machine that
5
+ * wanted 2, or a big one that wanted 6, had to set a shell variable, and nobody reading
6
+ * settings.json could see what the limit even was. Capped at HARD_MAX_CONCURRENT_SUBTASKS
7
+ * (32 — above Claude Code's default 20, so a user who wants that width can have it)
8
+ * because this bounds real shared resources (CPU, the API rate limit, file handles) and a
9
+ * typo like 400 should degrade to "a lot" rather than fork-bomb the machine.
10
+ */
11
+ export declare function resolveMaxConcurrentSubtasks(settingsRaw?: Record<string, unknown>): number;
12
+ /** Resolve the session-total sub-agent ceiling: env → settings.json → default. */
13
+ export declare function resolveMaxSubagentsPerSession(settingsRaw?: Record<string, unknown>): number;
14
+ /**
15
+ * Minimal concurrency gate. Hand-rolled rather than pulling in `p-limit` because
16
+ * the CLI ships as a single esbuild bundle with no node_modules, and this is a
17
+ * dozen lines.
18
+ */
19
+ export declare function createLimiter(max: number): <T>(fn: () => Promise<T>) => Promise<T>;
20
+ /** In-flight count per depth — used only to detect queueing, so the notice is accurate. */
21
+ export declare const _inFlightByDepth: Map<number, number>;
22
+ export declare function subTaskLimiter(depth: number, workDir?: string): {
23
+ run: <T>(fn: () => Promise<T>) => Promise<T>;
24
+ max: number;
25
+ };
26
+ /** Test-only: forget the memoised limiter so a new limit can take effect. */
27
+ export declare function _resetSubTaskLimiter(): void;
28
+ export declare const PROCESS_BUDGET_KEY = "\0process";
29
+ /**
30
+ * Start a fresh sub-agent budget. Call when a NEW conversation begins.
31
+ *
32
+ * Exported for clients that reuse one process across conversations (the VS Code extension
33
+ * host). A client that never calls it gets process-lifetime semantics, which is correct
34
+ * for a one-shot CLI invocation.
35
+ */
36
+ export declare function resetSessionSubAgentBudget(sessionKey?: string): void;
37
+ /**
38
+ * Claim one slot against the session total. Returns an error string when exhausted.
39
+ *
40
+ * `notify` surfaces exhaustion to the HUMAN exactly once. Without it only the model is
41
+ * told, and a model instructed to "do the remaining work directly" complies silently — so
42
+ * the user never learns delegation was capped, which is the one thing a runaway guard has
43
+ * to make visible.
44
+ */
45
+ export declare function claimSessionSubAgentSlot(workDir?: string, notify?: (msg: string) => void, sessionKey?: string): string | null;
46
+ /**
47
+ * Give back a slot claimed for a spawn that never started an agent.
48
+ *
49
+ * The claim happens at the dispatch site, BEFORE the concurrency limiter, so that a spawn
50
+ * which is already over budget is refused immediately instead of queueing behind running
51
+ * siblings. That ordering is what makes the guard useful in a runaway — but it also means
52
+ * the claim precedes runSubTask's own validation, which rejects on several paths without
53
+ * ever starting a loop (empty prompt, a `deny` rule, an unusable resume id).
54
+ *
55
+ * Without a refund those rejections spend budget: a model retrying against a deny rule would
56
+ * silently burn the whole allowance on spawns that never ran, then lose delegation for the
57
+ * session with a notice blaming a runaway that never happened. The counter's contract is
58
+ * "sub-agents STARTED", and this is what keeps that true while preserving fast refusal.
59
+ *
60
+ * Floored at 0 so a double refund can never manufacture budget.
61
+ */
62
+ export declare function refundSessionSubAgentSlot(sessionKey?: string): void;
63
+ /** Test-only: observe the session counter without exporting the mutable binding. */
64
+ export declare function _sessionSubAgentCount(sessionKey?: string): number;
65
+ //# sourceMappingURL=subAgentBudget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subAgentBudget.d.ts","sourceRoot":"","sources":["../../src/agent/subAgentBudget.ts"],"names":[],"mappings":"AAyCA;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAW9F;AAoBD,kFAAkF;AAClF,wBAAgB,6BAA6B,CAAC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAa/F;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAgBlF;AA8BD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,qBAA4B,CAAC;AAE1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG;IAC/D,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7C,GAAG,EAAE,MAAM,CAAC;CACb,CAuBA;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAkBD,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAQ9C;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAGpE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7H;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAGnE;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjE"}
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROCESS_BUDGET_KEY = exports._inFlightByDepth = void 0;
4
+ exports.resolveMaxConcurrentSubtasks = resolveMaxConcurrentSubtasks;
5
+ exports.resolveMaxSubagentsPerSession = resolveMaxSubagentsPerSession;
6
+ exports.createLimiter = createLimiter;
7
+ exports.subTaskLimiter = subTaskLimiter;
8
+ exports._resetSubTaskLimiter = _resetSubTaskLimiter;
9
+ exports.resetSessionSubAgentBudget = resetSessionSubAgentBudget;
10
+ exports.claimSessionSubAgentSlot = claimSessionSubAgentSlot;
11
+ exports.refundSessionSubAgentSlot = refundSessionSubAgentSlot;
12
+ exports._sessionSubAgentCount = _sessionSubAgentCount;
13
+ const rules_1 = require("../permissions/rules");
14
+ // ─── Sub-agent fan-out limiter ────────────────────────────────────────────────
15
+ //
16
+ // Tool calls in one turn run via Promise.all with no ceiling. For ordinary tools
17
+ // that is right — they're cheap and mostly I/O — but a `task` call spawns a WHOLE
18
+ // nested agent loop: its own model stream, its own tool executions, its own
19
+ // sub-process spawns. A model that emits ten `task` blocks in one turn therefore
20
+ // starts ten concurrent agents, each billing tokens and competing for the same
21
+ // CPU, file handles and API rate limit. The practical symptoms are the ones users
22
+ // report as "it got slow and then stalled": every sub-agent's stream slows, some
23
+ // trip their own stall watchdog, and one shared rate limit is spread across ten
24
+ // callers.
25
+ //
26
+ // Anthropic hit the same wall and capped Claude Code's concurrent subagents at 20
27
+ // (v2.1.217, July 2026, CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS); community guidance for
28
+ // everyday work settles around 3-5 because past that the synthesis overhead cancels
29
+ // the parallelism.
30
+ //
31
+ // Default 10, raised from 4 (2026-09-29), with a hard ceiling of 32 (see below). Measured
32
+ // on a 12-core dev box against the Nexrall repo, the per-agent latency of a
33
+ // search_files + glob pair was 116 ms at N=4, 176 ms at N=10, 300 ms at N=20 and 415 ms
34
+ // at N=30. Local CPU is NOT what limits fan-out; the model API is (rounds are seconds
35
+ // long, tools are milliseconds). What did limit it was the SERVER: /api/code was IP
36
+ // rate-limited at 100 req/15 min, which even 4 agents exhausted. That is now per-user
37
+ // (backend shared/utils/codeRateLimit.js), so the old "4" no longer protects anything
38
+ // that 10 does not.
39
+ //
40
+ // Why not default 20/30 like the headline number: the default applies to EVERY user, on
41
+ // every model, including ones with a low per-key TPM, and each concurrent agent is its
42
+ // own bill. Wide fan-out is opt-in (maxConcurrentSubtasks / NEXRALL_MAX_CONCURRENT_SUBTASKS
43
+ // up to 32), narrow fan-out is the safe default.
44
+ //
45
+ // This is a QUEUE, not a rejection: every sub-task still runs, just at most N at a
46
+ // time. Failing the excess would be worse than serialising it.
47
+ const DEFAULT_MAX_CONCURRENT_SUBTASKS = 10;
48
+ /** Hard ceiling on maxConcurrentSubtasks, whatever settings.json (repo-controlled) says. */
49
+ const HARD_MAX_CONCURRENT_SUBTASKS = 32;
50
+ /**
51
+ * Resolve the fan-out limit: env → settings.json → default.
52
+ *
53
+ * Was env-ONLY, the same gap as the sub-task timeout: a project on a small machine that
54
+ * wanted 2, or a big one that wanted 6, had to set a shell variable, and nobody reading
55
+ * settings.json could see what the limit even was. Capped at HARD_MAX_CONCURRENT_SUBTASKS
56
+ * (32 — above Claude Code's default 20, so a user who wants that width can have it)
57
+ * because this bounds real shared resources (CPU, the API rate limit, file handles) and a
58
+ * typo like 400 should degrade to "a lot" rather than fork-bomb the machine.
59
+ */
60
+ function resolveMaxConcurrentSubtasks(settingsRaw = {}) {
61
+ // `Math.max(1, …)` matters: a fractional value like 0.5 passes the `> 0` guard, then floors
62
+ // to 0, and createLimiter(0) queues every task with nothing left to ever release them — a
63
+ // silent permanent hang with no timeout and no error. Harmless when only depth 0 used the
64
+ // limiter; now that every level does, it would wedge the whole tree.
65
+ const clamp = (n) => Math.max(1, Math.min(Math.floor(n), HARD_MAX_CONCURRENT_SUBTASKS));
66
+ const fromEnv = Number(process.env.NEXRALL_MAX_CONCURRENT_SUBTASKS);
67
+ if (Number.isFinite(fromEnv) && fromEnv > 0)
68
+ return clamp(fromEnv);
69
+ const fromSettings = Number(settingsRaw.maxConcurrentSubtasks);
70
+ if (Number.isFinite(fromSettings) && fromSettings > 0)
71
+ return clamp(fromSettings);
72
+ return DEFAULT_MAX_CONCURRENT_SUBTASKS;
73
+ }
74
+ // ── Session TOTAL, distinct from the per-moment concurrency gate ─────────────
75
+ //
76
+ // maxConcurrentSubtasks bounds how many run AT ONCE; it does not bound how many run
77
+ // IN TOTAL. With a queue rather than a rejection, "4 at a time" and "unbounded" are the
78
+ // same thing given enough turns — the limiter just meters the spend, it never stops it.
79
+ // A model in a retry loop could spawn sub-agents indefinitely and the only signal would
80
+ // be the bill.
81
+ //
82
+ // This gap did not matter much while sub-agents were leaves: only the main agent could
83
+ // spawn, so the count grew linearly with its own turns. With nesting it grows like a
84
+ // tree, which is exactly why Anthropic added a per-session subagent ceiling alongside
85
+ // their concurrency cap rather than relying on concurrency alone.
86
+ //
87
+ // 100 is chosen to be invisible in real work (a heavy orchestration session uses a few
88
+ // dozen) and decisive in a runaway. Unlike the concurrency gate this REJECTS rather than
89
+ // queues: a queue that never drains is a hang, and the point here is to stop.
90
+ const DEFAULT_MAX_SUBAGENTS_PER_SESSION = 100;
91
+ /** Resolve the session-total sub-agent ceiling: env → settings.json → default. */
92
+ function resolveMaxSubagentsPerSession(settingsRaw = {}) {
93
+ // Clamped, unlike the first draft of this function. The argument for leaving it unbounded
94
+ // was that it only moves a counter — but `.nexrall/settings.json` is REPO-CONTROLLED and
95
+ // merged last, so a cloned repo could set 999999 and neutralise the one ceiling that makes
96
+ // a default depth > 1 defensible, turning `depth 5 x 16 wide` into an unbounded spend on a
97
+ // machine whose owner only opened a project. Depth and concurrency were already clamped for
98
+ // exactly this reason; this was the gap between them.
99
+ const floor = (n) => Math.max(1, Math.min(Math.floor(n), 10000));
100
+ const fromEnv = Number(process.env.NEXRALL_MAX_SUBAGENTS_PER_SESSION);
101
+ if (Number.isFinite(fromEnv) && fromEnv > 0)
102
+ return floor(fromEnv);
103
+ const fromSettings = Number(settingsRaw.maxSubagentsPerSession);
104
+ if (Number.isFinite(fromSettings) && fromSettings > 0)
105
+ return floor(fromSettings);
106
+ return DEFAULT_MAX_SUBAGENTS_PER_SESSION;
107
+ }
108
+ /**
109
+ * Minimal concurrency gate. Hand-rolled rather than pulling in `p-limit` because
110
+ * the CLI ships as a single esbuild bundle with no node_modules, and this is a
111
+ * dozen lines.
112
+ */
113
+ function createLimiter(max) {
114
+ let active = 0;
115
+ const queue = [];
116
+ const release = () => {
117
+ active--;
118
+ queue.shift()?.();
119
+ };
120
+ return async (fn) => {
121
+ if (active >= max)
122
+ await new Promise((resolve) => queue.push(resolve));
123
+ active++;
124
+ try {
125
+ return await fn();
126
+ }
127
+ finally {
128
+ release();
129
+ }
130
+ };
131
+ }
132
+ // Process-wide, deliberately: the limit exists to protect shared resources (CPU,
133
+ // the API rate limit, file handles), and those are shared across every concurrent
134
+ // turn in this process, not just the tool calls of one message.
135
+ //
136
+ // Built LAZILY on first use rather than at module load, because the limit can now come
137
+ // from settings.json and the workspace is not known when this module is imported.
138
+ // Once created it is reused for the process lifetime — rebuilding it per turn would
139
+ // reset `active` and let the ceiling be exceeded, which is worse than not honouring a
140
+ // mid-session settings change.
141
+ // ONE LIMITER PER DEPTH, which is what makes nesting safe to gate at all.
142
+ //
143
+ // The old design gated only `depth === 0` and left nested spawns ungated — deliberately,
144
+ // because a single shared limiter deadlocks the moment a slot-holder re-enters it: a
145
+ // parent holding one of N slots waits for a child that can only start when a slot frees,
146
+ // and if all N are held by such parents the run wedges forever. While sub-agents were
147
+ // leaves that could not happen, so "gate the top, leave the rest" cost nothing.
148
+ //
149
+ // With nesting it costs everything: nested fan-out becomes completely unbounded, which is
150
+ // worse than the deadlock it was avoiding.
151
+ //
152
+ // Keying the limiter by depth fixes both at once. A depth-D run only ever waits on the
153
+ // depth-(D+1) limiter, never its own, so the wait-for graph is strictly ordered by depth —
154
+ // a DAG, and a DAG cannot deadlock. Every level is independently bounded, so worst-case
155
+ // concurrency is bounded per level rather than unbounded below level 1.
156
+ const _subTaskLimiters = new Map();
157
+ /** The tapered ceiling actually applied at each depth, so the queue notice can report it. */
158
+ const _subTaskLimitMaxByDepth = new Map();
159
+ let _subTaskLimitMax = 0;
160
+ /** In-flight count per depth — used only to detect queueing, so the notice is accurate. */
161
+ exports._inFlightByDepth = new Map();
162
+ function subTaskLimiter(depth, workDir) {
163
+ if (!_subTaskLimitMax) {
164
+ _subTaskLimitMax = resolveMaxConcurrentSubtasks(workDir ? (0, rules_1.loadSettings)(workDir).raw : {});
165
+ }
166
+ let run = _subTaskLimiters.get(depth);
167
+ let max = _subTaskLimitMaxByDepth.get(depth) ?? 0;
168
+ if (!run) {
169
+ // TAPERED per level, not `max` at every level.
170
+ //
171
+ // Giving each depth the full `max` multiplies total concurrency by the depth limit: 10
172
+ // becomes 30 by default and 32×5 = 160 at the configured maxima. The value is justified by
173
+ // shared resources — CPU, file handles, ONE API rate limit — none of which care which
174
+ // level a loop is running at, so honouring `4` per level silently abandons the limit the
175
+ // user set. Halving per level bounds the total at ~2× `max` (10+5+3 = 18 by default) while keeping the
176
+ // per-depth structure that makes the wait-for graph acyclic.
177
+ //
178
+ // Never below 1: a level with 0 slots is a permanent hang, not a restriction.
179
+ max = Math.max(1, Math.ceil(_subTaskLimitMax / 2 ** Math.max(0, depth - 1)));
180
+ run = createLimiter(max);
181
+ _subTaskLimiters.set(depth, run);
182
+ _subTaskLimitMaxByDepth.set(depth, max);
183
+ }
184
+ return { run, max };
185
+ }
186
+ /** Test-only: forget the memoised limiter so a new limit can take effect. */
187
+ function _resetSubTaskLimiter() {
188
+ _subTaskLimiters.clear();
189
+ _subTaskLimitMaxByDepth.clear();
190
+ exports._inFlightByDepth.clear();
191
+ _subTaskLimitMax = 0;
192
+ _subAgentBudgets.clear();
193
+ }
194
+ const _subAgentBudgets = new Map();
195
+ exports.PROCESS_BUDGET_KEY = '\0process';
196
+ function budgetFor(sessionKey) {
197
+ const key = sessionKey || exports.PROCESS_BUDGET_KEY;
198
+ let b = _subAgentBudgets.get(key);
199
+ if (!b) {
200
+ b = { used: 0, max: 0, notified: false };
201
+ _subAgentBudgets.set(key, b);
202
+ }
203
+ return b;
204
+ }
205
+ /**
206
+ * Start a fresh sub-agent budget. Call when a NEW conversation begins.
207
+ *
208
+ * Exported for clients that reuse one process across conversations (the VS Code extension
209
+ * host). A client that never calls it gets process-lifetime semantics, which is correct
210
+ * for a one-shot CLI invocation.
211
+ */
212
+ function resetSessionSubAgentBudget(sessionKey) {
213
+ // No key: the legacy "new conversation" call — reset the process-wide budget only.
214
+ _subAgentBudgets.delete(sessionKey || exports.PROCESS_BUDGET_KEY);
215
+ }
216
+ /**
217
+ * Claim one slot against the session total. Returns an error string when exhausted.
218
+ *
219
+ * `notify` surfaces exhaustion to the HUMAN exactly once. Without it only the model is
220
+ * told, and a model instructed to "do the remaining work directly" complies silently — so
221
+ * the user never learns delegation was capped, which is the one thing a runaway guard has
222
+ * to make visible.
223
+ */
224
+ function claimSessionSubAgentSlot(workDir, notify, sessionKey) {
225
+ const b = budgetFor(sessionKey);
226
+ if (!b.max) {
227
+ b.max = resolveMaxSubagentsPerSession(workDir ? (0, rules_1.loadSettings)(workDir).raw : {});
228
+ }
229
+ const _sessionCapMax = b.max;
230
+ if (b.used >= b.max) {
231
+ if (!b.notified) {
232
+ b.notified = true;
233
+ notify?.(`\u26a0\ufe0f Sub-agent budget reached (${_sessionCapMax} this session) \u2014 further delegation is ` +
234
+ 'blocked and the agent will continue without it. Raise "maxSubagentsPerSession" in ' +
235
+ '.nexrall/settings.json if this was legitimate work.');
236
+ }
237
+ return (`Sub-agent budget for this session is exhausted (${_sessionCapMax} started). This is a ` +
238
+ 'runaway-delegation guard, not a per-task limit: do the remaining work directly, and say ' +
239
+ 'in your final message that you hit the delegation cap.');
240
+ }
241
+ b.used++;
242
+ return null;
243
+ }
244
+ /**
245
+ * Give back a slot claimed for a spawn that never started an agent.
246
+ *
247
+ * The claim happens at the dispatch site, BEFORE the concurrency limiter, so that a spawn
248
+ * which is already over budget is refused immediately instead of queueing behind running
249
+ * siblings. That ordering is what makes the guard useful in a runaway — but it also means
250
+ * the claim precedes runSubTask's own validation, which rejects on several paths without
251
+ * ever starting a loop (empty prompt, a `deny` rule, an unusable resume id).
252
+ *
253
+ * Without a refund those rejections spend budget: a model retrying against a deny rule would
254
+ * silently burn the whole allowance on spawns that never ran, then lose delegation for the
255
+ * session with a notice blaming a runaway that never happened. The counter's contract is
256
+ * "sub-agents STARTED", and this is what keeps that true while preserving fast refusal.
257
+ *
258
+ * Floored at 0 so a double refund can never manufacture budget.
259
+ */
260
+ function refundSessionSubAgentSlot(sessionKey) {
261
+ const b = budgetFor(sessionKey);
262
+ if (b.used > 0)
263
+ b.used--;
264
+ }
265
+ /** Test-only: observe the session counter without exporting the mutable binding. */
266
+ function _sessionSubAgentCount(sessionKey) {
267
+ return budgetFor(sessionKey).used;
268
+ }
269
+ //# sourceMappingURL=subAgentBudget.js.map
@@ -0,0 +1,15 @@
1
+ import type { Message, ToolResult, AgentLoopOptions } from '../types';
2
+ import { type AgentType } from './agentTypes';
3
+ /**
4
+ * A conversation to FORK into the sub-agent (the `/subtask` command) instead of running it
5
+ * with a fresh, isolated context. The parent's transcript is copied in as the child's
6
+ * opening history, so it starts out knowing everything the main agent knows.
7
+ */
8
+ export interface ForkContext {
9
+ /** The conversation so far, as the caller's live array — copied, never aliased. */
10
+ messages: Message[];
11
+ }
12
+ export declare function runSubTask(input: Record<string, unknown>, options: AgentLoopOptions, agentTypes: AgentType[], started: {
13
+ value: boolean;
14
+ } | undefined, runChild: (messages: Message[], options: AgentLoopOptions) => Promise<Message[]>, fork?: ForkContext): Promise<ToolResult>;
15
+ //# sourceMappingURL=subTask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subTask.d.ts","sourceRoot":"","sources":["../../src/agent/subTask.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAiB,MAAM,UAAU,CAAC;AAGrF,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAezF;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,SAAS,EAAE,EAQvB,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAEvC,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,EAIhF,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,CAAC,CAmvBrB"}