@xingular/harness 1.1.1 → 1.3.1-unstable.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.
- package/CHANGELOG.md +48 -0
- package/README.md +36 -28
- package/dist/cli.js +10837 -6159
- package/dist/sdk/accounts/accounts.d.ts +27 -3
- package/dist/sdk/accounts/accounts.js +78 -24
- package/dist/sdk/accounts/secrets.d.ts +12 -0
- package/dist/sdk/accounts/secrets.js +114 -7
- package/dist/sdk/accounts/store.d.ts +26 -1
- package/dist/sdk/accounts/store.js +1 -1
- package/dist/sdk/agents/builtin.js +18 -4
- package/dist/sdk/agents/planResearch.d.ts +27 -0
- package/dist/sdk/agents/planResearch.js +82 -0
- package/dist/sdk/agents/profiles.d.ts +5 -1
- package/dist/sdk/agents/profiles.js +21 -5
- package/dist/sdk/annotations/pageScript.js +349 -48
- package/dist/sdk/annotations/part.js +34 -23
- package/dist/sdk/annotations/store.d.ts +7 -0
- package/dist/sdk/annotations/store.js +13 -5
- package/dist/sdk/browser/backend.d.ts +16 -0
- package/dist/sdk/browser/backend.js +17 -0
- package/dist/sdk/browser/contracts.d.ts +19 -0
- package/dist/sdk/browser/contracts.js +116 -0
- package/dist/sdk/browser/tools.d.ts +1 -4
- package/dist/sdk/browser/tools.js +14 -91
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/cli.js +5 -4
- package/dist/sdk/commands/list.d.ts +3 -0
- package/dist/sdk/commands/list.js +75 -0
- package/dist/sdk/commands/registry.d.ts +258 -0
- package/dist/sdk/commands/registry.js +48 -0
- package/dist/sdk/commands/routes.d.ts +16 -0
- package/dist/sdk/commands/routes.js +33 -0
- package/dist/sdk/core/commandBudget.d.ts +23 -0
- package/dist/sdk/core/commandBudget.js +57 -0
- package/dist/sdk/core/index.js +17 -1
- package/dist/sdk/core/interactiveProfile.d.ts +46 -0
- package/dist/sdk/core/interactiveProfile.js +143 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspace.d.ts +13 -0
- package/dist/sdk/core/workspace.js +88 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +20 -0
- package/dist/sdk/core/workspaceBashTool.js +50 -0
- package/dist/sdk/core/workspaceSandbox.d.ts +41 -0
- package/dist/sdk/core/workspaceSandbox.js +360 -0
- package/dist/sdk/core/workspaceWriteOperations.d.ts +33 -0
- package/dist/sdk/core/workspaceWriteOperations.js +224 -0
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/doctor/types.d.ts +2 -2
- package/dist/sdk/harness.d.ts +7 -0
- package/dist/sdk/harness.js +42 -2
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +8 -3
- package/dist/sdk/index.js +4 -1
- package/dist/sdk/pi/runtime.d.ts +2 -1
- package/dist/sdk/pi/runtime.js +2 -2
- package/dist/sdk/plugins/builtin.js +32 -15
- package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
- package/dist/sdk/plugins/builtinRegistry.js +30 -8
- package/dist/sdk/plugins/catalogue.js +2 -1
- package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
- package/dist/sdk/plugins/commandPrefix.js +21 -0
- package/dist/sdk/plugins/firstParty.d.ts +1 -1
- package/dist/sdk/plugins/firstParty.js +8 -2
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +6 -5
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +2 -1
- package/dist/sdk/plugins/surfaces.d.ts +5 -0
- package/dist/sdk/plugins/surfaces.js +11 -0
- package/dist/sdk/plugins/types.d.ts +15 -0
- package/dist/sdk/prompt/compile.d.ts +14 -0
- package/dist/sdk/prompt/compile.js +65 -31
- package/dist/sdk/prompt/standard.d.ts +17 -0
- package/dist/sdk/prompt/standard.js +21 -0
- package/dist/sdk/server/methods.js +22 -2
- package/dist/sdk/server/remote.js +36 -11
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
- package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
- package/dist/sdk/sessions/continuation.d.ts +7 -2
- package/dist/sdk/sessions/continuation.js +11 -6
- package/dist/sdk/sessions/contracts.d.ts +190 -4
- package/dist/sdk/sessions/contracts.js +71 -8
- package/dist/sdk/sessions/control.d.ts +72 -0
- package/dist/sdk/sessions/control.js +158 -0
- package/dist/sdk/sessions/initialBash.d.ts +7 -0
- package/dist/sdk/sessions/initialBash.js +7 -0
- package/dist/sdk/sessions/planMode.d.ts +14 -3
- package/dist/sdk/sessions/planMode.js +74 -17
- package/dist/sdk/sessions/registry.d.ts +1 -1
- package/dist/sdk/sessions/registry.js +2 -2
- package/dist/sdk/sessions/runStore.d.ts +14 -0
- package/dist/sdk/sessions/runStore.js +38 -1
- package/dist/sdk/sessions/runner.d.ts +40 -2
- package/dist/sdk/sessions/runner.js +161 -13
- package/dist/sdk/sessions/sessions.d.ts +23 -3
- package/dist/sdk/sessions/sessions.js +225 -22
- package/dist/sdk/sessions/subagentService.d.ts +24 -0
- package/dist/sdk/sessions/subagentService.js +225 -26
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +106 -0
- package/dist/sdk/sessions/workSlice.js +329 -0
- package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
- package/dist/sdk/sessions/workspaceBoundary.js +46 -0
- package/dist/sdk/terminal/browserControl.js +2 -1
- package/dist/sdk/terminal/controller.d.ts +31 -3
- package/dist/sdk/terminal/controller.js +81 -29
- package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
- package/dist/sdk/terminal/opentui/ansi.js +192 -0
- package/dist/sdk/terminal/opentui/model.d.ts +163 -0
- package/dist/sdk/terminal/opentui/model.js +482 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
- package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
- package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
- package/dist/sdk/terminal/opentui/theme.js +88 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -1
- package/dist/sdk/terminal/piExtension.js +177 -63
- package/dist/sdk/terminal/standalone.d.ts +7 -0
- package/dist/sdk/terminal/standalone.js +72 -13
- package/dist/sdk/terminal/subagentMonitor.d.ts +28 -6
- package/dist/sdk/terminal/subagentMonitor.js +61 -9
- package/dist/sdk/terminal/terminals.d.ts +26 -2
- package/dist/sdk/terminal/terminals.js +325 -43
- package/dist/sdk/terminal/trajectory.d.ts +6 -1
- package/dist/sdk/terminal/trajectory.js +8 -0
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
- package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
- package/dist/sdk/terminal/tui/chrome.js +13 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
- package/dist/sdk/terminal/tui/commandPages.js +17 -4
- package/dist/sdk/terminal/tui/commands.d.ts +6 -0
- package/dist/sdk/terminal/tui/commands.js +213 -124
- package/dist/sdk/terminal/tui/list.d.ts +14 -1
- package/dist/sdk/terminal/tui/list.js +96 -14
- package/dist/sdk/terminal/tui/pageOverlay.js +21 -5
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
- package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
- package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +8 -6
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +206 -28
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +104 -19
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
- package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
- package/dist/sdk/terminal/tui/piAdapter.js +3 -5
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
- package/dist/sdk/terminal/tui/runtime.js +23 -3
- package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
- package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
- package/dist/sdk/tools/checkpoint.d.ts +27 -0
- package/dist/sdk/tools/checkpoint.js +154 -0
- package/dist/sdk/tools/getCapabilities.d.ts +31 -0
- package/dist/sdk/tools/getCapabilities.js +34 -0
- package/dist/sdk/tools/sessionName.d.ts +2 -0
- package/dist/sdk/tools/sessionName.js +5 -3
- package/dist/sdk/tools/spawnSubagent.js +11 -8
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/usage/analytics.d.ts +20 -0
- package/dist/sdk/usage/analytics.js +151 -0
- package/dist/sdk/usage/ledger.d.ts +38 -0
- package/dist/sdk/usage/ledger.js +142 -0
- package/dist/sdk/usage/limits/adapters.d.ts +11 -0
- package/dist/sdk/usage/limits/adapters.js +349 -0
- package/dist/sdk/usage/limits/capture.d.ts +8 -0
- package/dist/sdk/usage/limits/capture.js +47 -0
- package/dist/sdk/usage/limits/http.d.ts +12 -0
- package/dist/sdk/usage/limits/http.js +51 -0
- package/dist/sdk/usage/limits/normalize.d.ts +15 -0
- package/dist/sdk/usage/limits/normalize.js +211 -0
- package/dist/sdk/usage/limits/service.d.ts +30 -0
- package/dist/sdk/usage/limits/service.js +176 -0
- package/dist/sdk/usage/limits/types.d.ts +65 -0
- package/dist/sdk/usage/limits/types.js +1 -0
- package/dist/sdk/usage/serverMethods.d.ts +3 -0
- package/dist/sdk/usage/serverMethods.js +22 -0
- package/dist/sdk/usage/service.d.ts +32 -0
- package/dist/sdk/usage/service.js +66 -0
- package/dist/sdk/usage/settings.d.ts +7 -0
- package/dist/sdk/usage/settings.js +28 -0
- package/dist/sdk/usage/types.d.ts +60 -0
- package/dist/sdk/usage/types.js +4 -0
- package/dist/terminal-opentui.js +9204 -4812
- package/dist/terminal-pi.js +1848 -272
- package/docs/architecture/00-overview.md +1 -1
- package/docs/architecture/01-extension-points.md +1 -1
- package/docs/architecture/02-sessions-and-runs.md +169 -36
- package/docs/architecture/04-prompt.md +55 -15
- package/docs/architecture/06-plugins-and-capabilities.md +36 -15
- package/docs/architecture/07-tools-and-gate.md +131 -25
- package/docs/architecture/08-memory-and-history.md +4 -2
- package/docs/architecture/09-accounts-and-connectors.md +39 -3
- package/docs/architecture/10-server-protocol.md +71 -9
- package/docs/architecture/11-terminal.md +148 -60
- package/docs/architecture/12-storage.md +30 -2
- package/docs/architecture/13-world-tools.md +22 -1
- package/docs/architecture/14-surfaces-and-parity.md +58 -8
- package/docs/architecture/README.md +20 -17
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
- package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
- package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
- package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
- package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
- package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
- package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
- package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
- package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
- package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
- package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +258 -0
- package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
- package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
- package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
- package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
- package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
- package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
- package/docs/architecture/proposals/README.md +24 -6
- package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
- package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
- package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
- package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
- package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
- package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
- package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
- package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
- package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
- package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
- package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
- package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
- package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
- package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
- package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
- package/docs/doctor.md +14 -1
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
- package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
- package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
- package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
- package/docs/parity.md +83 -27
- package/docs/releases/1.1.1.md +28 -0
- package/docs/releases/1.2.0.md +49 -0
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/4.0.0.md +10 -9
- package/docs/releases/README.md +35 -30
- package/docs/study/00-index.md +3 -0
- package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
- package/docs/terminal-tui.md +162 -82
- package/package.json +4 -3
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -54
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { closeSync, fsyncSync, linkSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
5
|
+
import { fileSecretStore } from "../accounts/secrets.js";
|
|
6
|
+
const fail = () => { throw new Error('Invalid interactive profile configuration.'); };
|
|
7
|
+
const object = (value) => {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
9
|
+
return fail();
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
const keys = (value, expected) => {
|
|
13
|
+
if (Object.keys(value).length !== expected.length || expected.some(key => !Object.hasOwn(value, key)))
|
|
14
|
+
fail();
|
|
15
|
+
};
|
|
16
|
+
const absolute = (value) => {
|
|
17
|
+
if (typeof value !== 'string' || value.length > 4096 || value.includes('\0') || !isAbsolute(value))
|
|
18
|
+
return fail();
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
const identifier = (value) => {
|
|
22
|
+
if (typeof value !== 'string' || value.length > 128 || !/^[A-Za-z0-9_-]+$/.test(value))
|
|
23
|
+
return fail();
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
/** Strict, bounded, credential-free configuration. Unknown fields are errors, not ignored secrets. */
|
|
27
|
+
export function validateInteractiveProfile(value) {
|
|
28
|
+
const record = object(value);
|
|
29
|
+
keys(record, ['version', 'dataRoot', 'stateDir', 'defaultVariantScope', 'secretBackend']);
|
|
30
|
+
if (record.version !== 1)
|
|
31
|
+
return fail();
|
|
32
|
+
const backend = object(record.secretBackend);
|
|
33
|
+
let secretBackend;
|
|
34
|
+
if (backend.kind === 'file') {
|
|
35
|
+
keys(backend, ['kind', 'path']);
|
|
36
|
+
secretBackend = { kind: 'file', path: absolute(backend.path) };
|
|
37
|
+
}
|
|
38
|
+
else if (backend.kind === 'host') {
|
|
39
|
+
keys(backend, ['kind', 'id']);
|
|
40
|
+
secretBackend = { kind: 'host', id: identifier(backend.id) };
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
return fail();
|
|
44
|
+
return {
|
|
45
|
+
version: 1,
|
|
46
|
+
dataRoot: absolute(record.dataRoot),
|
|
47
|
+
stateDir: absolute(record.stateDir),
|
|
48
|
+
defaultVariantScope: identifier(record.defaultVariantScope),
|
|
49
|
+
secretBackend
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function interactiveProfilePath(home = homedir()) {
|
|
53
|
+
return join(absolute(home), '.xingular-harness', 'profile.json');
|
|
54
|
+
}
|
|
55
|
+
function selectedConfigPath(location = {}) {
|
|
56
|
+
return location.configPath === undefined ? interactiveProfilePath(location.home) : absolute(location.configPath);
|
|
57
|
+
}
|
|
58
|
+
/** Read only. Missing is legacy mode; malformed or inaccessible is never silently ignored. */
|
|
59
|
+
export function readInteractiveProfile(location = {}) {
|
|
60
|
+
const path = selectedConfigPath(location);
|
|
61
|
+
try {
|
|
62
|
+
const info = statSync(path);
|
|
63
|
+
if (!info.isFile() || info.size > 16_384)
|
|
64
|
+
return fail();
|
|
65
|
+
return validateInteractiveProfile(JSON.parse(readFileSync(path, 'utf8')));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.code === 'ENOENT')
|
|
69
|
+
return null;
|
|
70
|
+
// Do not include JSON contents, paths, or backend details in diagnostics.
|
|
71
|
+
throw new Error('Interactive profile could not be read or validated.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function overrides(value) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return {};
|
|
77
|
+
const record = object(value);
|
|
78
|
+
if (Object.keys(record).some(key => !['dataRoot', 'stateDir', 'defaultVariantScope', 'secretBackend'].includes(key)))
|
|
79
|
+
return fail();
|
|
80
|
+
return record;
|
|
81
|
+
}
|
|
82
|
+
/** Explicit roots > selected profile > caller's legacy defaults. Never creates roots or merges data. */
|
|
83
|
+
export function resolveInteractiveProfile(options = {}) {
|
|
84
|
+
const home = absolute(options.home ?? homedir());
|
|
85
|
+
const root = join(home, '.xingular-harness');
|
|
86
|
+
const selected = readInteractiveProfile(options);
|
|
87
|
+
const legacy = overrides(options.legacyDefaults);
|
|
88
|
+
const explicit = overrides(options.overrides);
|
|
89
|
+
const dataRoot = explicit.dataRoot ?? selected?.dataRoot ?? legacy.dataRoot ?? join(root, 'data');
|
|
90
|
+
const stateDir = explicit.stateDir ?? selected?.stateDir ?? legacy.stateDir ?? join(root, 'state');
|
|
91
|
+
const defaultVariantScope = explicit.defaultVariantScope ?? selected?.defaultVariantScope ?? legacy.defaultVariantScope ?? 'default';
|
|
92
|
+
const secretBackend = explicit.secretBackend ?? selected?.secretBackend ?? legacy.secretBackend ?? {
|
|
93
|
+
kind: 'file',
|
|
94
|
+
path: join(stateDir, 'secrets.json')
|
|
95
|
+
};
|
|
96
|
+
return validateInteractiveProfile({ version: 1, dataRoot, stateDir, defaultVariantScope, secretBackend });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Explicit adoption only. Creates the profile file, never roots or credentials; it never replaces
|
|
100
|
+
* an existing profile or merges data. A pre-profile host must supply its selected backend in
|
|
101
|
+
* `legacyDefaults` so an absent profile cannot silently downgrade it to the file store.
|
|
102
|
+
*/
|
|
103
|
+
export function adoptInteractiveProfile(profile, location = {}) {
|
|
104
|
+
const validated = validateInteractiveProfile(profile);
|
|
105
|
+
const path = selectedConfigPath(location);
|
|
106
|
+
const folder = dirname(path);
|
|
107
|
+
mkdirSync(folder, { recursive: true, mode: 0o700 });
|
|
108
|
+
const temporary = join(folder, `.profile-${randomUUID()}.tmp`);
|
|
109
|
+
try {
|
|
110
|
+
const fd = openSync(temporary, 'wx', 0o600);
|
|
111
|
+
try {
|
|
112
|
+
writeFileSync(fd, `${JSON.stringify(validated, null, 2)}\n`);
|
|
113
|
+
fsyncSync(fd);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
closeSync(fd);
|
|
117
|
+
}
|
|
118
|
+
linkSync(temporary, path);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
throw new Error('Interactive profile adoption failed; existing configuration was not replaced.');
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
rmSync(temporary, { force: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Every participating process must map this host id to the same credential store. The account
|
|
129
|
+
* lock serializes access but cannot unify distinct native Keychain identities; no file fallback is
|
|
130
|
+
* allowed when a selected host adapter is unavailable.
|
|
131
|
+
*/
|
|
132
|
+
export async function interactiveProfileSecretStore(profile, host) {
|
|
133
|
+
const { secretBackend } = validateInteractiveProfile(profile);
|
|
134
|
+
if (secretBackend.kind === 'file')
|
|
135
|
+
return fileSecretStore(secretBackend.path);
|
|
136
|
+
if (!host)
|
|
137
|
+
throw new Error('The selected credential backend is unavailable; supply its host SecretStore adapter.');
|
|
138
|
+
const store = await host(secretBackend);
|
|
139
|
+
if (!store || !['get', 'set', 'delete'].every(key => typeof store[key] === 'function')) {
|
|
140
|
+
throw new Error('The selected credential backend did not provide a SecretStore.');
|
|
141
|
+
}
|
|
142
|
+
return store;
|
|
143
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ShellKind = 'bash' | 'powershell';
|
|
2
|
+
export interface Shell {
|
|
3
|
+
kind: ShellKind;
|
|
4
|
+
executable: string;
|
|
5
|
+
/** Arguments placed before the command text. */
|
|
6
|
+
args: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface ShellLookup {
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
/** Whether a file exists at an absolute path. */
|
|
11
|
+
exists?: (path: string) => boolean;
|
|
12
|
+
/** The first match for an executable name on the search path. */
|
|
13
|
+
onPath?: (name: string) => string | null;
|
|
14
|
+
}
|
|
15
|
+
/** The shell for this machine, resolved once. Null when none was found. */
|
|
16
|
+
export declare function hostShell(): Shell | null;
|
|
17
|
+
export declare function resolveShell(platform: NodeJS.Platform, lookup?: ShellLookup): Shell | null;
|
|
18
|
+
/** The full argument list that runs `command` in `shell`. */
|
|
19
|
+
export declare function shellArguments(shell: Shell, command: string): string[];
|
|
20
|
+
export declare function shellLabel(shell: Shell): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The shell the Bash tool runs commands in. POSIX systems use Bash. Windows uses Git Bash when it
|
|
2
|
+
// is installed and PowerShell otherwise, so a fresh machine can run commands with nothing extra.
|
|
3
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
4
|
+
import { win32 } from 'node:path';
|
|
5
|
+
import { findBinary } from "./path.js";
|
|
6
|
+
const POWERSHELL_ARGS = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command'];
|
|
7
|
+
// Windows PowerShell writes the console code page by default; ask for UTF-8 like Pi does.
|
|
8
|
+
const POWERSHELL_UTF8 = 'try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}\n';
|
|
9
|
+
let cached;
|
|
10
|
+
/** The shell for this machine, resolved once. Null when none was found. */
|
|
11
|
+
export function hostShell() {
|
|
12
|
+
if (cached === undefined)
|
|
13
|
+
cached = resolveShell(process.platform);
|
|
14
|
+
return cached;
|
|
15
|
+
}
|
|
16
|
+
export function resolveShell(platform, lookup = {}) {
|
|
17
|
+
const env = lookup.env ?? process.env;
|
|
18
|
+
const exists = lookup.exists ?? isFile;
|
|
19
|
+
const onPath = lookup.onPath ?? findBinary;
|
|
20
|
+
if (platform !== 'win32') {
|
|
21
|
+
const executable = ['/bin/bash', '/usr/bin/bash'].find((candidate) => exists(candidate));
|
|
22
|
+
return executable ? { kind: 'bash', executable, args: ['-c'] } : null;
|
|
23
|
+
}
|
|
24
|
+
const gitBash = [env.ProgramFiles, env['ProgramFiles(x86)']]
|
|
25
|
+
.filter((folder) => Boolean(folder))
|
|
26
|
+
.map((folder) => win32.join(folder, 'Git', 'bin', 'bash.exe'))
|
|
27
|
+
.find((candidate) => exists(candidate));
|
|
28
|
+
if (gitBash)
|
|
29
|
+
return { kind: 'bash', executable: gitBash, args: ['-c'] };
|
|
30
|
+
const bashOnPath = onPath('bash.exe');
|
|
31
|
+
if (bashOnPath && !isWslLauncher(bashOnPath, env))
|
|
32
|
+
return { kind: 'bash', executable: bashOnPath, args: ['-c'] };
|
|
33
|
+
const existing = (folder, ...parts) => {
|
|
34
|
+
if (!folder)
|
|
35
|
+
return null;
|
|
36
|
+
const candidate = win32.join(folder, ...parts);
|
|
37
|
+
return exists(candidate) ? candidate : null;
|
|
38
|
+
};
|
|
39
|
+
// PowerShell 7 first; Windows PowerShell ships with every supported Windows version.
|
|
40
|
+
const powershell = onPath('pwsh.exe')
|
|
41
|
+
?? existing(env.ProgramFiles, 'PowerShell', '7', 'pwsh.exe')
|
|
42
|
+
?? onPath('powershell.exe')
|
|
43
|
+
?? existing(env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
44
|
+
return powershell ? { kind: 'powershell', executable: powershell, args: POWERSHELL_ARGS } : null;
|
|
45
|
+
}
|
|
46
|
+
/** The full argument list that runs `command` in `shell`. */
|
|
47
|
+
export function shellArguments(shell, command) {
|
|
48
|
+
return [...shell.args, shell.kind === 'powershell' ? `${POWERSHELL_UTF8}${command}` : command];
|
|
49
|
+
}
|
|
50
|
+
export function shellLabel(shell) {
|
|
51
|
+
if (shell.kind === 'powershell')
|
|
52
|
+
return 'PowerShell';
|
|
53
|
+
return /\\git\\/iu.test(shell.executable) ? 'Git Bash' : 'Bash';
|
|
54
|
+
}
|
|
55
|
+
// System32\bash.exe starts WSL, a separate Linux system that does not see Windows paths the same
|
|
56
|
+
// way and needs its command on stdin. It is not a usable Bash for this tool.
|
|
57
|
+
function isWslLauncher(path, env) {
|
|
58
|
+
const normalized = win32.normalize(path).toLowerCase();
|
|
59
|
+
const systemRoot = env.SystemRoot;
|
|
60
|
+
if (systemRoot) {
|
|
61
|
+
return ['System32', 'Sysnative'].some((folder) => normalized === win32.join(systemRoot, folder, 'bash.exe').toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/u.test(normalized);
|
|
64
|
+
}
|
|
65
|
+
function isFile(path) {
|
|
66
|
+
try {
|
|
67
|
+
accessSync(path, constants.F_OK);
|
|
68
|
+
return statSync(path).isFile();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The nearest Git repository containing `cwd`, or its canonical directory when there is none. */
|
|
2
|
+
export declare function resolveWorkspaceRoot(cwd: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a write target relative to `cwd` (the workspace root by default), and returns its
|
|
5
|
+
* canonical path only when the target stays inside the canonical workspace root.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveWorkspaceWritePath(workspaceRoot: string, candidate: string, cwd?: string): string;
|
|
8
|
+
/** Canonicalizes an absolute, relative, or home-relative write target without imposing a root. */
|
|
9
|
+
export declare function resolveWritePath(candidate: string, cwd?: string): string;
|
|
10
|
+
/** Whether a canonical target is within the workspace's canonical filesystem root. */
|
|
11
|
+
export declare function isWithinWorkspace(workspaceRoot: string, canonicalCandidate: string): boolean;
|
|
12
|
+
/** Revalidates a root pinned by an active run; a replaced or redirected root fails closed. */
|
|
13
|
+
export declare function verifyWorkspaceRoot(workspaceRoot: string): string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { lstatSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { invalid } from "./errors.js";
|
|
5
|
+
/** The nearest Git repository containing `cwd`, or its canonical directory when there is none. */
|
|
6
|
+
export function resolveWorkspaceRoot(cwd) {
|
|
7
|
+
const canonicalCwd = realpathSync(cwd);
|
|
8
|
+
if (!statSync(canonicalCwd).isDirectory())
|
|
9
|
+
throw invalid('The working directory must be a directory.');
|
|
10
|
+
if (dirname(canonicalCwd) === canonicalCwd)
|
|
11
|
+
throw invalid('The filesystem root cannot be used as an active project workspace.');
|
|
12
|
+
let directory = canonicalCwd;
|
|
13
|
+
while (true) {
|
|
14
|
+
try {
|
|
15
|
+
const marker = lstatSync(resolve(directory, '.git'));
|
|
16
|
+
if (marker.isDirectory() || marker.isFile())
|
|
17
|
+
return directory;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code !== 'ENOENT')
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
const parent = dirname(directory);
|
|
24
|
+
if (parent === directory)
|
|
25
|
+
return canonicalCwd;
|
|
26
|
+
directory = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolves a write target relative to `cwd` (the workspace root by default), and returns its
|
|
31
|
+
* canonical path only when the target stays inside the canonical workspace root.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveWorkspaceWritePath(workspaceRoot, candidate, cwd = workspaceRoot) {
|
|
34
|
+
const canonicalCandidate = resolveWritePath(candidate, cwd);
|
|
35
|
+
if (!isWithinWorkspace(workspaceRoot, canonicalCandidate))
|
|
36
|
+
throw invalid('Write target is outside the active project workspace.');
|
|
37
|
+
return canonicalCandidate;
|
|
38
|
+
}
|
|
39
|
+
/** Canonicalizes an absolute, relative, or home-relative write target without imposing a root. */
|
|
40
|
+
export function resolveWritePath(candidate, cwd = process.cwd()) {
|
|
41
|
+
const expanded = candidate === '~'
|
|
42
|
+
? homedir()
|
|
43
|
+
: candidate.startsWith('~/') || candidate.startsWith('~\\')
|
|
44
|
+
? join(homedir(), candidate.slice(2))
|
|
45
|
+
: candidate;
|
|
46
|
+
const absoluteCandidate = isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
|
|
47
|
+
return canonicalizeTarget(absoluteCandidate);
|
|
48
|
+
}
|
|
49
|
+
/** Whether a canonical target is within the workspace's canonical filesystem root. */
|
|
50
|
+
export function isWithinWorkspace(workspaceRoot, canonicalCandidate) {
|
|
51
|
+
const canonicalRoot = realpathSync(workspaceRoot);
|
|
52
|
+
if (!statSync(canonicalRoot).isDirectory())
|
|
53
|
+
throw invalid('The workspace root must be a directory.');
|
|
54
|
+
const fromRoot = relative(canonicalRoot, canonicalCandidate);
|
|
55
|
+
return fromRoot === '' || (fromRoot !== '..' && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot));
|
|
56
|
+
}
|
|
57
|
+
/** Revalidates a root pinned by an active run; a replaced or redirected root fails closed. */
|
|
58
|
+
export function verifyWorkspaceRoot(workspaceRoot) {
|
|
59
|
+
const canonicalRoot = realpathSync(workspaceRoot);
|
|
60
|
+
if (dirname(canonicalRoot) === canonicalRoot)
|
|
61
|
+
throw invalid('The filesystem root cannot be used as an active project workspace.');
|
|
62
|
+
if (canonicalRoot !== resolve(workspaceRoot) || !statSync(canonicalRoot).isDirectory()) {
|
|
63
|
+
throw invalid('The active project workspace changed after this run began.');
|
|
64
|
+
}
|
|
65
|
+
return canonicalRoot;
|
|
66
|
+
}
|
|
67
|
+
function canonicalizeTarget(path) {
|
|
68
|
+
const missing = [];
|
|
69
|
+
let current = path;
|
|
70
|
+
while (true) {
|
|
71
|
+
let exists = false;
|
|
72
|
+
try {
|
|
73
|
+
lstatSync(current);
|
|
74
|
+
exists = true;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code !== 'ENOENT')
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
if (exists)
|
|
81
|
+
return resolve(realpathSync(current), ...missing.reverse());
|
|
82
|
+
const parent = dirname(current);
|
|
83
|
+
if (parent === current)
|
|
84
|
+
throw invalid('Write target could not be resolved.');
|
|
85
|
+
missing.push(basename(current));
|
|
86
|
+
current = parent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BashOperations } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { type Shell } from "./shell.js";
|
|
3
|
+
/** Quotes command text and escapes terminal control/format characters for approval details. */
|
|
4
|
+
export declare function formatBashCommandForApproval(command: string): string;
|
|
5
|
+
interface ShellToolFactories<T> {
|
|
6
|
+
createBashTool: (cwd: string, options: {
|
|
7
|
+
operations: BashOperations;
|
|
8
|
+
}) => T;
|
|
9
|
+
createPowerShellTool: (cwd: string, options: {
|
|
10
|
+
operations: BashOperations;
|
|
11
|
+
}) => T;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The `bash` tool for this machine. The capability keeps the name `bash` on every platform; on a
|
|
15
|
+
* Windows machine without Git Bash it runs PowerShell and carries PowerShell's description.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createWorkspaceShellTool<T>(pi: ShellToolFactories<T>, cwd: string, operations: BashOperations, shell?: Shell | null, platform?: NodeJS.Platform): T;
|
|
18
|
+
/** Adds the explicit pre-execution escape request to Pi's native Bash tool. */
|
|
19
|
+
export declare function withWorkspaceWriteScope<T>(tool: T, note?: string): T;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
|
+
import { COMMAND_TIMEOUT_CEILING_SECONDS, COMMAND_TIMEOUT_MAX_SECONDS, DEFAULT_COMMAND_TIMEOUT_SECONDS, resolveCommandTimeout } from "./commandBudget.js";
|
|
3
|
+
import { hostShell, shellLabel } from "./shell.js";
|
|
4
|
+
import { runWithWorkspaceWriteScope } from "./workspaceSandbox.js";
|
|
5
|
+
/** Quotes command text and escapes terminal control/format characters for approval details. */
|
|
6
|
+
export function formatBashCommandForApproval(command) {
|
|
7
|
+
return JSON.stringify(command).replace(/[\u007f-\u009f\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff]/gu, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The `bash` tool for this machine. The capability keeps the name `bash` on every platform; on a
|
|
11
|
+
* Windows machine without Git Bash it runs PowerShell and carries PowerShell's description.
|
|
12
|
+
*/
|
|
13
|
+
export function createWorkspaceShellTool(pi, cwd, operations, shell = hostShell(), platform = process.platform) {
|
|
14
|
+
const native = shell?.kind === 'powershell'
|
|
15
|
+
? { ...pi.createPowerShellTool(cwd, { operations }), name: 'bash' }
|
|
16
|
+
: pi.createBashTool(cwd, { operations });
|
|
17
|
+
const note = platform === 'win32' && shell
|
|
18
|
+
? ` On this machine commands run in ${shellLabel(shell)}. Windows has no workspace sandbox, so each command asks for approval unless Full Permission is on, and plan mode cannot run commands.`
|
|
19
|
+
: '';
|
|
20
|
+
return withWorkspaceWriteScope(native, note);
|
|
21
|
+
}
|
|
22
|
+
/** Adds the explicit pre-execution escape request to Pi's native Bash tool. */
|
|
23
|
+
export function withWorkspaceWriteScope(tool, note = '') {
|
|
24
|
+
const native = tool;
|
|
25
|
+
const execute = native.execute.bind(tool);
|
|
26
|
+
return {
|
|
27
|
+
...tool,
|
|
28
|
+
description: `${native.description}${note} Workspace writes are the default. Set workspaceWriteScope to "outside" to request approval before broader writes. Commands run for ${DEFAULT_COMMAND_TIMEOUT_SECONDS} seconds unless timeout says otherwise; above ${COMMAND_TIMEOUT_CEILING_SECONDS} seconds a reason is required, and ${COMMAND_TIMEOUT_MAX_SECONDS} seconds is the maximum.`,
|
|
29
|
+
parameters: Type.Object({
|
|
30
|
+
command: Type.String({ description: 'Shell command to execute' }),
|
|
31
|
+
timeout: Type.Optional(Type.Number({ description: `Timeout in seconds (default ${DEFAULT_COMMAND_TIMEOUT_SECONDS})` })),
|
|
32
|
+
reason: Type.Optional(Type.String({ description: `Why a command needs more than ${COMMAND_TIMEOUT_CEILING_SECONDS} seconds` })),
|
|
33
|
+
workspaceWriteScope: Type.Optional(Type.Union([
|
|
34
|
+
Type.Literal('workspace'),
|
|
35
|
+
Type.Literal('outside')
|
|
36
|
+
], { description: 'Write scope for this command. Defaults to workspace; outside requests approval before running.' }))
|
|
37
|
+
}),
|
|
38
|
+
execute: (...args) => {
|
|
39
|
+
const input = args[1];
|
|
40
|
+
const scope = input?.workspaceWriteScope === 'outside' ? 'outside' : 'workspace';
|
|
41
|
+
const budget = resolveCommandTimeout(input?.timeout, input?.reason);
|
|
42
|
+
if (!budget.ok)
|
|
43
|
+
return { content: [{ type: 'text', text: budget.message }], details: { refused: true } };
|
|
44
|
+
// The native tool reads the timeout from its own arguments; give it the effective one.
|
|
45
|
+
const callArgs = [...args];
|
|
46
|
+
callArgs[1] = { ...(input ?? {}), timeout: budget.timeout };
|
|
47
|
+
return runWithWorkspaceWriteScope(scope, () => execute(...callArgs));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { BashOperations } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { type Shell } from "./shell.js";
|
|
3
|
+
/** `read-only` (plan mode) permits no workspace writes, no network and no outside processes. */
|
|
4
|
+
export type WorkspaceWriteScope = 'workspace' | 'outside' | 'read-only';
|
|
5
|
+
export declare function runWithWorkspaceWriteScope<T>(scope: WorkspaceWriteScope, action: () => T): T;
|
|
6
|
+
export interface WorkspaceWriteScopeRequest {
|
|
7
|
+
command: string;
|
|
8
|
+
cwd: string;
|
|
9
|
+
workspaceRoot: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
}
|
|
12
|
+
export interface WorkspaceBoundaryBypassRequest {
|
|
13
|
+
command: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
workspaceRoot: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkspaceBashOptions {
|
|
18
|
+
/** The explicit Full Permission switch can bypass the workspace boundary before the command runs. */
|
|
19
|
+
bypassWorkspaceBoundary?: (request: WorkspaceBoundaryBypassRequest) => Promise<boolean>;
|
|
20
|
+
requestWorkspaceWriteScope?: (request: WorkspaceWriteScopeRequest) => Promise<boolean>;
|
|
21
|
+
/** Overrides the per-call scope; `undefined` falls back to it. */
|
|
22
|
+
workspaceWriteScope?: () => WorkspaceWriteScope | undefined;
|
|
23
|
+
/** Exposed for platform-specific tests; production uses the current operating system. */
|
|
24
|
+
platform?: NodeJS.Platform;
|
|
25
|
+
/** Exposed for tests; production uses the machine's shell (`hostShell`). */
|
|
26
|
+
shell?: () => Shell | null;
|
|
27
|
+
}
|
|
28
|
+
/** Why a Windows command asks first: Windows has no write-boundary sandbox the harness can use. */
|
|
29
|
+
export declare const WINDOWS_UNSANDBOXED_REASON = "Windows has no workspace write-boundary sandbox, so this command would run without one. It will not run until this one-call request is approved.";
|
|
30
|
+
export interface WorkspaceSandboxCommand {
|
|
31
|
+
executable: string;
|
|
32
|
+
args: string[];
|
|
33
|
+
marker: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The operating system's write-boundary sandbox for this platform, or null when it is not
|
|
37
|
+
* installed. Bash cannot run without it, so readiness diagnostics report it before a command runs.
|
|
38
|
+
*/
|
|
39
|
+
export declare function workspaceSandboxBackend(platform?: NodeJS.Platform): string | null;
|
|
40
|
+
export declare function workspaceSandboxCommand(platform: NodeJS.Platform, workspaceRoot: string, cwd: string, command: string, additionalWriteRoots?: readonly string[], readOnlyTempRoot?: string | null): WorkspaceSandboxCommand;
|
|
41
|
+
export declare function createWorkspaceBashOperations(workspaceRoot: string | (() => string | null), options?: WorkspaceBashOptions): BashOperations;
|