@yagni-app/code-staging 1.2.3-staging.1701.1 → 1.2.3-staging.1708.1
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.
- package/dist/extension/condensedTools.d.ts +8 -1
- package/dist/extension/condensedTools.js +2 -0
- package/dist/extension/index.js +15 -0
- package/dist/extension/sandbox/session.d.ts +5 -2
- package/dist/extension/sandbox/session.js +16 -5
- package/dist/extension/toolchain/spawnHook.d.ts +63 -0
- package/dist/extension/toolchain/spawnHook.js +127 -0
- package/package.json +2 -2
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* pattern) so tests run against plain text. Renderer exceptions are swallowed
|
|
25
25
|
* by pi (degrading to the built-in fallback), so everything stays total.
|
|
26
26
|
*/
|
|
27
|
-
import { SettingsManager, type ExtensionAPI, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
27
|
+
import { SettingsManager, type BashSpawnHook, type ExtensionAPI, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
28
28
|
import type { RenderTheme } from "./subagentRender.js";
|
|
29
29
|
import { ToolRunTracker } from "./toolRuns.js";
|
|
30
30
|
/** Lines of write content shown collapsed (mirrors Claude Code's preview). */
|
|
@@ -96,6 +96,13 @@ export interface RegisterCondensedToolsDeps {
|
|
|
96
96
|
* model's tool calls.
|
|
97
97
|
*/
|
|
98
98
|
wrapBash?: (def: ToolDefinition) => ToolDefinition;
|
|
99
|
+
/**
|
|
100
|
+
* A spawn hook on the base bash definition (the build sandbox's toolchain
|
|
101
|
+
* hook), so the model's commands get the repository's toolchain even when
|
|
102
|
+
* the sandbox composition is identity. The sandbox, when on, builds its own
|
|
103
|
+
* definition and composes the same hook before its proxy hook.
|
|
104
|
+
*/
|
|
105
|
+
bashSpawnHook?: BashSpawnHook;
|
|
99
106
|
}
|
|
100
107
|
/**
|
|
101
108
|
* Re-register the seven built-ins with condensed renderers. Returns the
|
|
@@ -357,6 +357,8 @@ export function registerCondensedTools(pi, deps = {}) {
|
|
|
357
357
|
catch {
|
|
358
358
|
toolOptions = { bash: {}, read: {} };
|
|
359
359
|
}
|
|
360
|
+
if (deps.bashSpawnHook)
|
|
361
|
+
toolOptions.bash = { ...toolOptions.bash, spawnHook: deps.bashSpawnHook };
|
|
360
362
|
const buildDefinitions = (cwd) => {
|
|
361
363
|
const defs = {
|
|
362
364
|
read: createReadToolDefinition(cwd, toolOptions.read),
|
package/dist/extension/index.js
CHANGED
|
@@ -44,6 +44,7 @@ import { isSandboxDenialOutput, sandboxAutoAllowDecision, sandboxDenialSignature
|
|
|
44
44
|
import { sandboxFailureKey } from "./permission/approvedPrefixes.js";
|
|
45
45
|
import { createEscapeTally } from "./sandbox/escapeTally.js";
|
|
46
46
|
import { createGrantCensus } from "./permission/grantCensus.js";
|
|
47
|
+
import { toolchainSpawnHookFromEnv, toolchainUserBashOperations } from "./toolchain/spawnHook.js";
|
|
47
48
|
import { registerTelemetry as defaultRegisterTelemetry } from "./telemetry/register.js";
|
|
48
49
|
import { loadHooksConfig, makeHookRunner, registerHooks } from "./hooks.js";
|
|
49
50
|
import { loadPermissionRules } from "./permissionRules/loadConfig.js";
|
|
@@ -900,11 +901,17 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
900
901
|
// surfacing. Nothing is pruned, nothing is written back to rules.json.
|
|
901
902
|
const grantCensus = createGrantCensus();
|
|
902
903
|
grantCensus.seed(sessionGrants);
|
|
904
|
+
// A build sandbox's toolchain (spec 2026-10-08 §1): when the harness names a
|
|
905
|
+
// readable toolchain file, every bash spawn and `!` command runs on the
|
|
906
|
+
// repository's PATH and variables while this process stays on the image's
|
|
907
|
+
// Node. Composed before the sandbox's proxy hook. Absent everywhere else.
|
|
908
|
+
const toolchainHook = evalMode ? undefined : toolchainSpawnHookFromEnv(env);
|
|
903
909
|
const sandboxHandle = evalMode
|
|
904
910
|
? null
|
|
905
911
|
: registerSandbox(pi, {
|
|
906
912
|
cwd: process.cwd(),
|
|
907
913
|
env,
|
|
914
|
+
...(toolchainHook ? { baseSpawnHook: toolchainHook } : {}),
|
|
908
915
|
hasUI: (ctx) => ctx.hasUI,
|
|
909
916
|
onShellResolutionRetry: (outcome) => telemetry.sandboxShellResolutionRetry(outcome),
|
|
910
917
|
escapeBreakdown: () => escapeTally.breakdown(),
|
|
@@ -918,6 +925,13 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
918
925
|
});
|
|
919
926
|
if (sandboxHandle)
|
|
920
927
|
sandboxHandle.setRules(loadedRules.rules);
|
|
928
|
+
if (toolchainHook) {
|
|
929
|
+
// After the sandbox's own handler: a sandboxed `!` command is answered
|
|
930
|
+
// there (with this hook applied); any other runs here on the toolchain.
|
|
931
|
+
pi.on("user_bash", () => ({
|
|
932
|
+
operations: toolchainUserBashOperations(toolchainHook, process.cwd()),
|
|
933
|
+
}));
|
|
934
|
+
}
|
|
921
935
|
const permissionGate = registerPermissionGate(pi, {
|
|
922
936
|
...(sandboxHandle
|
|
923
937
|
? {
|
|
@@ -1528,6 +1542,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
1528
1542
|
// built-ins last), so the sandbox composes INTO it here. Identity when
|
|
1529
1543
|
// the sandbox is off.
|
|
1530
1544
|
...(sandboxHandle ? { wrapBash: sandboxHandle.composeBash } : {}),
|
|
1545
|
+
...(toolchainHook ? { bashSpawnHook: toolchainHook } : {}),
|
|
1531
1546
|
});
|
|
1532
1547
|
condensedToolsRegistered = true;
|
|
1533
1548
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* some providers). /sandbox (M5) toggles by rewriting settings and asking
|
|
12
12
|
* for a session restart, the same way Claude persists the toggle.
|
|
13
13
|
*/
|
|
14
|
-
import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { type BashSpawnHook, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { type SandboxSettings } from "./config.js";
|
|
16
16
|
import { YagniSandboxManager } from "./manager.js";
|
|
17
17
|
import { type SandboxNetworkChoice } from "./panel.js";
|
|
@@ -31,7 +31,7 @@ import type { PermissionRule } from "../permissionRules/loadConfig.js";
|
|
|
31
31
|
export declare function makeBashComposition(manager: YagniSandboxManager, settings: () => SandboxSettings, cwd: string, onShellResolutionRetry?: (outcome: "recovered" | "exhausted") => void, fsScope?: () => {
|
|
32
32
|
allowWrite: readonly string[];
|
|
33
33
|
denyWrite: readonly string[];
|
|
34
|
-
} | null): (def: ToolDefinition) => ToolDefinition;
|
|
34
|
+
} | null, baseSpawnHook?: BashSpawnHook): (def: ToolDefinition) => ToolDefinition;
|
|
35
35
|
export interface SandboxSessionHandle {
|
|
36
36
|
manager: YagniSandboxManager;
|
|
37
37
|
settings: () => SandboxSettings;
|
|
@@ -94,6 +94,9 @@ export interface RegisterSandboxOptions {
|
|
|
94
94
|
* the /sandbox panel's grants section — the never-matched surfacing.
|
|
95
95
|
* Absent ⇒ no grants section. */
|
|
96
96
|
grantCensus?: () => import("../permission/grantCensus.js").GrantCensusEntry[];
|
|
97
|
+
/** A spawn hook every bash spawn runs before the sandbox's own (the
|
|
98
|
+
* toolchain hook in a build sandbox). Absent: the sandbox hook alone. */
|
|
99
|
+
baseSpawnHook?: BashSpawnHook;
|
|
97
100
|
}
|
|
98
101
|
/**
|
|
99
102
|
* Register the sandbox surfaces on the ExtensionAPI. Returns the session
|
|
@@ -19,6 +19,7 @@ import { Key } from "@earendil-works/pi-tui";
|
|
|
19
19
|
import { createBashToolDefinition, getShellConfig, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import { bgLaunchResult, startBackgroundJob } from "../bgJobs.js";
|
|
21
21
|
import { logEvent } from "../errorSink.js";
|
|
22
|
+
import { composeSpawnHooks } from "../toolchain/spawnHook.js";
|
|
22
23
|
import { codeStateHome } from "../stateHome.js";
|
|
23
24
|
import { isDebug } from "../diagnostics.js";
|
|
24
25
|
import { mutateConfigJson, mutateLocalConfig } from "../settingsFiles.js";
|
|
@@ -40,13 +41,14 @@ import { effectiveRules } from "../permissionRules/loadConfig.js";
|
|
|
40
41
|
* here would be overwritten and the sandbox would silently never reach
|
|
41
42
|
* model-driven tool calls.
|
|
42
43
|
*/
|
|
43
|
-
export function makeBashComposition(manager, settings, cwd, onShellResolutionRetry, fsScope) {
|
|
44
|
+
export function makeBashComposition(manager, settings, cwd, onShellResolutionRetry, fsScope, baseSpawnHook) {
|
|
44
45
|
return (def) => {
|
|
45
46
|
if (!settings().enabled)
|
|
46
47
|
return def;
|
|
47
48
|
const userShellPath = SettingsManager.create(cwd).getShellPath();
|
|
48
49
|
const { shell: binShell } = getShellConfig(userShellPath);
|
|
49
|
-
|
|
50
|
+
// The toolchain hook (when the session has one) first, then the proxy env on top.
|
|
51
|
+
const spawnHook = composeSpawnHooks(baseSpawnHook, makeSandboxSpawnHook(manager));
|
|
50
52
|
const sandboxBash = createBashToolDefinition(cwd, {
|
|
51
53
|
shellPath: userShellPath,
|
|
52
54
|
spawnHook,
|
|
@@ -460,10 +462,17 @@ export function registerSandbox(pi, opts) {
|
|
|
460
462
|
logShellResolutionRetry(outcome);
|
|
461
463
|
opts.onShellResolutionRetry?.(outcome);
|
|
462
464
|
};
|
|
463
|
-
const composeBash = makeBashComposition(manager, () => currentSettings, opts.cwd, onShellResolutionRetry, fsScope);
|
|
465
|
+
const composeBash = makeBashComposition(manager, () => currentSettings, opts.cwd, onShellResolutionRetry, fsScope, opts.baseSpawnHook);
|
|
464
466
|
const registerOwnBash = () => {
|
|
465
|
-
if (!currentSettings.enabled)
|
|
467
|
+
if (!currentSettings.enabled) {
|
|
468
|
+
// Sandbox off: stock bash stays, except that a base hook (the build
|
|
469
|
+
// sandbox's toolchain) still has to reach the model's spawns.
|
|
470
|
+
if (!opts.baseSpawnHook)
|
|
471
|
+
return;
|
|
472
|
+
const userShellPath = SettingsManager.create(opts.cwd).getShellPath();
|
|
473
|
+
pi.registerTool(createBashToolDefinition(opts.cwd, { shellPath: userShellPath, spawnHook: opts.baseSpawnHook }));
|
|
466
474
|
return;
|
|
475
|
+
}
|
|
467
476
|
const userShellPath = SettingsManager.create(opts.cwd).getShellPath();
|
|
468
477
|
const stockBash = createBashToolDefinition(opts.cwd, { shellPath: userShellPath });
|
|
469
478
|
pi.registerTool(composeBash(stockBash));
|
|
@@ -500,9 +509,11 @@ export function registerSandbox(pi, opts) {
|
|
|
500
509
|
signal,
|
|
501
510
|
onRetry: onShellResolutionRetry,
|
|
502
511
|
});
|
|
512
|
+
const baseEnv = env ?? process.env;
|
|
503
513
|
const child = spawn(shell, [...args, wrapped], {
|
|
504
514
|
cwd,
|
|
505
|
-
|
|
515
|
+
// The toolchain's PATH and variables when the session has a base hook.
|
|
516
|
+
env: opts.baseSpawnHook ? opts.baseSpawnHook({ command, cwd, env: baseEnv }).env : baseEnv,
|
|
506
517
|
detached: true,
|
|
507
518
|
stdio: ["ignore", "pipe", "pipe"],
|
|
508
519
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolchain spawn hook (spec 2026-10-08 §1, "Two runtimes, one hand-off").
|
|
3
|
+
*
|
|
4
|
+
* In Wright's sandbox our CLI and pi run on the image's own Node (the harness
|
|
5
|
+
* puts `/opt/yagni/runtime-bin` first on their PATH), while the commands the
|
|
6
|
+
* model runs belong on the repository's toolchain: its Node 20, its Go 1.24.
|
|
7
|
+
* The backend writes the plan's PATH and environment to a toolchain file
|
|
8
|
+
* before setup, and the harness names it in `YAGNI_TOOLCHAIN_FILE`. This hook
|
|
9
|
+
* hands that PATH and environment to every bash spawn (and `user_bash`); the
|
|
10
|
+
* command string is never touched, so the Guardian reviews exactly what the
|
|
11
|
+
* model wrote. It composes before the sandbox's proxy hook, so the proxy
|
|
12
|
+
* variables still layer on top.
|
|
13
|
+
*
|
|
14
|
+
* Without the variable (a developer's own terminal) nothing changes.
|
|
15
|
+
*
|
|
16
|
+
* The format's producer is `toolchainFileContents` in
|
|
17
|
+
* `packages/shared/src/toolchainFile.ts`; this package does not depend on
|
|
18
|
+
* @yagni/shared, so it reads the same `{ version: 1, path, env, tools }` with
|
|
19
|
+
* its own parser. A change there is a change here.
|
|
20
|
+
*/
|
|
21
|
+
import { type BashOperations, type BashSpawnHook } from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import { type SinkEvent } from "../errorSink.js";
|
|
23
|
+
export interface ToolchainFile {
|
|
24
|
+
version: 1;
|
|
25
|
+
/** The plan's whole PATH, system directories included. */
|
|
26
|
+
path: string;
|
|
27
|
+
/** Every other toolchain variable; never PATH. */
|
|
28
|
+
env: Record<string, string>;
|
|
29
|
+
tools: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
version: string;
|
|
32
|
+
origin: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
type Log = (event: SinkEvent) => void;
|
|
36
|
+
/** The file, or null when the text is not a well-formed version 1 file. */
|
|
37
|
+
export declare function parseToolchainFile(text: string): ToolchainFile | null;
|
|
38
|
+
/**
|
|
39
|
+
* Read the toolchain file synchronously (activation is sync). Fail-soft: a
|
|
40
|
+
* missing, unreadable or malformed file is one `toolchain.file_unreadable`
|
|
41
|
+
* line and null, and the session runs exactly as it would without the file.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readToolchainFile(path: string, log?: Log): ToolchainFile | null;
|
|
44
|
+
/**
|
|
45
|
+
* `env.PATH` becomes the file's PATH and the file's other variables merge
|
|
46
|
+
* beneath the spawn's own environment. `command` and `cwd` pass through.
|
|
47
|
+
*/
|
|
48
|
+
export declare function makeToolchainSpawnHook(file: ToolchainFile): BashSpawnHook;
|
|
49
|
+
/** The hooks applied in order (first to last), skipping absent ones; undefined when none is present. */
|
|
50
|
+
export declare function composeSpawnHooks(...hooks: Array<BashSpawnHook | undefined>): BashSpawnHook | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* The hook for this process: present only when `YAGNI_TOOLCHAIN_FILE` names a
|
|
53
|
+
* readable, well-formed file. Unset is silent (not a sandbox); set but
|
|
54
|
+
* unreadable logs once.
|
|
55
|
+
*/
|
|
56
|
+
export declare function toolchainSpawnHookFromEnv(env?: NodeJS.ProcessEnv, log?: Log): BashSpawnHook | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* `user_bash` (`!` commands) through pi's own local backend, with the hook's
|
|
59
|
+
* environment: the same toolchain the model's bash tool gets.
|
|
60
|
+
*/
|
|
61
|
+
export declare function toolchainUserBashOperations(hook: BashSpawnHook, cwd: string): BashOperations;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=spawnHook.d.ts.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolchain spawn hook (spec 2026-10-08 §1, "Two runtimes, one hand-off").
|
|
3
|
+
*
|
|
4
|
+
* In Wright's sandbox our CLI and pi run on the image's own Node (the harness
|
|
5
|
+
* puts `/opt/yagni/runtime-bin` first on their PATH), while the commands the
|
|
6
|
+
* model runs belong on the repository's toolchain: its Node 20, its Go 1.24.
|
|
7
|
+
* The backend writes the plan's PATH and environment to a toolchain file
|
|
8
|
+
* before setup, and the harness names it in `YAGNI_TOOLCHAIN_FILE`. This hook
|
|
9
|
+
* hands that PATH and environment to every bash spawn (and `user_bash`); the
|
|
10
|
+
* command string is never touched, so the Guardian reviews exactly what the
|
|
11
|
+
* model wrote. It composes before the sandbox's proxy hook, so the proxy
|
|
12
|
+
* variables still layer on top.
|
|
13
|
+
*
|
|
14
|
+
* Without the variable (a developer's own terminal) nothing changes.
|
|
15
|
+
*
|
|
16
|
+
* The format's producer is `toolchainFileContents` in
|
|
17
|
+
* `packages/shared/src/toolchainFile.ts`; this package does not depend on
|
|
18
|
+
* @yagni/shared, so it reads the same `{ version: 1, path, env, tools }` with
|
|
19
|
+
* its own parser. A change there is a change here.
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { createLocalBashOperations, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
import { logEvent } from "../errorSink.js";
|
|
24
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
/** The file, or null when the text is not a well-formed version 1 file. */
|
|
26
|
+
export function parseToolchainFile(text) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(text);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Not JSON: the caller logs the null.
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (!isRecord(parsed) || parsed.version !== 1)
|
|
36
|
+
return null;
|
|
37
|
+
const { path, env, tools } = parsed;
|
|
38
|
+
if (typeof path !== "string" || path.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
if (!isRecord(env) || "PATH" in env)
|
|
41
|
+
return null;
|
|
42
|
+
const parsedEnv = {};
|
|
43
|
+
for (const [name, value] of Object.entries(env)) {
|
|
44
|
+
if (typeof value !== "string")
|
|
45
|
+
return null;
|
|
46
|
+
parsedEnv[name] = value;
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(tools))
|
|
49
|
+
return null;
|
|
50
|
+
const parsedTools = [];
|
|
51
|
+
for (const tool of tools) {
|
|
52
|
+
if (!isRecord(tool))
|
|
53
|
+
return null;
|
|
54
|
+
const { name, version, origin } = tool;
|
|
55
|
+
if (typeof name !== "string" || typeof version !== "string" || typeof origin !== "string")
|
|
56
|
+
return null;
|
|
57
|
+
parsedTools.push({ name, version, origin });
|
|
58
|
+
}
|
|
59
|
+
return { version: 1, path, env: parsedEnv, tools: parsedTools };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read the toolchain file synchronously (activation is sync). Fail-soft: a
|
|
63
|
+
* missing, unreadable or malformed file is one `toolchain.file_unreadable`
|
|
64
|
+
* line and null, and the session runs exactly as it would without the file.
|
|
65
|
+
*/
|
|
66
|
+
export function readToolchainFile(path, log = logEvent) {
|
|
67
|
+
let text;
|
|
68
|
+
try {
|
|
69
|
+
text = readFileSync(path, "utf8");
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const code = isRecord(err) && typeof err.code === "string" ? err.code : "read_failed";
|
|
73
|
+
log({ source: "toolchain", level: "warn", event: "file_unreadable", fields: { path, reason: code } });
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const file = parseToolchainFile(text);
|
|
77
|
+
if (!file)
|
|
78
|
+
log({ source: "toolchain", level: "warn", event: "file_unreadable", fields: { path, reason: "malformed" } });
|
|
79
|
+
return file;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `env.PATH` becomes the file's PATH and the file's other variables merge
|
|
83
|
+
* beneath the spawn's own environment. `command` and `cwd` pass through.
|
|
84
|
+
*/
|
|
85
|
+
export function makeToolchainSpawnHook(file) {
|
|
86
|
+
return (ctx) => ({
|
|
87
|
+
...ctx,
|
|
88
|
+
env: { ...file.env, ...ctx.env, PATH: file.path },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** The hooks applied in order (first to last), skipping absent ones; undefined when none is present. */
|
|
92
|
+
export function composeSpawnHooks(...hooks) {
|
|
93
|
+
const present = hooks.filter((hook) => hook !== undefined);
|
|
94
|
+
if (present.length === 0)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (present.length === 1)
|
|
97
|
+
return present[0];
|
|
98
|
+
return (ctx) => present.reduce((acc, hook) => hook(acc), ctx);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The hook for this process: present only when `YAGNI_TOOLCHAIN_FILE` names a
|
|
102
|
+
* readable, well-formed file. Unset is silent (not a sandbox); set but
|
|
103
|
+
* unreadable logs once.
|
|
104
|
+
*/
|
|
105
|
+
export function toolchainSpawnHookFromEnv(env = process.env, log = logEvent) {
|
|
106
|
+
const path = env.YAGNI_TOOLCHAIN_FILE?.trim();
|
|
107
|
+
if (!path)
|
|
108
|
+
return undefined;
|
|
109
|
+
const file = readToolchainFile(path, log);
|
|
110
|
+
return file ? makeToolchainSpawnHook(file) : undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `user_bash` (`!` commands) through pi's own local backend, with the hook's
|
|
114
|
+
* environment: the same toolchain the model's bash tool gets.
|
|
115
|
+
*/
|
|
116
|
+
export function toolchainUserBashOperations(hook, cwd) {
|
|
117
|
+
// The user's configured shell, as the sandbox's own `!` path resolves it.
|
|
118
|
+
const shellPath = SettingsManager.create(cwd).getShellPath();
|
|
119
|
+
const local = createLocalBashOperations(shellPath ? { shellPath } : undefined);
|
|
120
|
+
return {
|
|
121
|
+
exec: (command, cwd, options) => local.exec(command, cwd, {
|
|
122
|
+
...options,
|
|
123
|
+
env: hook({ command, cwd, env: options.env ?? process.env }).env,
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=spawnHook.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.2.3-staging.
|
|
3
|
+
"version": "1.2.3-staging.1708.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"typebox": "^1.3.15"
|
|
60
60
|
},
|
|
61
|
-
"yagniSourceSha": "
|
|
61
|
+
"yagniSourceSha": "78225de1501acd6df6c53f8419800e89c68d71e2"
|
|
62
62
|
}
|