@demicodes/shell 0.10.1 → 0.10.3
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/host-fs.d.mts +1 -0
- package/dist/host-fs.mjs +4 -4
- package/dist/index.d.mts +31 -62
- package/dist/index.mjs +162 -226
- package/dist/{storage-DB9hjZ2G.d.mts → storage-D7EhVuLY.d.mts} +15 -14
- package/dist/storage.d.mts +1 -1
- package/dist/storage.mjs +1 -1
- package/package.json +2 -2
package/dist/host-fs.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as Host } from "./host-DIg4RxcL.mjs";
|
|
2
2
|
import { BufferEncoding, CpOptions, DirentEntry, FileContent, FsStat, IFileSystem, MkdirOptions, ReadFileOptions, RmOptions, WriteFileOptions } from "@demicodes/just-bash/fs/interface";
|
|
3
|
+
|
|
3
4
|
//#region src/host-fs.d.ts
|
|
4
5
|
type VirtualFileSystemNode = {
|
|
5
6
|
kind: 'file';
|
package/dist/host-fs.mjs
CHANGED
|
@@ -166,13 +166,13 @@ function encodingFrom(options) {
|
|
|
166
166
|
function encodeContent(content, encoding) {
|
|
167
167
|
if (content instanceof Uint8Array) return content;
|
|
168
168
|
if (encoding === "binary" || encoding === "latin1") return latin1ToBytes(content);
|
|
169
|
-
if (encoding === "base64") return base64ToBytes
|
|
169
|
+
if (encoding === "base64") return base64ToBytes(content);
|
|
170
170
|
if (encoding === "hex") return hexToBytes(content);
|
|
171
171
|
return encodeUtf8(content);
|
|
172
172
|
}
|
|
173
173
|
function decodeBytes(bytes, encoding) {
|
|
174
174
|
if (encoding === "binary" || encoding === "latin1") return bytesToLatin1(bytes);
|
|
175
|
-
if (encoding === "base64") return bytesToBase64
|
|
175
|
+
if (encoding === "base64") return bytesToBase64(bytes);
|
|
176
176
|
if (encoding === "hex") return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
177
177
|
return decodeUtf8(bytes);
|
|
178
178
|
}
|
|
@@ -186,10 +186,10 @@ function bytesToLatin1(bytes) {
|
|
|
186
186
|
for (let index = 0; index < bytes.length; index += 8192) result += String.fromCharCode(...bytes.subarray(index, index + 8192));
|
|
187
187
|
return result;
|
|
188
188
|
}
|
|
189
|
-
function base64ToBytes
|
|
189
|
+
function base64ToBytes(content) {
|
|
190
190
|
return latin1ToBytes(atob(content));
|
|
191
191
|
}
|
|
192
|
-
function bytesToBase64
|
|
192
|
+
function bytesToBase64(bytes) {
|
|
193
193
|
return btoa(bytesToLatin1(bytes));
|
|
194
194
|
}
|
|
195
195
|
function hexToBytes(content) {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { a as HostProcess, c as HostSpawnHandle, i as HostFileSystem, l as HostSpawnParams, n as HostDirent, o as HostProcessOutputChunk, r as HostFileStat, s as HostSpawnExit, t as Host, u as HostStore } from "./host-DIg4RxcL.mjs";
|
|
2
2
|
import { HostBackedFileSystem, VirtualFileSystemNode, VirtualFileSystemProvider, virtualDirectory, virtualFile } from "./host-fs.mjs";
|
|
3
|
-
import { _ as runRegisteredCommand, a as
|
|
3
|
+
import { _ as runRegisteredCommand, a as CommandExecutionContext, c as CommandOutputSpec, d as CommandRunResult, f as CommandStdin, g as renderCommandPrompt, h as parseCommandInput, i as CommandAsset, l as CommandRegistry, m as ParsedCommandInput, n as COMMAND_PROMPT_DEFAULTS, o as CommandIO, p as CommandStorage, r as Command, s as CommandInputSpec, t as AgentSessionCommandStorage, u as CommandRunContext } from "./storage-D7EhVuLY.mjs";
|
|
4
4
|
import { CommandName } from "@demicodes/just-bash/commands";
|
|
5
5
|
import { Interpreter, InterpreterState } from "@demicodes/just-bash/interpreter";
|
|
6
6
|
import { CommandRegistry as CommandRegistry$1, ExecResult } from "@demicodes/just-bash/types";
|
|
7
|
+
|
|
7
8
|
//#region src/environment-state.d.ts
|
|
8
9
|
interface ExecAccumulator {
|
|
9
10
|
stdout: string;
|
|
10
11
|
stderr: string;
|
|
11
12
|
audit: BashAuditEvent[];
|
|
12
13
|
commandMetadata: CommandMetadataRecord[];
|
|
14
|
+
assets: CommandAsset[];
|
|
13
15
|
}
|
|
14
16
|
interface ShellSession {
|
|
15
17
|
id: string;
|
|
16
|
-
|
|
17
|
-
commandStorageId: string;
|
|
18
|
+
commandScopeId: string;
|
|
18
19
|
state: InterpreterState;
|
|
19
20
|
fs: HostBackedFileSystem;
|
|
20
21
|
interpreter: Interpreter;
|
|
@@ -56,8 +57,6 @@ interface ForegroundProcess {
|
|
|
56
57
|
/** Everything the process wrote, including redirected output — this is what
|
|
57
58
|
* the interpreter observes as the command's stdout/stderr. */
|
|
58
59
|
rawStdoutBuffer: string;
|
|
59
|
-
/** Raw stdout byte chunks for byte-clean pipeline continuation. */
|
|
60
|
-
rawStdoutBytes: Uint8Array[];
|
|
61
60
|
rawStderrBuffer: string;
|
|
62
61
|
/** Output routed to the visible sinks only (redirections excluded) — this is
|
|
63
62
|
* what command records and model previews show. */
|
|
@@ -91,21 +90,15 @@ interface BashEnvironmentOptions {
|
|
|
91
90
|
shellIdFactory?: () => string;
|
|
92
91
|
commandIdFactory?: () => string;
|
|
93
92
|
initialEnv?: Record<string, string>;
|
|
94
|
-
/** Cap on a command's text stdout. Guards against log floods. */
|
|
95
|
-
maxOutputBytes?: number;
|
|
96
93
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* carried to be looked at — a picture, a clip — and there a megabyte buys far
|
|
103
|
-
* more than a megabyte of text does, so the same number would forbid the
|
|
104
|
-
* ordinary case. This layer deliberately stops at that distinction: which
|
|
105
|
-
* modality the bytes are, and what a given model should be shown, is decided
|
|
106
|
-
* above, where models are known.
|
|
94
|
+
* Per-exec env injection for agent-owned shells. Evaluated on every exec with
|
|
95
|
+
* the owning agent session id; returned vars are set and exported, so both
|
|
96
|
+
* registered commands (ctx.env) and spawned external processes observe them.
|
|
97
|
+
* Lets a product harness expose session-scoped context (identity, routing)
|
|
98
|
+
* that changes between execs, which static initialEnv cannot express.
|
|
107
99
|
*/
|
|
108
|
-
|
|
100
|
+
execEnv?: (agentSessionId: string) => Record<string, string>;
|
|
101
|
+
maxOutputBytes?: number;
|
|
109
102
|
}
|
|
110
103
|
interface ShellExecInput {
|
|
111
104
|
script: string;
|
|
@@ -114,18 +107,6 @@ interface ShellExecInput {
|
|
|
114
107
|
timeoutMs?: number;
|
|
115
108
|
maxOutputBytes?: number;
|
|
116
109
|
signal?: AbortSignal;
|
|
117
|
-
/**
|
|
118
|
-
* Run in a dedicated one-shot shell instead of the session default shell, so
|
|
119
|
-
* cd/env side effects never leak into other execs sharing the session. The
|
|
120
|
-
* caller owns the shell and should `disposeShell(snapshot.shellId)` when done.
|
|
121
|
-
* Mutually exclusive with `shellId`.
|
|
122
|
-
*/
|
|
123
|
-
ephemeral?: boolean;
|
|
124
|
-
/**
|
|
125
|
-
* Initial working directory of the shell this exec creates. Requires
|
|
126
|
-
* `ephemeral` — a persistent shell owns its cwd (that is what `cd` is for).
|
|
127
|
-
*/
|
|
128
|
-
cwd?: string;
|
|
129
110
|
}
|
|
130
111
|
interface ShellStatusInput {
|
|
131
112
|
commandId: string;
|
|
@@ -144,7 +125,7 @@ interface ShellAbortInput {
|
|
|
144
125
|
commandId: string;
|
|
145
126
|
maxOutputBytes?: number;
|
|
146
127
|
}
|
|
147
|
-
interface
|
|
128
|
+
interface StreamArtifact {
|
|
148
129
|
path: string;
|
|
149
130
|
offset: number;
|
|
150
131
|
delta: string;
|
|
@@ -160,7 +141,7 @@ interface ShellOutputRecordChunk extends ShellOutputChunk {
|
|
|
160
141
|
offset: number;
|
|
161
142
|
bytes: number;
|
|
162
143
|
}
|
|
163
|
-
interface
|
|
144
|
+
interface ShellOutputArtifact {
|
|
164
145
|
path: string;
|
|
165
146
|
offset: number;
|
|
166
147
|
text: string;
|
|
@@ -193,46 +174,35 @@ interface CommandMetadataRecord {
|
|
|
193
174
|
args: string[];
|
|
194
175
|
metadata: unknown;
|
|
195
176
|
}
|
|
196
|
-
|
|
197
|
-
interface BinaryStdout {
|
|
198
|
-
data: Uint8Array;
|
|
199
|
-
/** True when the stream exceeded the applicable cap; data is capped. */
|
|
200
|
-
truncated: boolean;
|
|
201
|
-
/** Total byte count of the un-capped stream. */
|
|
202
|
-
totalBytes: number;
|
|
203
|
-
/** The byte ceiling that applied, so the boundary above can name it. */
|
|
204
|
-
limitBytes: number;
|
|
205
|
-
}
|
|
206
|
-
type ShellCommandStatus = {
|
|
177
|
+
type ShellCommandSnapshot = {
|
|
207
178
|
status: 'exited';
|
|
208
179
|
shellId: string;
|
|
209
180
|
commandId: string;
|
|
210
181
|
exitCode: number;
|
|
211
|
-
stdout:
|
|
212
|
-
stderr:
|
|
213
|
-
output:
|
|
182
|
+
stdout: StreamArtifact;
|
|
183
|
+
stderr: StreamArtifact;
|
|
184
|
+
output: ShellOutputArtifact;
|
|
214
185
|
runningMs: number;
|
|
215
186
|
idleMs: number;
|
|
216
187
|
audit: BashAuditEvent[];
|
|
217
188
|
commandMetadata?: CommandMetadataRecord[];
|
|
218
|
-
|
|
219
|
-
binaryStdout?: BinaryStdout;
|
|
189
|
+
assets?: CommandAsset[];
|
|
220
190
|
} | {
|
|
221
191
|
status: 'running';
|
|
222
192
|
shellId: string;
|
|
223
193
|
commandId: string;
|
|
224
|
-
stdout:
|
|
225
|
-
stderr:
|
|
226
|
-
output:
|
|
194
|
+
stdout: StreamArtifact;
|
|
195
|
+
stderr: StreamArtifact;
|
|
196
|
+
output: ShellOutputArtifact;
|
|
227
197
|
runningMs: number;
|
|
228
198
|
idleMs: number;
|
|
229
199
|
} | {
|
|
230
200
|
status: 'aborted';
|
|
231
201
|
shellId: string;
|
|
232
202
|
commandId: string;
|
|
233
|
-
stdout:
|
|
234
|
-
stderr:
|
|
235
|
-
output:
|
|
203
|
+
stdout: StreamArtifact;
|
|
204
|
+
stderr: StreamArtifact;
|
|
205
|
+
output: ShellOutputArtifact;
|
|
236
206
|
runningMs: number;
|
|
237
207
|
idleMs: number;
|
|
238
208
|
};
|
|
@@ -244,21 +214,20 @@ declare class BashEnvironment {
|
|
|
244
214
|
private readonly shellIdFactory;
|
|
245
215
|
private readonly commandIdFactory;
|
|
246
216
|
private readonly initialEnv;
|
|
217
|
+
private readonly execEnv?;
|
|
247
218
|
private readonly defaultOutputLimitBytes;
|
|
248
|
-
private readonly defaultBinaryLimitBytes;
|
|
249
219
|
private readonly shells;
|
|
250
220
|
private readonly defaultShellByAgentSessionId;
|
|
251
221
|
private readonly commandsById;
|
|
252
222
|
private readonly artifacts;
|
|
253
223
|
constructor(options: BashEnvironmentOptions);
|
|
254
224
|
getShell(shellId: string): ShellSession | null;
|
|
255
|
-
hasCommand(commandId: string): boolean;
|
|
256
225
|
registerCommand(command: Command): void;
|
|
257
226
|
registeredCommands(): Command[];
|
|
258
|
-
exec(input: ShellExecInput): Promise<
|
|
259
|
-
status(input: ShellStatusInput): Promise<
|
|
260
|
-
write(input: ShellWriteInput): Promise<
|
|
261
|
-
abort(input: ShellAbortInput): Promise<
|
|
227
|
+
exec(input: ShellExecInput): Promise<ShellCommandSnapshot>;
|
|
228
|
+
status(input: ShellStatusInput): Promise<ShellCommandSnapshot>;
|
|
229
|
+
write(input: ShellWriteInput): Promise<ShellCommandSnapshot>;
|
|
230
|
+
abort(input: ShellAbortInput): Promise<ShellCommandSnapshot>;
|
|
262
231
|
releaseCommand(commandId: string): Promise<boolean>;
|
|
263
232
|
disposeShell(shellId: string): Promise<boolean>;
|
|
264
233
|
disposeAllShells(): Promise<void>;
|
|
@@ -282,7 +251,7 @@ declare class BashEnvironment {
|
|
|
282
251
|
private finishExited;
|
|
283
252
|
private collectAborted;
|
|
284
253
|
private collectAbortedWithoutForeground;
|
|
285
|
-
private
|
|
254
|
+
private snapshotCommand;
|
|
286
255
|
private lookupVirtualArtifact;
|
|
287
256
|
private commandArtifactIds;
|
|
288
257
|
private commandArtifact;
|
|
@@ -321,4 +290,4 @@ declare function shellQuote(value: string): string;
|
|
|
321
290
|
/** Picks a heredoc delimiter that does not collide with any line already in `body`. */
|
|
322
291
|
declare function heredocDelimiter(body: string): string;
|
|
323
292
|
//#endregion
|
|
324
|
-
export { AgentSessionCommandStorage, BashAuditEvent, BashEnvironment, BashEnvironmentOptions,
|
|
293
|
+
export { AgentSessionCommandStorage, BashAuditEvent, BashEnvironment, BashEnvironmentOptions, COMMAND_PROMPT_DEFAULTS, Command, CommandAsset, CommandExecutionContext, CommandIO, CommandInputSpec, CommandMetadataRecord, CommandOutputSpec, CommandRegistry, CommandRunContext, CommandRunResult, CommandStdin, CommandStorage, DEMI_PORTABLE_COMMANDS, Host, HostBackedFileSystem, HostDirent, HostFileStat, HostFileSystem, HostProcess, HostProcessOutputChunk, HostSpawnExit, HostSpawnHandle, HostSpawnParams, HostStore, MAX_TIMEOUT_MS, ParsedCommandInput, RESERVED_COMMAND_NAMES, ShellAbortInput, ShellCommandSnapshot, ShellExecInput, ShellOutputArtifact, ShellOutputChunk, ShellOutputRecordChunk, ShellStatusInput, ShellWriteInput, StreamArtifact, VirtualFileSystemNode, VirtualFileSystemProvider, heredocDelimiter, parseCommandInput, renderCommandPrompt, runRegisteredCommand, shellQuote, virtualDirectory, virtualFile };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HostBackedFileSystem, virtualDirectory, virtualFile } from "./host-fs.mjs";
|
|
2
2
|
import { AgentSessionCommandStorage } from "./storage.mjs";
|
|
3
|
-
import { asError,
|
|
3
|
+
import { asError, concatBytes, decodeUtf8, encodeUtf8, tail, utf8Bytes, utf8Slice } from "@demicodes/utils";
|
|
4
4
|
import { createLazyCommands, getCommandNames } from "@demicodes/just-bash/commands";
|
|
5
5
|
import { ArithmeticError, BadSubstitutionError, ExecutionLimitError, ExitError, Interpreter } from "@demicodes/just-bash/interpreter";
|
|
6
6
|
import { decodeBytesToUtf8, unsafeBytesFromLatin1 } from "@demicodes/just-bash/encoding";
|
|
@@ -10,19 +10,26 @@ import { LexerError } from "@demicodes/just-bash/parser/lexer";
|
|
|
10
10
|
import { resolveLimits } from "@demicodes/just-bash/limits";
|
|
11
11
|
//#region src/portable-commands.ts
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Individually excluded even though just-bash calls them portable: each
|
|
14
|
+
* regresses a real, already-passing test elsewhere in this monorepo when
|
|
15
|
+
* dispatched through this package's fork-command wrapping specifically
|
|
16
|
+
* (isolated just-bash unit tests for these same commands pass fine, so the
|
|
17
|
+
* break is in how @demicodes/shell wraps them, not in just-bash itself):
|
|
16
18
|
*
|
|
17
|
-
* - `bash`, `sh`:
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
19
|
+
* - `bash`, `sh`: nested "run a script string through the interpreter again".
|
|
20
|
+
* Their stdout never reaches the caller here — `sh -c "printf hi"` and
|
|
21
|
+
* `bash -c "echo hi"` both exit 0 with empty output, while the exact same
|
|
22
|
+
* scripts run un-nested work fine. This package's own `AgentServer` test
|
|
23
|
+
* spawns `sh -c` for real and asserts on the real output.
|
|
24
|
+
* - `sleep`: just-bash's version is a plain timer, not a real backgroundable,
|
|
25
|
+
* abortable OS process. This package's own environment tests use `sleep 10`
|
|
26
|
+
* specifically as a long-running foreground process to exercise abort/
|
|
27
|
+
* timeout handling (`env.abort()` / status-while-`running`), which needs a
|
|
28
|
+
* real process to abort.
|
|
29
|
+
*
|
|
30
|
+
* Keeping these three off the list preserves their pre-existing
|
|
31
|
+
* fall-through to Host.process.spawn, same as before this list started
|
|
32
|
+
* tracking just-bash's registry.
|
|
26
33
|
*/
|
|
27
34
|
const REAL_SPAWN_DEPENDENT_COMMANDS = /* @__PURE__ */ new Set([
|
|
28
35
|
"bash",
|
|
@@ -108,12 +115,6 @@ const RESERVED_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
|
108
115
|
]);
|
|
109
116
|
//#endregion
|
|
110
117
|
//#region src/command.ts
|
|
111
|
-
function emptyStdin() {
|
|
112
|
-
return {
|
|
113
|
-
text: "",
|
|
114
|
-
bytes: /* @__PURE__ */ new Uint8Array(0)
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
118
|
const EXECUTION_ONLY_FIELDS = [
|
|
118
119
|
"effects",
|
|
119
120
|
"successOutput",
|
|
@@ -124,7 +125,6 @@ const EXECUTION_ONLY_FIELDS = [
|
|
|
124
125
|
"output",
|
|
125
126
|
"examples"
|
|
126
127
|
];
|
|
127
|
-
const COMMAND_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
128
128
|
var CommandRegistry = class {
|
|
129
129
|
commands = /* @__PURE__ */ new Map();
|
|
130
130
|
register(command) {
|
|
@@ -139,14 +139,14 @@ var CommandRegistry = class {
|
|
|
139
139
|
list() {
|
|
140
140
|
return [...this.commands.values()];
|
|
141
141
|
}
|
|
142
|
-
|
|
143
|
-
const rendered = this.list().map((command) =>
|
|
142
|
+
renderPrompt() {
|
|
143
|
+
const rendered = this.list().map((command) => renderCommandPrompt(command)).join("\n\n");
|
|
144
144
|
if (!rendered) return rendered;
|
|
145
|
-
return `${
|
|
145
|
+
return `${COMMAND_PROMPT_DEFAULTS}\n\n${rendered}`;
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
|
-
const
|
|
149
|
-
function parseCommandInput(root, argv, stdin =
|
|
148
|
+
const COMMAND_PROMPT_DEFAULTS = "Unless a command states otherwise: success prints raw text on stdout, failure writes an error message to stderr and exits non-zero.";
|
|
149
|
+
function parseCommandInput(root, argv, stdin = { text: "" }) {
|
|
150
150
|
if (argv[0] !== root.name) throw new Error(`Expected command "${root.name}", received "${argv[0] ?? ""}"`);
|
|
151
151
|
let node = root;
|
|
152
152
|
const path = [root.name];
|
|
@@ -157,7 +157,7 @@ function parseCommandInput(root, argv, stdin = emptyStdin()) {
|
|
|
157
157
|
throw new Error(`Command "${path.join(" ")}" requires a subcommand`);
|
|
158
158
|
}
|
|
159
159
|
const token = argv[index];
|
|
160
|
-
if (token === "
|
|
160
|
+
if (token === "prompt") return {
|
|
161
161
|
path: [...path],
|
|
162
162
|
help: true,
|
|
163
163
|
values: {},
|
|
@@ -182,12 +182,6 @@ function parseArgs(command, path, argv, startIndex, stdin) {
|
|
|
182
182
|
let positionalIndex = 0;
|
|
183
183
|
for (let i = startIndex; i < argv.length; i += 1) {
|
|
184
184
|
const token = argv[i];
|
|
185
|
-
if (token === "--help") return {
|
|
186
|
-
path: [...path],
|
|
187
|
-
help: true,
|
|
188
|
-
values: {},
|
|
189
|
-
json: false
|
|
190
|
-
};
|
|
191
185
|
if (token === "--json") {
|
|
192
186
|
json = true;
|
|
193
187
|
continue;
|
|
@@ -229,13 +223,13 @@ function resolveCommand(root, path) {
|
|
|
229
223
|
return node;
|
|
230
224
|
}
|
|
231
225
|
async function runRegisteredCommand(root, ctx) {
|
|
232
|
-
const stdin = ctx.stdin ??
|
|
226
|
+
const stdin = ctx.stdin ?? { text: "" };
|
|
233
227
|
const parsed = parseCommandInput(root, ctx.argv, stdin);
|
|
234
228
|
const displayPath = parsed.path.join(" ");
|
|
235
229
|
if (parsed.help) {
|
|
236
230
|
const node = resolveCommand(root, parsed.path);
|
|
237
231
|
const parentPath = parsed.path.length > 1 ? parsed.path.slice(0, -1).join(" ") : "";
|
|
238
|
-
await ctx.io.stdout(`${
|
|
232
|
+
await ctx.io.stdout(`${renderCommandPrompt(node, parentPath)}\n`);
|
|
239
233
|
return { exitCode: 0 };
|
|
240
234
|
}
|
|
241
235
|
const command = resolveCommand(root, parsed.path);
|
|
@@ -249,8 +243,7 @@ async function runRegisteredCommand(root, ctx) {
|
|
|
249
243
|
env: ctx.env,
|
|
250
244
|
cwd: ctx.cwd,
|
|
251
245
|
io: parsed.json ? capture : ctx.io,
|
|
252
|
-
storage: ctx.storage
|
|
253
|
-
host: ctx.host
|
|
246
|
+
storage: ctx.storage
|
|
254
247
|
});
|
|
255
248
|
if (parsed.json && result.exitCode === 0) {
|
|
256
249
|
const raw = capture.stdoutText();
|
|
@@ -269,7 +262,7 @@ async function runRegisteredCommand(root, ctx) {
|
|
|
269
262
|
}
|
|
270
263
|
return result;
|
|
271
264
|
}
|
|
272
|
-
function
|
|
265
|
+
function renderCommandPrompt(command, parentPath = "") {
|
|
273
266
|
const path = parentPath ? `${parentPath} ${command.name}` : command.name;
|
|
274
267
|
const blocks = [];
|
|
275
268
|
const lines = [`${path}: ${command.summary}`];
|
|
@@ -304,11 +297,10 @@ function renderCommandHelp(command, parentPath = "") {
|
|
|
304
297
|
for (const child of children) lines.push(` ${path} ${child.name} — ${child.summary}`);
|
|
305
298
|
}
|
|
306
299
|
blocks.push(lines.join("\n"));
|
|
307
|
-
for (const child of children) blocks.push(
|
|
300
|
+
for (const child of children) blocks.push(renderCommandPrompt(child, path));
|
|
308
301
|
return blocks.join("\n\n");
|
|
309
302
|
}
|
|
310
303
|
function validateCommandTree(command, path) {
|
|
311
|
-
if (!COMMAND_NAME_PATTERN.test(command.name)) throw new Error(`CommandRegistry: "${path}" has invalid name "${command.name}"; use letters, numbers, underscores, and hyphens`);
|
|
312
304
|
const hasRun = typeof command.run === "function";
|
|
313
305
|
const children = command.subcommands ?? [];
|
|
314
306
|
if (!hasRun && children.length === 0) throw new Error(`CommandRegistry: "${path}" must have run() and/or subcommands`);
|
|
@@ -322,6 +314,7 @@ function validateCommandTree(command, path) {
|
|
|
322
314
|
}
|
|
323
315
|
const seen = /* @__PURE__ */ new Set();
|
|
324
316
|
for (const child of children) {
|
|
317
|
+
if (child.name === "prompt") throw new Error(`CommandRegistry: "${path} prompt" is reserved for the help pseudo-subcommand`);
|
|
325
318
|
if (seen.has(child.name)) throw new Error(`CommandRegistry: duplicate subcommand "${path} ${child.name}"`);
|
|
326
319
|
seen.add(child.name);
|
|
327
320
|
validateCommandTree(child, `${path} ${child.name}`);
|
|
@@ -405,6 +398,9 @@ var CapturingIO = class {
|
|
|
405
398
|
async stderr(data) {
|
|
406
399
|
await this.target.stderr(data);
|
|
407
400
|
}
|
|
401
|
+
async asset(asset) {
|
|
402
|
+
await this.target.asset(asset);
|
|
403
|
+
}
|
|
408
404
|
stdoutText() {
|
|
409
405
|
return decodeUtf8(concatBytes(this.chunks));
|
|
410
406
|
}
|
|
@@ -516,10 +512,8 @@ function createOutputSinks(fs, cwd, redirections) {
|
|
|
516
512
|
function recordForegroundChunk(foreground, sourceFd, chunk) {
|
|
517
513
|
const text = decodeUtf8(chunk);
|
|
518
514
|
foreground.lastOutputAt = Date.now();
|
|
519
|
-
if (sourceFd === 1)
|
|
520
|
-
|
|
521
|
-
foreground.rawStdoutBytes.push(chunk);
|
|
522
|
-
} else foreground.rawStderrBuffer += text;
|
|
515
|
+
if (sourceFd === 1) foreground.rawStdoutBuffer += text;
|
|
516
|
+
else foreground.rawStderrBuffer += text;
|
|
523
517
|
const sink = foreground.outputSinks[sourceFd];
|
|
524
518
|
if (sink.kind === "file" || sink.kind === "null") {
|
|
525
519
|
sink.bytes.push(chunk);
|
|
@@ -621,63 +615,62 @@ function appendVisibleChunk(foreground, targetFd, text, byteLength) {
|
|
|
621
615
|
//#endregion
|
|
622
616
|
//#region src/command-artifact-store.ts
|
|
623
617
|
/**
|
|
624
|
-
* Owns persistence of shell command artifacts: a per-storage
|
|
618
|
+
* Owns persistence of shell command artifacts: a per-scope storage cache plus the
|
|
625
619
|
* set of released (tombstoned) commands. `BashEnvironment` delegates the storage
|
|
626
620
|
* and release-tracking side of the `/@` virtual filesystem here, keeping only the
|
|
627
621
|
* in-memory record lookups that need live command state.
|
|
628
622
|
*/
|
|
629
623
|
var CommandArtifactStore = class {
|
|
630
624
|
store;
|
|
631
|
-
|
|
625
|
+
storageByScope = /* @__PURE__ */ new Map();
|
|
632
626
|
released = /* @__PURE__ */ new Set();
|
|
633
627
|
constructor(store) {
|
|
634
628
|
this.store = store;
|
|
635
629
|
}
|
|
636
|
-
/** The artifact storage for
|
|
637
|
-
storageFor(
|
|
638
|
-
const existing = this.
|
|
630
|
+
/** The artifact storage for a command scope, created (and cached) on first use. */
|
|
631
|
+
storageFor(scopeId) {
|
|
632
|
+
const existing = this.storageByScope.get(scopeId);
|
|
639
633
|
if (existing) return existing;
|
|
640
|
-
const storage = new AgentSessionCommandStorage(this.store,
|
|
641
|
-
this.
|
|
634
|
+
const storage = new AgentSessionCommandStorage(this.store, scopeId);
|
|
635
|
+
this.storageByScope.set(scopeId, storage);
|
|
642
636
|
return storage;
|
|
643
637
|
}
|
|
644
638
|
/** Whether a command's artifact has been released (tombstoned). */
|
|
645
|
-
isReleased(
|
|
646
|
-
return this.released.has(this.key(
|
|
639
|
+
isReleased(scopeId, commandId) {
|
|
640
|
+
return this.released.has(this.key(scopeId, commandId));
|
|
647
641
|
}
|
|
648
642
|
/** Persists an artifact unless its command has already been released. */
|
|
649
|
-
persist(
|
|
650
|
-
if (this.isReleased(
|
|
651
|
-
this.storageFor(
|
|
643
|
+
persist(scopeId, commandId, artifact) {
|
|
644
|
+
if (this.isReleased(scopeId, commandId)) return;
|
|
645
|
+
this.storageFor(scopeId).writeJson(`commands/${commandId}/artifact.json`, artifact).catch(() => {});
|
|
652
646
|
}
|
|
653
647
|
/** Tombstones a command and removes its persisted artifact. */
|
|
654
|
-
async release(
|
|
655
|
-
this.released.add(this.key(
|
|
656
|
-
await this.storageFor(
|
|
648
|
+
async release(scopeId, commandId) {
|
|
649
|
+
this.released.add(this.key(scopeId, commandId));
|
|
650
|
+
await this.storageFor(scopeId).delete(`commands/${commandId}/artifact.json`).catch(() => {});
|
|
657
651
|
}
|
|
658
|
-
key(
|
|
659
|
-
return `${
|
|
652
|
+
key(scopeId, commandId) {
|
|
653
|
+
return `${scopeId}\0${commandId}`;
|
|
660
654
|
}
|
|
661
655
|
};
|
|
662
656
|
//#endregion
|
|
663
657
|
//#region src/registered-command-adapter.ts
|
|
664
|
-
function commandToForkCommand(session, command, storage
|
|
658
|
+
function commandToForkCommand(session, command, storage) {
|
|
665
659
|
return {
|
|
666
660
|
name: command.name,
|
|
667
661
|
consumesStdin: treeConsumesStdin(command),
|
|
668
662
|
execute: async (args, ctx) => {
|
|
669
|
-
const
|
|
663
|
+
const stdinText = decodeForkStdin(ctx.stdin);
|
|
670
664
|
const io = createForwardingIO();
|
|
671
665
|
const argv = [command.name, ...args];
|
|
672
666
|
try {
|
|
673
667
|
const result = await runRegisteredCommand(command, {
|
|
674
668
|
argv,
|
|
675
|
-
stdin,
|
|
669
|
+
stdin: { text: stdinText },
|
|
676
670
|
env: mapToRecord(ctx.env),
|
|
677
671
|
cwd: ctx.cwd,
|
|
678
672
|
io,
|
|
679
|
-
storage
|
|
680
|
-
host
|
|
673
|
+
storage
|
|
681
674
|
});
|
|
682
675
|
session.accumulator.audit.push({
|
|
683
676
|
kind: "registered-command",
|
|
@@ -692,8 +685,7 @@ function commandToForkCommand(session, command, storage, host) {
|
|
|
692
685
|
metadata: result.metadata
|
|
693
686
|
});
|
|
694
687
|
return {
|
|
695
|
-
stdout: io.
|
|
696
|
-
stdoutKind: "bytes",
|
|
688
|
+
stdout: io.stdoutText(),
|
|
697
689
|
stderr: io.stderrText(),
|
|
698
690
|
exitCode: result.exitCode
|
|
699
691
|
};
|
|
@@ -706,31 +698,38 @@ function commandToForkCommand(session, command, storage, host) {
|
|
|
706
698
|
exitCode: 1
|
|
707
699
|
});
|
|
708
700
|
return {
|
|
709
|
-
stdout: io.
|
|
710
|
-
stdoutKind: "bytes",
|
|
701
|
+
stdout: io.stdoutText(),
|
|
711
702
|
stderr: `${io.stderrText()}${command.name}: ${message}\n`,
|
|
712
703
|
exitCode: 1
|
|
713
704
|
};
|
|
705
|
+
} finally {
|
|
706
|
+
if (io.assets().length > 0) session.accumulator.assets.push(...io.assets());
|
|
714
707
|
}
|
|
715
708
|
}
|
|
716
709
|
};
|
|
717
710
|
}
|
|
718
|
-
/** Collects command output as raw bytes; stdout stays byte-clean for the pipe. */
|
|
719
711
|
var ForwardingIO = class {
|
|
720
712
|
stdoutChunks = [];
|
|
721
713
|
stderrChunks = [];
|
|
714
|
+
assetItems = [];
|
|
722
715
|
async stdout(data) {
|
|
723
716
|
this.stdoutChunks.push(typeof data === "string" ? encodeUtf8(data) : data);
|
|
724
717
|
}
|
|
725
718
|
async stderr(data) {
|
|
726
719
|
this.stderrChunks.push(typeof data === "string" ? encodeUtf8(data) : data);
|
|
727
720
|
}
|
|
728
|
-
|
|
729
|
-
|
|
721
|
+
asset(asset) {
|
|
722
|
+
this.assetItems.push(asset);
|
|
723
|
+
}
|
|
724
|
+
stdoutText() {
|
|
725
|
+
return decodeUtf8(concatBytes(this.stdoutChunks));
|
|
730
726
|
}
|
|
731
727
|
stderrText() {
|
|
732
728
|
return decodeUtf8(concatBytes(this.stderrChunks));
|
|
733
729
|
}
|
|
730
|
+
assets() {
|
|
731
|
+
return this.assetItems;
|
|
732
|
+
}
|
|
734
733
|
};
|
|
735
734
|
function createForwardingIO() {
|
|
736
735
|
return new ForwardingIO();
|
|
@@ -744,52 +743,26 @@ function mapToRecord(map) {
|
|
|
744
743
|
for (const [key, value] of map) record[key] = value;
|
|
745
744
|
return record;
|
|
746
745
|
}
|
|
747
|
-
/** Pipes hand stdin over as a latin1-packed byte string; expose bytes and a UTF-8 text view. */
|
|
748
746
|
function decodeForkStdin(stdin) {
|
|
749
|
-
if (!stdin) return
|
|
750
|
-
|
|
751
|
-
bytes: /* @__PURE__ */ new Uint8Array(0)
|
|
752
|
-
};
|
|
753
|
-
if (stdin instanceof Uint8Array) return {
|
|
754
|
-
text: decodeUtf8(stdin),
|
|
755
|
-
bytes: stdin
|
|
756
|
-
};
|
|
747
|
+
if (!stdin) return "";
|
|
748
|
+
if (stdin instanceof Uint8Array) return decodeUtf8(stdin);
|
|
757
749
|
const latin1 = stdin;
|
|
758
|
-
if (!latin1) return
|
|
759
|
-
text: "",
|
|
760
|
-
bytes: /* @__PURE__ */ new Uint8Array(0)
|
|
761
|
-
};
|
|
762
|
-
const bytes = encodeLatin1(latin1);
|
|
750
|
+
if (!latin1) return "";
|
|
763
751
|
let hasHighByte = false;
|
|
764
|
-
let hasWideChar = false;
|
|
765
752
|
for (let i = 0; i < latin1.length; i += 1) {
|
|
766
753
|
const code = latin1.charCodeAt(i);
|
|
767
|
-
if (code > 255)
|
|
768
|
-
|
|
754
|
+
if (code > 255) return latin1;
|
|
755
|
+
if (code > 127) hasHighByte = true;
|
|
769
756
|
}
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if (!hasHighByte) return {
|
|
775
|
-
text: latin1,
|
|
776
|
-
bytes
|
|
777
|
-
};
|
|
778
|
-
return {
|
|
779
|
-
text: decodeUtf8(bytes),
|
|
780
|
-
bytes
|
|
781
|
-
};
|
|
757
|
+
if (!hasHighByte) return latin1;
|
|
758
|
+
const bytes = new Uint8Array(latin1.length);
|
|
759
|
+
for (let i = 0; i < latin1.length; i += 1) bytes[i] = latin1.charCodeAt(i);
|
|
760
|
+
return decodeUtf8(bytes);
|
|
782
761
|
}
|
|
783
762
|
//#endregion
|
|
784
763
|
//#region src/environment.ts
|
|
785
764
|
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
786
765
|
const DEFAULT_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
787
|
-
/**
|
|
788
|
-
* Ceiling for a raw-byte final stream. Sized so an ordinary viewing-grade clip
|
|
789
|
-
* survives the shell and reaches the layer that decides what to do with it,
|
|
790
|
-
* while still bounding a runaway producer.
|
|
791
|
-
*/
|
|
792
|
-
const DEFAULT_BINARY_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
793
766
|
/** Upper bound for a single exec observation window (also the command-bridge wait ceiling). */
|
|
794
767
|
const MAX_TIMEOUT_MS = 6e5;
|
|
795
768
|
var BashEnvironment = class {
|
|
@@ -798,8 +771,8 @@ var BashEnvironment = class {
|
|
|
798
771
|
shellIdFactory;
|
|
799
772
|
commandIdFactory;
|
|
800
773
|
initialEnv;
|
|
774
|
+
execEnv;
|
|
801
775
|
defaultOutputLimitBytes;
|
|
802
|
-
defaultBinaryLimitBytes;
|
|
803
776
|
shells = /* @__PURE__ */ new Map();
|
|
804
777
|
defaultShellByAgentSessionId = /* @__PURE__ */ new Map();
|
|
805
778
|
commandsById = /* @__PURE__ */ new Map();
|
|
@@ -811,15 +784,12 @@ var BashEnvironment = class {
|
|
|
811
784
|
this.shellIdFactory = options.shellIdFactory ?? (() => globalThis.crypto.randomUUID());
|
|
812
785
|
this.commandIdFactory = options.commandIdFactory ?? (() => globalThis.crypto.randomUUID());
|
|
813
786
|
this.initialEnv = options.initialEnv ?? {};
|
|
787
|
+
this.execEnv = options.execEnv;
|
|
814
788
|
this.defaultOutputLimitBytes = options.maxOutputBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
|
|
815
|
-
this.defaultBinaryLimitBytes = options.maxBinaryBytes ?? DEFAULT_BINARY_LIMIT_BYTES;
|
|
816
789
|
}
|
|
817
790
|
getShell(shellId) {
|
|
818
791
|
return this.shells.get(shellId) ?? null;
|
|
819
792
|
}
|
|
820
|
-
hasCommand(commandId) {
|
|
821
|
-
return this.commandsById.has(commandId);
|
|
822
|
-
}
|
|
823
793
|
registerCommand(command) {
|
|
824
794
|
if (this.commands.get(command.name)) return;
|
|
825
795
|
this.commands.register(command);
|
|
@@ -829,13 +799,20 @@ var BashEnvironment = class {
|
|
|
829
799
|
}
|
|
830
800
|
async exec(input) {
|
|
831
801
|
const timeoutMs = normalizeTimeoutMs(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
832
|
-
|
|
833
|
-
if (input.cwd !== void 0 && !input.ephemeral) throw new Error("ShellExecInput: \"cwd\" requires \"ephemeral\"; a persistent shell owns its cwd");
|
|
834
|
-
if (input.cwd !== void 0) {
|
|
835
|
-
if (!(await this.host.fs.stat(input.cwd).catch(() => null))?.isDirectory) throw new Error(`Shell exec cwd is not a directory: ${input.cwd}`);
|
|
836
|
-
}
|
|
837
|
-
const session = input.shellId ? this.requireShell(input.shellId) : input.ephemeral ? this.createShell(input.agentSessionId, input.cwd) : this.availableDefaultShell(input.agentSessionId);
|
|
802
|
+
const session = input.shellId ? this.requireShell(input.shellId) : this.availableDefaultShell(input.agentSessionId);
|
|
838
803
|
if (session.exited) throw new Error(`Shell session "${session.id}" has exited`);
|
|
804
|
+
if (input.agentSessionId) {
|
|
805
|
+
session.state.env.set("DEMI_AGENT_SESSION_ID", input.agentSessionId);
|
|
806
|
+
const extraEnv = this.execEnv?.(input.agentSessionId);
|
|
807
|
+
if (extraEnv) {
|
|
808
|
+
const exported = session.state.exportedVars ?? /* @__PURE__ */ new Set();
|
|
809
|
+
session.state.exportedVars = exported;
|
|
810
|
+
for (const [key, value] of Object.entries(extraEnv)) {
|
|
811
|
+
session.state.env.set(key, value);
|
|
812
|
+
exported.add(key);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
839
816
|
if (session.pendingExec || session.foreground) {
|
|
840
817
|
const commandId = session.activeCommandId ?? session.foreground?.commandId ?? "unknown";
|
|
841
818
|
throw new Error(`Shell session "${session.id}" is already running command "${commandId}"`);
|
|
@@ -847,7 +824,7 @@ var BashEnvironment = class {
|
|
|
847
824
|
}
|
|
848
825
|
async status(input) {
|
|
849
826
|
const record = this.requireCommand(input.commandId);
|
|
850
|
-
return this.
|
|
827
|
+
return this.snapshotCommand(record, input);
|
|
851
828
|
}
|
|
852
829
|
async write(input) {
|
|
853
830
|
const record = this.requireCommand(input.commandId);
|
|
@@ -857,11 +834,11 @@ var BashEnvironment = class {
|
|
|
857
834
|
const data = typeof input.stdin === "string" ? encodeUtf8(input.stdin) : input.stdin;
|
|
858
835
|
if (data.byteLength === 0) throw new Error("shell_write field \"stdin\" must not be empty; use shell_status to poll");
|
|
859
836
|
await foreground.handle.writeStdin(data);
|
|
860
|
-
return this.
|
|
837
|
+
return this.snapshotCommand(record, input);
|
|
861
838
|
}
|
|
862
839
|
async abort(input) {
|
|
863
840
|
const record = this.requireCommand(input.commandId);
|
|
864
|
-
if (record.status !== "running") return this.
|
|
841
|
+
if (record.status !== "running") return this.snapshotCommand(record, input);
|
|
865
842
|
const session = this.requireShell(record.shellId);
|
|
866
843
|
const foreground = this.requireForegroundCommand(session, record.id);
|
|
867
844
|
foreground.abortController.abort();
|
|
@@ -873,7 +850,7 @@ var BashEnvironment = class {
|
|
|
873
850
|
const record = this.commandsById.get(commandId);
|
|
874
851
|
if (!record || record.status === "running") return false;
|
|
875
852
|
this.commandsById.delete(commandId);
|
|
876
|
-
await this.artifacts.release(record.
|
|
853
|
+
await this.artifacts.release(record.commandScopeId, commandId);
|
|
877
854
|
return true;
|
|
878
855
|
}
|
|
879
856
|
async disposeShell(shellId) {
|
|
@@ -939,22 +916,25 @@ var BashEnvironment = class {
|
|
|
939
916
|
if (!agentSessionId || shell.exited || !shell.pendingExec && !shell.foreground) return shell;
|
|
940
917
|
return this.createShell(agentSessionId);
|
|
941
918
|
}
|
|
942
|
-
createShell(agentSessionId
|
|
919
|
+
createShell(agentSessionId) {
|
|
943
920
|
const id = this.shellIdFactory();
|
|
944
|
-
const
|
|
945
|
-
const cwd =
|
|
946
|
-
const fs = new HostBackedFileSystem(this.host, { lookup: (path) => this.lookupVirtualArtifact(
|
|
921
|
+
const commandScopeId = agentSessionId ?? id;
|
|
922
|
+
const cwd = this.host.defaultCwd;
|
|
923
|
+
const fs = new HostBackedFileSystem(this.host, { lookup: (path) => this.lookupVirtualArtifact(commandScopeId, path) });
|
|
947
924
|
const env = /* @__PURE__ */ new Map();
|
|
948
925
|
for (const [key, value] of Object.entries(this.initialEnv)) env.set(key, value);
|
|
949
926
|
env.set("PWD", cwd);
|
|
950
|
-
|
|
927
|
+
env.set("DEMI_SESSION_ID", commandScopeId);
|
|
951
928
|
env.set("DEMI_SHELL_ID", id);
|
|
952
929
|
if (!env.has("IFS")) env.set("IFS", " \n");
|
|
953
930
|
if (!env.has("PS1")) env.set("PS1", "");
|
|
954
931
|
if (!env.has("PS2")) env.set("PS2", "> ");
|
|
955
932
|
if (!env.has("SHLVL")) env.set("SHLVL", "1");
|
|
956
|
-
const exportedVars = /* @__PURE__ */ new Set([
|
|
957
|
-
|
|
933
|
+
const exportedVars = /* @__PURE__ */ new Set([
|
|
934
|
+
"PWD",
|
|
935
|
+
"DEMI_SESSION_ID",
|
|
936
|
+
"DEMI_SHELL_ID"
|
|
937
|
+
]);
|
|
958
938
|
for (const key of env.keys()) if (key !== key.toLowerCase()) exportedVars.add(key);
|
|
959
939
|
for (const key of Object.keys(this.initialEnv)) exportedVars.add(key);
|
|
960
940
|
const state = {
|
|
@@ -1015,8 +995,7 @@ var BashEnvironment = class {
|
|
|
1015
995
|
const forkCommands = /* @__PURE__ */ new Map();
|
|
1016
996
|
const session = {
|
|
1017
997
|
id,
|
|
1018
|
-
|
|
1019
|
-
commandStorageId,
|
|
998
|
+
commandScopeId,
|
|
1020
999
|
state,
|
|
1021
1000
|
fs,
|
|
1022
1001
|
interpreter: void 0,
|
|
@@ -1025,7 +1004,8 @@ var BashEnvironment = class {
|
|
|
1025
1004
|
stdout: "",
|
|
1026
1005
|
stderr: "",
|
|
1027
1006
|
audit: [],
|
|
1028
|
-
commandMetadata: []
|
|
1007
|
+
commandMetadata: [],
|
|
1008
|
+
assets: []
|
|
1029
1009
|
},
|
|
1030
1010
|
foregroundWaiters: /* @__PURE__ */ new Set(),
|
|
1031
1011
|
backgroundJobs: /* @__PURE__ */ new Map(),
|
|
@@ -1033,8 +1013,8 @@ var BashEnvironment = class {
|
|
|
1033
1013
|
exited: false
|
|
1034
1014
|
};
|
|
1035
1015
|
for (const command of createPortableCommands(session)) forkCommands.set(command.name, command);
|
|
1036
|
-
const storage = new AgentSessionCommandStorage(this.host.store,
|
|
1037
|
-
for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage
|
|
1016
|
+
const storage = new AgentSessionCommandStorage(this.host.store, commandScopeId);
|
|
1017
|
+
for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage));
|
|
1038
1018
|
session.abortController = new AbortController();
|
|
1039
1019
|
session.interpreter = new Interpreter({
|
|
1040
1020
|
fs,
|
|
@@ -1064,7 +1044,6 @@ var BashEnvironment = class {
|
|
|
1064
1044
|
}
|
|
1065
1045
|
async runScript(session, script, input) {
|
|
1066
1046
|
const record = this.createCommandRecord(session, script);
|
|
1067
|
-
record.outputLimitBytes = input.maxOutputBytes ?? this.defaultOutputLimitBytes;
|
|
1068
1047
|
let ast;
|
|
1069
1048
|
try {
|
|
1070
1049
|
ast = parse(script);
|
|
@@ -1076,7 +1055,7 @@ var BashEnvironment = class {
|
|
|
1076
1055
|
record.exitCode = 2;
|
|
1077
1056
|
session.state.lastExitCode = 2;
|
|
1078
1057
|
session.activeCommandId = void 0;
|
|
1079
|
-
return this.
|
|
1058
|
+
return this.snapshotCommand(record, input);
|
|
1080
1059
|
}
|
|
1081
1060
|
throw error;
|
|
1082
1061
|
}
|
|
@@ -1084,7 +1063,8 @@ var BashEnvironment = class {
|
|
|
1084
1063
|
stdout: "",
|
|
1085
1064
|
stderr: "",
|
|
1086
1065
|
audit: [],
|
|
1087
|
-
commandMetadata: []
|
|
1066
|
+
commandMetadata: [],
|
|
1067
|
+
assets: []
|
|
1088
1068
|
};
|
|
1089
1069
|
session.abortController = new AbortController();
|
|
1090
1070
|
session.activeCommandId = record.id;
|
|
@@ -1107,7 +1087,7 @@ var BashEnvironment = class {
|
|
|
1107
1087
|
const record = {
|
|
1108
1088
|
id,
|
|
1109
1089
|
shellId: session.id,
|
|
1110
|
-
|
|
1090
|
+
commandScopeId: session.commandScopeId,
|
|
1111
1091
|
script,
|
|
1112
1092
|
startedAt: now,
|
|
1113
1093
|
lastOutputAt: now,
|
|
@@ -1120,7 +1100,7 @@ var BashEnvironment = class {
|
|
|
1120
1100
|
outputOffset: 0,
|
|
1121
1101
|
audit: [],
|
|
1122
1102
|
commandMetadata: [],
|
|
1123
|
-
|
|
1103
|
+
assets: []
|
|
1124
1104
|
};
|
|
1125
1105
|
this.commandsById.set(id, record);
|
|
1126
1106
|
return record;
|
|
@@ -1241,7 +1221,7 @@ var BashEnvironment = class {
|
|
|
1241
1221
|
foreground = outcome.foreground;
|
|
1242
1222
|
continue;
|
|
1243
1223
|
}
|
|
1244
|
-
if (outcome.kind === "timeout") return this.
|
|
1224
|
+
if (outcome.kind === "timeout") return this.snapshotCommand(record, input);
|
|
1245
1225
|
if (outcome.kind === "aborted") {
|
|
1246
1226
|
const activeForeground = foreground ?? session.foreground;
|
|
1247
1227
|
if (!activeForeground) return this.collectAbortedWithoutForeground(session, record, input);
|
|
@@ -1326,7 +1306,6 @@ var BashEnvironment = class {
|
|
|
1326
1306
|
startedAt,
|
|
1327
1307
|
lastOutputAt: startedAt,
|
|
1328
1308
|
rawStdoutBuffer: "",
|
|
1329
|
-
rawStdoutBytes: [],
|
|
1330
1309
|
rawStderrBuffer: "",
|
|
1331
1310
|
stdoutBuffer: "",
|
|
1332
1311
|
stderrBuffer: "",
|
|
@@ -1347,7 +1326,7 @@ var BashEnvironment = class {
|
|
|
1347
1326
|
};
|
|
1348
1327
|
session.foreground = foreground;
|
|
1349
1328
|
notifyForegroundWaiters(session.foregroundWaiters, foreground);
|
|
1350
|
-
if (opts.stdin && opts.stdin.length > 0) await handle.writeStdin(
|
|
1329
|
+
if (opts.stdin && opts.stdin.length > 0) await handle.writeStdin(encodeUtf8(opts.stdin));
|
|
1351
1330
|
if (opts.stdinProvided) await handle.closeStdin();
|
|
1352
1331
|
if (handle.output) {
|
|
1353
1332
|
foreground.stdoutPump = pumpOutputStream(handle.output, (chunk) => {
|
|
@@ -1360,7 +1339,7 @@ var BashEnvironment = class {
|
|
|
1360
1339
|
}
|
|
1361
1340
|
const exit = await foreground.exitPromise;
|
|
1362
1341
|
await Promise.allSettled([foreground.stdoutPump, foreground.stderrPump]);
|
|
1363
|
-
const stdout =
|
|
1342
|
+
const stdout = foreground.rawStdoutBuffer;
|
|
1364
1343
|
const exitCode = exit.exitCode ?? 127;
|
|
1365
1344
|
const stderr = exit.exitCode === null && foreground.rawStderrBuffer.length === 0 ? `${command}: ${exit.signal ?? "command not found"}\n` : foreground.rawStderrBuffer;
|
|
1366
1345
|
foreground.audit[0] = {
|
|
@@ -1381,13 +1360,12 @@ var BashEnvironment = class {
|
|
|
1381
1360
|
session.foreground = void 0;
|
|
1382
1361
|
return {
|
|
1383
1362
|
stdout,
|
|
1384
|
-
stdoutKind: "bytes",
|
|
1385
1363
|
stderr,
|
|
1386
1364
|
exitCode
|
|
1387
1365
|
};
|
|
1388
1366
|
}
|
|
1389
1367
|
collectExited(session, record, resultOrError, foreground, input = {}) {
|
|
1390
|
-
if (record.status !== "running") return this.
|
|
1368
|
+
if (record.status !== "running") return this.snapshotCommand(record, input);
|
|
1391
1369
|
if (resultOrError instanceof Error) {
|
|
1392
1370
|
if (resultOrError instanceof ExitError) {
|
|
1393
1371
|
session.exited = true;
|
|
@@ -1425,36 +1403,12 @@ var BashEnvironment = class {
|
|
|
1425
1403
|
appendRecordOutput(record, "stderr", text);
|
|
1426
1404
|
return this.finishExited(session, record, 1, input);
|
|
1427
1405
|
}
|
|
1428
|
-
const
|
|
1429
|
-
let stdoutText;
|
|
1430
|
-
let binary;
|
|
1431
|
-
if (hasWideChar(raw)) stdoutText = raw;
|
|
1432
|
-
else {
|
|
1433
|
-
const bytes = encodeLatin1(raw);
|
|
1434
|
-
const strict = decodeUtf8Strict(bytes);
|
|
1435
|
-
if (strict !== null) stdoutText = strict;
|
|
1436
|
-
else {
|
|
1437
|
-
const cap = this.defaultBinaryLimitBytes;
|
|
1438
|
-
const truncated = bytes.length > cap;
|
|
1439
|
-
binary = {
|
|
1440
|
-
data: truncated ? bytes.slice(0, cap) : bytes,
|
|
1441
|
-
truncated,
|
|
1442
|
-
totalBytes: bytes.length,
|
|
1443
|
-
limitBytes: cap
|
|
1444
|
-
};
|
|
1445
|
-
stdoutText = `<binary stdout: ${bytes.length} bytes${truncated ? `, exceeds the ${cap}-byte binary limit` : ""}; raw bytes at /@/commands/${record.id}/stdout.bin>\n`;
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1406
|
+
const stdoutText = foreground ? resultOrError.stdout : decodeBytesToUtf8(unsafeBytesFromLatin1(resultOrError.stdout));
|
|
1448
1407
|
const stderrText = foreground ? resultOrError.stderr : decodeBytesToUtf8(unsafeBytesFromLatin1(resultOrError.stderr));
|
|
1449
1408
|
session.accumulator.stdout += stdoutText;
|
|
1450
1409
|
session.accumulator.stderr += stderrText;
|
|
1451
|
-
if (
|
|
1452
|
-
if (
|
|
1453
|
-
else if (binary) {
|
|
1454
|
-
record.outputChunks = [];
|
|
1455
|
-
appendRecordOutput(record, "stdout", stdoutText);
|
|
1456
|
-
appendRecordOutput(record, "stderr", stderrText);
|
|
1457
|
-
} else if (record.outputChunks.length === 0) {
|
|
1410
|
+
if (foreground) record.outputChunks = [...foreground.outputChunks];
|
|
1411
|
+
else if (record.outputChunks.length === 0) {
|
|
1458
1412
|
appendRecordOutput(record, "stdout", stdoutText);
|
|
1459
1413
|
appendRecordOutput(record, "stderr", stderrText);
|
|
1460
1414
|
}
|
|
@@ -1473,12 +1427,13 @@ var BashEnvironment = class {
|
|
|
1473
1427
|
record.exitCode = exitCode;
|
|
1474
1428
|
record.audit = [...session.accumulator.audit];
|
|
1475
1429
|
record.commandMetadata = [...session.accumulator.commandMetadata];
|
|
1430
|
+
record.assets = [...session.accumulator.assets];
|
|
1476
1431
|
session.pendingExec = void 0;
|
|
1477
1432
|
if (session.activeCommandId === record.id) session.activeCommandId = void 0;
|
|
1478
|
-
return this.
|
|
1433
|
+
return this.snapshotCommand(record, input);
|
|
1479
1434
|
}
|
|
1480
1435
|
async collectAborted(session, record, foreground, input = {}) {
|
|
1481
|
-
if (record.status !== "running") return this.
|
|
1436
|
+
if (record.status !== "running") return this.snapshotCommand(record, input);
|
|
1482
1437
|
foreground.abortController.abort();
|
|
1483
1438
|
foreground.handle.kill("SIGTERM").catch(() => {});
|
|
1484
1439
|
await flushForegroundSinks(session, foreground);
|
|
@@ -1490,10 +1445,10 @@ var BashEnvironment = class {
|
|
|
1490
1445
|
session.foreground = void 0;
|
|
1491
1446
|
session.pendingExec = void 0;
|
|
1492
1447
|
if (session.activeCommandId === record.id) session.activeCommandId = void 0;
|
|
1493
|
-
return this.
|
|
1448
|
+
return this.snapshotCommand(record, input);
|
|
1494
1449
|
}
|
|
1495
1450
|
collectAbortedWithoutForeground(session, record, input = {}) {
|
|
1496
|
-
if (record.status !== "running") return this.
|
|
1451
|
+
if (record.status !== "running") return this.snapshotCommand(record, input);
|
|
1497
1452
|
session.abortController?.abort();
|
|
1498
1453
|
session.pendingExec = void 0;
|
|
1499
1454
|
if (session.activeCommandId === record.id) session.activeCommandId = void 0;
|
|
@@ -1505,9 +1460,9 @@ var BashEnvironment = class {
|
|
|
1505
1460
|
}
|
|
1506
1461
|
record.lastOutputAt = Date.now();
|
|
1507
1462
|
record.status = "aborted";
|
|
1508
|
-
return this.
|
|
1463
|
+
return this.snapshotCommand(record, input);
|
|
1509
1464
|
}
|
|
1510
|
-
|
|
1465
|
+
snapshotCommand(record, input = {}) {
|
|
1511
1466
|
const foreground = this.shells.get(record.shellId)?.foreground;
|
|
1512
1467
|
if (record.status === "running" && foreground?.commandId === record.id) {
|
|
1513
1468
|
record.stdout = foreground.stdoutBuffer;
|
|
@@ -1516,9 +1471,9 @@ var BashEnvironment = class {
|
|
|
1516
1471
|
record.lastOutputAt = foreground.lastOutputAt;
|
|
1517
1472
|
}
|
|
1518
1473
|
const maxOutputBytes = input.maxOutputBytes ?? this.defaultOutputLimitBytes;
|
|
1519
|
-
const stdout =
|
|
1520
|
-
const stderr =
|
|
1521
|
-
const output =
|
|
1474
|
+
const stdout = streamArtifact(record, "stdout", input.stdoutOffset, maxOutputBytes);
|
|
1475
|
+
const stderr = streamArtifact(record, "stderr", input.stderrOffset, maxOutputBytes);
|
|
1476
|
+
const output = streamOutputArtifact(record, input.outputOffset, maxOutputBytes);
|
|
1522
1477
|
const base = {
|
|
1523
1478
|
shellId: record.shellId,
|
|
1524
1479
|
commandId: record.id,
|
|
@@ -1537,7 +1492,7 @@ var BashEnvironment = class {
|
|
|
1537
1492
|
audit: record.audit
|
|
1538
1493
|
};
|
|
1539
1494
|
if (record.commandMetadata.length > 0) result.commandMetadata = record.commandMetadata;
|
|
1540
|
-
if (record.
|
|
1495
|
+
if (record.assets.length > 0) result.assets = record.assets;
|
|
1541
1496
|
return result;
|
|
1542
1497
|
}
|
|
1543
1498
|
if (record.status === "aborted") return {
|
|
@@ -1549,56 +1504,52 @@ var BashEnvironment = class {
|
|
|
1549
1504
|
status: "running"
|
|
1550
1505
|
};
|
|
1551
1506
|
}
|
|
1552
|
-
async lookupVirtualArtifact(
|
|
1507
|
+
async lookupVirtualArtifact(scopeId, path) {
|
|
1553
1508
|
const parts = path.split("/").filter(Boolean);
|
|
1554
1509
|
if (parts.length === 1 && parts[0] === "@") return virtualDirectory(["commands"]);
|
|
1555
|
-
if (parts.length === 2 && parts[0] === "@" && parts[1] === "commands") return virtualDirectory(await this.commandArtifactIds(
|
|
1510
|
+
if (parts.length === 2 && parts[0] === "@" && parts[1] === "commands") return virtualDirectory(await this.commandArtifactIds(scopeId));
|
|
1556
1511
|
if (parts.length === 3 && parts[0] === "@" && parts[1] === "commands") {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
const entries = [
|
|
1512
|
+
if (!await this.commandArtifact(scopeId, parts[2])) return null;
|
|
1513
|
+
return virtualDirectory([
|
|
1560
1514
|
"meta.json",
|
|
1561
1515
|
"stderr.txt",
|
|
1562
1516
|
"stdout.txt"
|
|
1563
|
-
];
|
|
1564
|
-
if (artifact.stdoutBinary) entries.push("stdout.bin");
|
|
1565
|
-
return virtualDirectory(entries);
|
|
1517
|
+
]);
|
|
1566
1518
|
}
|
|
1567
1519
|
if (parts.length !== 4 || parts[0] !== "@" || parts[1] !== "commands") return null;
|
|
1568
|
-
const artifact = await this.commandArtifact(
|
|
1520
|
+
const artifact = await this.commandArtifact(scopeId, parts[2]);
|
|
1569
1521
|
if (!artifact) return null;
|
|
1570
1522
|
const fileName = parts[3];
|
|
1571
1523
|
if (fileName === "stdout.txt") return virtualFile(encodeUtf8(artifact.stdout));
|
|
1572
|
-
if (fileName === "stdout.bin" && artifact.stdoutBinary) return virtualFile(base64ToBytes(artifact.stdoutBinary.base64));
|
|
1573
1524
|
if (fileName === "stderr.txt") return virtualFile(encodeUtf8(artifact.stderr));
|
|
1574
1525
|
if (fileName === "meta.json") return virtualFile(encodeUtf8(`${JSON.stringify(commandArtifactMeta(artifact), null, 2)}\n`));
|
|
1575
1526
|
return null;
|
|
1576
1527
|
}
|
|
1577
|
-
async commandArtifactIds(
|
|
1528
|
+
async commandArtifactIds(scopeId) {
|
|
1578
1529
|
const ids = /* @__PURE__ */ new Set();
|
|
1579
|
-
for (const record of this.commandsById.values()) if (record.
|
|
1580
|
-
const keys = await this.artifacts.storageFor(
|
|
1530
|
+
for (const record of this.commandsById.values()) if (record.commandScopeId === scopeId && !this.artifacts.isReleased(scopeId, record.id)) ids.add(record.id);
|
|
1531
|
+
const keys = await this.artifacts.storageFor(scopeId).list("commands").catch(() => []);
|
|
1581
1532
|
for (const key of keys) {
|
|
1582
1533
|
const match = /^commands\/([^/]+)\/artifact\.json$/.exec(key);
|
|
1583
|
-
if (match && !this.artifacts.isReleased(
|
|
1534
|
+
if (match && !this.artifacts.isReleased(scopeId, match[1])) ids.add(match[1]);
|
|
1584
1535
|
}
|
|
1585
1536
|
return [...ids];
|
|
1586
1537
|
}
|
|
1587
|
-
async commandArtifact(
|
|
1588
|
-
if (this.artifacts.isReleased(
|
|
1538
|
+
async commandArtifact(scopeId, commandId) {
|
|
1539
|
+
if (this.artifacts.isReleased(scopeId, commandId)) return null;
|
|
1589
1540
|
const record = this.commandsById.get(commandId);
|
|
1590
|
-
if (record?.
|
|
1541
|
+
if (record?.commandScopeId === scopeId) {
|
|
1591
1542
|
this.syncRunningRecord(record);
|
|
1592
|
-
return
|
|
1543
|
+
return persistedArtifactFromRecord(record);
|
|
1593
1544
|
}
|
|
1594
|
-
const value = await this.artifacts.storageFor(
|
|
1595
|
-
return
|
|
1545
|
+
const value = await this.artifacts.storageFor(scopeId).readJson(`commands/${commandId}/artifact.json`).catch(() => null);
|
|
1546
|
+
return isPersistedShellCommandArtifact(value) ? value : null;
|
|
1596
1547
|
}
|
|
1597
1548
|
persistCommandArtifact(record) {
|
|
1598
|
-
const fingerprint = `${record.status}:${record.exitCode ?? ""}:${record.stdout.length}:${record.stderr.length}
|
|
1549
|
+
const fingerprint = `${record.status}:${record.exitCode ?? ""}:${record.stdout.length}:${record.stderr.length}`;
|
|
1599
1550
|
if (record.persistedFingerprint === fingerprint) return;
|
|
1600
1551
|
record.persistedFingerprint = fingerprint;
|
|
1601
|
-
this.artifacts.persist(record.
|
|
1552
|
+
this.artifacts.persist(record.commandScopeId, record.id, persistedArtifactFromRecord(record));
|
|
1602
1553
|
}
|
|
1603
1554
|
syncRunningRecord(record) {
|
|
1604
1555
|
const foreground = this.shells.get(record.shellId)?.foreground;
|
|
@@ -1626,16 +1577,11 @@ function createPortableCommands(session) {
|
|
|
1626
1577
|
}
|
|
1627
1578
|
}));
|
|
1628
1579
|
}
|
|
1629
|
-
/** True when the string contains a char > 0xFF, i.e. already-decoded Unicode text. */
|
|
1630
|
-
function hasWideChar(value) {
|
|
1631
|
-
for (let i = 0; i < value.length; i += 1) if (value.charCodeAt(i) > 255) return true;
|
|
1632
|
-
return false;
|
|
1633
|
-
}
|
|
1634
1580
|
function normalizeTimeoutMs(value) {
|
|
1635
1581
|
if (!Number.isFinite(value) || value < 1 || value > 6e5) throw new Error(`timeoutMs must be between 1 and ${MAX_TIMEOUT_MS}`);
|
|
1636
1582
|
return Math.floor(value);
|
|
1637
1583
|
}
|
|
1638
|
-
function
|
|
1584
|
+
function streamArtifact(record, stream, explicitOffset, maxOutputBytes) {
|
|
1639
1585
|
const text = stream === "stdout" ? record.stdout : record.stderr;
|
|
1640
1586
|
const totalBytes = utf8Bytes(text);
|
|
1641
1587
|
const boundedOffset = clampOffset(explicitOffset ?? (stream === "stdout" ? record.stdoutOffset : record.stderrOffset), totalBytes);
|
|
@@ -1655,7 +1601,7 @@ function streamView(record, stream, explicitOffset, maxOutputBytes) {
|
|
|
1655
1601
|
truncated
|
|
1656
1602
|
};
|
|
1657
1603
|
}
|
|
1658
|
-
function
|
|
1604
|
+
function streamOutputArtifact(record, explicitOffset, maxOutputBytes) {
|
|
1659
1605
|
const totalBytes = record.outputChunks.reduce((total, chunk) => total + chunk.bytes, 0);
|
|
1660
1606
|
const offset = clampOffset(explicitOffset ?? record.outputOffset, totalBytes);
|
|
1661
1607
|
const byteLimit = Math.max(0, Math.floor(maxOutputBytes));
|
|
@@ -1709,7 +1655,7 @@ function ensureRecordOutputCoverage(record) {
|
|
|
1709
1655
|
appendRecordOutput(record, "stdout", record.stdout);
|
|
1710
1656
|
appendRecordOutput(record, "stderr", record.stderr);
|
|
1711
1657
|
}
|
|
1712
|
-
function
|
|
1658
|
+
function persistedArtifactFromRecord(record) {
|
|
1713
1659
|
return {
|
|
1714
1660
|
status: record.status,
|
|
1715
1661
|
shellId: record.shellId,
|
|
@@ -1718,12 +1664,7 @@ function commandArtifactFromRecord(record) {
|
|
|
1718
1664
|
lastOutputAt: record.lastOutputAt,
|
|
1719
1665
|
exitCode: record.exitCode ?? null,
|
|
1720
1666
|
stdout: record.stdout,
|
|
1721
|
-
stderr: record.stderr
|
|
1722
|
-
...record.binaryStdout ? { stdoutBinary: {
|
|
1723
|
-
base64: bytesToBase64(record.binaryStdout.data),
|
|
1724
|
-
truncated: record.binaryStdout.truncated,
|
|
1725
|
-
totalBytes: record.binaryStdout.totalBytes
|
|
1726
|
-
} } : {}
|
|
1667
|
+
stderr: record.stderr
|
|
1727
1668
|
};
|
|
1728
1669
|
}
|
|
1729
1670
|
function commandArtifactMeta(artifact) {
|
|
@@ -1745,15 +1686,10 @@ function commandArtifactMeta(artifact) {
|
|
|
1745
1686
|
stderr: {
|
|
1746
1687
|
path: stderrPath,
|
|
1747
1688
|
bytes: utf8Bytes(artifact.stderr)
|
|
1748
|
-
}
|
|
1749
|
-
...artifact.stdoutBinary ? { stdoutBinary: {
|
|
1750
|
-
path: `/@/commands/${artifact.commandId}/stdout.bin`,
|
|
1751
|
-
bytes: artifact.stdoutBinary.totalBytes,
|
|
1752
|
-
truncated: artifact.stdoutBinary.truncated
|
|
1753
|
-
} } : {}
|
|
1689
|
+
}
|
|
1754
1690
|
};
|
|
1755
1691
|
}
|
|
1756
|
-
function
|
|
1692
|
+
function isPersistedShellCommandArtifact(value) {
|
|
1757
1693
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1758
1694
|
const record = value;
|
|
1759
1695
|
return (record.status === "running" || record.status === "exited" || record.status === "aborted") && typeof record.shellId === "string" && typeof record.commandId === "string" && typeof record.startedAt === "number" && typeof record.lastOutputAt === "number" && (typeof record.exitCode === "number" || record.exitCode === null) && typeof record.stdout === "string" && typeof record.stderr === "string";
|
|
@@ -1785,4 +1721,4 @@ function heredocDelimiter(body) {
|
|
|
1785
1721
|
return delimiter;
|
|
1786
1722
|
}
|
|
1787
1723
|
//#endregion
|
|
1788
|
-
export { AgentSessionCommandStorage, BashEnvironment,
|
|
1724
|
+
export { AgentSessionCommandStorage, BashEnvironment, COMMAND_PROMPT_DEFAULTS, CommandRegistry, DEMI_PORTABLE_COMMANDS, HostBackedFileSystem, MAX_TIMEOUT_MS, RESERVED_COMMAND_NAMES, heredocDelimiter, parseCommandInput, renderCommandPrompt, runRegisteredCommand, shellQuote, virtualDirectory, virtualFile };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as HostStore } from "./host-DIg4RxcL.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
|
|
3
4
|
//#region src/command.d.ts
|
|
4
5
|
type CommandInputSpec = Record<string, z.ZodType>;
|
|
5
6
|
interface CommandOutputSpec {
|
|
@@ -26,7 +27,7 @@ interface Command {
|
|
|
26
27
|
positionals?: string[];
|
|
27
28
|
stdinField?: string;
|
|
28
29
|
output?: CommandOutputSpec;
|
|
29
|
-
/** Required when `run` is set
|
|
30
|
+
/** Required when `run` is set (may be empty for fixtures). */
|
|
30
31
|
examples?: string[];
|
|
31
32
|
}
|
|
32
33
|
interface ParsedCommandInput {
|
|
@@ -35,7 +36,7 @@ interface ParsedCommandInput {
|
|
|
35
36
|
* For help: path of the node help was requested for.
|
|
36
37
|
*/
|
|
37
38
|
path: string[];
|
|
38
|
-
/** True when the invocation
|
|
39
|
+
/** True when the invocation was `<path…> prompt`. */
|
|
39
40
|
help: boolean;
|
|
40
41
|
values: Record<string, unknown>;
|
|
41
42
|
json: boolean;
|
|
@@ -48,23 +49,24 @@ interface CommandRunContext {
|
|
|
48
49
|
cwd: string;
|
|
49
50
|
io: CommandIO;
|
|
50
51
|
storage: CommandStorage;
|
|
51
|
-
/** Host of the BashEnvironment executing this command. */
|
|
52
|
-
host: Host;
|
|
53
52
|
}
|
|
54
53
|
interface CommandRunResult {
|
|
55
54
|
exitCode: number;
|
|
56
55
|
metadata?: unknown;
|
|
57
56
|
}
|
|
58
57
|
interface CommandStdin {
|
|
59
|
-
/** Stdin decoded as UTF-8 text (lossy for non-text input). */
|
|
60
58
|
text: string;
|
|
61
|
-
/** Raw stdin bytes, byte-identical to what the pipe delivered. */
|
|
62
|
-
bytes: Uint8Array;
|
|
63
59
|
}
|
|
64
|
-
|
|
60
|
+
/** A non-text content item a command emits to the model, peer to stdout text. */
|
|
61
|
+
type CommandAsset = {
|
|
62
|
+
type: 'image';
|
|
63
|
+
mediaType: string;
|
|
64
|
+
data: string;
|
|
65
|
+
};
|
|
65
66
|
interface CommandIO {
|
|
66
67
|
stdout(data: string | Uint8Array): Promise<void> | void;
|
|
67
68
|
stderr(data: string | Uint8Array): Promise<void> | void;
|
|
69
|
+
asset(asset: CommandAsset): Promise<void> | void;
|
|
68
70
|
}
|
|
69
71
|
interface CommandStorage {
|
|
70
72
|
readJson<T>(key: string): Promise<T | null>;
|
|
@@ -79,19 +81,18 @@ interface CommandExecutionContext {
|
|
|
79
81
|
cwd: string;
|
|
80
82
|
io: CommandIO;
|
|
81
83
|
storage: CommandStorage;
|
|
82
|
-
host: Host;
|
|
83
84
|
}
|
|
84
85
|
declare class CommandRegistry {
|
|
85
86
|
private readonly commands;
|
|
86
87
|
register(command: Command): void;
|
|
87
88
|
get(name: string): Command | null;
|
|
88
89
|
list(): Command[];
|
|
89
|
-
|
|
90
|
+
renderPrompt(): string;
|
|
90
91
|
}
|
|
91
|
-
declare const
|
|
92
|
+
declare const COMMAND_PROMPT_DEFAULTS = "Unless a command states otherwise: success prints raw text on stdout, failure writes an error message to stderr and exits non-zero.";
|
|
92
93
|
declare function parseCommandInput(root: Command, argv: string[], stdin?: CommandStdin): ParsedCommandInput;
|
|
93
94
|
declare function runRegisteredCommand(root: Command, ctx: CommandExecutionContext): Promise<CommandRunResult>;
|
|
94
|
-
declare function
|
|
95
|
+
declare function renderCommandPrompt(command: Command, parentPath?: string): string;
|
|
95
96
|
//#endregion
|
|
96
97
|
//#region src/storage.d.ts
|
|
97
98
|
declare class AgentSessionCommandStorage implements CommandStorage {
|
|
@@ -105,4 +106,4 @@ declare class AgentSessionCommandStorage implements CommandStorage {
|
|
|
105
106
|
private key;
|
|
106
107
|
}
|
|
107
108
|
//#endregion
|
|
108
|
-
export { runRegisteredCommand as _,
|
|
109
|
+
export { runRegisteredCommand as _, CommandExecutionContext as a, CommandOutputSpec as c, CommandRunResult as d, CommandStdin as f, renderCommandPrompt as g, parseCommandInput as h, CommandAsset as i, CommandRegistry as l, ParsedCommandInput as m, COMMAND_PROMPT_DEFAULTS as n, CommandIO as o, CommandStorage as p, Command as r, CommandInputSpec as s, AgentSessionCommandStorage as t, CommandRunContext as u };
|
package/dist/storage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as AgentSessionCommandStorage } from "./storage-
|
|
1
|
+
import { t as AgentSessionCommandStorage } from "./storage-D7EhVuLY.mjs";
|
|
2
2
|
export { AgentSessionCommandStorage };
|
package/dist/storage.mjs
CHANGED
|
@@ -5,7 +5,7 @@ var AgentSessionCommandStorage = class {
|
|
|
5
5
|
constructor(store, agentSessionId) {
|
|
6
6
|
this.store = store;
|
|
7
7
|
validateAgentSessionId(agentSessionId);
|
|
8
|
-
this.agentSessionPrefix =
|
|
8
|
+
this.agentSessionPrefix = `${agentSessionId}/`;
|
|
9
9
|
}
|
|
10
10
|
readJson(key) {
|
|
11
11
|
return this.store.readJson(this.key(key));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demicodes/shell",
|
|
3
3
|
"description": "Sandboxable bash engine and Host contract for Demi.",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.3",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@demicodes/utils": "^0.10.3",
|
|
22
23
|
"@demicodes/just-bash": "^3.1.0-demi.2",
|
|
23
|
-
"@demicodes/utils": "^0.10.1",
|
|
24
24
|
"zod": "^4.0.0"
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|