@ikarmic/forge-core 5.1.1 → 5.3.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 (81) hide show
  1. package/dist/agent/agent.js +208 -57
  2. package/dist/agent/enhance-prompt.js +2 -1
  3. package/dist/agent/goal.d.ts +5 -8
  4. package/dist/agent/prompts.d.ts +15 -0
  5. package/dist/agent/prompts.js +34 -0
  6. package/dist/agent/race-abort.d.ts +8 -0
  7. package/dist/agent/race-abort.js +24 -0
  8. package/dist/agent/retry.d.ts +50 -0
  9. package/dist/agent/retry.js +192 -0
  10. package/dist/agent/shell-command.d.ts +23 -0
  11. package/dist/agent/shell-command.js +93 -0
  12. package/dist/agent/stack-facts.d.ts +23 -0
  13. package/dist/agent/stack-facts.js +150 -0
  14. package/dist/catalog/models-dev-snapshot.d.ts +1 -0
  15. package/dist/catalog/models-dev-snapshot.js +3 -0
  16. package/dist/catalog/models-dev.d.ts +35 -0
  17. package/dist/catalog/models-dev.js +143 -0
  18. package/dist/code-index/parser.js +48 -3
  19. package/dist/context/compaction.js +8 -2
  20. package/dist/git/auto-commit.d.ts +4 -2
  21. package/dist/git/auto-commit.js +16 -5
  22. package/dist/github/github-tools.d.ts +2 -18
  23. package/dist/index.d.ts +11 -3
  24. package/dist/index.js +9 -2
  25. package/dist/journey/answers-block.js +14 -1
  26. package/dist/journey/offer.js +2 -1
  27. package/dist/mcp/json-schema-to-zod.js +12 -1
  28. package/dist/memory/session-digest.js +2 -1
  29. package/dist/orchestrator/index.d.ts +1 -1
  30. package/dist/orchestrator/index.js +1 -1
  31. package/dist/orchestrator/orchestrator.d.ts +45 -6
  32. package/dist/orchestrator/orchestrator.js +62 -12
  33. package/dist/orchestrator/router.d.ts +1 -1
  34. package/dist/orchestrator/router.js +4 -1
  35. package/dist/orchestrator/specialists.d.ts +6 -1
  36. package/dist/orchestrator/specialists.js +165 -68
  37. package/dist/orchestrator/types.d.ts +1 -1
  38. package/dist/policy/policy.d.ts +6 -59
  39. package/dist/pricing.d.ts +2 -0
  40. package/dist/pricing.js +47 -16
  41. package/dist/structured/schema.js +9 -12
  42. package/dist/tools/apply-patch.d.ts +1 -5
  43. package/dist/tools/ask-user.d.ts +2 -10
  44. package/dist/tools/ask-user.js +12 -4
  45. package/dist/tools/browser-tools.d.ts +15 -31
  46. package/dist/tools/codebase-search.d.ts +1 -7
  47. package/dist/tools/command-sandbox.d.ts +24 -0
  48. package/dist/tools/command-sandbox.js +73 -0
  49. package/dist/tools/dependency-audit.d.ts +30 -0
  50. package/dist/tools/dependency-audit.js +197 -0
  51. package/dist/tools/edit-file.d.ts +9 -15
  52. package/dist/tools/edit-file.js +64 -16
  53. package/dist/tools/fetch-url.d.ts +1 -18
  54. package/dist/tools/find-references.d.ts +1 -5
  55. package/dist/tools/find-symbol.d.ts +10 -8
  56. package/dist/tools/git-diff.d.ts +13 -0
  57. package/dist/tools/git-diff.js +85 -0
  58. package/dist/tools/index.d.ts +3 -1
  59. package/dist/tools/index.js +5 -1
  60. package/dist/tools/list-files.d.ts +1 -19
  61. package/dist/tools/list-symbols.d.ts +1 -5
  62. package/dist/tools/memory.d.ts +23 -43
  63. package/dist/tools/notebook.d.ts +11 -17
  64. package/dist/tools/read-file.d.ts +1 -11
  65. package/dist/tools/read-image.d.ts +1 -12
  66. package/dist/tools/read-only.d.ts +1 -1
  67. package/dist/tools/read-only.js +2 -0
  68. package/dist/tools/run-command.d.ts +1 -17
  69. package/dist/tools/run-command.js +12 -2
  70. package/dist/tools/search-files.d.ts +1 -17
  71. package/dist/tools/search-files.js +8 -49
  72. package/dist/tools/search-worker.d.ts +23 -0
  73. package/dist/tools/search-worker.js +77 -0
  74. package/dist/tools/sub-agent-wait.d.ts +2 -8
  75. package/dist/tools/sub-agent.d.ts +2 -70
  76. package/dist/tools/sub-agent.js +1 -0
  77. package/dist/tools/todo.d.ts +8 -20
  78. package/dist/tools/web-search.d.ts +1 -7
  79. package/dist/tools/write-file.d.ts +1 -7
  80. package/dist/types.d.ts +60 -1
  81. package/package.json +3 -3
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `git_diff`: the current changes, read-only, so Review and Security can see what changed without a
3
+ * `run_command` approval card. It runs one fixed program with fixed flags; the model chooses only a base
4
+ * ref, staged or not, and paths. A base that looks like an option is refused: `git diff --output=<file>`
5
+ * writes a file, and an option passed where a ref belongs is how a model (or a prompt it read) gets there.
6
+ */
7
+ import { execFile } from 'node:child_process';
8
+ import path from 'node:path';
9
+ import { promisify } from 'node:util';
10
+ import { z } from 'zod';
11
+ import { resolveOnPath } from '../lsp/client.js';
12
+ import { resolveAndCheckPath } from './shared.js';
13
+ import { shellEnv } from './shell-env.js';
14
+ const run = promisify(execFile);
15
+ /** The most diff text handed to the model; the rest is cut with a note. */
16
+ export const MAX_DIFF_CHARS = 60_000;
17
+ const params = z.object({
18
+ base: z
19
+ .string()
20
+ .optional()
21
+ .describe('Compare against this ref (a branch, tag, commit or range such as main...HEAD). Default: HEAD, i.e. your uncommitted changes.'),
22
+ staged: z.boolean().optional().describe('Only the staged changes (git diff --cached).'),
23
+ paths: z.array(z.string()).optional().describe('Limit the diff to these files or folders inside the project.'),
24
+ });
25
+ /** A ref git will read as a ref, never as an option: no leading dash, only the characters refs and ranges use. */
26
+ export function isSafeGitRef(ref) {
27
+ return ref.length > 0 && !ref.startsWith('-') && /^[A-Za-z0-9._/~^@{}:-]+$/.test(ref);
28
+ }
29
+ async function git(args, cwd) {
30
+ const env = shellEnv(process.env);
31
+ const program = resolveOnPath('git', env, process.platform) ?? 'git';
32
+ try {
33
+ // No repository code and no lock on the person's index: core.fsmonitor names a program git would run for
34
+ // status, and an optional index refresh can collide with the person's own commit (RB-41).
35
+ const safe = ['-c', 'core.fsmonitor=false', '--no-optional-locks', ...args];
36
+ const { stdout, stderr } = await run(program, safe, { cwd, env, maxBuffer: 64 * 1024 * 1024, windowsHide: true });
37
+ return { ok: true, stdout, stderr };
38
+ }
39
+ catch (err) {
40
+ const e = err;
41
+ return { ok: false, stdout: e.stdout ?? '', stderr: e.stderr ?? e.message };
42
+ }
43
+ }
44
+ function cut(text) {
45
+ if (text.length <= MAX_DIFF_CHARS)
46
+ return text;
47
+ const left = text.length - MAX_DIFF_CHARS;
48
+ return `${text.slice(0, MAX_DIFF_CHARS)}\n[diff cut: ${left} characters left out. Pass paths to see one file at a time.]`;
49
+ }
50
+ export const gitDiffTool = {
51
+ name: 'git_diff',
52
+ description: 'Show the current changes as a unified diff, plus new untracked files. Read-only: it never changes files or the repository. ' +
53
+ 'By default it compares your working tree and staged changes with HEAD; pass base to compare with a branch or commit, staged for staged changes only, paths to narrow it.',
54
+ parameters: params,
55
+ execute: async (args, ctx) => {
56
+ if (args.base !== undefined && !isSafeGitRef(args.base)) {
57
+ return `git_diff refused the base "${args.base}": a base must be a branch, tag, commit or range, and must not start with "-".`;
58
+ }
59
+ const rels = (args.paths ?? []).map((p) => path.relative(ctx.cwd, resolveAndCheckPath(p, ctx)) || '.');
60
+ const inside = await git(['rev-parse', '--is-inside-work-tree'], ctx.cwd);
61
+ if (!inside.ok || inside.stdout.trim() !== 'true')
62
+ return 'This folder is not a git repository, so there is no diff to show.';
63
+ const diffArgs = ['diff', '--no-ext-diff', '--no-textconv', '--no-color'];
64
+ // A repository with no commit has no HEAD: its staged files are the change.
65
+ const hasHead = (await git(['rev-parse', '--verify', '--quiet', 'HEAD'], ctx.cwd)).ok;
66
+ if (args.staged || (!args.base && !hasHead))
67
+ diffArgs.push('--cached');
68
+ else if (!args.base)
69
+ diffArgs.push('HEAD');
70
+ if (args.base)
71
+ diffArgs.push(args.base);
72
+ diffArgs.push('--', ...rels);
73
+ const diff = await git(diffArgs, ctx.cwd);
74
+ if (!diff.ok)
75
+ return `git diff failed: ${diff.stderr.trim()}`;
76
+ const status = await git(['status', '--porcelain=v1', '--', ...rels], ctx.cwd);
77
+ const parts = [diff.stdout.trim() ? diff.stdout : '(no changes in tracked files)'];
78
+ if (status.ok && status.stdout.trim())
79
+ parts.push(`git status:\n${status.stdout}`);
80
+ if (status.ok && /^\?\? /m.test(status.stdout)) {
81
+ parts.push('Untracked files (marked ??) are new and not shown in the diff above: read them with read_file.');
82
+ }
83
+ return cut(parts.join('\n'));
84
+ },
85
+ };
@@ -3,6 +3,8 @@ import { editFileTool } from './edit-file.js';
3
3
  import { fetchUrlTool } from './fetch-url.js';
4
4
  import { findReferencesTool } from './find-references.js';
5
5
  import { findSymbolTool } from './find-symbol.js';
6
+ import { dependencyAuditTool } from './dependency-audit.js';
7
+ import { gitDiffTool } from './git-diff.js';
6
8
  import { listFilesTool } from './list-files.js';
7
9
  import { listSymbolsTool } from './list-symbols.js';
8
10
  import { memoryForgetTool, memoryReadTool, memoryWriteTool } from './memory.js';
@@ -30,7 +32,7 @@ type AnyToolDefinition = ToolDefinition<any, any>;
30
32
  * intentional: the model sees this order in its tool list.
31
33
  */
32
34
  export declare const BUILTIN_TOOLS: readonly AnyToolDefinition[];
33
- export { applyPatchTool, askUserTool, editFileTool, editNotebookTool, fetchUrlTool, findReferencesTool, findSymbolTool, listFilesTool, listSymbolsTool, memoryForgetTool, memoryReadTool, memoryWriteTool, readFileTool, readImageTool, runCommandTool, searchFilesTool, subAgentTool, subAgentWaitTool, todoReadTool, todoWriteTool, writeFileTool, };
35
+ export { applyPatchTool, askUserTool, dependencyAuditTool, editFileTool, editNotebookTool, fetchUrlTool, findReferencesTool, findSymbolTool, gitDiffTool, listFilesTool, listSymbolsTool, memoryForgetTool, memoryReadTool, memoryWriteTool, readFileTool, readImageTool, runCommandTool, searchFilesTool, subAgentTool, subAgentWaitTool, todoReadTool, todoWriteTool, writeFileTool, };
34
36
  export type { ToolContext, ToolDefinition, ToolPolicy, ToolSandbox } from '../types.js';
35
37
  export { checkPolicyViolation, globToRegex, mergeToolSandbox, normalizePathPattern, pathMatchesAny } from './policy.js';
36
38
  export { redact, countRedactions, buildRules } from './redact.js';
@@ -3,6 +3,8 @@ import { editFileTool } from './edit-file.js';
3
3
  import { fetchUrlTool } from './fetch-url.js';
4
4
  import { findReferencesTool } from './find-references.js';
5
5
  import { findSymbolTool } from './find-symbol.js';
6
+ import { dependencyAuditTool } from './dependency-audit.js';
7
+ import { gitDiffTool } from './git-diff.js';
6
8
  import { listFilesTool } from './list-files.js';
7
9
  import { listSymbolsTool } from './list-symbols.js';
8
10
  import { memoryForgetTool, memoryReadTool, memoryWriteTool } from './memory.js';
@@ -30,6 +32,8 @@ export const BUILTIN_TOOLS = [
30
32
  findSymbolTool,
31
33
  listSymbolsTool,
32
34
  findReferencesTool,
35
+ gitDiffTool,
36
+ dependencyAuditTool,
33
37
  writeFileTool,
34
38
  editFileTool,
35
39
  applyPatchTool,
@@ -45,7 +49,7 @@ export const BUILTIN_TOOLS = [
45
49
  todoReadTool,
46
50
  askUserTool,
47
51
  ];
48
- export { applyPatchTool, askUserTool, editFileTool, editNotebookTool, fetchUrlTool, findReferencesTool, findSymbolTool, listFilesTool, listSymbolsTool, memoryForgetTool, memoryReadTool, memoryWriteTool, readFileTool, readImageTool, runCommandTool, searchFilesTool, subAgentTool, subAgentWaitTool, todoReadTool, todoWriteTool, writeFileTool, };
52
+ export { applyPatchTool, askUserTool, dependencyAuditTool, editFileTool, editNotebookTool, fetchUrlTool, findReferencesTool, findSymbolTool, gitDiffTool, listFilesTool, listSymbolsTool, memoryForgetTool, memoryReadTool, memoryWriteTool, readFileTool, readImageTool, runCommandTool, searchFilesTool, subAgentTool, subAgentWaitTool, todoReadTool, todoWriteTool, writeFileTool, };
49
53
  export { checkPolicyViolation, globToRegex, mergeToolSandbox, normalizePathPattern, pathMatchesAny } from './policy.js';
50
54
  export { redact, countRedactions, buildRules } from './redact.js';
51
55
  export { ToolResultCache, buildCacheKey, cachedExecute } from './result-cache.js';
@@ -2,30 +2,12 @@ import { z } from 'zod';
2
2
  import type { ToolDefinition } from '../types.js';
3
3
  declare const params: z.ZodObject<{
4
4
  path: z.ZodOptional<z.ZodString>;
5
- /** Maximum entries to return. Defaults to 500. */
6
5
  max_entries: z.ZodOptional<z.ZodNumber>;
7
- /** Include dotfiles. Default false. */
8
6
  include_hidden: z.ZodOptional<z.ZodBoolean>;
9
7
  include_ignored: z.ZodOptional<z.ZodBoolean>;
10
- /** Recurse into subdirectories. Default false. */
11
8
  recursive: z.ZodOptional<z.ZodBoolean>;
12
- /** Max recursion depth when recursive=true. Default 4. */
13
9
  max_depth: z.ZodOptional<z.ZodNumber>;
14
- }, "strip", z.ZodTypeAny, {
15
- path?: string | undefined;
16
- recursive?: boolean | undefined;
17
- max_entries?: number | undefined;
18
- include_hidden?: boolean | undefined;
19
- include_ignored?: boolean | undefined;
20
- max_depth?: number | undefined;
21
- }, {
22
- path?: string | undefined;
23
- recursive?: boolean | undefined;
24
- max_entries?: number | undefined;
25
- include_hidden?: boolean | undefined;
26
- include_ignored?: boolean | undefined;
27
- max_depth?: number | undefined;
28
- }>;
10
+ }, z.core.$strip>;
29
11
  export interface ListEntry {
30
12
  path: string;
31
13
  type: 'file' | 'directory' | 'other';
@@ -3,11 +3,7 @@ import type { SymbolKind } from '../code-index/types.js';
3
3
  import type { ToolDefinition } from '../types.js';
4
4
  declare const params: z.ZodObject<{
5
5
  path: z.ZodString;
6
- }, "strip", z.ZodTypeAny, {
7
- path: string;
8
- }, {
9
- path: string;
10
- }>;
6
+ }, z.core.$strip>;
11
7
  export interface SymbolOutlineEntry {
12
8
  name: string;
13
9
  kind: SymbolKind;
@@ -2,21 +2,13 @@ import { z } from 'zod';
2
2
  import { type MemoryType } from '../memory/memory-store.js';
3
3
  import type { ToolDefinition } from '../types.js';
4
4
  declare const readParams: z.ZodObject<{
5
- /**
6
- * Which memory to read: 'project' (default), 'global', or 'both'. 'both'
7
- * returns the merged view with project overriding global on heading
8
- * collisions: the same view the system-prompt injector sees.
9
- */
10
- scope: z.ZodOptional<z.ZodEnum<["project", "global", "both"]>>;
11
- /** Optional heading to filter by. Returns just that section's body. */
5
+ scope: z.ZodOptional<z.ZodEnum<{
6
+ project: "project";
7
+ global: "global";
8
+ both: "both";
9
+ }>>;
12
10
  section: z.ZodOptional<z.ZodString>;
13
- }, "strip", z.ZodTypeAny, {
14
- section?: string | undefined;
15
- scope?: "project" | "global" | "both" | undefined;
16
- }, {
17
- section?: string | undefined;
18
- scope?: "project" | "global" | "both" | undefined;
19
- }>;
11
+ }, z.core.$strip>;
20
12
  export interface MemoryReadResult {
21
13
  scope: 'project' | 'global' | 'both';
22
14
  content: string;
@@ -31,30 +23,21 @@ export declare const memoryReadTool: ToolDefinition<z.infer<typeof readParams>,
31
23
  /** Longest body `memory_write` accepts. Memory holds short facts, not documents. */
32
24
  export declare const MAX_BODY_CHARS = 1200;
33
25
  declare const writeParams: z.ZodObject<{
34
- /** Where to write: 'project' (default) or 'global'. */
35
- scope: z.ZodOptional<z.ZodEnum<["project", "global"]>>;
36
- /** Section heading. If the heading already exists (ignoring case and spacing), replaces its body. */
26
+ scope: z.ZodOptional<z.ZodEnum<{
27
+ project: "project";
28
+ global: "global";
29
+ }>>;
37
30
  heading: z.ZodString;
38
- /** Section body. Markdown allowed. */
39
31
  body: z.ZodString;
40
- type: z.ZodOptional<z.ZodEnum<["convention", "decision", "correction", "reference"]>>;
41
- refs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
32
+ type: z.ZodOptional<z.ZodEnum<{
33
+ convention: "convention";
34
+ decision: "decision";
35
+ correction: "correction";
36
+ reference: "reference";
37
+ }>>;
38
+ refs: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
39
  pin: z.ZodOptional<z.ZodBoolean>;
43
- }, "strip", z.ZodTypeAny, {
44
- body: string;
45
- heading: string;
46
- type?: "convention" | "decision" | "correction" | "reference" | undefined;
47
- refs?: string[] | undefined;
48
- pin?: boolean | undefined;
49
- scope?: "project" | "global" | undefined;
50
- }, {
51
- body: string;
52
- heading: string;
53
- type?: "convention" | "decision" | "correction" | "reference" | undefined;
54
- refs?: string[] | undefined;
55
- pin?: boolean | undefined;
56
- scope?: "project" | "global" | undefined;
57
- }>;
40
+ }, z.core.$strip>;
58
41
  export interface MemoryWriteResult {
59
42
  scope: 'project' | 'global';
60
43
  path: string;
@@ -69,15 +52,12 @@ export interface MemoryWriteResult {
69
52
  }
70
53
  export declare const memoryWriteTool: ToolDefinition<z.infer<typeof writeParams>, MemoryWriteResult>;
71
54
  declare const forgetParams: z.ZodObject<{
72
- scope: z.ZodOptional<z.ZodEnum<["project", "global"]>>;
55
+ scope: z.ZodOptional<z.ZodEnum<{
56
+ project: "project";
57
+ global: "global";
58
+ }>>;
73
59
  heading: z.ZodString;
74
- }, "strip", z.ZodTypeAny, {
75
- heading: string;
76
- scope?: "project" | "global" | undefined;
77
- }, {
78
- heading: string;
79
- scope?: "project" | "global" | undefined;
80
- }>;
60
+ }, z.core.$strip>;
81
61
  export interface MemoryForgetResult {
82
62
  scope: 'project' | 'global';
83
63
  heading: string;
@@ -39,26 +39,20 @@ export declare function outputText(output: unknown): string;
39
39
  export declare function renderNotebook(nb: Notebook): string;
40
40
  declare const params: z.ZodObject<{
41
41
  path: z.ZodString;
42
- mode: z.ZodOptional<z.ZodEnum<["replace", "insert", "delete"]>>;
42
+ mode: z.ZodOptional<z.ZodEnum<{
43
+ replace: "replace";
44
+ delete: "delete";
45
+ insert: "insert";
46
+ }>>;
43
47
  cell_index: z.ZodOptional<z.ZodNumber>;
44
48
  cell_id: z.ZodOptional<z.ZodString>;
45
49
  source: z.ZodOptional<z.ZodString>;
46
- cell_type: z.ZodOptional<z.ZodEnum<["code", "markdown", "raw"]>>;
47
- }, "strip", z.ZodTypeAny, {
48
- path: string;
49
- mode?: "replace" | "delete" | "insert" | undefined;
50
- source?: string | undefined;
51
- cell_type?: "code" | "raw" | "markdown" | undefined;
52
- cell_index?: number | undefined;
53
- cell_id?: string | undefined;
54
- }, {
55
- path: string;
56
- mode?: "replace" | "delete" | "insert" | undefined;
57
- source?: string | undefined;
58
- cell_type?: "code" | "raw" | "markdown" | undefined;
59
- cell_index?: number | undefined;
60
- cell_id?: string | undefined;
61
- }>;
50
+ cell_type: z.ZodOptional<z.ZodEnum<{
51
+ code: "code";
52
+ raw: "raw";
53
+ markdown: "markdown";
54
+ }>>;
55
+ }, z.core.$strip>;
62
56
  export interface EditNotebookResult {
63
57
  path: string;
64
58
  mode: 'replace' | 'insert' | 'delete';
@@ -2,19 +2,9 @@ import { z } from 'zod';
2
2
  import type { ToolDefinition } from '../types.js';
3
3
  declare const params: z.ZodObject<{
4
4
  path: z.ZodString;
5
- /** 1-based line number to start reading from. Omit to start at the beginning. */
6
5
  offset: z.ZodOptional<z.ZodNumber>;
7
- /** Maximum number of lines to return. Defaults to 2000. */
8
6
  max_lines: z.ZodOptional<z.ZodNumber>;
9
- }, "strip", z.ZodTypeAny, {
10
- path: string;
11
- offset?: number | undefined;
12
- max_lines?: number | undefined;
13
- }, {
14
- path: string;
15
- offset?: number | undefined;
16
- max_lines?: number | undefined;
17
- }>;
7
+ }, z.core.$strip>;
18
8
  export interface ReadFileResult {
19
9
  path: string;
20
10
  totalLines: number;
@@ -26,19 +26,8 @@ import { z } from 'zod';
26
26
  import type { ToolDefinition } from '../types.js';
27
27
  declare const params: z.ZodObject<{
28
28
  path: z.ZodString;
29
- /**
30
- * Maximum bytes to read. The tool errors if the file exceeds this
31
- * limit; the default is 5 MB which covers most screenshots and
32
- * diagrams without flooding the model context.
33
- */
34
29
  max_bytes: z.ZodOptional<z.ZodNumber>;
35
- }, "strip", z.ZodTypeAny, {
36
- path: string;
37
- max_bytes?: number | undefined;
38
- }, {
39
- path: string;
40
- max_bytes?: number | undefined;
41
- }>;
30
+ }, z.core.$strip>;
42
31
  export interface ReadImageResult {
43
32
  path: string;
44
33
  mime: string;
@@ -3,6 +3,6 @@
3
3
  * anything. Asking before each of them is noise, so they are approved without a prompt by default;
4
4
  * edits, commands, web access and sub-agents still ask.
5
5
  */
6
- export declare const READ_ONLY_TOOLS: readonly ["read_file", "list_files", "search_files", "find_symbol", "list_symbols", "find_references", "codebase_search", "memory_read", "sub_agent_wait"];
6
+ export declare const READ_ONLY_TOOLS: readonly ["read_file", "list_files", "search_files", "find_symbol", "list_symbols", "find_references", "git_diff", "codebase_search", "memory_read", "sub_agent_wait"];
7
7
  /** The tools that run without a prompt: the user's list, or the read-only tools when none is set. */
8
8
  export declare function resolveAutoApprove(configured?: readonly string[]): string[];
@@ -10,6 +10,8 @@ export const READ_ONLY_TOOLS = [
10
10
  'find_symbol',
11
11
  'list_symbols',
12
12
  'find_references',
13
+ // Runs one fixed git command; refuses option-shaped refs.
14
+ 'git_diff',
13
15
  'codebase_search',
14
16
  'memory_read',
15
17
  // Only observes the sub-agents the person already let start.
@@ -2,25 +2,9 @@ import { z } from 'zod';
2
2
  import type { ToolDefinition } from '../types.js';
3
3
  declare const params: z.ZodObject<{
4
4
  command: z.ZodString;
5
- /**
6
- * Optional explicit timeout in ms. Default 60_000 (1 minute). Set to 0 to
7
- * disable (not recommended).
8
- */
9
5
  timeout_ms: z.ZodOptional<z.ZodNumber>;
10
- /**
11
- * Optional working directory override. Must be inside the project root
12
- * unless the configured ToolContext permits otherwise.
13
- */
14
6
  cwd: z.ZodOptional<z.ZodString>;
15
- }, "strip", z.ZodTypeAny, {
16
- command: string;
17
- timeout_ms?: number | undefined;
18
- cwd?: string | undefined;
19
- }, {
20
- command: string;
21
- timeout_ms?: number | undefined;
22
- cwd?: string | undefined;
23
- }>;
7
+ }, z.core.$strip>;
24
8
  export interface RunCommandResult {
25
9
  command: string;
26
10
  exitCode: number | null;
@@ -1,5 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { tmpdir } from 'node:os';
2
3
  import { z } from 'zod';
4
+ import { sandboxCommand } from './command-sandbox.js';
3
5
  import { checkPolicyViolation } from './policy.js';
4
6
  import { resolveAndCheckPath } from './shared.js';
5
7
  import { shellEnv } from './shell-env.js';
@@ -64,10 +66,18 @@ export const runCommandTool = {
64
66
  if (violation)
65
67
  throw new Error(`[sandbox] ${violation}`);
66
68
  const timeoutMs = args.timeout_ms ?? DEFAULT_TIMEOUT_MS;
69
+ // The OS sandbox wraps the command line; the result still reports the command the model asked for.
70
+ let commandLine = args.command;
71
+ if (ctx.commandSandbox) {
72
+ const boxed = sandboxCommand(args.command, { cwd, sandbox: ctx.commandSandbox, platform: process.platform, tmpDir: tmpdir() });
73
+ if (boxed.kind === 'refused')
74
+ throw new Error(`[sandbox] ${boxed.reason}`);
75
+ commandLine = boxed.command;
76
+ }
67
77
  if (ctx.commandRunner) {
68
78
  const started = Date.now();
69
79
  const out = await ctx.commandRunner({
70
- command: args.command,
80
+ command: commandLine,
71
81
  cwd,
72
82
  timeoutMs,
73
83
  ...(ctx.abortSignal ? { signal: ctx.abortSignal } : {}),
@@ -91,7 +101,7 @@ export const runCommandTool = {
91
101
  }
92
102
  return new Promise((resolve, reject) => {
93
103
  const started = Date.now();
94
- const child = spawn(args.command, {
104
+ const child = spawn(commandLine, {
95
105
  shell: true,
96
106
  cwd,
97
107
  // Windows: never run a same-named program the repository ships in its root (see shell-env.ts).
@@ -4,26 +4,10 @@ declare const params: z.ZodObject<{
4
4
  pattern: z.ZodString;
5
5
  path: z.ZodOptional<z.ZodString>;
6
6
  include: z.ZodOptional<z.ZodString>;
7
- /** Maximum matches to return. Default 200. */
8
7
  max_results: z.ZodOptional<z.ZodNumber>;
9
8
  include_ignored: z.ZodOptional<z.ZodBoolean>;
10
- /** Show a few lines of context around each match. */
11
9
  context_lines: z.ZodOptional<z.ZodNumber>;
12
- }, "strip", z.ZodTypeAny, {
13
- pattern: string;
14
- path?: string | undefined;
15
- include?: string | undefined;
16
- max_results?: number | undefined;
17
- include_ignored?: boolean | undefined;
18
- context_lines?: number | undefined;
19
- }, {
20
- pattern: string;
21
- path?: string | undefined;
22
- include?: string | undefined;
23
- max_results?: number | undefined;
24
- include_ignored?: boolean | undefined;
25
- context_lines?: number | undefined;
26
- }>;
10
+ }, z.core.$strip>;
27
11
  export interface SearchMatch {
28
12
  path: string;
29
13
  line: number;
@@ -1,9 +1,9 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import path from 'node:path';
3
2
  import { z } from 'zod';
4
3
  import { globToRegex } from './policy.js';
5
4
  import { IgnoreRules } from './ignore-rules.js';
6
- import { resolveAndCheckPath, truncateByLines, DEFAULT_IGNORED_DIRS } from './shared.js';
5
+ import { DEFAULT_SEARCH_TIMEOUT_MS, matchInWorker } from './search-worker.js';
6
+ import { resolveAndCheckPath, DEFAULT_IGNORED_DIRS } from './shared.js';
7
7
  const params = z.object({
8
8
  pattern: z.string().describe('Regex pattern to search for in file contents.'),
9
9
  path: z
@@ -33,15 +33,10 @@ export const searchFilesTool = {
33
33
  const includeRe = args.include ? globToRegex(args.include) : null;
34
34
  const maxResults = args.max_results ?? 200;
35
35
  const contextLines = args.context_lines ?? 1;
36
- const matches = [];
37
- let truncated = false;
38
- let filesScanned = 0;
36
+ // The walk (I/O and ignore rules) stays here; reading and matching run in a worker with a time limit (RB-22).
37
+ const files = [];
39
38
  const rules = args.include_ignored ? null : IgnoreRules.forRoot(ctx.cwd);
40
39
  async function walk(dir) {
41
- if (matches.length >= maxResults) {
42
- truncated = true;
43
- return;
44
- }
45
40
  const { readdir } = await import('node:fs/promises');
46
41
  let entries;
47
42
  try {
@@ -54,10 +49,6 @@ export const searchFilesTool = {
54
49
  throw err;
55
50
  }
56
51
  for (const entry of entries) {
57
- if (matches.length >= maxResults) {
58
- truncated = true;
59
- return;
60
- }
61
52
  if (entry.name.startsWith('.'))
62
53
  continue;
63
54
  if (DEFAULT_IGNORED_DIRS.has(entry.name))
@@ -72,40 +63,7 @@ export const searchFilesTool = {
72
63
  const rel = path.relative(root, abs).split(path.sep).join('/');
73
64
  if (includeRe && !includeRe.test(rel))
74
65
  continue;
75
- await searchFile(abs, rel);
76
- }
77
- }
78
- }
79
- async function searchFile(abs, rel) {
80
- filesScanned++;
81
- let buf;
82
- try {
83
- buf = await readFile(abs);
84
- }
85
- catch {
86
- return;
87
- }
88
- // Skip binary files.
89
- if (buf.includes(0))
90
- return;
91
- const text = buf.toString('utf8');
92
- const lines = text.split(/\r?\n/);
93
- for (let i = 0; i < lines.length; i++) {
94
- if (matches.length >= maxResults) {
95
- truncated = true;
96
- return;
97
- }
98
- const line = lines[i];
99
- regex.lastIndex = 0;
100
- if (regex.test(line)) {
101
- const start = Math.max(0, i - contextLines);
102
- const end = Math.min(lines.length - 1, i + contextLines);
103
- const block = [];
104
- for (let j = start; j <= end; j++) {
105
- const marker = j === i ? '>' : ' ';
106
- block.push(`${marker} ${String(j + 1).padStart(6)}\t${lines[j]}`);
107
- }
108
- matches.push({ path: rel, line: i + 1, text: truncateByLines(block.join('\n'), 10) });
66
+ files.push({ abs, rel });
109
67
  }
110
68
  }
111
69
  }
@@ -113,12 +71,13 @@ export const searchFilesTool = {
113
71
  if (!stat)
114
72
  throw new Error(`Path not found: ${root}`);
115
73
  if (stat.isFile()) {
116
- await searchFile(root, path.basename(root));
74
+ files.push({ abs: root, rel: path.basename(root) });
117
75
  }
118
76
  else {
119
77
  await walk(root);
120
78
  }
121
- return { pattern: args.pattern, matches, truncated, filesScanned };
79
+ const result = await matchInWorker({ files, source: regex.source, flags: regex.flags, maxResults, contextLines }, { timeoutMs: ctx.searchTimeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS });
80
+ return { pattern: args.pattern, ...result };
122
81
  },
123
82
  };
124
83
  /**
@@ -0,0 +1,23 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import type { SearchMatch } from './search-files.js';
3
+ export interface MatchJob {
4
+ files: Array<{
5
+ abs: string;
6
+ rel: string;
7
+ }>;
8
+ source: string;
9
+ flags: string;
10
+ maxResults: number;
11
+ contextLines: number;
12
+ }
13
+ export interface MatchResult {
14
+ matches: SearchMatch[];
15
+ truncated: boolean;
16
+ filesScanned: number;
17
+ }
18
+ export declare const DEFAULT_SEARCH_TIMEOUT_MS = 20000;
19
+ /** Match `job` in a worker; throws when it runs longer than `timeoutMs`. Falls back to in-process if no worker starts. */
20
+ export declare function matchInWorker(job: MatchJob, opts: {
21
+ timeoutMs: number;
22
+ startWorker?: (source: string, data: MatchJob) => Worker;
23
+ }): Promise<MatchResult>;