@workerdeck/server 2.14.0 → 3.0.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/build/index.d.mts +12 -3
- package/build/index.mjs +321 -22
- package/build/index.mjs.map +1 -1
- package/package.json +7 -6
package/build/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
-
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, CostLedgerState, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, LocalShellSource, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SHELL_REFUSAL, SessionInstructions, SessionRunnerConfig, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
2
|
+
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, CostLedgerState, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, LocalShellSource, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SHELL_REFUSAL, SessionInstructions, SessionRunnerConfig, ShellDirectory, ToolExecutionCall, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
|
|
3
3
|
import { JobQueue, QueueAdapter } from "@workerdeck/queue";
|
|
4
4
|
import { ByModel, CreateSessionRequest, JobEvent, MessageAttachment, ModelRate, ProfileEngine, ProfileInfo, ProfileSpend, ProfileUsage, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig, ShellEndReason, ShellInfo, ShellOwner } from "@workerdeck/protocol";
|
|
5
5
|
//#region src/services/bridge.d.ts
|
|
@@ -188,7 +188,7 @@ type ShellAttachment = {
|
|
|
188
188
|
detach: () => void;
|
|
189
189
|
};
|
|
190
190
|
type ShellOutputQuery = {
|
|
191
|
-
view: 'text' | 'raw';
|
|
191
|
+
view: 'text' | 'raw' | 'screen';
|
|
192
192
|
tail?: number;
|
|
193
193
|
};
|
|
194
194
|
type ShellErrorContext = {
|
|
@@ -196,6 +196,9 @@ type ShellErrorContext = {
|
|
|
196
196
|
sessionId?: string;
|
|
197
197
|
shellId?: string;
|
|
198
198
|
};
|
|
199
|
+
type ShellDirectoryDeps = {
|
|
200
|
+
runnerFor?: (sessionId: string) => Runner | undefined;
|
|
201
|
+
};
|
|
199
202
|
type ShellRegistryOptions = {
|
|
200
203
|
generation: string;
|
|
201
204
|
artifactDir: string | null;
|
|
@@ -228,18 +231,22 @@ type ShellRegistry = {
|
|
|
228
231
|
list: (sessionId: string) => ShellInfo[];
|
|
229
232
|
running: () => ShellInfo[];
|
|
230
233
|
kill: (sessionId: string, shellId: string, reason?: ShellEndReason) => ShellInfo | undefined;
|
|
234
|
+
setAgentWrite: (sessionId: string, shellId: string, enabled: boolean) => ShellInfo | undefined;
|
|
231
235
|
killAll: (reason: ShellEndReason) => number;
|
|
232
236
|
killAllSync: () => void;
|
|
233
237
|
attach: (sessionId: string, shellId: string, sink: ShellSink) => Promise<ShellAttachment>;
|
|
234
238
|
write: (sessionId: string, shellId: string, data: string) => void;
|
|
235
239
|
resize: (sessionId: string, shellId: string, size: ShellSize) => ShellSize;
|
|
236
240
|
output: (sessionId: string, shellId: string, query: ShellOutputQuery) => Promise<string | undefined>;
|
|
241
|
+
applicationCursorKeys: (sessionId: string, shellId: string) => Promise<boolean>;
|
|
242
|
+
changed: (sessionId: string, shellId: string, timeoutMs: number) => Promise<void>;
|
|
237
243
|
hydrate: () => Promise<void>;
|
|
238
244
|
sweep: () => Promise<void>;
|
|
239
245
|
flush: () => Promise<void>;
|
|
240
246
|
decorate: (info: SessionInfo) => SessionInfo;
|
|
241
247
|
watch: (runner: Runner) => () => void;
|
|
242
248
|
};
|
|
249
|
+
declare function createShellDirectory(registry: ShellRegistry, deps?: ShellDirectoryDeps): ShellDirectory;
|
|
243
250
|
declare function shellPermitted(shells: ShellRegistry | null, runner: Runner, operator: boolean): boolean;
|
|
244
251
|
declare function createShellRegistry(options: ShellRegistryOptions): ShellRegistry;
|
|
245
252
|
//#endregion
|
|
@@ -271,6 +278,7 @@ declare class SpendLedger {
|
|
|
271
278
|
declare function createFileSpendStore(path: string, onError?: (error: unknown) => void): SpendStore;
|
|
272
279
|
//#endregion
|
|
273
280
|
//#region src/options.d.ts
|
|
281
|
+
type ShellAgentWriteOption = 'read-only' | 'gated' | 'allow';
|
|
274
282
|
type SdkSessionLister = (options: {
|
|
275
283
|
dir?: string;
|
|
276
284
|
limit?: number;
|
|
@@ -296,6 +304,7 @@ type WorkerServerOptions = {
|
|
|
296
304
|
artifactMaxBytes?: number;
|
|
297
305
|
artifactTtlMs?: number;
|
|
298
306
|
maxRunningPerSession?: number;
|
|
307
|
+
agentWrite?: ShellAgentWriteOption;
|
|
299
308
|
};
|
|
300
309
|
attachments?: {
|
|
301
310
|
maxFileBytes?: number;
|
|
@@ -502,5 +511,5 @@ declare class ProfileUsageTracker {
|
|
|
502
511
|
usage(profile: string, now?: number): ProfileUsage | undefined;
|
|
503
512
|
}
|
|
504
513
|
//#endregion
|
|
505
|
-
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type CarriedSession, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProcessRow, type ProcessTable, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type ReloadPlan, SHELL_REFUSAL, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type ShellRegistry, type ShellRegistryOptions, type ShellSink, SpendLedger, type SpendRecord, type SpendStore, type StoredSessionRecord, type StoredShellIndex, type StoredShellRecord, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createShellRegistry, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, shellPermitted, toDurableRecord };
|
|
514
|
+
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type CarriedSession, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProcessRow, type ProcessTable, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type ReloadPlan, SHELL_REFUSAL, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type ShellAgentWriteOption, type ShellDirectoryDeps, type ShellRegistry, type ShellRegistryOptions, type ShellSink, SpendLedger, type SpendRecord, type SpendStore, type StoredSessionRecord, type StoredShellIndex, type StoredShellRecord, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createShellDirectory, createShellRegistry, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, shellPermitted, toDurableRecord };
|
|
506
515
|
//# sourceMappingURL=index.d.mts.map
|
package/build/index.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { WebSocketServer } from "ws";
|
|
4
|
-
import { BrowserBridgeExecutor, PEER_MESSAGE_MAX_CHARS, PEER_RECENT_DEFAULT, PEER_RECENT_MAX, SHELL_REFUSAL, SessionRunner, attachmentKind, checkClaudeAuth, clampShellTail, createEngineSession, getEngineAdapter, installPeerDirectory, installShellDirectory, isSlashCommand, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines, shellDirectoryHandle, shellSummary, shellTail, ttyText } from "@workerdeck/core";
|
|
4
|
+
import { BrowserBridgeExecutor, PEER_MESSAGE_MAX_CHARS, PEER_RECENT_DEFAULT, PEER_RECENT_MAX, SHELL_REFUSAL, SessionRunner, TTY_REDRAW_CARRY, agentMayWrite, attachmentKind, checkClaudeAuth, clampShellTail, createEngineSession, encodeShellKeys, getEngineAdapter, installPeerDirectory, installShellDirectory, isSlashCommand, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines, shellDirectoryHandle, shellOwnershipRefusal, shellSummary, shellTail, shellToolNeedsCard, ttyRedraws, ttyText } from "@workerdeck/core";
|
|
5
5
|
import { JobQueue } from "@workerdeck/queue";
|
|
6
|
-
import { ENGINE_CAPABILITIES, PEER_MENTION_MAX, PROTOCOL_VERSION, SHELL_ARTIFACT_MAX_BYTES, SHELL_ARTIFACT_TTL_MS, SHELL_ATTACH_REPLAY_BYTES, SHELL_COLS, SHELL_COMMAND_MAX, SHELL_INPUT_MAX, SHELL_LABEL_MAX, SHELL_LINGER_MS, SHELL_MAX_COLS, SHELL_MAX_ROWS, SHELL_MAX_RUNNING_PER_SESSION, SHELL_MAX_RUNNING_TOTAL, SHELL_MIN_COLS, SHELL_MIN_ROWS, SHELL_ROWS, SHELL_SPILL_BYTES, SHELL_TAIL_FLUSH_MS, SHELL_TAIL_RING_BYTES, costOfByModel, imagePartRef, isJobRun, mergeByModel, peerMentionKey, peerMentionSlug, pickCreateSessionRequest, scanPeerMentions, sessionState, setPricingOverrides, subscriptionComparison, supportsPermissionMode } from "@workerdeck/protocol";
|
|
6
|
+
import { ENGINE_CAPABILITIES, PEER_MENTION_MAX, PROTOCOL_VERSION, SHELL_ARTIFACT_MAX_BYTES, SHELL_ARTIFACT_TTL_MS, SHELL_ATTACH_REPLAY_BYTES, SHELL_COLS, SHELL_COMMAND_MAX, SHELL_INPUT_MAX, SHELL_LABEL_MAX, SHELL_LINGER_MS, SHELL_MAX_COLS, SHELL_MAX_ROWS, SHELL_MAX_RUNNING_PER_SESSION, SHELL_MAX_RUNNING_TOTAL, SHELL_MIN_COLS, SHELL_MIN_ROWS, SHELL_READ_MAX_LINES, SHELL_ROWS, SHELL_SPILL_BYTES, SHELL_TAIL_FLUSH_MS, SHELL_TAIL_RING_BYTES, SHELL_WAIT_DEFAULT_MS, SHELL_WAIT_MAX_MS, costOfByModel, imagePartRef, isJobRun, mergeByModel, peerMentionKey, peerMentionSlug, pickCreateSessionRequest, scanPeerMentions, sessionState, setPricingOverrides, subscriptionComparison, supportsPermissionMode } from "@workerdeck/protocol";
|
|
7
7
|
import { arch, homedir, hostname, platform, tmpdir } from "node:os";
|
|
8
8
|
import { closeSync, constants, createReadStream, existsSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
11
|
+
import xtermHeadless from "@xterm/headless";
|
|
11
12
|
import { spawnSync } from "node:child_process";
|
|
12
13
|
//#region src/lib/http.ts
|
|
13
14
|
const CONTENT_TYPES = {
|
|
@@ -197,7 +198,7 @@ function parseSessionRoute(basePath, url) {
|
|
|
197
198
|
};
|
|
198
199
|
if (parts[1] === "shells" && parts.length <= 4) {
|
|
199
200
|
const action = parts[3];
|
|
200
|
-
if (action !== void 0 && action !== "output" && action !== "kill") return null;
|
|
201
|
+
if (action !== void 0 && action !== "output" && action !== "kill" && action !== "agent-write") return null;
|
|
201
202
|
return {
|
|
202
203
|
id: decodeURIComponent(parts[0]),
|
|
203
204
|
shells: true,
|
|
@@ -786,6 +787,8 @@ const HOST_ONLY_KEYS = new Set(Object.keys({
|
|
|
786
787
|
sessionInfoFn: true,
|
|
787
788
|
peers: true,
|
|
788
789
|
shells: true,
|
|
790
|
+
shellAgentWrite: true,
|
|
791
|
+
createdByOperator: true,
|
|
789
792
|
connectFn: true,
|
|
790
793
|
codexHome: true,
|
|
791
794
|
codexPathOverride: true,
|
|
@@ -1114,7 +1117,9 @@ const EPHEMERAL_CONFIG_KEYS = [
|
|
|
1114
1117
|
"extraOptions",
|
|
1115
1118
|
"env",
|
|
1116
1119
|
"peers",
|
|
1117
|
-
"instructions"
|
|
1120
|
+
"instructions",
|
|
1121
|
+
"shells",
|
|
1122
|
+
"shellAgentWrite"
|
|
1118
1123
|
];
|
|
1119
1124
|
function toDurableRecord(record) {
|
|
1120
1125
|
const config = { ...record.config };
|
|
@@ -1543,6 +1548,68 @@ function handleProjectIcon(projects, req, res, cwd) {
|
|
|
1543
1548
|
res.end(read.data);
|
|
1544
1549
|
}
|
|
1545
1550
|
//#endregion
|
|
1551
|
+
//#region src/services/shell-screen.ts
|
|
1552
|
+
var ShellScreen = class {
|
|
1553
|
+
#term;
|
|
1554
|
+
#closing;
|
|
1555
|
+
#final;
|
|
1556
|
+
constructor(cols, rows) {
|
|
1557
|
+
this.#term = new xtermHeadless.Terminal({
|
|
1558
|
+
cols,
|
|
1559
|
+
rows,
|
|
1560
|
+
scrollback: 0,
|
|
1561
|
+
allowProposedApi: true
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
write(data) {
|
|
1565
|
+
this.#term?.write(data);
|
|
1566
|
+
}
|
|
1567
|
+
resize(cols, rows) {
|
|
1568
|
+
try {
|
|
1569
|
+
this.#term?.resize(cols, rows);
|
|
1570
|
+
} catch {}
|
|
1571
|
+
}
|
|
1572
|
+
async snapshot() {
|
|
1573
|
+
if (this.#closing) await this.#closing;
|
|
1574
|
+
if (this.#final !== void 0) return this.#final;
|
|
1575
|
+
const term = this.#term;
|
|
1576
|
+
if (!term) return "";
|
|
1577
|
+
await drained(term);
|
|
1578
|
+
return render(term);
|
|
1579
|
+
}
|
|
1580
|
+
async applicationCursorKeys() {
|
|
1581
|
+
const term = this.#term;
|
|
1582
|
+
if (!term) return false;
|
|
1583
|
+
await drained(term);
|
|
1584
|
+
return term.modes.applicationCursorKeysMode;
|
|
1585
|
+
}
|
|
1586
|
+
close() {
|
|
1587
|
+
const term = this.#term;
|
|
1588
|
+
if (!term || this.#closing) return;
|
|
1589
|
+
this.#term = void 0;
|
|
1590
|
+
this.#closing = drained(term).then(() => {
|
|
1591
|
+
this.#final = render(term);
|
|
1592
|
+
term.dispose();
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
async function renderScreen(raw, cols, rows) {
|
|
1597
|
+
const screen = new ShellScreen(cols, rows);
|
|
1598
|
+
screen.write(raw);
|
|
1599
|
+
screen.close();
|
|
1600
|
+
return screen.snapshot();
|
|
1601
|
+
}
|
|
1602
|
+
function drained(term) {
|
|
1603
|
+
return new Promise((resolve) => term.write("", resolve));
|
|
1604
|
+
}
|
|
1605
|
+
function render(term) {
|
|
1606
|
+
const buffer = term.buffer.active;
|
|
1607
|
+
const lines = [];
|
|
1608
|
+
for (let y = 0; y < term.rows; y++) lines.push(buffer.getLine(buffer.viewportY + y)?.translateToString(true) ?? "");
|
|
1609
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
1610
|
+
return lines.join("\n");
|
|
1611
|
+
}
|
|
1612
|
+
//#endregion
|
|
1546
1613
|
//#region src/services/process-tree.ts
|
|
1547
1614
|
const PS_ARGS = ["-eo", "pid=,ppid=,pgid="];
|
|
1548
1615
|
const PS_TIMEOUT_MS = 2e3;
|
|
@@ -1687,18 +1754,147 @@ async function loadPty() {
|
|
|
1687
1754
|
}
|
|
1688
1755
|
return ptyModule;
|
|
1689
1756
|
}
|
|
1690
|
-
function createShellDirectory(registry) {
|
|
1757
|
+
function createShellDirectory(registry, deps = {}) {
|
|
1758
|
+
const take = async (from, shellId, options) => {
|
|
1759
|
+
const info = registry.get(from, shellId);
|
|
1760
|
+
if (!info) return;
|
|
1761
|
+
const view = options.view ?? (info.interactive ? "screen" : "lines");
|
|
1762
|
+
const fresh = options.since === void 0 ? void 0 : await freshText(from, shellId, info.bytes - options.since);
|
|
1763
|
+
if (view === "screen") {
|
|
1764
|
+
const text = await registry.output(from, shellId, { view: "screen" });
|
|
1765
|
+
if (text === void 0) return;
|
|
1766
|
+
const lines = text === "" ? 0 : text.split("\n").length;
|
|
1767
|
+
return {
|
|
1768
|
+
info,
|
|
1769
|
+
read: {
|
|
1770
|
+
view,
|
|
1771
|
+
text,
|
|
1772
|
+
lines,
|
|
1773
|
+
totalLines: lines,
|
|
1774
|
+
truncated: false
|
|
1775
|
+
},
|
|
1776
|
+
haystack: fresh ?? text
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
const text = await registry.output(from, shellId, { view: "text" });
|
|
1780
|
+
if (text === void 0) return;
|
|
1781
|
+
return {
|
|
1782
|
+
info,
|
|
1783
|
+
read: shellTail(text, clampShellTail(options.tail)),
|
|
1784
|
+
haystack: fresh ?? shellTail(text, SHELL_READ_MAX_LINES).text
|
|
1785
|
+
};
|
|
1786
|
+
};
|
|
1787
|
+
const freshText = async (from, shellId, bytes) => {
|
|
1788
|
+
if (bytes <= 0) return "";
|
|
1789
|
+
const raw = await registry.output(from, shellId, {
|
|
1790
|
+
view: "raw",
|
|
1791
|
+
tail: bytes
|
|
1792
|
+
});
|
|
1793
|
+
return raw === void 0 ? "" : ttyText(raw);
|
|
1794
|
+
};
|
|
1795
|
+
const settle = async (from, shellId, options) => {
|
|
1796
|
+
const needles = options.waitFor ?? [];
|
|
1797
|
+
const started = Date.now();
|
|
1798
|
+
const timeoutMs = Math.max(0, Math.min(options.timeoutMs ?? SHELL_WAIT_DEFAULT_MS, SHELL_WAIT_MAX_MS));
|
|
1799
|
+
for (;;) {
|
|
1800
|
+
const taken = await take(from, shellId, options);
|
|
1801
|
+
if (!taken) return;
|
|
1802
|
+
const result = {
|
|
1803
|
+
shell: shellSummary(taken.info),
|
|
1804
|
+
...taken.read
|
|
1805
|
+
};
|
|
1806
|
+
if (needles.length === 0) return result;
|
|
1807
|
+
const ms = Date.now() - started;
|
|
1808
|
+
const match = needles.find((needle) => taken.haystack.includes(needle));
|
|
1809
|
+
if (match !== void 0) return {
|
|
1810
|
+
...result,
|
|
1811
|
+
wait: {
|
|
1812
|
+
outcome: "matched",
|
|
1813
|
+
match,
|
|
1814
|
+
ms
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
if (taken.info.status !== "running") return {
|
|
1818
|
+
...result,
|
|
1819
|
+
wait: {
|
|
1820
|
+
outcome: "exited",
|
|
1821
|
+
ms
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
if (ms >= timeoutMs) return {
|
|
1825
|
+
...result,
|
|
1826
|
+
wait: {
|
|
1827
|
+
outcome: "timeout",
|
|
1828
|
+
ms
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
await registry.changed(from, shellId, timeoutMs - ms);
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
const writable = (from, shellId) => {
|
|
1835
|
+
const info = registry.get(from, shellId);
|
|
1836
|
+
if (!info) return;
|
|
1837
|
+
if (!agentMayWrite(info, from)) throw new Error(shellOwnershipRefusal(shellId));
|
|
1838
|
+
return info;
|
|
1839
|
+
};
|
|
1691
1840
|
return {
|
|
1692
1841
|
list: async (from) => registry.list(from).map(shellSummary),
|
|
1693
|
-
read:
|
|
1694
|
-
|
|
1842
|
+
read: (from, shellId, options) => settle(from, shellId, { ...options }),
|
|
1843
|
+
run: async (from, options) => {
|
|
1844
|
+
const runner = deps.runnerFor?.(from);
|
|
1845
|
+
if (!runner?.queueLocalCommand) throw new Error(SHELL_REFUSAL);
|
|
1846
|
+
const { shell, source } = await registry.spawn({
|
|
1847
|
+
runner,
|
|
1848
|
+
command: options.command,
|
|
1849
|
+
owner: "agent"
|
|
1850
|
+
});
|
|
1851
|
+
try {
|
|
1852
|
+
runner.queueLocalCommand(source);
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
registry.kill(from, shell.id);
|
|
1855
|
+
throw error;
|
|
1856
|
+
}
|
|
1857
|
+
const result = await settle(from, shell.id, {
|
|
1858
|
+
waitFor: options.waitFor,
|
|
1859
|
+
timeoutMs: options.timeoutMs
|
|
1860
|
+
});
|
|
1861
|
+
if (!result) throw new Error(`shell ${shell.id} vanished as it started`);
|
|
1862
|
+
return result;
|
|
1863
|
+
},
|
|
1864
|
+
write: async (from, shellId, options) => {
|
|
1865
|
+
const info = writable(from, shellId);
|
|
1695
1866
|
if (!info) return;
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1867
|
+
if (info.status !== "running") throw new Error(`shell ${shellId} has already ended; there is nothing to type into`);
|
|
1868
|
+
const application = await registry.applicationCursorKeys(from, shellId);
|
|
1869
|
+
const data = (options.data ?? "") + encodeShellKeys(options.keys ?? [], { applicationCursorKeys: application });
|
|
1870
|
+
if (data.length === 0) throw new Error("nothing to write: give data, keys or both");
|
|
1871
|
+
const since = registry.get(from, shellId)?.bytes ?? info.bytes;
|
|
1872
|
+
registry.write(from, shellId, data);
|
|
1873
|
+
return settle(from, shellId, {
|
|
1874
|
+
waitFor: options.waitFor,
|
|
1875
|
+
timeoutMs: options.timeoutMs,
|
|
1876
|
+
since
|
|
1877
|
+
});
|
|
1878
|
+
},
|
|
1879
|
+
kill: async (from, shellId) => {
|
|
1880
|
+
const info = writable(from, shellId);
|
|
1881
|
+
if (!info) return;
|
|
1882
|
+
if (info.status !== "running") return {
|
|
1699
1883
|
shell: shellSummary(info),
|
|
1700
|
-
|
|
1884
|
+
killed: false
|
|
1885
|
+
};
|
|
1886
|
+
const killed = registry.kill(from, shellId, "killed") ?? info;
|
|
1887
|
+
return {
|
|
1888
|
+
shell: shellSummary(killed),
|
|
1889
|
+
killed: true
|
|
1701
1890
|
};
|
|
1891
|
+
},
|
|
1892
|
+
grant: async (from, shellId) => {
|
|
1893
|
+
const info = registry.get(from, shellId);
|
|
1894
|
+
if (!info) return;
|
|
1895
|
+
if (info.owner === "agent") return shellSummary(info);
|
|
1896
|
+
const granted = registry.setAgentWrite(from, shellId, true);
|
|
1897
|
+
return granted ? shellSummary(granted) : void 0;
|
|
1702
1898
|
}
|
|
1703
1899
|
};
|
|
1704
1900
|
}
|
|
@@ -1861,6 +2057,7 @@ function createShellRegistry(options) {
|
|
|
1861
2057
|
entry.info.status = "exited";
|
|
1862
2058
|
entry.info.endedAt = Date.now();
|
|
1863
2059
|
entry.info.endReason = reason;
|
|
2060
|
+
delete entry.info.agentWrite;
|
|
1864
2061
|
clearTimeout(entry.clock);
|
|
1865
2062
|
clearTimeout(entry.notify);
|
|
1866
2063
|
clearInterval(entry.tailTimer);
|
|
@@ -1868,6 +2065,7 @@ function createShellRegistry(options) {
|
|
|
1868
2065
|
entry.child = void 0;
|
|
1869
2066
|
entry.pid = void 0;
|
|
1870
2067
|
if (entry.artifact instanceof LiveArtifact) entry.artifact.close();
|
|
2068
|
+
entry.screen?.close();
|
|
1871
2069
|
for (const sink of entry.sinks) try {
|
|
1872
2070
|
sink.end(reason);
|
|
1873
2071
|
} catch {}
|
|
@@ -1959,6 +2157,7 @@ function createShellRegistry(options) {
|
|
|
1959
2157
|
sinks: /* @__PURE__ */ new Set(),
|
|
1960
2158
|
listeners: /* @__PURE__ */ new Set()
|
|
1961
2159
|
};
|
|
2160
|
+
entry.screen = new ShellScreen(info.cols, info.rows);
|
|
1962
2161
|
state.entries.set(id, entry);
|
|
1963
2162
|
const env = shellChildEnv(process.env);
|
|
1964
2163
|
env.TERM = TERM;
|
|
@@ -1988,6 +2187,15 @@ function createShellRegistry(options) {
|
|
|
1988
2187
|
if (entry.info.status !== "running") return;
|
|
1989
2188
|
const changed = artifact.append(data);
|
|
1990
2189
|
entry.info.bytes = artifact.bytes;
|
|
2190
|
+
entry.screen?.write(data);
|
|
2191
|
+
if (!entry.info.interactive) {
|
|
2192
|
+
const scanned = (entry.redrawCarry ?? "") + data;
|
|
2193
|
+
if (ttyRedraws(scanned)) {
|
|
2194
|
+
entry.info.interactive = true;
|
|
2195
|
+
entry.redrawCarry = void 0;
|
|
2196
|
+
persist(state);
|
|
2197
|
+
} else entry.redrawCarry = scanned.slice(-TTY_REDRAW_CARRY);
|
|
2198
|
+
}
|
|
1991
2199
|
if (changed) {
|
|
1992
2200
|
if (artifact.capped && !entry.info.capped) {
|
|
1993
2201
|
entry.info.capped = true;
|
|
@@ -2060,6 +2268,22 @@ function createShellRegistry(options) {
|
|
|
2060
2268
|
kill(state, entry, reason);
|
|
2061
2269
|
return { ...entry.info };
|
|
2062
2270
|
},
|
|
2271
|
+
setAgentWrite: (sessionId, shellId, enabled) => {
|
|
2272
|
+
const state = sessions.get(sessionId);
|
|
2273
|
+
const entry = state?.entries.get(shellId);
|
|
2274
|
+
if (!state || !entry) return;
|
|
2275
|
+
if (enabled) {
|
|
2276
|
+
if (entry.info.owner !== "user") throw new Error(`shell ${shellId} was started by the agent, which may already type into it`);
|
|
2277
|
+
if (entry.info.status !== "running") throw new Error(`shell ${shellId} has already ended; there is nothing to grant`);
|
|
2278
|
+
}
|
|
2279
|
+
if (entry.info.agentWrite === true !== enabled) {
|
|
2280
|
+
if (enabled) entry.info.agentWrite = true;
|
|
2281
|
+
else delete entry.info.agentWrite;
|
|
2282
|
+
persist(state);
|
|
2283
|
+
fire(entry);
|
|
2284
|
+
}
|
|
2285
|
+
return { ...entry.info };
|
|
2286
|
+
},
|
|
2063
2287
|
killAll: (reason) => {
|
|
2064
2288
|
stopped = true;
|
|
2065
2289
|
if (sweeper) {
|
|
@@ -2128,16 +2352,36 @@ function createShellRegistry(options) {
|
|
|
2128
2352
|
} catch {}
|
|
2129
2353
|
entry.info.cols = next.cols;
|
|
2130
2354
|
entry.info.rows = next.rows;
|
|
2355
|
+
entry.screen?.resize(next.cols, next.rows);
|
|
2131
2356
|
}
|
|
2132
2357
|
return next;
|
|
2133
2358
|
},
|
|
2134
2359
|
output: async (sessionId, shellId, query) => {
|
|
2135
2360
|
const entry = find(sessionId, shellId);
|
|
2136
2361
|
if (!entry) return;
|
|
2362
|
+
if (query.view === "screen") {
|
|
2363
|
+
if (entry.screen) return entry.screen.snapshot();
|
|
2364
|
+
return renderScreen((await entry.artifact.replay(SHELL_ATTACH_REPLAY_BYTES)).toString("utf8"), entry.info.cols, entry.info.rows);
|
|
2365
|
+
}
|
|
2137
2366
|
if (query.tail === void 0 && query.view === "text") return entry.artifact.textView();
|
|
2138
2367
|
const text = (await entry.artifact.read(query.tail)).toString("utf8");
|
|
2139
2368
|
return query.view === "raw" ? text : ttyText(text);
|
|
2140
2369
|
},
|
|
2370
|
+
applicationCursorKeys: (sessionId, shellId) => find(sessionId, shellId)?.screen?.applicationCursorKeys() ?? Promise.resolve(false),
|
|
2371
|
+
changed: (sessionId, shellId, timeoutMs) => {
|
|
2372
|
+
const entry = find(sessionId, shellId);
|
|
2373
|
+
if (!entry || entry.info.status !== "running") return Promise.resolve();
|
|
2374
|
+
return new Promise((resolve) => {
|
|
2375
|
+
const done = () => {
|
|
2376
|
+
clearTimeout(timer);
|
|
2377
|
+
entry.listeners.delete(done);
|
|
2378
|
+
resolve();
|
|
2379
|
+
};
|
|
2380
|
+
const timer = setTimeout(done, Math.max(0, timeoutMs));
|
|
2381
|
+
timer.unref();
|
|
2382
|
+
entry.listeners.add(done);
|
|
2383
|
+
});
|
|
2384
|
+
},
|
|
2141
2385
|
hydrate: async () => {
|
|
2142
2386
|
if (dir) {
|
|
2143
2387
|
let names = [];
|
|
@@ -2529,6 +2773,7 @@ async function readRange(path, start, end) {
|
|
|
2529
2773
|
}
|
|
2530
2774
|
//#endregion
|
|
2531
2775
|
//#region src/routes/shells.ts
|
|
2776
|
+
const AGENT_WRITE_BODY_MAX = 1024;
|
|
2532
2777
|
async function handleShells(ctx, req, res, route, runner, operator) {
|
|
2533
2778
|
const { shells } = ctx;
|
|
2534
2779
|
const sessionId = route.id;
|
|
@@ -2565,13 +2810,52 @@ async function handleShells(ctx, req, res, route, runner, operator) {
|
|
|
2565
2810
|
json(res, 200, { shell });
|
|
2566
2811
|
return;
|
|
2567
2812
|
}
|
|
2813
|
+
if (route.shellAction === "agent-write") {
|
|
2814
|
+
if (req.method !== "POST") {
|
|
2815
|
+
json(res, 405, { error: "method not allowed" });
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
if (!runner || !shellPermitted(shells, runner, operator)) {
|
|
2819
|
+
json(res, 403, { error: SHELL_REFUSAL });
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
let body;
|
|
2823
|
+
try {
|
|
2824
|
+
body = await readJsonBody(req, AGENT_WRITE_BODY_MAX);
|
|
2825
|
+
} catch {
|
|
2826
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
if (typeof body.enabled !== "boolean") {
|
|
2830
|
+
json(res, 400, { error: "enabled must be a boolean" });
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
if (body.enabled && runner.info().shellAgentWrite === void 0) {
|
|
2834
|
+
json(res, 409, { error: "the agent has no shell write tools on this session (shell.agentWrite is read-only)" });
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
let shell;
|
|
2838
|
+
try {
|
|
2839
|
+
shell = shells.setAgentWrite(sessionId, route.shellId, body.enabled);
|
|
2840
|
+
} catch (error) {
|
|
2841
|
+
json(res, 409, { error: error instanceof Error ? error.message : String(error) });
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
if (!shell) {
|
|
2845
|
+
json(res, 404, { error: "shell not found" });
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
json(res, 200, { shell });
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2568
2851
|
if (req.method !== "GET") {
|
|
2569
2852
|
json(res, 405, { error: "method not allowed" });
|
|
2570
2853
|
return;
|
|
2571
2854
|
}
|
|
2572
2855
|
if (route.shellAction === "output") {
|
|
2573
2856
|
const url = new URL(req.url ?? "/", "http://internal");
|
|
2574
|
-
const
|
|
2857
|
+
const requested = url.searchParams.get("view");
|
|
2858
|
+
const view = requested === "raw" || requested === "screen" ? requested : "text";
|
|
2575
2859
|
const tailParam = url.searchParams.get("tail");
|
|
2576
2860
|
const tail = tailParam === null ? void 0 : Number(tailParam);
|
|
2577
2861
|
if (tail !== void 0 && (!Number.isInteger(tail) || tail <= 0)) {
|
|
@@ -2666,7 +2950,7 @@ async function handleSessions(ctx, req, res, route, auth) {
|
|
|
2666
2950
|
json(res, vetted.status, { error: vetted.error });
|
|
2667
2951
|
return;
|
|
2668
2952
|
}
|
|
2669
|
-
const runner = await factory.createRunner(factory.buildRunnerConfig(vetted.request));
|
|
2953
|
+
const runner = await factory.createRunner(factory.buildRunnerConfig(vetted.request, { operator: authSvc.isOperator(auth) }));
|
|
2670
2954
|
factory.watchAuthSource(runner);
|
|
2671
2955
|
json(res, 201, { session: projects.withProject(runner.info()) });
|
|
2672
2956
|
return;
|
|
@@ -4393,6 +4677,12 @@ var SessionRegistry = class {
|
|
|
4393
4677
|
};
|
|
4394
4678
|
//#endregion
|
|
4395
4679
|
//#region src/services/session-factory.ts
|
|
4680
|
+
function withPrincipal(config, operator) {
|
|
4681
|
+
return operator === void 0 ? config : {
|
|
4682
|
+
...config,
|
|
4683
|
+
createdByOperator: operator
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4396
4686
|
function createSessionFactory(deps) {
|
|
4397
4687
|
const { adapterFor, profiles, refs } = deps;
|
|
4398
4688
|
const subscriptionNoticeShown = /* @__PURE__ */ new Set();
|
|
@@ -4472,14 +4762,15 @@ function createSessionFactory(deps) {
|
|
|
4472
4762
|
...config,
|
|
4473
4763
|
defaultApprovalTimeoutMs: deps.approvalTimeoutMs
|
|
4474
4764
|
};
|
|
4475
|
-
const buildRunnerConfig = (req) => {
|
|
4765
|
+
const buildRunnerConfig = (req, principal) => {
|
|
4476
4766
|
const profile = req.profile !== void 0 ? profiles.get(req.profile) : void 0;
|
|
4477
|
-
|
|
4478
|
-
|
|
4767
|
+
const operator = principal?.operator ?? req.createdByOperator;
|
|
4768
|
+
if (!profile) return withApprovalDefault(withPrincipal(withScope(deps.hostBuildRunnerConfig(req), req.scope), operator));
|
|
4769
|
+
const config = withApprovalDefault(withPrincipal(withScope(deps.hostBuildRunnerConfig({
|
|
4479
4770
|
...req,
|
|
4480
4771
|
model: req.model ?? profile.defaults?.model ?? profile.provider?.model,
|
|
4481
4772
|
permissionMode: req.permissionMode ?? profile.defaults?.permissionMode
|
|
4482
|
-
}), req.scope));
|
|
4773
|
+
}), req.scope), operator));
|
|
4483
4774
|
if (engineOf(profile) !== "claude") return config;
|
|
4484
4775
|
const base = config.env ?? process.env;
|
|
4485
4776
|
const env = claudeSessionEnv(profile, base);
|
|
@@ -4505,9 +4796,15 @@ function createSessionFactory(deps) {
|
|
|
4505
4796
|
const capabilities = profile?.capabilities ?? adapterFor(profile?.engine).capabilities;
|
|
4506
4797
|
const config = {
|
|
4507
4798
|
...built,
|
|
4508
|
-
...deps.peers ? { peers: deps.peers } : {}
|
|
4509
|
-
...deps.shells && capabilities.hostCwd === true ? { shells: deps.shells } : {}
|
|
4799
|
+
...deps.peers ? { peers: deps.peers } : {}
|
|
4510
4800
|
};
|
|
4801
|
+
delete config.shells;
|
|
4802
|
+
delete config.shellAgentWrite;
|
|
4803
|
+
if (deps.shells && capabilities.hostCwd === true) {
|
|
4804
|
+
config.shells = deps.shells;
|
|
4805
|
+
const write = deps.shellAgentWrite ?? "read-only";
|
|
4806
|
+
if (write !== "read-only" && built.createdByOperator === true) config.shellAgentWrite = write;
|
|
4807
|
+
}
|
|
4511
4808
|
if (config.instructions !== void 0 && capabilities.systemInstructions === false) throw new Error(`the ${engineOf(profile)} engine cannot deliver system instructions`);
|
|
4512
4809
|
const runner = profile && isProviderProfile(profile) ? await deps.createEngineRunner({
|
|
4513
4810
|
config,
|
|
@@ -4695,7 +4992,7 @@ function createWorkerServer(options = {}) {
|
|
|
4695
4992
|
options: options.peers
|
|
4696
4993
|
});
|
|
4697
4994
|
if (peers) installPeerDirectory(peers);
|
|
4698
|
-
installShellDirectory(shells ? createShellDirectory(shells) : void 0);
|
|
4995
|
+
installShellDirectory(shells ? createShellDirectory(shells, { runnerFor: (id) => refs.registry?.get(id) }) : void 0);
|
|
4699
4996
|
const factory = createSessionFactory({
|
|
4700
4997
|
adapterFor,
|
|
4701
4998
|
profiles,
|
|
@@ -4707,6 +5004,7 @@ function createWorkerServer(options = {}) {
|
|
|
4707
5004
|
requireApiKey: options.requireApiKey,
|
|
4708
5005
|
peers: peers ? peerDirectoryHandle() : void 0,
|
|
4709
5006
|
shells: shells ? shellDirectoryHandle() : void 0,
|
|
5007
|
+
shellAgentWrite: options.shell?.agentWrite,
|
|
4710
5008
|
refs
|
|
4711
5009
|
});
|
|
4712
5010
|
const availability = new AvailabilityTracker({
|
|
@@ -5128,6 +5426,7 @@ async function createProviderRunner(ctx, options) {
|
|
|
5128
5426
|
const backend = isPerCall ? (call) => {
|
|
5129
5427
|
return options.executor(call) === "browser" ? "browser" : "server";
|
|
5130
5428
|
} : options.executor === "browser" ? "browser" : "server";
|
|
5429
|
+
const shouldApprove = config.shellAgentWrite === void 0 ? options.shouldApprove : (call) => shellToolNeedsCard(call.toolName, config.shellAgentWrite) || options.shouldApprove?.(call) === true;
|
|
5131
5430
|
return createEngineSession({
|
|
5132
5431
|
config: {
|
|
5133
5432
|
...config,
|
|
@@ -5146,7 +5445,7 @@ async function createProviderRunner(ctx, options) {
|
|
|
5146
5445
|
mcpTools: options.mcpTools,
|
|
5147
5446
|
instructions: options.instructions,
|
|
5148
5447
|
executionLimits: options.executionLimits,
|
|
5149
|
-
shouldApprove
|
|
5448
|
+
shouldApprove,
|
|
5150
5449
|
approvalTimeoutMs: config.defaultApprovalTimeoutMs === void 0 ? options.approvalTimeoutMs : config.defaultApprovalTimeoutMs,
|
|
5151
5450
|
seedVfs: options.seedVfs
|
|
5152
5451
|
});
|
|
@@ -5191,6 +5490,6 @@ function createFileProfileStore(path = join(process.cwd(), ".workerdeck", "profi
|
|
|
5191
5490
|
};
|
|
5192
5491
|
}
|
|
5193
5492
|
//#endregion
|
|
5194
|
-
export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SHELL_REFUSAL, SessionNotifier, SessionParkManager, SessionRegistry, SpendLedger, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createShellRegistry, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, shellPermitted, toDurableRecord };
|
|
5493
|
+
export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SHELL_REFUSAL, SessionNotifier, SessionParkManager, SessionRegistry, SpendLedger, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createShellDirectory, createShellRegistry, createWorkerServer, dayKey, isDormant, reloadPlan, sandboxedProviderProfile, shellPermitted, toDurableRecord };
|
|
5195
5494
|
|
|
5196
5495
|
//# sourceMappingURL=index.mjs.map
|