@gaunt-sloth/agent 2.0.0-alpha.2 → 2.0.0-alpha.21

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 (86) hide show
  1. package/README.md +10 -0
  2. package/dist/builtInToolsConfig.d.ts +17 -3
  3. package/dist/builtInToolsConfig.js +41 -14
  4. package/dist/builtInToolsConfig.js.map +1 -1
  5. package/dist/core/GthDeepAgent.d.ts +63 -13
  6. package/dist/core/GthDeepAgent.js +360 -130
  7. package/dist/core/GthDeepAgent.js.map +1 -1
  8. package/dist/core/debugCapture.d.ts +8 -49
  9. package/dist/core/debugCapture.js +1 -1
  10. package/dist/core/debugCapture.js.map +1 -1
  11. package/dist/core/deepAgentPermissions.d.ts +73 -13
  12. package/dist/core/deepAgentPermissions.js +191 -29
  13. package/dist/core/deepAgentPermissions.js.map +1 -1
  14. package/dist/core/gthAcpServer.js +11 -0
  15. package/dist/core/gthAcpServer.js.map +1 -1
  16. package/dist/core/gthDeepAgentFactory.d.ts +3 -0
  17. package/dist/core/gthDeepAgentFactory.js +9 -1
  18. package/dist/core/gthDeepAgentFactory.js.map +1 -1
  19. package/dist/core/resolveAgentFactory.d.ts +14 -0
  20. package/dist/core/resolveAgentFactory.js +18 -0
  21. package/dist/core/resolveAgentFactory.js.map +1 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp/OAuthClientProviderImpl.js +2 -2
  26. package/dist/mcp/OAuthClientProviderImpl.js.map +1 -1
  27. package/dist/mcp/tlsTrust.d.ts +61 -0
  28. package/dist/mcp/tlsTrust.js +109 -0
  29. package/dist/mcp/tlsTrust.js.map +1 -0
  30. package/dist/middleware/registry.d.ts +1 -1
  31. package/dist/middleware/registry.js +1 -1
  32. package/dist/middleware/types.d.ts +1 -1
  33. package/dist/middleware/types.js +1 -1
  34. package/dist/modules/a2a/A2AClientWrapper.d.ts +46 -0
  35. package/dist/modules/a2a/A2AClientWrapper.js +71 -0
  36. package/dist/modules/a2a/A2AClientWrapper.js.map +1 -1
  37. package/dist/modules/acpModule.d.ts +5 -2
  38. package/dist/modules/acpModule.js +13 -2
  39. package/dist/modules/acpModule.js.map +1 -1
  40. package/dist/modules/apiAgUiModule.js +48 -16
  41. package/dist/modules/apiAgUiModule.js.map +1 -1
  42. package/dist/modules/interactiveSessionModule.js +140 -7
  43. package/dist/modules/interactiveSessionModule.js.map +1 -1
  44. package/dist/resolvers.js +104 -9
  45. package/dist/resolvers.js.map +1 -1
  46. package/dist/tools/GthCustomToolkit.js +24 -9
  47. package/dist/tools/GthCustomToolkit.js.map +1 -1
  48. package/dist/tools/GthDevToolkit.d.ts +45 -1
  49. package/dist/tools/GthDevToolkit.js +229 -31
  50. package/dist/tools/GthDevToolkit.js.map +1 -1
  51. package/dist/tools/GthFileSystemToolkit.d.ts +47 -0
  52. package/dist/tools/GthFileSystemToolkit.js +305 -18
  53. package/dist/tools/GthFileSystemToolkit.js.map +1 -1
  54. package/dist/tools/gthChecklistTool.d.ts +30 -0
  55. package/dist/tools/gthChecklistTool.js +81 -0
  56. package/dist/tools/gthChecklistTool.js.map +1 -0
  57. package/dist/tools/gthGrepTool.d.ts +54 -0
  58. package/dist/tools/gthGrepTool.js +482 -0
  59. package/dist/tools/gthGrepTool.js.map +1 -0
  60. package/dist/tools/shell/allowlist.d.ts +11 -0
  61. package/dist/tools/shell/allowlist.js +12 -0
  62. package/dist/tools/shell/allowlist.js.map +1 -0
  63. package/dist/tools/shell/arity.d.ts +11 -0
  64. package/dist/tools/shell/arity.js +12 -0
  65. package/dist/tools/shell/arity.js.map +1 -0
  66. package/dist/tools/shell/env.d.ts +22 -0
  67. package/dist/tools/shell/env.js +110 -0
  68. package/dist/tools/shell/env.js.map +1 -0
  69. package/dist/tools/shell/hardline.d.ts +15 -0
  70. package/dist/tools/shell/hardline.js +88 -0
  71. package/dist/tools/shell/hardline.js.map +1 -0
  72. package/dist/tools/shell/normalize.d.ts +10 -0
  73. package/dist/tools/shell/normalize.js +11 -0
  74. package/dist/tools/shell/normalize.js.map +1 -0
  75. package/dist/tools/shell/outputBuffer.d.ts +53 -0
  76. package/dist/tools/shell/outputBuffer.js +157 -0
  77. package/dist/tools/shell/outputBuffer.js.map +1 -0
  78. package/dist/tools/shell/workDir.d.ts +11 -0
  79. package/dist/tools/shell/workDir.js +45 -0
  80. package/dist/tools/shell/workDir.js.map +1 -0
  81. package/dist/utils/mcpAuthError.d.ts +36 -0
  82. package/dist/utils/mcpAuthError.js +89 -0
  83. package/dist/utils/mcpAuthError.js.map +1 -0
  84. package/dist/utils/mcpUtils.js +16 -0
  85. package/dist/utils/mcpUtils.js.map +1 -1
  86. package/package.json +14 -13
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module tools/shell/allowlist
3
+ *
4
+ * Re-export of the EXT-9 Tier-2 allow-list engine. The implementation lives in
5
+ * `@gaunt-sloth/core` (`core/shell/allowlist`) because the core {@link GthAgentRunner}
6
+ * owns the per-instance session store and the loaded persisted store and consults
7
+ * `matchesApproval` before prompting (core cannot import from `@gaunt-sloth/agent`).
8
+ *
9
+ * See the core module for the exact safe-bin / anti-widening matching rule.
10
+ */
11
+ export { matchesApproval, hasWideningFlag, AllowlistStore, PersistedAllowlist, } from '@gaunt-sloth/core/core/shell/allowlist.js';
12
+ //# sourceMappingURL=allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../src/tools/shell/allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,eAAe,EACf,eAAe,EACf,cAAc,EACd,kBAAkB,GAGnB,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @module tools/shell/arity
3
+ *
4
+ * Re-export of the EXT-9 Tier-2 command classifier. The implementation lives in
5
+ * `@gaunt-sloth/core` (`core/shell/arity`) because the core {@link GthAgentRunner}
6
+ * must consult it BEFORE prompting (and core cannot import from `@gaunt-sloth/agent`).
7
+ * This stable agent-side path is kept for tests and any agent-layer consumers.
8
+ *
9
+ * See the core module for the arity table scope and the anti-injection fail-closed rule.
10
+ */
11
+ export { classifyCommand, tokenize, meaningfulPrefixTokens, type CommandClassification, } from '@gaunt-sloth/core/core/shell/arity.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module tools/shell/arity
3
+ *
4
+ * Re-export of the EXT-9 Tier-2 command classifier. The implementation lives in
5
+ * `@gaunt-sloth/core` (`core/shell/arity`) because the core {@link GthAgentRunner}
6
+ * must consult it BEFORE prompting (and core cannot import from `@gaunt-sloth/agent`).
7
+ * This stable agent-side path is kept for tests and any agent-layer consumers.
8
+ *
9
+ * See the core module for the arity table scope and the anti-injection fail-closed rule.
10
+ */
11
+ export { classifyCommand, tokenize, meaningfulPrefixTokens, } from '@gaunt-sloth/core/core/shell/arity.js';
12
+ //# sourceMappingURL=arity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arity.js","sourceRoot":"","sources":["../../../src/tools/shell/arity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,eAAe,EACf,QAAQ,EACR,sBAAsB,GAEvB,MAAM,uCAAuC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Explicit blocklist of LLM-provider and cloud credentials. Covers the providers
3
+ * gaunt-sloth (and its consumers) can be configured against, plus the standard
4
+ * cloud secret-bearing vars. Matched case-insensitively.
5
+ */
6
+ export declare const CREDENTIAL_BLOCKLIST: ReadonlyArray<string>;
7
+ /**
8
+ * Allow-list of credential-shaped names that must survive the wildcard sweep
9
+ * because gaunt-sloth legitimately depends on them. Matched case-insensitively.
10
+ */
11
+ export declare const CREDENTIAL_ALLOWLIST: ReadonlyArray<string>;
12
+ /**
13
+ * True when an env var name should be scrubbed from the child environment.
14
+ * Exported for testing.
15
+ */
16
+ export declare function shouldScrubEnvVar(name: string): boolean;
17
+ /**
18
+ * Build the child environment for a spawned shell command: a copy of the parent
19
+ * env with LLM/cloud credentials removed. Defaults to the live `process.env`
20
+ * (via systemUtils); a source can be injected for testing.
21
+ */
22
+ export declare function buildScrubbedEnv(source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @module tools/shell/env
3
+ *
4
+ * Credential scrubbing for the shell tool's child environment. By default a
5
+ * spawned child inherits `process.env` verbatim, so an approved (or yolo'd)
6
+ * command can `echo $ANTHROPIC_API_KEY` and exfiltrate the operator's LLM/cloud
7
+ * credentials. {@link buildScrubbedEnv} returns a copy of the parent env with
8
+ * those credentials removed before spawn.
9
+ *
10
+ * Policy (deliberately scoped):
11
+ * - Strip LLM provider keys and cloud-provider secrets (the explicit blocklist +
12
+ * a wildcard sweep for `*_API_KEY` / `*_TOKEN` / `*_SECRET` / `*SECRET_KEY`).
13
+ * - LEAVE generic dev env intact (PATH, HOME, SHELL, LANG, npm/pnpm config, …)
14
+ * so normal commands still work.
15
+ * - LEAVE `GITHUB_TOKEN` / `GH_TOKEN` intact: gaunt-sloth's content/requirement
16
+ * providers shell out to `gh` (`gh pr diff`, `gh issue view`), so stripping
17
+ * these would break first-class workflows. They are explicitly allow-listed
18
+ * against the wildcard `*_TOKEN` sweep.
19
+ *
20
+ * Patterned after hermes-agent `_HERMES_PROVIDER_ENV_BLOCKLIST` (tools/environments/local.py)
21
+ * — but narrower: we only own the provider/cloud-secret floor.
22
+ */
23
+ import { env as processEnv } from '@gaunt-sloth/core/utils/systemUtils.js';
24
+ /**
25
+ * Explicit blocklist of LLM-provider and cloud credentials. Covers the providers
26
+ * gaunt-sloth (and its consumers) can be configured against, plus the standard
27
+ * cloud secret-bearing vars. Matched case-insensitively.
28
+ */
29
+ export const CREDENTIAL_BLOCKLIST = [
30
+ // LLM providers
31
+ 'ANTHROPIC_API_KEY',
32
+ 'ANTHROPIC_AUTH_TOKEN',
33
+ 'CLAUDE_CODE_OAUTH_TOKEN',
34
+ 'OPENAI_API_KEY',
35
+ 'GOOGLE_API_KEY',
36
+ 'GEMINI_API_KEY',
37
+ 'GOOGLE_APPLICATION_CREDENTIALS',
38
+ 'GROQ_API_KEY',
39
+ 'XAI_API_KEY',
40
+ 'DEEPSEEK_API_KEY',
41
+ 'MISTRAL_API_KEY',
42
+ 'OPENROUTER_API_KEY',
43
+ 'COHERE_API_KEY',
44
+ 'TOGETHER_API_KEY',
45
+ 'PERPLEXITY_API_KEY',
46
+ 'FIREWORKS_API_KEY',
47
+ // Azure OpenAI
48
+ 'AZURE_OPENAI_API_KEY',
49
+ 'AZURE_API_KEY',
50
+ // Cloud provider secrets (AWS / GCP)
51
+ 'AWS_SECRET_ACCESS_KEY',
52
+ 'AWS_SESSION_TOKEN',
53
+ 'AWS_ACCESS_KEY_ID',
54
+ ];
55
+ /**
56
+ * Allow-list of credential-shaped names that must survive the wildcard sweep
57
+ * because gaunt-sloth legitimately depends on them. Matched case-insensitively.
58
+ */
59
+ export const CREDENTIAL_ALLOWLIST = [
60
+ // `gh` CLI auth — used by the github content/requirement providers.
61
+ 'GITHUB_TOKEN',
62
+ 'GH_TOKEN',
63
+ ];
64
+ // Wildcard sweep: any var whose name ends in one of these suffixes is treated as
65
+ // a secret and stripped (unless allow-listed). Catches provider keys we didn't
66
+ // enumerate (e.g. a new `FOO_API_KEY`).
67
+ const SECRET_SUFFIXES = [
68
+ /_API_KEY$/i,
69
+ /_SECRET_ACCESS_KEY$/i,
70
+ /_SECRET_KEY$/i,
71
+ /_SECRET$/i,
72
+ /_TOKEN$/i,
73
+ ];
74
+ function isAllowlisted(name) {
75
+ return CREDENTIAL_ALLOWLIST.some((a) => a.toUpperCase() === name.toUpperCase());
76
+ }
77
+ function isBlocklisted(name) {
78
+ return CREDENTIAL_BLOCKLIST.some((b) => b.toUpperCase() === name.toUpperCase());
79
+ }
80
+ function matchesSecretSuffix(name) {
81
+ return SECRET_SUFFIXES.some((re) => re.test(name));
82
+ }
83
+ /**
84
+ * True when an env var name should be scrubbed from the child environment.
85
+ * Exported for testing.
86
+ */
87
+ export function shouldScrubEnvVar(name) {
88
+ if (isAllowlisted(name))
89
+ return false;
90
+ if (isBlocklisted(name))
91
+ return true;
92
+ return matchesSecretSuffix(name);
93
+ }
94
+ /**
95
+ * Build the child environment for a spawned shell command: a copy of the parent
96
+ * env with LLM/cloud credentials removed. Defaults to the live `process.env`
97
+ * (via systemUtils); a source can be injected for testing.
98
+ */
99
+ export function buildScrubbedEnv(source = processEnv) {
100
+ const scrubbed = {};
101
+ for (const [key, value] of Object.entries(source)) {
102
+ if (value === undefined)
103
+ continue;
104
+ if (shouldScrubEnvVar(key))
105
+ continue;
106
+ scrubbed[key] = value;
107
+ }
108
+ return scrubbed;
109
+ }
110
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/tools/shell/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,MAAM,wCAAwC,CAAC;AAE3E;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA0B;IACzD,gBAAgB;IAChB,mBAAmB;IACnB,sBAAsB;IACtB,yBAAyB;IACzB,gBAAgB;IAChB,gBAAgB;IAChB,gBAAgB;IAChB,gCAAgC;IAChC,cAAc;IACd,aAAa;IACb,kBAAkB;IAClB,iBAAiB;IACjB,oBAAoB;IACpB,gBAAgB;IAChB,kBAAkB;IAClB,oBAAoB;IACpB,mBAAmB;IACnB,eAAe;IACf,sBAAsB;IACtB,eAAe;IACf,qCAAqC;IACrC,uBAAuB;IACvB,mBAAmB;IACnB,mBAAmB;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA0B;IACzD,oEAAoE;IACpE,cAAc;IACd,UAAU;CACX,CAAC;AAEF,iFAAiF;AACjF,+EAA+E;AAC/E,wCAAwC;AACxC,MAAM,eAAe,GAAG;IACtB,YAAY;IACZ,sBAAsB;IACtB,eAAe;IACf,WAAW;IACX,UAAU;CACX,CAAC;AAEF,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY;IACvC,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAM,GAAsB,UAAU;IACrE,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,iBAAiB,CAAC,GAAG,CAAC;YAAE,SAAS;QACrC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Hardline patterns: [regex, human description]. Matched case-insensitively
3
+ * against the normalized command.
4
+ */
5
+ export declare const HARDLINE_PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
6
+ export interface HardlineMatch {
7
+ /** Human-readable reason the command was refused. */
8
+ description: string;
9
+ }
10
+ /**
11
+ * Check a raw command against the hardline blocklist. Normalizes first so
12
+ * obfuscated variants are caught. Returns the match (with a description) when the
13
+ * command is catastrophic, or `null` when it is allowed to proceed.
14
+ */
15
+ export declare function checkHardline(command: string): HardlineMatch | null;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @module tools/shell/hardline
3
+ *
4
+ * Unbypassable hardline blocklist for the shell tool. These are catastrophic,
5
+ * non-recoverable commands (wipe the root filesystem, format a disk, overwrite a
6
+ * raw block device, fork-bomb, take the host down). They are refused inside
7
+ * `executeCommand` itself — BEFORE spawn — so the refusal fires regardless of
8
+ * yolo (`shellYolo`), any allow-list, or the confirmation path. yolo deliberately
9
+ * bypasses the *confirmation*; it does NOT bypass this floor.
10
+ *
11
+ * Recoverable-but-costly operations (e.g. `git reset --hard`, `rm -rf ./build`,
12
+ * `chmod -R 777 ./dir`, `curl | sh`) are intentionally NOT here — those are what
13
+ * the confirmation dialog / yolo are for.
14
+ *
15
+ * Patterns match the NORMALIZED command ({@link ./normalize.js}) so obfuscation
16
+ * (ANSI/fullwidth/backslash splits/whitespace padding) cannot bypass them.
17
+ *
18
+ * Patterned after hermes-agent `tools/approval.py` HARDLINE_PATTERNS.
19
+ */
20
+ import { normalizeCommand } from '#src/tools/shell/normalize.js';
21
+ // Matches a position where the shell would begin parsing a NEW command: start of
22
+ // string, after a separator (; & | newline), after `$(` or backtick, optionally
23
+ // consuming leading wrappers (sudo/env VAR=VAL/exec/nohup/setsid/time). Used by
24
+ // the shutdown-family patterns so they don't false-positive on `echo reboot`.
25
+ const CMD_POS = '(?:^|[;&|\\n`]|\\$\\()' +
26
+ '\\s*' +
27
+ '(?:sudo\\s+(?:-[^\\s]+\\s+)*)?' +
28
+ '(?:env\\s+(?:\\w+=\\S*\\s+)*)?' +
29
+ '(?:(?:exec|nohup|setsid|time)\\s+)*' +
30
+ '\\s*';
31
+ /**
32
+ * Hardline patterns: [regex, human description]. Matched case-insensitively
33
+ * against the normalized command.
34
+ */
35
+ export const HARDLINE_PATTERNS = [
36
+ // rm -rf targeting the root filesystem (`/`, `/*`).
37
+ [/\brm\s+(?:-[^\s]*\s+)*\/\s*\*?\s*(?:$|[;&|])/, 'recursive delete of root filesystem'],
38
+ // rm -rf targeting protected system directories (with optional /* suffix).
39
+ [
40
+ /\brm\s+(?:-[^\s]*\s+)*(?:\/(?:home|root|etc|usr|var|bin|sbin|boot|lib|lib64|opt|sys|proc))(?:\/\*)?\s*(?:$|[;&|])/,
41
+ 'recursive delete of system directory',
42
+ ],
43
+ // rm -rf targeting the home directory (~ or $HOME).
44
+ // Note: patterns match the LOWERCASED normalized command, so $HOME → $home.
45
+ [
46
+ /\brm\s+(?:-[^\s]*\s+)*(?:~|\$home)(?:\/\*)?\s*(?:$|[;&|])/,
47
+ 'recursive delete of home directory',
48
+ ],
49
+ // Filesystem format.
50
+ [/\bmkfs(?:\.[a-z0-9]+)?\b/, 'format filesystem (mkfs)'],
51
+ // dd writing to a raw block device.
52
+ [/\bdd\b[^\n]*\bof=\/dev\/(?:sd|nvme|hd|mmcblk|vd|xvd)[a-z0-9]*/, 'dd to raw block device'],
53
+ // Shell redirection to a raw block device (`> /dev/sda`).
54
+ [/>\s*\/dev\/(?:sd|nvme|hd|mmcblk|vd|xvd)[a-z0-9]*\b/, 'redirect to raw block device'],
55
+ // Classic fork bomb `:(){ :|:& };:`.
56
+ [/:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, 'fork bomb'],
57
+ // chmod -R 777 / (recursive world-writable on root).
58
+ [
59
+ /\bchmod\s+(?:-[^\s]*\s+)*(?:-r|--recursive)\s+(?:-[^\s]*\s+)*777\s+\//,
60
+ 'recursive chmod 777 of root',
61
+ ],
62
+ // Kill every process on the system (`kill -1`, `kill -9 -1`).
63
+ [/\bkill\s+(?:-[^\s]+\s+)*-1\b/, 'kill all processes'],
64
+ // System shutdown / reboot — anchored to a command position so `echo reboot`
65
+ // and `grep shutdown log` don't trip it.
66
+ [new RegExp(CMD_POS + '(?:shutdown|reboot|halt|poweroff)\\b'), 'system shutdown/reboot'],
67
+ [new RegExp(CMD_POS + 'init\\s+[06]\\b'), 'init 0/6 (shutdown/reboot)'],
68
+ [
69
+ new RegExp(CMD_POS + 'systemctl\\s+(?:poweroff|reboot|halt|kexec)\\b'),
70
+ 'systemctl poweroff/reboot',
71
+ ],
72
+ [new RegExp(CMD_POS + 'telinit\\s+[06]\\b'), 'telinit 0/6 (shutdown/reboot)'],
73
+ ];
74
+ /**
75
+ * Check a raw command against the hardline blocklist. Normalizes first so
76
+ * obfuscated variants are caught. Returns the match (with a description) when the
77
+ * command is catastrophic, or `null` when it is allowed to proceed.
78
+ */
79
+ export function checkHardline(command) {
80
+ const normalized = normalizeCommand(command).toLowerCase();
81
+ for (const [pattern, description] of HARDLINE_PATTERNS) {
82
+ if (pattern.test(normalized)) {
83
+ return { description };
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ //# sourceMappingURL=hardline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hardline.js","sourceRoot":"","sources":["../../../src/tools/shell/hardline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,iFAAiF;AACjF,gFAAgF;AAChF,gFAAgF;AAChF,8EAA8E;AAC9E,MAAM,OAAO,GACX,wBAAwB;IACxB,MAAM;IACN,gCAAgC;IAChC,gCAAgC;IAChC,qCAAqC;IACrC,MAAM,CAAC;AAET;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA6C;IACzE,oDAAoD;IACpD,CAAC,8CAA8C,EAAE,qCAAqC,CAAC;IACvF,2EAA2E;IAC3E;QACE,mHAAmH;QACnH,sCAAsC;KACvC;IACD,oDAAoD;IACpD,4EAA4E;IAC5E;QACE,2DAA2D;QAC3D,oCAAoC;KACrC;IACD,qBAAqB;IACrB,CAAC,0BAA0B,EAAE,0BAA0B,CAAC;IACxD,oCAAoC;IACpC,CAAC,+DAA+D,EAAE,wBAAwB,CAAC;IAC3F,0DAA0D;IAC1D,CAAC,oDAAoD,EAAE,8BAA8B,CAAC;IACtF,qCAAqC;IACrC,CAAC,gDAAgD,EAAE,WAAW,CAAC;IAC/D,qDAAqD;IACrD;QACE,uEAAuE;QACvE,6BAA6B;KAC9B;IACD,8DAA8D;IAC9D,CAAC,8BAA8B,EAAE,oBAAoB,CAAC;IACtD,6EAA6E;IAC7E,yCAAyC;IACzC,CAAC,IAAI,MAAM,CAAC,OAAO,GAAG,sCAAsC,CAAC,EAAE,wBAAwB,CAAC;IACxF,CAAC,IAAI,MAAM,CAAC,OAAO,GAAG,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACvE;QACE,IAAI,MAAM,CAAC,OAAO,GAAG,gDAAgD,CAAC;QACtE,2BAA2B;KAC5B;IACD,CAAC,IAAI,MAAM,CAAC,OAAO,GAAG,oBAAoB,CAAC,EAAE,+BAA+B,CAAC;CAC9E,CAAC;AAOF;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3D,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,iBAAiB,EAAE,CAAC;QACvD,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,WAAW,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @module tools/shell/normalize
3
+ *
4
+ * Re-export of the canonical command normalizer, which now lives in
5
+ * `@gaunt-sloth/core` (`core/shell/normalize`) so the core runner's EXT-9 Tier-2
6
+ * allow-list classifier and the agent's Tier-1 hardline blocklist share ONE
7
+ * implementation. Kept as a stable import path for existing agent-side consumers
8
+ * (`tools/shell/hardline`, specs).
9
+ */
10
+ export { normalizeCommand } from '@gaunt-sloth/core/core/shell/normalize.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @module tools/shell/normalize
3
+ *
4
+ * Re-export of the canonical command normalizer, which now lives in
5
+ * `@gaunt-sloth/core` (`core/shell/normalize`) so the core runner's EXT-9 Tier-2
6
+ * allow-list classifier and the agent's Tier-1 hardline blocklist share ONE
7
+ * implementation. Kept as a stable import path for existing agent-side consumers
8
+ * (`tools/shell/hardline`, specs).
9
+ */
10
+ export { normalizeCommand } from '@gaunt-sloth/core/core/shell/normalize.js';
11
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/tools/shell/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,53 @@
1
+ export interface CappedOutput {
2
+ /** The bounded text to surface to the model (head + tail with a gap note). */
3
+ text: string;
4
+ /** True when the output exceeded the budget and was truncated. */
5
+ truncated: boolean;
6
+ /** Total bytes captured (the full, untruncated size). */
7
+ totalBytes: number;
8
+ /** Absolute path to the spilled full output, or undefined when not truncated. */
9
+ spillPath?: string;
10
+ }
11
+ /**
12
+ * Accumulates command output up to a byte budget using a head + tail window.
13
+ * The full output is retained in memory so it can be spilled to disk on demand;
14
+ * the in-memory full copy is itself bounded at a hard safety ceiling to avoid
15
+ * unbounded growth from a runaway command.
16
+ */
17
+ export declare class OutputBuffer {
18
+ private head;
19
+ private headBytes;
20
+ private readonly tailChunks;
21
+ private tailBytes;
22
+ private totalBytesCount;
23
+ /** Full copy for spillover, bounded by a hard ceiling. */
24
+ private full;
25
+ private fullTruncated;
26
+ private readonly headBudget;
27
+ private readonly tailBudget;
28
+ private readonly fullCeiling;
29
+ /**
30
+ * @param maxOutputBytes total byte budget for the returned (head+tail) window.
31
+ * @param fullCeiling hard ceiling for the in-memory full copy kept for spillover.
32
+ * Defaults to a generous 16MB so the spill file holds the real output for
33
+ * typical noisy logs, while still bounding memory for a runaway command.
34
+ */
35
+ constructor(maxOutputBytes: number, fullCeiling?: number);
36
+ /** Append a chunk of output. Byte accounting uses UTF-8 byte length. */
37
+ append(chunk: string): void;
38
+ /** Push into the tail ring-buffer, evicting oldest data beyond the budget. */
39
+ private pushTail;
40
+ get totalBytes(): number;
41
+ get isTruncated(): boolean;
42
+ /** The full captured output (bounded by the in-memory ceiling). */
43
+ getFull(): string;
44
+ /**
45
+ * Finalize the capture. When not truncated, returns the output verbatim. When
46
+ * truncated, spills the full output to a temp file and returns head + a gap
47
+ * note (with the file path) + tail.
48
+ *
49
+ * @param spill optional injected spill function (path-returning) for testing.
50
+ * Defaults to writing into `os.tmpdir()`.
51
+ */
52
+ finalize(spill?: (content: string) => string): CappedOutput;
53
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @module tools/shell/outputBuffer
3
+ *
4
+ * Bounded capture of a shell command's combined stdout/stderr for the value
5
+ * returned to the model. A noisy build/test log can be megabytes; dumping it
6
+ * verbatim into a ToolMessage blows the context window. This buffer keeps a
7
+ * HEAD window (the first N bytes) and a TAIL ring-buffer (the last N bytes) and
8
+ * drops the middle, while accumulating the FULL output separately so it can be
9
+ * spilled to a temp file the model can re-read with `read_file`.
10
+ *
11
+ * Live streaming to the terminal is unaffected — the toolkit still writes every
12
+ * chunk to stdout as it arrives; only the returned string is capped.
13
+ *
14
+ * Patterned after opencode `bash.ts` (per-stream cap + temp-file tail) and
15
+ * openclaw `bash-tools.shared.ts` `truncateMiddle`.
16
+ */
17
+ import { writeFileSync } from 'node:fs';
18
+ import { tmpdir } from 'node:os';
19
+ import path from 'node:path';
20
+ /**
21
+ * Accumulates command output up to a byte budget using a head + tail window.
22
+ * The full output is retained in memory so it can be spilled to disk on demand;
23
+ * the in-memory full copy is itself bounded at a hard safety ceiling to avoid
24
+ * unbounded growth from a runaway command.
25
+ */
26
+ export class OutputBuffer {
27
+ head = '';
28
+ headBytes = 0;
29
+ tailChunks = [];
30
+ tailBytes = 0;
31
+ totalBytesCount = 0;
32
+ /** Full copy for spillover, bounded by a hard ceiling. */
33
+ full = '';
34
+ fullTruncated = false;
35
+ headBudget;
36
+ tailBudget;
37
+ fullCeiling;
38
+ /**
39
+ * @param maxOutputBytes total byte budget for the returned (head+tail) window.
40
+ * @param fullCeiling hard ceiling for the in-memory full copy kept for spillover.
41
+ * Defaults to a generous 16MB so the spill file holds the real output for
42
+ * typical noisy logs, while still bounding memory for a runaway command.
43
+ */
44
+ constructor(maxOutputBytes, fullCeiling = 16 * 1024 * 1024) {
45
+ // Split the budget evenly between head and tail.
46
+ this.headBudget = Math.max(1, Math.floor(maxOutputBytes / 2));
47
+ this.tailBudget = Math.max(1, maxOutputBytes - this.headBudget);
48
+ // The full copy must hold at least the preview window.
49
+ this.fullCeiling = Math.max(fullCeiling, maxOutputBytes);
50
+ }
51
+ /** Append a chunk of output. Byte accounting uses UTF-8 byte length. */
52
+ append(chunk) {
53
+ const bytes = Buffer.byteLength(chunk, 'utf8');
54
+ this.totalBytesCount += bytes;
55
+ // Maintain the full copy up to the ceiling (for spillover). Store as much of
56
+ // an oversized chunk as fits rather than dropping it wholesale.
57
+ if (!this.fullTruncated) {
58
+ const used = Buffer.byteLength(this.full, 'utf8');
59
+ const room = this.fullCeiling - used;
60
+ if (bytes <= room) {
61
+ this.full += chunk;
62
+ }
63
+ else {
64
+ if (room > 0) {
65
+ this.full += Buffer.from(chunk, 'utf8').subarray(0, room).toString('utf8');
66
+ }
67
+ this.fullTruncated = true;
68
+ }
69
+ }
70
+ // Fill the head window first.
71
+ if (this.headBytes < this.headBudget) {
72
+ const room = this.headBudget - this.headBytes;
73
+ if (bytes <= room) {
74
+ this.head += chunk;
75
+ this.headBytes += bytes;
76
+ return;
77
+ }
78
+ // Partial: take what fits into head, the rest flows to tail.
79
+ const take = Buffer.from(chunk, 'utf8').subarray(0, room).toString('utf8');
80
+ this.head += take;
81
+ this.headBytes += Buffer.byteLength(take, 'utf8');
82
+ const rest = chunk.slice(take.length);
83
+ this.pushTail(rest);
84
+ return;
85
+ }
86
+ this.pushTail(chunk);
87
+ }
88
+ /** Push into the tail ring-buffer, evicting oldest data beyond the budget. */
89
+ pushTail(chunk) {
90
+ if (chunk.length === 0)
91
+ return;
92
+ this.tailChunks.push(chunk);
93
+ this.tailBytes += Buffer.byteLength(chunk, 'utf8');
94
+ while (this.tailBytes > this.tailBudget && this.tailChunks.length > 1) {
95
+ const removed = this.tailChunks.shift();
96
+ this.tailBytes -= Buffer.byteLength(removed, 'utf8');
97
+ }
98
+ // If a single chunk alone exceeds the tail budget, trim it from the front.
99
+ if (this.tailBytes > this.tailBudget && this.tailChunks.length === 1) {
100
+ const only = this.tailChunks[0];
101
+ const overflow = this.tailBytes - this.tailBudget;
102
+ const buf = Buffer.from(only, 'utf8');
103
+ const trimmed = buf.subarray(overflow).toString('utf8');
104
+ this.tailChunks[0] = trimmed;
105
+ this.tailBytes = Buffer.byteLength(trimmed, 'utf8');
106
+ }
107
+ }
108
+ get totalBytes() {
109
+ return this.totalBytesCount;
110
+ }
111
+ get isTruncated() {
112
+ return this.totalBytesCount > this.headBytes + this.tailBytes;
113
+ }
114
+ /** The full captured output (bounded by the in-memory ceiling). */
115
+ getFull() {
116
+ return this.full;
117
+ }
118
+ /**
119
+ * Finalize the capture. When not truncated, returns the output verbatim. When
120
+ * truncated, spills the full output to a temp file and returns head + a gap
121
+ * note (with the file path) + tail.
122
+ *
123
+ * @param spill optional injected spill function (path-returning) for testing.
124
+ * Defaults to writing into `os.tmpdir()`.
125
+ */
126
+ finalize(spill = defaultSpill) {
127
+ const tail = this.tailChunks.join('');
128
+ if (!this.isTruncated) {
129
+ return {
130
+ text: this.head + tail,
131
+ truncated: false,
132
+ totalBytes: this.totalBytesCount,
133
+ };
134
+ }
135
+ const spillPath = spill(this.full);
136
+ const fullNote = this.fullTruncated
137
+ ? `First ${Buffer.byteLength(this.full, 'utf8')} bytes`
138
+ : 'Full output';
139
+ const droppedNote = `\n\n... [output truncated: ${this.totalBytesCount} bytes total; ` +
140
+ `showing first ${this.headBytes} + last ${this.tailBytes} bytes. ` +
141
+ `${fullNote} written to ${spillPath} — use read_file to inspect.] ...\n\n`;
142
+ return {
143
+ text: this.head + droppedNote + tail,
144
+ truncated: true,
145
+ totalBytes: this.totalBytesCount,
146
+ spillPath,
147
+ };
148
+ }
149
+ }
150
+ /** Default spill: write to a uniquely-named file in the OS temp dir. */
151
+ function defaultSpill(content) {
152
+ const name = `gsloth-shell-${Date.now()}-${Math.random().toString(36).slice(2, 10)}.log`;
153
+ const filePath = path.join(tmpdir(), name);
154
+ writeFileSync(filePath, content, 'utf8');
155
+ return filePath;
156
+ }
157
+ //# sourceMappingURL=outputBuffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputBuffer.js","sourceRoot":"","sources":["../../../src/tools/shell/outputBuffer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Working directory the shell tool spawns in: the ACP session override when set (ACP transport),
3
+ * otherwise `getCurrentWorkDir()` (local runner + AG-UI, unchanged).
4
+ */
5
+ export declare function getShellWorkDir(): string;
6
+ /**
7
+ * ACP-only: point the shell tool at the current ACP session's fs-backend root (the resolved
8
+ * `session/new.cwd`, or the startup workspace when a session carries no cwd). Pass `undefined` to
9
+ * clear the override and restore the `getCurrentWorkDir()` default (used by tests).
10
+ */
11
+ export declare function setAcpShellWorkDir(cwd: string | undefined): void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Resolves the working directory the shell tool (`run_shell_command`) spawns in, keeping it aligned
4
+ * with the deepagents filesystem-backend root so the shell and the fs tools (ls/read_file/write_file/
5
+ * edit_file/glob/grep) share ONE path namespace.
6
+ *
7
+ * DEFAULT (local `code`/`chat` runner + AG-UI): `getCurrentWorkDir()`. On these surfaces the fs
8
+ * backend is rooted at `getCurrentWorkDir()` by `GthDeepAgent.init()` (`rootDir: getCurrentWorkDir()`),
9
+ * so the shell already matches it — this is exactly EXT-22 S4. These surfaces NEVER call
10
+ * {@link setAcpShellWorkDir}, so {@link getShellWorkDir} stays `getCurrentWorkDir()` for them.
11
+ *
12
+ * ACP transport override: unlike the local runner, the ACP server does NOT call `init()`; it re-roots
13
+ * ITS per-session fs backend to each `session/new.cwd` (see `gthAcpServer.ts`). The shell tool
14
+ * instances are built ONCE at startup (`buildDeepAgentParams`), so they cannot themselves see the
15
+ * later session cwd. The ACP server therefore updates this module-level override on every
16
+ * `session/new`; the shell reads it at spawn time so it tracks the session fs root.
17
+ *
18
+ * This also removes the EXT-22 S4 `INIT_CWD` hazard on ACP: `getCurrentWorkDir()` prefers `INIT_CWD`,
19
+ * which is stale in the long-lived ACP subprocess (the reason `acpModule` roots the startup workspace
20
+ * at raw `process.cwd()`). Once the override tracks the session cwd, the ACP shell never consults
21
+ * `INIT_CWD`.
22
+ *
23
+ * Scope note: this mirrors the ACP design, which is ALREADY "latest-session-cwd wins" process-wide —
24
+ * `gthAcpServer` clobbers every backend to the newest session cwd. A single process-wide override has
25
+ * the same semantics. The ACP subprocess is dedicated (never co-hosts a local runner / AG-UI), so the
26
+ * override can never leak into a non-ACP surface.
27
+ */
28
+ import { getCurrentWorkDir } from '@gaunt-sloth/core/utils/systemUtils.js';
29
+ let acpShellWorkDir;
30
+ /**
31
+ * Working directory the shell tool spawns in: the ACP session override when set (ACP transport),
32
+ * otherwise `getCurrentWorkDir()` (local runner + AG-UI, unchanged).
33
+ */
34
+ export function getShellWorkDir() {
35
+ return acpShellWorkDir ?? getCurrentWorkDir();
36
+ }
37
+ /**
38
+ * ACP-only: point the shell tool at the current ACP session's fs-backend root (the resolved
39
+ * `session/new.cwd`, or the startup workspace when a session carries no cwd). Pass `undefined` to
40
+ * clear the override and restore the `getCurrentWorkDir()` default (used by tests).
41
+ */
42
+ export function setAcpShellWorkDir(cwd) {
43
+ acpShellWorkDir = cwd;
44
+ }
45
+ //# sourceMappingURL=workDir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workDir.js","sourceRoot":"","sources":["../../../src/tools/shell/workDir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAE3E,IAAI,eAAmC,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,eAAe,IAAI,iBAAiB,EAAE,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,eAAe,GAAG,GAAG,CAAC;AACxB,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * EXT-31 — classify and format MCP integration connect/tool-load failures so an expired or invalid
4
+ * credential is SURFACED (named + actionable) instead of silently dropping the integration's tools.
5
+ *
6
+ * The `@langchain/mcp-adapters` client only tags a bare HTTP 401 as an authentication error; a 403
7
+ * (forbidden / expired scope) otherwise falls through as a generic "Failed to connect". This module
8
+ * classifies auth failures independently (401/403 + credential keywords) so a stale token is not
9
+ * mistaken for "this integration was never configured" or an unrelated transient error.
10
+ *
11
+ * Pure + side-effect free: callers do the surfacing (`displayWarning`) with the returned message.
12
+ */
13
+ /** Coarse classification of an MCP connect/tool-load failure. */
14
+ export type McpConnectErrorKind = 'auth' | 'other';
15
+ /** Options that tune the suggested fix path in the surfaced message. */
16
+ export interface McpConnectFailureOptions {
17
+ /** The server uses OAuth (`authProvider: 'OAuth'`) — suggest re-running the OAuth login. */
18
+ oauth?: boolean;
19
+ }
20
+ /**
21
+ * Classify an MCP connect/tool-load failure as a credential problem (`auth`) or anything else
22
+ * (`other`). Detection is independent of the adapter (which only tags 401), so a 403 or an
23
+ * "expired token" surfaced by the upstream server is still recognised as auth.
24
+ */
25
+ export declare function classifyMcpConnectError(error: unknown): McpConnectErrorKind;
26
+ /**
27
+ * Build the user-facing message for a failed MCP integration. For `auth` failures it names the
28
+ * integration, states the credential is expired/invalid, and points at the concrete fix path
29
+ * (OAuth login or the API token / authorization header, plus the `mcpServers` entry in gth config).
30
+ * For `other` failures it surfaces the failure plainly and explicitly says it is NOT an auth error,
31
+ * so an unrelated transient never triggers a spurious "re-authenticate" nudge.
32
+ *
33
+ * Either way the caller degrades gracefully AFTER surfacing this — the integration's tools are
34
+ * dropped for the session, but the user (and the log) now knows why.
35
+ */
36
+ export declare function formatMcpConnectFailureMessage(serverName: string | undefined, error: unknown, options?: McpConnectFailureOptions): string;