@demicodes/shell 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as HostFileSystem, c as HostProcessOutputChunk, d as HostSpawnParams, f as HostStore, i as HostFileStat, l as HostSpawnExit, m as createLogicalHostCwd, n as HostCwd, o as HostIdentity, p as SpawnErrorKind, r as HostDirent, s as HostProcess, t as Host, u as HostSpawnHandle } from "./host-BupAFtK6.mjs";
|
|
2
2
|
import { HostBackedFileSystem, HostBackedFileSystemOptions } from "./host-fs.mjs";
|
|
3
|
-
import { _ as runRegisteredCommand, a as CommandIO, c as CommandRegistry, d as CommandStdin, f as CommandStorage, g as renderCommandHelp, h as parseCommandInput, i as CommandExecutionContext, l as CommandRunContext, m as emptyStdin, n as COMMAND_HELP_DEFAULTS, o as CommandInputSpec, p as ParsedCommandInput, r as Command, s as CommandOutputSpec, t as AgentSessionCommandStorage, u as CommandRunResult } from "./storage-
|
|
3
|
+
import { _ as runRegisteredCommand, a as CommandIO, c as CommandRegistry, d as CommandStdin, f as CommandStorage, g as renderCommandHelp, h as parseCommandInput, i as CommandExecutionContext, l as CommandRunContext, m as emptyStdin, n as COMMAND_HELP_DEFAULTS, o as CommandInputSpec, p as ParsedCommandInput, r as Command, s as CommandOutputSpec, t as AgentSessionCommandStorage, u as CommandRunResult } from "./storage-BDRwHNOB.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";
|
package/dist/index.mjs
CHANGED
|
@@ -128,6 +128,7 @@ function emptyStdin() {
|
|
|
128
128
|
bytes: /* @__PURE__ */ new Uint8Array(0)
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
+
async function* emptyStdinStream() {}
|
|
131
132
|
const EXECUTION_ONLY_FIELDS = [
|
|
132
133
|
"successOutput",
|
|
133
134
|
"failureOutput",
|
|
@@ -221,7 +222,7 @@ function parseArgs(command, path, argv, startIndex, stdin) {
|
|
|
221
222
|
setParsedValue(values, field, token);
|
|
222
223
|
positionalIndex += 1;
|
|
223
224
|
}
|
|
224
|
-
if (command.stdinField) values[command.stdinField] = stdin.text;
|
|
225
|
+
if (command.stdinField && values[command.stdinField] === void 0) values[command.stdinField] = stdin.text;
|
|
225
226
|
return {
|
|
226
227
|
path: [...path],
|
|
227
228
|
help: false,
|
|
@@ -262,7 +263,9 @@ async function runRegisteredCommand(root, ctx) {
|
|
|
262
263
|
cwd: ctx.cwd,
|
|
263
264
|
io: parsed.json ? capture : ctx.io,
|
|
264
265
|
storage: ctx.storage,
|
|
265
|
-
host: ctx.host
|
|
266
|
+
host: ctx.host,
|
|
267
|
+
signal: ctx.signal ?? new AbortController().signal,
|
|
268
|
+
stdinStream: ctx.stdinStream ?? emptyStdinStream()
|
|
266
269
|
});
|
|
267
270
|
if (parsed.json && result.exitCode === 0) {
|
|
268
271
|
const raw = capture.stdoutText();
|
|
@@ -696,24 +699,34 @@ var CommandArtifactStore = class {
|
|
|
696
699
|
};
|
|
697
700
|
//#endregion
|
|
698
701
|
//#region src/registered-command-adapter.ts
|
|
699
|
-
|
|
702
|
+
/**
|
|
703
|
+
* Runs a registered command as a shell foreground job: it exposes the same
|
|
704
|
+
* control surface as a host process (abort signal, live stdout/stderr view,
|
|
705
|
+
* stdin as a post-start chunk stream) through a virtual process handle, so
|
|
706
|
+
* `shell_status` / `shell_write` / `shell_abort` apply uniformly.
|
|
707
|
+
*/
|
|
708
|
+
function commandToForkCommand(session, command, storage, host, captureLimitBytes) {
|
|
700
709
|
return {
|
|
701
710
|
name: command.name,
|
|
702
711
|
consumesStdin: treeConsumesStdin(command),
|
|
703
712
|
execute: async (args, ctx) => {
|
|
704
713
|
const stdin = decodeForkStdin(ctx.stdin);
|
|
705
|
-
const io = createForwardingIO();
|
|
706
714
|
const argv = [command.name, ...args];
|
|
715
|
+
const job = new VirtualForegroundJob(session, command.name, args, ctx.cwd, captureLimitBytes);
|
|
716
|
+
job.install();
|
|
707
717
|
try {
|
|
708
|
-
const result = await runRegisteredCommand(command, {
|
|
718
|
+
const result = await Promise.race([runRegisteredCommand(command, {
|
|
709
719
|
argv,
|
|
710
720
|
stdin,
|
|
711
721
|
env: mapToRecord(ctx.env),
|
|
712
722
|
cwd: ctx.cwd,
|
|
713
|
-
io,
|
|
723
|
+
io: job.io,
|
|
714
724
|
storage,
|
|
715
|
-
host
|
|
716
|
-
|
|
725
|
+
host,
|
|
726
|
+
signal: job.signal,
|
|
727
|
+
stdinStream: job.stdinChunks()
|
|
728
|
+
}), job.killedResult()]);
|
|
729
|
+
if (job.foreground.captureOverflowed) return job.overflowResult();
|
|
717
730
|
session.accumulator.audit.push({
|
|
718
731
|
kind: "registered-command",
|
|
719
732
|
name: command.name,
|
|
@@ -727,12 +740,13 @@ function commandToForkCommand(session, command, storage, host) {
|
|
|
727
740
|
metadata: result.metadata
|
|
728
741
|
});
|
|
729
742
|
return {
|
|
730
|
-
stdout:
|
|
743
|
+
stdout: job.stdoutLatin1(),
|
|
731
744
|
stdoutKind: "bytes",
|
|
732
|
-
stderr:
|
|
745
|
+
stderr: job.stderrText(),
|
|
733
746
|
exitCode: result.exitCode
|
|
734
747
|
};
|
|
735
748
|
} catch (error) {
|
|
749
|
+
if (job.foreground.captureOverflowed) return job.overflowResult();
|
|
736
750
|
const message = error instanceof Error ? error.message : String(error);
|
|
737
751
|
session.accumulator.audit.push({
|
|
738
752
|
kind: "registered-command",
|
|
@@ -741,35 +755,171 @@ function commandToForkCommand(session, command, storage, host) {
|
|
|
741
755
|
exitCode: 1
|
|
742
756
|
});
|
|
743
757
|
return {
|
|
744
|
-
stdout:
|
|
758
|
+
stdout: job.stdoutLatin1(),
|
|
745
759
|
stdoutKind: "bytes",
|
|
746
|
-
stderr: `${
|
|
760
|
+
stderr: `${job.stderrText()}${command.name}: ${message}\n`,
|
|
747
761
|
exitCode: 1
|
|
748
762
|
};
|
|
763
|
+
} finally {
|
|
764
|
+
job.release();
|
|
749
765
|
}
|
|
750
766
|
}
|
|
751
767
|
};
|
|
752
768
|
}
|
|
753
|
-
/**
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
769
|
+
/**
|
|
770
|
+
* In-process stand-in for an OS process. `kill` maps to the abort signal
|
|
771
|
+
* (there is no OS process to signal); a SIGKILL additionally abandons the
|
|
772
|
+
* run so the shell never waits on an in-process function that ignores its
|
|
773
|
+
* signal.
|
|
774
|
+
*/
|
|
775
|
+
var VirtualForegroundJob = class {
|
|
776
|
+
session;
|
|
777
|
+
captureLimitBytes;
|
|
778
|
+
foreground;
|
|
779
|
+
io;
|
|
780
|
+
abortController = new AbortController();
|
|
781
|
+
stdinQueue = new StdinChunkQueue();
|
|
782
|
+
settleExit;
|
|
783
|
+
killed;
|
|
784
|
+
killedPromise;
|
|
785
|
+
exitPromise;
|
|
786
|
+
installed = false;
|
|
787
|
+
constructor(session, command, args, cwd, captureLimitBytes) {
|
|
788
|
+
this.session = session;
|
|
789
|
+
this.captureLimitBytes = captureLimitBytes;
|
|
790
|
+
this.exitPromise = new Promise((resolve) => {
|
|
791
|
+
this.settleExit = resolve;
|
|
792
|
+
});
|
|
793
|
+
this.killedPromise = new Promise((_resolve, reject) => {
|
|
794
|
+
this.killed = reject;
|
|
795
|
+
});
|
|
796
|
+
this.killedPromise.catch(() => {});
|
|
797
|
+
const handle = {
|
|
798
|
+
stdout: emptyByteStream(),
|
|
799
|
+
stderr: emptyByteStream(),
|
|
800
|
+
writeStdin: async (data) => this.stdinQueue.push(data),
|
|
801
|
+
closeStdin: async () => this.stdinQueue.close(),
|
|
802
|
+
kill: async (signal) => {
|
|
803
|
+
this.abortController.abort();
|
|
804
|
+
this.stdinQueue.close();
|
|
805
|
+
if (signal === "SIGKILL") {
|
|
806
|
+
this.settleExit({
|
|
807
|
+
exitCode: 137,
|
|
808
|
+
signal: "SIGKILL"
|
|
809
|
+
});
|
|
810
|
+
this.killed(/* @__PURE__ */ new Error(`${command}: killed`));
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
wait: () => this.exitPromise
|
|
814
|
+
};
|
|
815
|
+
const startedAt = Date.now();
|
|
816
|
+
this.foreground = {
|
|
817
|
+
commandId: session.activeCommandId ?? "",
|
|
818
|
+
command,
|
|
819
|
+
args,
|
|
820
|
+
cwd,
|
|
821
|
+
handle,
|
|
822
|
+
startedAt,
|
|
823
|
+
lastOutputAt: startedAt,
|
|
824
|
+
rawStdoutBuffer: "",
|
|
825
|
+
rawStdoutBytes: [],
|
|
826
|
+
rawStderrBuffer: "",
|
|
827
|
+
stdoutBuffer: "",
|
|
828
|
+
stderrBuffer: "",
|
|
829
|
+
outputChunks: [],
|
|
830
|
+
outputBytes: 0,
|
|
831
|
+
capturedBytes: 0,
|
|
832
|
+
captureOverflowed: false,
|
|
833
|
+
audit: [],
|
|
834
|
+
stdoutPump: Promise.resolve(),
|
|
835
|
+
stderrPump: Promise.resolve(),
|
|
836
|
+
exitPromise: this.exitPromise,
|
|
837
|
+
outputSinks: createOutputSinks(session.fs, cwd, void 0),
|
|
838
|
+
abortController: this.abortController
|
|
839
|
+
};
|
|
840
|
+
this.io = {
|
|
841
|
+
stdout: (data) => {
|
|
842
|
+
recordForegroundChunk(this.foreground, 1, toBytes(data), this.captureLimitBytes);
|
|
843
|
+
},
|
|
844
|
+
stderr: (data) => {
|
|
845
|
+
recordForegroundChunk(this.foreground, 2, toBytes(data), this.captureLimitBytes);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
759
848
|
}
|
|
760
|
-
|
|
761
|
-
this.
|
|
849
|
+
get signal() {
|
|
850
|
+
return this.abortController.signal;
|
|
851
|
+
}
|
|
852
|
+
stdinChunks() {
|
|
853
|
+
return this.stdinQueue.stream();
|
|
854
|
+
}
|
|
855
|
+
killedResult() {
|
|
856
|
+
return this.killedPromise;
|
|
857
|
+
}
|
|
858
|
+
/** Registers this job as the session foreground so shell control verbs route here. */
|
|
859
|
+
install() {
|
|
860
|
+
if (this.session.foreground || !this.foreground.commandId) return;
|
|
861
|
+
this.session.foreground = this.foreground;
|
|
862
|
+
this.installed = true;
|
|
863
|
+
notifyForegroundWaiters(this.session.foregroundWaiters, this.foreground);
|
|
864
|
+
}
|
|
865
|
+
release() {
|
|
866
|
+
this.stdinQueue.close();
|
|
867
|
+
this.settleExit({ exitCode: 0 });
|
|
868
|
+
if (this.installed && this.session.foreground === this.foreground) this.session.foreground = void 0;
|
|
762
869
|
}
|
|
763
870
|
stdoutLatin1() {
|
|
764
|
-
return decodeLatin1(concatBytes(this.
|
|
871
|
+
return decodeLatin1(concatBytes(this.foreground.rawStdoutBytes));
|
|
765
872
|
}
|
|
766
873
|
stderrText() {
|
|
767
|
-
return
|
|
874
|
+
return this.foreground.rawStderrBuffer;
|
|
875
|
+
}
|
|
876
|
+
overflowResult() {
|
|
877
|
+
return {
|
|
878
|
+
stdout: "",
|
|
879
|
+
stdoutKind: "bytes",
|
|
880
|
+
stderr: `${this.foreground.command}: output exceeded the ${this.captureLimitBytes}-byte capture limit and the command was stopped; the shell buffers whole command outputs in memory — narrow the output at the source (filters, head, tighter paths)\n`,
|
|
881
|
+
exitCode: 137
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
/** Async chunk queue: each pushed chunk is delivered once, in order; close ends the stream. */
|
|
886
|
+
var StdinChunkQueue = class {
|
|
887
|
+
chunks = [];
|
|
888
|
+
waiter = null;
|
|
889
|
+
isClosed = false;
|
|
890
|
+
push(data) {
|
|
891
|
+
if (this.isClosed) return;
|
|
892
|
+
this.chunks.push(data);
|
|
893
|
+
this.wake();
|
|
894
|
+
}
|
|
895
|
+
close() {
|
|
896
|
+
if (this.isClosed) return;
|
|
897
|
+
this.isClosed = true;
|
|
898
|
+
this.wake();
|
|
899
|
+
}
|
|
900
|
+
async *stream() {
|
|
901
|
+
while (true) {
|
|
902
|
+
const chunk = this.chunks.shift();
|
|
903
|
+
if (chunk) {
|
|
904
|
+
yield chunk;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (this.isClosed) return;
|
|
908
|
+
await new Promise((resolve) => {
|
|
909
|
+
this.waiter = resolve;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
wake() {
|
|
914
|
+
const waiter = this.waiter;
|
|
915
|
+
this.waiter = null;
|
|
916
|
+
waiter?.();
|
|
768
917
|
}
|
|
769
918
|
};
|
|
770
|
-
function
|
|
771
|
-
return
|
|
919
|
+
function toBytes(data) {
|
|
920
|
+
return typeof data === "string" ? encodeUtf8(data) : data;
|
|
772
921
|
}
|
|
922
|
+
async function* emptyByteStream() {}
|
|
773
923
|
function treeConsumesStdin(command) {
|
|
774
924
|
if (command.stdinField) return true;
|
|
775
925
|
return command.subcommands?.some(treeConsumesStdin) ?? false;
|
|
@@ -1082,7 +1232,7 @@ var BashEnvironment = class {
|
|
|
1082
1232
|
};
|
|
1083
1233
|
for (const command of createPortableCommands(session)) forkCommands.set(command.name, command);
|
|
1084
1234
|
const storage = new AgentSessionCommandStorage(this.host.store, commandStorageId);
|
|
1085
|
-
for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage, this.host));
|
|
1235
|
+
for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage, this.host, this.captureLimitBytes));
|
|
1086
1236
|
session.abortController = new AbortController();
|
|
1087
1237
|
const limits = resolveLimits({
|
|
1088
1238
|
maxOutputSize: this.captureLimitBytes,
|
|
@@ -47,6 +47,13 @@ interface CommandRunContext {
|
|
|
47
47
|
storage: CommandStorage;
|
|
48
48
|
/** Host of the BashEnvironment executing this command. */
|
|
49
49
|
host: Host;
|
|
50
|
+
/** Aborted when the shell command is aborted (shell_abort, shell teardown). */
|
|
51
|
+
signal: AbortSignal;
|
|
52
|
+
/**
|
|
53
|
+
* Stdin written after the command started: each `shell_write` call arrives
|
|
54
|
+
* as one chunk. Ends when the command's shell job is released.
|
|
55
|
+
*/
|
|
56
|
+
stdinStream: AsyncIterable<Uint8Array>;
|
|
50
57
|
}
|
|
51
58
|
interface CommandRunResult {
|
|
52
59
|
exitCode: number;
|
|
@@ -77,6 +84,8 @@ interface CommandExecutionContext {
|
|
|
77
84
|
io: CommandIO;
|
|
78
85
|
storage: CommandStorage;
|
|
79
86
|
host: Host;
|
|
87
|
+
signal?: AbortSignal;
|
|
88
|
+
stdinStream?: AsyncIterable<Uint8Array>;
|
|
80
89
|
}
|
|
81
90
|
declare class CommandRegistry {
|
|
82
91
|
private readonly commands;
|
package/dist/storage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as AgentSessionCommandStorage } from "./storage-
|
|
1
|
+
import { t as AgentSessionCommandStorage } from "./storage-BDRwHNOB.mjs";
|
|
2
2
|
export { AgentSessionCommandStorage };
|
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.
|
|
4
|
+
"version": "0.19.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@demicodes/just-bash": "^3.1.0-demi.4",
|
|
23
|
-
"@demicodes/utils": "^0.
|
|
23
|
+
"@demicodes/utils": "^0.19.1",
|
|
24
24
|
"zod": "^4.0.0"
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|