@ikarmic/forge-core 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/agent/agent.d.ts +8 -0
  2. package/dist/agent/agent.js +123 -22
  3. package/dist/agent/background.d.ts +58 -0
  4. package/dist/agent/background.js +139 -0
  5. package/dist/agent/enhance-prompt.d.ts +3 -1
  6. package/dist/agent/enhance-prompt.js +6 -2
  7. package/dist/agent/goal.d.ts +54 -0
  8. package/dist/agent/goal.js +65 -0
  9. package/dist/agents/agent-files.d.ts +43 -0
  10. package/dist/agents/agent-files.js +109 -0
  11. package/dist/checkpoints/store.js +5 -1
  12. package/dist/commands/custom-commands.d.ts +9 -4
  13. package/dist/commands/custom-commands.js +13 -2
  14. package/dist/config-file.d.ts +43 -0
  15. package/dist/config-file.js +249 -5
  16. package/dist/config-merge.d.ts +13 -0
  17. package/dist/config-merge.js +67 -0
  18. package/dist/config-schema.d.ts +340 -0
  19. package/dist/config-schema.js +126 -0
  20. package/dist/config.js +2 -1
  21. package/dist/context/compaction.js +4 -0
  22. package/dist/context/mask.js +1 -1
  23. package/dist/custom-providers.d.ts +95 -0
  24. package/dist/custom-providers.js +120 -0
  25. package/dist/git/auto-commit.d.ts +2 -2
  26. package/dist/git/auto-commit.js +5 -4
  27. package/dist/github/github-tools.d.ts +2 -2
  28. package/dist/hooks/runner.js +3 -2
  29. package/dist/import/claude-code.d.ts +39 -0
  30. package/dist/import/claude-code.js +254 -0
  31. package/dist/index.d.ts +32 -4
  32. package/dist/index.js +24 -3
  33. package/dist/lsp/client.d.ts +5 -0
  34. package/dist/lsp/client.js +39 -3
  35. package/dist/mcp/index.d.ts +5 -1
  36. package/dist/mcp/index.js +3 -1
  37. package/dist/mcp/manager.d.ts +17 -3
  38. package/dist/mcp/manager.js +27 -2
  39. package/dist/mcp/marketplace.d.ts +2 -0
  40. package/dist/mcp/marketplace.js +10 -4
  41. package/dist/mcp/mcp-client.d.ts +12 -2
  42. package/dist/mcp/mcp-client.js +74 -17
  43. package/dist/mcp/mcp-config.d.ts +2 -0
  44. package/dist/mcp/mcp-config.js +1 -0
  45. package/dist/mcp/oauth.d.ts +55 -0
  46. package/dist/mcp/oauth.js +197 -0
  47. package/dist/mcp/resource-tools.d.ts +8 -0
  48. package/dist/mcp/resource-tools.js +56 -0
  49. package/dist/memory/session-digest.d.ts +12 -0
  50. package/dist/memory/session-digest.js +47 -0
  51. package/dist/memory/stale.js +2 -1
  52. package/dist/model-alias.d.ts +18 -2
  53. package/dist/model-alias.js +27 -3
  54. package/dist/model-catalog.js +0 -1
  55. package/dist/model-list.js +2 -1
  56. package/dist/model-overrides.d.ts +30 -0
  57. package/dist/model-overrides.js +48 -0
  58. package/dist/orchestrator/specialists-registry.js +2 -3
  59. package/dist/orchestrator/specialists.js +4 -4
  60. package/dist/policy/ca-certs.d.ts +8 -0
  61. package/dist/policy/ca-certs.js +30 -0
  62. package/dist/policy/proxy.d.ts +10 -3
  63. package/dist/policy/proxy.js +33 -6
  64. package/dist/pricing.js +4 -0
  65. package/dist/providers.d.ts +7 -0
  66. package/dist/providers.js +39 -5
  67. package/dist/semantic/embedder.d.ts +15 -0
  68. package/dist/semantic/embedder.js +25 -0
  69. package/dist/semantic/semantic-index.d.ts +10 -2
  70. package/dist/semantic/semantic-index.js +16 -7
  71. package/dist/sessions/session-store.js +2 -1
  72. package/dist/skills/builtin.d.ts +2 -0
  73. package/dist/skills/builtin.js +68 -0
  74. package/dist/skills/index.d.ts +2 -0
  75. package/dist/skills/index.js +2 -0
  76. package/dist/skills/loader.d.ts +10 -3
  77. package/dist/skills/loader.js +20 -7
  78. package/dist/skills/marketplace.js +2 -1
  79. package/dist/skills/remote.d.ts +14 -0
  80. package/dist/skills/remote.js +131 -0
  81. package/dist/skills/types.d.ts +10 -0
  82. package/dist/steering/extra-instructions.d.ts +8 -0
  83. package/dist/steering/extra-instructions.js +104 -0
  84. package/dist/steering/folder-instructions.d.ts +8 -0
  85. package/dist/steering/folder-instructions.js +28 -0
  86. package/dist/steering/select.js +2 -1
  87. package/dist/timed-fetch.d.ts +12 -0
  88. package/dist/timed-fetch.js +83 -0
  89. package/dist/tools/apply-patch.d.ts +52 -0
  90. package/dist/tools/apply-patch.js +197 -0
  91. package/dist/tools/ask-user.d.ts +27 -0
  92. package/dist/tools/ask-user.js +32 -0
  93. package/dist/tools/browser-tools.d.ts +6 -6
  94. package/dist/tools/browser-tools.js +3 -13
  95. package/dist/tools/codebase-search.d.ts +4 -2
  96. package/dist/tools/codebase-search.js +2 -2
  97. package/dist/tools/edit-file.js +5 -3
  98. package/dist/tools/fetch-url.d.ts +7 -0
  99. package/dist/tools/fetch-url.js +52 -19
  100. package/dist/tools/find-symbol.d.ts +2 -2
  101. package/dist/tools/ignore-rules.js +2 -1
  102. package/dist/tools/index.d.ts +5 -1
  103. package/dist/tools/index.js +10 -1
  104. package/dist/tools/memory.d.ts +6 -6
  105. package/dist/tools/notebook.d.ts +2 -2
  106. package/dist/tools/permission-rules.d.ts +32 -0
  107. package/dist/tools/permission-rules.js +182 -0
  108. package/dist/tools/read-file.d.ts +5 -0
  109. package/dist/tools/read-file.js +7 -2
  110. package/dist/tools/read-only.d.ts +1 -1
  111. package/dist/tools/read-only.js +2 -0
  112. package/dist/tools/run-command.d.ts +4 -2
  113. package/dist/tools/run-command.js +12 -8
  114. package/dist/tools/sandbox-loader.js +3 -13
  115. package/dist/tools/search-files.d.ts +8 -2
  116. package/dist/tools/search-files.js +43 -0
  117. package/dist/tools/shared.d.ts +5 -0
  118. package/dist/tools/shared.js +30 -2
  119. package/dist/tools/shell-env.d.ts +7 -0
  120. package/dist/tools/shell-env.js +9 -0
  121. package/dist/tools/sub-agent-wait.d.ts +29 -0
  122. package/dist/tools/sub-agent-wait.js +60 -0
  123. package/dist/tools/sub-agent.d.ts +50 -4
  124. package/dist/tools/sub-agent.js +210 -125
  125. package/dist/tools/text-encoding.d.ts +10 -0
  126. package/dist/tools/text-encoding.js +40 -0
  127. package/dist/tools/todo.d.ts +36 -0
  128. package/dist/tools/todo.js +42 -0
  129. package/dist/tools/write-file.js +15 -9
  130. package/dist/tools/write-tools.d.ts +12 -0
  131. package/dist/tools/write-tools.js +18 -0
  132. package/dist/types.d.ts +73 -0
  133. package/dist/worktree/worktree.d.ts +3 -0
  134. package/dist/worktree/worktree.js +22 -0
  135. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The tools that change files. Anything that lists "the write tools" (read-only modes, plan and ask modes, dry
3
+ * runs, checkpoints, permission rules) must include every one of these; `tests/write-tools-guard.test.ts` scans
4
+ * the code for lists that do not. Add a new file-changing tool here and in that scan's failures.
5
+ */
6
+ export declare const FILE_WRITE_TOOL_NAMES: readonly ["write_file", "edit_file", "edit_notebook", "apply_patch"];
7
+ /**
8
+ * The files a tool call works on: every file of an `apply_patch` (its paths are inside the patch text), else the
9
+ * call's `path`. Anything that reacts to "a file was written or read" (auto-lint, steering, changed-file lists) uses
10
+ * this, never `args.path` alone.
11
+ */
12
+ export declare function filesTouchedBy(toolName: string, args: unknown): string[];
@@ -0,0 +1,18 @@
1
+ import { patchedPaths } from './apply-patch.js';
2
+ /**
3
+ * The tools that change files. Anything that lists "the write tools" (read-only modes, plan and ask modes, dry
4
+ * runs, checkpoints, permission rules) must include every one of these; `tests/write-tools-guard.test.ts` scans
5
+ * the code for lists that do not. Add a new file-changing tool here and in that scan's failures.
6
+ */
7
+ export const FILE_WRITE_TOOL_NAMES = ['write_file', 'edit_file', 'edit_notebook', 'apply_patch'];
8
+ /**
9
+ * The files a tool call works on: every file of an `apply_patch` (its paths are inside the patch text), else the
10
+ * call's `path`. Anything that reacts to "a file was written or read" (auto-lint, steering, changed-file lists) uses
11
+ * this, never `args.path` alone.
12
+ */
13
+ export function filesTouchedBy(toolName, args) {
14
+ const a = (args ?? {});
15
+ if (toolName === 'apply_patch')
16
+ return typeof a.patch === 'string' ? patchedPaths(a.patch) : [];
17
+ return typeof a.path === 'string' ? [a.path] : [];
18
+ }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ModelMessage, LanguageModel } from 'ai';
2
2
  import type { z } from 'zod';
3
+ import type { BackgroundAgents } from './agent/background.js';
3
4
  import type { Attachment } from './attachments.js';
4
5
  import type { SkillRegistry } from './skills/types.js';
5
6
  /**
@@ -20,6 +21,11 @@ export type ProviderName = 'openai' | 'anthropic' | 'google' | 'ollama' | 'openr
20
21
  export interface ModelConfig {
21
22
  provider: ProviderName;
22
23
  model: string;
24
+ /**
25
+ * A named provider (preset or `customProviders`) resolved to where the call goes. Set together with
26
+ * `provider: 'openai-compatible'`; `createLanguageModel` then ignores the OPENAI_COMPATIBLE_* environment.
27
+ */
28
+ endpoint?: import('./custom-providers.js').ProviderEndpoint;
23
29
  /**
24
30
  * Reasoning effort for models that support it. Passed straight through
25
31
  * to the AI SDK's own unified `reasoning` call option: no per-provider
@@ -27,6 +33,8 @@ export interface ModelConfig {
27
33
  * behavior, unchanged from today.
28
34
  */
29
35
  reasoningEffort?: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
36
+ /** Sampling temperature (0 to 2); omitted = the provider's default. Set by an agent file. */
37
+ temperature?: number;
30
38
  }
31
39
  /**
32
40
  * A tool definition as the agent sees it: a name, a Zod parameter schema, and
@@ -34,6 +42,18 @@ export interface ModelConfig {
34
42
  * so it can decide whether to expose the `execute` function (autonomous mode)
35
43
  * or strip it (interactive mode where approval is required).
36
44
  */
45
+ /** A question for the person (`ask_user`). */
46
+ export interface UserQuestion {
47
+ question: string;
48
+ options?: string[];
49
+ allowCustom?: boolean;
50
+ }
51
+ /** One step of the agent's checklist (`todo_write`). */
52
+ export interface TodoItem {
53
+ id: string;
54
+ content: string;
55
+ status: 'pending' | 'in_progress' | 'completed';
56
+ }
37
57
  export interface ToolDefinition<TArgs = unknown, TResult = unknown> {
38
58
  name: string;
39
59
  description: string;
@@ -44,6 +64,8 @@ export interface ToolDefinition<TArgs = unknown, TResult = unknown> {
44
64
  * approval even in `--auto` mode unless the user passes `--yes-destructive`.
45
65
  */
46
66
  destructive?: boolean;
67
+ /** True for tools that only change the session's own bookkeeping (the todo list): never asked about, whatever the mode. */
68
+ noApproval?: boolean;
47
69
  /**
48
70
  * Optional text shown in the approval prompt so the user sees what the call
49
71
  * will change (for example a diff). Failures are swallowed: no preview, still asked.
@@ -84,8 +106,28 @@ export interface ToolContext {
84
106
  * the default `child_process` runner.
85
107
  */
86
108
  commandRunner?: CommandRunner;
109
+ /** The model sub-agents use instead of the parent's (`AgentConfig.subAgentModel`). */
110
+ subAgentModel?: ModelConfig;
111
+ /** Folder `AGENTS.md` paths already shown in this run; `read_file` adds each folder's file once. */
112
+ shownFolderInstructions?: Set<string>;
113
+ /** Longest output `run_command` returns per stream before cutting the middle (`AgentConfig.maxToolOutputChars`). */
114
+ maxOutputChars?: number;
115
+ /** Asks the person a question (`ask_user`); absent when no surface can answer. */
116
+ askUser?: (question: UserQuestion, signal?: AbortSignal) => Promise<string>;
117
+ /** The run's todo list. `set` also tells the UI (`todo_updated`). Supplied by the Agent. */
118
+ todos?: {
119
+ get(): TodoItem[];
120
+ set(todos: TodoItem[]): void;
121
+ };
87
122
  /** The run's abort signal; long-running tools (run_command) stop when it fires. */
88
123
  abortSignal?: AbortSignal;
124
+ /** The tools the parent runs without asking; a sub-agent starts from the same list (it has no one to ask). */
125
+ autoApproveTools?: readonly string[];
126
+ /** The parent's permission rules and tool sandbox, handed to a sub-agent so it cannot step around them. */
127
+ permissionRules?: import('./tools/permission-rules.js').PermissionConfig;
128
+ toolSandbox?: ToolSandbox;
129
+ /** Where `sub_agent { background: true }` runs go; absent where no surface can show them (the child then runs to completion as usual). */
130
+ background?: BackgroundAgents;
89
131
  /** Longest body `memory_write` accepts, in characters; absent = the default of 1200. */
90
132
  memoryMaxBodyChars?: number;
91
133
  /** A command that outlives its timeout must be stopped, not left running (verification checks; a dev server must not be). */
@@ -233,6 +275,9 @@ export type AgentEvent = {
233
275
  name: string;
234
276
  id: string;
235
277
  args: unknown;
278
+ } | {
279
+ type: 'todo_updated';
280
+ todos: TodoItem[];
236
281
  } | {
237
282
  type: 'iteration';
238
283
  iteration: number;
@@ -514,6 +559,12 @@ export interface AgentConfig {
514
559
  * working directory into the system prompt.
515
560
  */
516
561
  disableProjectInstructions?: boolean;
562
+ /** Extra instruction sources (files, globs, URLs) from the user's `instructions` setting. */
563
+ extraInstructions?: string[];
564
+ /** What connected MCP servers say about using them (see `mcpInstructionsBlock`); added to the system prompt as it is. */
565
+ mcpInstructions?: string;
566
+ /** Longest `run_command` output per stream, in characters. Default 200000. */
567
+ maxToolOutputChars?: number;
517
568
  /**
518
569
  * A ready-made `<project-commands>` block (see `formatProjectCommandsBlock`) telling the model how
519
570
  * this project is checked and built. Added to the system prompt after the project instructions.
@@ -609,6 +660,9 @@ export interface AgentConfig {
609
660
  */
610
661
  semanticSearch?: {
611
662
  embedder?: import('./semantic/embedder.js').Embedder;
663
+ extensions?: string[];
664
+ minScore?: number;
665
+ defaultLimit?: number;
612
666
  };
613
667
  /**
614
668
  * After the model finishes a run that edited files, run these commands and
@@ -625,6 +679,13 @@ export interface AgentConfig {
625
679
  * "a destructive tool is never auto-approved"; see `isListedCommand`.
626
680
  */
627
681
  autoApproveCommands?: string[];
682
+ /**
683
+ * Ordered permission rules (`read`, `edit`, `bash`, `webfetch`, `websearch`, `task`, `external_directory`, `*`):
684
+ * the last matching rule wins; `deny` blocks the call, `ask` always prompts (even in `auto` mode), `allow` runs it
685
+ * without a prompt. `.env` reads and commands that reach outside `cwd` prompt unless a rule says otherwise.
686
+ * See tools/permission-rules.ts.
687
+ */
688
+ permissionRules?: import('./tools/permission-rules.js').PermissionConfig;
628
689
  /**
629
690
  * Put a token-budgeted outline of the codebase (files and their top symbol
630
691
  * signatures, ranked by how much other code depends on them) in the system prompt.
@@ -669,6 +730,18 @@ export interface AgentConfig {
669
730
  modelRouter?: ModelRouter;
670
731
  /** Replace how `run_command` executes. See {@link CommandRunner}. */
671
732
  commandRunner?: CommandRunner;
733
+ /**
734
+ * Answers `ask_user`. When absent, or when `auto` is on, the model is not offered the tool.
735
+ */
736
+ askUser?: (question: UserQuestion, signal?: AbortSignal) => Promise<string>;
737
+ /** The model `sub_agent` children run on; the parent's model when unset. */
738
+ subAgentModel?: ModelConfig;
739
+ /** Where background sub-agents are kept between messages. Without it `sub_agent { background: true }` runs the child to completion. */
740
+ backgroundAgents?: BackgroundAgents;
741
+ /** The todo list a run starts with (the chat keeps it between turns). */
742
+ initialTodos?: TodoItem[];
743
+ /** Never offer `ask_user`, even when `askUser` is set (a goal run must not stop to ask). */
744
+ disableAskUser?: boolean;
672
745
  /** Custom approval callback. If absent, uses an internal yes/no gate. */
673
746
  approve?: (event: ApprovalRequest) => Promise<ApprovalDecision> | ApprovalDecision;
674
747
  /** Abort signal. */
@@ -237,6 +237,7 @@ export declare class WorktreeManager {
237
237
  * clean up so we don't leak orphan directories.
238
238
  */
239
239
  create(opts?: CreateOptions): Promise<WorktreeInfo>;
240
+ private createNow;
240
241
  /**
241
242
  * Remove a worktree previously created by this manager. By default
242
243
  * refuses if the worktree is dirty (uncommitted changes); pass
@@ -252,6 +253,7 @@ export declare class WorktreeManager {
252
253
  * outcome (e.g. emit "removed worktree + branch X" on the CLI).
253
254
  */
254
255
  remove(worktreePath: string, opts?: RemoveOptions): Promise<RemoveResult>;
256
+ private removeNow;
255
257
  /**
256
258
  * Best-effort cleanup of every worktree this manager created. Used
257
259
  * by the sub_agent tool's finally block so a child crash doesn't
@@ -270,6 +272,7 @@ export declare class WorktreeManager {
270
272
  * leak.
271
273
  */
272
274
  pruneStale(): Promise<number>;
275
+ private pruneStaleNow;
273
276
  /**
274
277
  * List worktrees under our `.forge/worktrees/` directory. Returns
275
278
  * the empty array when none exist or git isn't available.
@@ -284,6 +284,9 @@ export class WorktreeManager {
284
284
  * clean up so we don't leak orphan directories.
285
285
  */
286
286
  async create(opts = {}) {
287
+ return onRepoQueue(this.repoRoot, () => this.createNow(opts));
288
+ }
289
+ async createNow(opts) {
287
290
  // Generate a label + branch name first so we can pass them to git.
288
291
  const label = opts.label ? sanitizeLabel(opts.label) : randomLabel();
289
292
  const branch = opts.branchName ?? `forge/${label}-${await shortSha(this.repoRoot)}`;
@@ -341,6 +344,9 @@ export class WorktreeManager {
341
344
  * outcome (e.g. emit "removed worktree + branch X" on the CLI).
342
345
  */
343
346
  async remove(worktreePath, opts = {}) {
347
+ return onRepoQueue(this.repoRoot, () => this.removeNow(worktreePath, opts));
348
+ }
349
+ async removeNow(worktreePath, opts) {
344
350
  const resolved = path.resolve(worktreePath);
345
351
  const normalizedRoot = path.resolve(this.worktreesRoot);
346
352
  if (!resolved.startsWith(normalizedRoot + path.sep) && resolved !== normalizedRoot) {
@@ -395,6 +401,9 @@ export class WorktreeManager {
395
401
  * leak.
396
402
  */
397
403
  async pruneStale() {
404
+ return onRepoQueue(this.repoRoot, () => this.pruneStaleNow());
405
+ }
406
+ async pruneStaleNow() {
398
407
  let pruned = 0;
399
408
  try {
400
409
  const { stdout } = await runGit(this.repoRoot, [
@@ -626,6 +635,19 @@ export function canonicalPath(p) {
626
635
  * with the stderr message in the Error. We use `execFile` (not `exec`)
627
636
  * so user-controlled input can't shell-inject.
628
637
  */
638
+ const repoQueues = new Map();
639
+ /**
640
+ * Run `work` after every earlier queued worktree change of the same repository has finished. Parallel steps create
641
+ * and remove worktrees at the same moment, and two `git worktree add` / `remove` calls on one repository fight over
642
+ * git's lock files; one of them then fails at random, mostly under load. The agents still run in parallel: only these
643
+ * short git changes take turns.
644
+ */
645
+ function onRepoQueue(repoRoot, work) {
646
+ const key = canonicalPath(repoRoot);
647
+ const next = (repoQueues.get(key) ?? Promise.resolve()).then(work, work);
648
+ repoQueues.set(key, next.catch(() => undefined));
649
+ return next;
650
+ }
629
651
  async function runGit(cwd, args) {
630
652
  return await execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });
631
653
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikarmic/forge-core",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Agent engine for IKarmic AI Forge. Model loop, tools, MCP, sandbox, memory, compaction, plan mode, skills, hooks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",