@demicodes/agent 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { d as TranscriptPatch, l as ServerFrame, n as AgentServerTransport, o as ClientFrame, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandSnapshotLike } from "./transport-BkAbPvRc.mjs";
2
- import { a as AgentClientListener, i as AgentClient, n as createWebSocketClientTransport, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-D6zJ4PLC.mjs";
1
+ import { d as TranscriptPatch, l as ServerFrame, n as AgentServerTransport, o as ClientFrame, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandSnapshotLike } from "./transport-DVa_82EW.mjs";
2
+ import { a as AgentClientListener, i as AgentClient, n as createWebSocketClientTransport, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-Cn_rhIIX.mjs";
3
3
  import { ProviderSelection } from "@demicodes/provider";
4
4
  export { AgentClient, type AgentClientListener, type AgentClientTransport, type AgentServerTransport, type AgentTransport, type ClientFrame, type ClientSessionEvent, type JsonWebSocket, type ProviderSelection, type ServerFrame, type ShellCommandSnapshotLike, type TranscriptPatch, createWebSocketClientTransport, createWebSocketServerTransport };
@@ -1,2 +1,2 @@
1
- import { n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-o6r-jV5p.mjs";
1
+ import { n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-Bod0puog.mjs";
2
2
  export { AgentClient, createWebSocketClientTransport, createWebSocketServerTransport };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,8 @@
1
- import { A as AgentToolInvokeResult, B as DEFAULT_TURN_RETRY_POLICY, C as AgentSessionRestoreParams, D as AgentTool, E as AgentSystemPromptContext, F as CompactionWindow, H as isRetryableCode, I as DrainedTranscriptPatches, L as Transcript, M as SessionEvent, N as SessionEventListener, O as AgentToolContext, P as ToolContinuation, R as TranscriptOptions, S as AgentSessionParams, T as AgentSessionStore, U as resolveRetryPolicy, V as TurnRetryPolicy, W as retryDelayMs, _ as AgentHarnessRuntime, a as createInProcessTransportPair, b as AgentReferenceResolveContext, c as ConversationSummary, d as TranscriptPatch, f as AbortResult, g as AgentHarnessContext, h as AgentHarness, i as InProcessTransportPair, j as ExternalMutationReservation, k as AgentToolInvokeContext, l as ServerFrame, m as AgentDisposeContext, n as AgentServerTransport, o as ClientFrame, p as AbortTarget, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandSnapshotLike, v as AgentLifecycleEvent, w as AgentSessionSnapshot, x as AgentSessionOptions, y as AgentPromptContext, z as estimateTranscriptBlockTokens } from "./transport-BkAbPvRc.mjs";
2
- import { a as AgentClientListener, i as AgentClient, n as createWebSocketClientTransport, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-D6zJ4PLC.mjs";
3
- import { BashEnvironment, BashEnvironmentOptions, ShellCommandSnapshot } from "@demicodes/shell";
1
+ import { A as AgentToolInvokeResult, B as DEFAULT_TURN_RETRY_POLICY, C as AgentSessionRestoreParams, D as AgentTool, E as AgentSystemPromptContext, F as CompactionWindow, H as isRetryableCode, I as DrainedTranscriptPatches, L as Transcript, M as SessionEvent, N as SessionEventListener, O as AgentToolContext, P as ToolContinuation, R as TranscriptOptions, S as AgentSessionParams, T as AgentSessionStore, U as resolveRetryPolicy, V as TurnRetryPolicy, W as retryDelayMs, _ as AgentHarnessRuntime, a as createInProcessTransportPair, b as AgentReferenceResolveContext, c as ConversationSummary, d as TranscriptPatch, f as AbortResult, g as AgentHarnessContext, h as AgentHarness, i as InProcessTransportPair, j as ExternalMutationReservation, k as AgentToolInvokeContext, l as ServerFrame, m as AgentDisposeContext, n as AgentServerTransport, o as ClientFrame, p as AbortTarget, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandSnapshotLike, v as AgentLifecycleEvent, w as AgentSessionSnapshot, x as AgentSessionOptions, y as AgentPromptContext, z as estimateTranscriptBlockTokens } from "./transport-DVa_82EW.mjs";
2
+ import { a as AgentClientListener, i as AgentClient, n as createWebSocketClientTransport, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-Cn_rhIIX.mjs";
3
+ import { BashEnvironment, BashEnvironmentOptions, Host, ShellCommandSnapshot } from "@demicodes/shell";
4
4
  import { AgentProvider, Provider } from "@demicodes/provider";
5
- import { Block, ModelSelection, QueuedMessage, SessionPhase, UserContentBlock } from "@demicodes/core";
6
-
5
+ import { Block, Model, ModelSelection, QueuedMessage, SessionPhase, UserContentBlock } from "@demicodes/core";
7
6
  //#region src/session.d.ts
8
7
  type ActiveTurnPhase = 'provider_streaming' | 'tool_executing' | 'compacting' | 'finalizing';
9
8
  declare class AgentSession<State> {
@@ -20,6 +19,7 @@ declare class AgentSession<State> {
20
19
  private readonly listeners;
21
20
  private readonly pendingActions;
22
21
  private readonly queued;
22
+ get modelSelection(): ModelSelection;
23
23
  private readonly transcriptLog;
24
24
  private readonly agentState;
25
25
  private currentPhase;
@@ -144,10 +144,6 @@ declare class AgentSession<State> {
144
144
  private resolveIdleWaiters;
145
145
  }
146
146
  //#endregion
147
- //#region src/json-codec.d.ts
148
- declare function stringifyAgentJson(value: unknown): string;
149
- declare function parseAgentJson<T>(text: string): T;
150
- //#endregion
151
147
  //#region src/patch.d.ts
152
148
  /**
153
149
  * Applies journal-produced transcript patches to a block list, returning a new
@@ -157,21 +153,6 @@ declare function parseAgentJson<T>(text: string): T;
157
153
  declare function applyTranscriptPatches(blocks: Block[], patches: TranscriptPatch[]): Block[];
158
154
  declare function cloneBlocks(blocks: Block[]): Block[];
159
155
  //#endregion
160
- //#region src/command-tools.d.ts
161
- interface CommandToolProjectionOptions {
162
- /** Leaf paths to project (e.g. 'editor create'). Empty/absent = all leaves. */
163
- include?: string[];
164
- exclude?: string[];
165
- }
166
- /**
167
- * Projects registered command leaves onto the native tool surface: one
168
- * AgentTool per leaf, named by its path (editor create -> editor_create).
169
- * The tool renders the same shell invocation the model could type and runs it
170
- * through BashEnvironment.exec, so both projections share one implementation,
171
- * one audit trail, and one artifact store.
172
- */
173
- declare function createCommandProjectionTools<State = unknown>(environment: BashEnvironment, options?: CommandToolProjectionOptions, reservedNames?: ReadonlySet<string>): AgentTool<State>[];
174
- //#endregion
175
156
  //#region src/server.d.ts
176
157
  /** Session tuning forwarded to every AgentSession this server creates. */
177
158
  interface AgentServerSessionOptions {
@@ -182,33 +163,79 @@ interface AgentServerSessionOptions {
182
163
  };
183
164
  persistIntervalMs?: number;
184
165
  }
166
+ /**
167
+ * Host-agnostic hook invoked once per `open()` before BashEnvironment is built.
168
+ * LocalHost uses this to inject PATH / env for the command bridge; AgentServer
169
+ * itself does not know about UDS, shims, or bin directories.
170
+ */
171
+ interface PrepareSessionShellContext {
172
+ agentSessionId: string;
173
+ host: Host;
174
+ commandNames: readonly string[];
175
+ /** Shell options from AgentServer construction (before this hook). */
176
+ shell: Omit<BashEnvironmentOptions, 'host' | 'commands'>;
177
+ }
178
+ type PrepareSessionShell = (ctx: PrepareSessionShellContext) => Omit<BashEnvironmentOptions, 'host' | 'commands'> | Promise<Omit<BashEnvironmentOptions, 'host' | 'commands'>>;
185
179
  interface AgentServerOptions {
186
180
  agent: AgentHarness<unknown>;
187
181
  providers: Provider[];
188
182
  shell?: Omit<BashEnvironmentOptions, 'host' | 'commands'>;
189
183
  session?: AgentServerSessionOptions;
190
184
  /**
191
- * When set, registered command leaves are ALSO exposed as native tools (the
192
- * second projection of the same CommandSpec implementations). Off by default:
193
- * the shell form alone keeps the smallest tool surface.
185
+ * Optional host-side shell prep (env, PATH, etc.). Implementation-agnostic
186
+ * command bridge wiring lives in `@demicodes/host-local`, not here.
194
187
  */
195
- commandTools?: CommandToolProjectionOptions;
188
+ prepareSessionShell?: PrepareSessionShell;
196
189
  }
197
190
  interface AgentTransportBinding {
198
191
  close(): Promise<void>;
199
192
  }
193
+ /** Options for {@link AgentServer.runCommandLine}. */
194
+ interface RunCommandLineOptions {
195
+ cwd: string;
196
+ stdin: string;
197
+ signal?: AbortSignal;
198
+ }
199
+ /** Result of {@link AgentServer.runCommandLine}. */
200
+ interface RunCommandLineResult {
201
+ exitCode: number;
202
+ /** UTF-8 text, or base64 when `stdoutEncoding` is 'base64'. */
203
+ stdout: string;
204
+ stdoutEncoding?: 'base64';
205
+ stderr: string;
206
+ }
207
+ declare class RunCommandLineSessionNotFoundError extends Error {
208
+ readonly agentSessionId: string;
209
+ constructor(agentSessionId: string);
210
+ }
211
+ declare class RunCommandLineCommandNotRegisteredError extends Error {
212
+ readonly commandName: string;
213
+ constructor(commandName: string);
214
+ }
215
+ declare class RunCommandLineTimeoutError extends Error {
216
+ readonly commandId: string;
217
+ readonly partialStdout: string;
218
+ readonly partialStderr: string;
219
+ constructor(commandId: string, partialStdout: string, partialStderr: string);
220
+ }
200
221
  declare class AgentServer {
201
222
  private readonly agent;
202
223
  private readonly providers;
203
224
  private readonly shellOptions;
204
225
  private readonly sessionOptions;
205
- private readonly commandToolOptions;
226
+ private readonly prepareSessionShell;
206
227
  private readonly bindings;
207
228
  private readonly sessionOwnership;
208
229
  constructor(options: AgentServerOptions);
209
230
  client(): AgentClient;
210
231
  attachTransport(transport: AgentServerTransport): AgentTransportBinding;
211
232
  close(): Promise<void>;
233
+ /**
234
+ * Runs one registered-command invocation to completion for a live session.
235
+ * Transport-agnostic: callers (e.g. LocalHost command bridge) supply their
236
+ * own IPC; AgentServer only knows how to exec against the open session shell.
237
+ */
238
+ runCommandLine(agentSessionId: string, name: string, args: string[], opts: RunCommandLineOptions): Promise<RunCommandLineResult>;
212
239
  }
213
240
  //#endregion
214
241
  //#region src/tools.d.ts
@@ -221,10 +248,12 @@ interface ShellToolResultOptions {
221
248
  includePreview?: boolean;
222
249
  previewBudgetTokens?: number;
223
250
  exposeCommandHandle?: boolean;
251
+ /** Model receiving this result; gates binary-stream media attachment. */
252
+ model?: Model;
224
253
  }
225
254
  declare function shellPreviewBudgetTokens(contextWindow: number): number;
226
255
  declare function toShellToolResult(result: ShellCommandSnapshot, toolCallId?: string, options?: ShellToolResultOptions): AgentToolInvokeResult;
227
256
  declare function finishShellToolResult<State>(environment: BashEnvironment, result: ShellCommandSnapshot, ctx: AgentToolInvokeContext<State>): Promise<AgentToolInvokeResult>;
228
257
  declare function shellCommandHandleRequired(result: ShellCommandSnapshot, budgetTokens: number): boolean;
229
258
  //#endregion
230
- export { AbortResult, AbortTarget, ActiveTurnPhase, AgentClient, AgentClientListener, AgentClientTransport, AgentDisposeContext, AgentHarness, AgentHarnessContext, AgentHarnessRuntime, AgentLifecycleEvent, AgentPromptContext, AgentReferenceResolveContext, AgentServer, AgentServerOptions, AgentServerSessionOptions, AgentServerTransport, AgentSession, AgentSessionOptions, AgentSessionParams, AgentSessionRestoreParams, AgentSessionSnapshot, AgentSessionStore, AgentSystemPromptContext, AgentTool, AgentToolContext, AgentToolInvokeContext, AgentToolInvokeResult, AgentTransport, AgentTransportBinding, ClientFrame, ClientSessionEvent, CommandToolProjectionOptions, CompactionWindow, ConversationSummary, DEFAULT_TURN_RETRY_POLICY, DrainedTranscriptPatches, ExternalMutationReservation, InProcessTransportPair, JsonWebSocket, ServerFrame, SessionEvent, SessionEventListener, ShellCommandSnapshotLike, ShellToolResultOptions, StandardAgentToolOptions, ToolContinuation, Transcript, TranscriptOptions, TranscriptPatch, TurnRetryPolicy, applyTranscriptPatches, cloneBlocks, createCommandProjectionTools, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, finishShellToolResult, isRetryableCode, parseAgentJson, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, stringifyAgentJson, toShellToolResult };
259
+ export { AbortResult, AbortTarget, ActiveTurnPhase, AgentClient, AgentClientListener, AgentClientTransport, AgentDisposeContext, AgentHarness, AgentHarnessContext, AgentHarnessRuntime, AgentLifecycleEvent, AgentPromptContext, AgentReferenceResolveContext, AgentServer, AgentServerOptions, AgentServerSessionOptions, AgentServerTransport, AgentSession, AgentSessionOptions, AgentSessionParams, AgentSessionRestoreParams, AgentSessionSnapshot, AgentSessionStore, AgentSystemPromptContext, AgentTool, AgentToolContext, AgentToolInvokeContext, AgentToolInvokeResult, AgentTransport, AgentTransportBinding, ClientFrame, ClientSessionEvent, CompactionWindow, ConversationSummary, DEFAULT_TURN_RETRY_POLICY, DrainedTranscriptPatches, ExternalMutationReservation, InProcessTransportPair, JsonWebSocket, PrepareSessionShell, PrepareSessionShellContext, RunCommandLineCommandNotRegisteredError, RunCommandLineOptions, RunCommandLineResult, RunCommandLineSessionNotFoundError, RunCommandLineTimeoutError, ServerFrame, SessionEvent, SessionEventListener, ShellCommandSnapshotLike, ShellToolResultOptions, StandardAgentToolOptions, ToolContinuation, Transcript, TranscriptOptions, TranscriptPatch, TurnRetryPolicy, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, finishShellToolResult, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-o6r-jV5p.mjs";
2
- import { n as stringifyAgentJson, t as parseAgentJson } from "./json-codec-BFLXlg3K.mjs";
3
- import { AbortError, abortable, asError, asRecord, asString, createId, delay, isAbortError, isRecord, noop, parseJsonOrString, safeJsonStringify, throwIfAborted, truncate } from "@demicodes/utils";
4
- import { BashEnvironment, CommandRegistry, listRegisteredCommandOperations } from "@demicodes/shell";
1
+ import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-Bod0puog.mjs";
2
+ import { AbortError, abortable, asError, asRecord, asString, bytesToBase64, createId, delay, isAbortError, isRecord, noop, parseJsonOrString, safeJsonStringify, throwIfAborted, truncate } from "@demicodes/utils";
3
+ import { BashEnvironment, CommandRegistry, MAX_TIMEOUT_MS, heredocDelimiter, shellQuote } from "@demicodes/shell";
5
4
  import { providerRuntime } from "@demicodes/provider";
5
+ import { modelAcceptsMediaType, sniffModelMediaType } from "@demicodes/core";
6
6
  //#region src/transcript.ts
7
7
  const DEFAULT_MODEL_TEXT_HEAD_CHARS = 8e3;
8
8
  const DEFAULT_MODEL_TEXT_TAIL_CHARS = 8e3;
@@ -532,6 +532,7 @@ function stringifyUserContent(content) {
532
532
  switch (content.type) {
533
533
  case "text": return content.text;
534
534
  case "image": return content.source.type === "url" ? content.source.url : content.source.mediaType;
535
+ case "video": return content.source.type === "url" ? content.source.url : content.source.mediaType;
535
536
  case "document": return `${content.source.fileName} ${content.source.mediaType}`;
536
537
  case "reference": return content.reference;
537
538
  }
@@ -540,6 +541,7 @@ function stringifyToolResult(content) {
540
541
  switch (content.type) {
541
542
  case "text": return content.text;
542
543
  case "image": return content.source.mediaType;
544
+ case "video": return content.source.mediaType;
543
545
  }
544
546
  }
545
547
  function boundUserContent(content, headChars, tailChars) {
@@ -1139,6 +1141,9 @@ var AgentSession = class AgentSession {
1139
1141
  listeners = /* @__PURE__ */ new Set();
1140
1142
  pendingActions = [];
1141
1143
  queued = [];
1144
+ get modelSelection() {
1145
+ return this.model;
1146
+ }
1142
1147
  transcriptLog;
1143
1148
  agentState;
1144
1149
  currentPhase = "idle";
@@ -2212,13 +2217,14 @@ function toShellToolResult(result, toolCallId = "", options = {}) {
2212
2217
  type: "text",
2213
2218
  text: formatShellToolResult(result, options)
2214
2219
  }];
2215
- if (result.status === "exited" && result.assets) for (const asset of result.assets) output.push({
2216
- type: "image",
2217
- source: {
2218
- mediaType: asset.mediaType,
2219
- data: asset.data
2220
- }
2221
- });
2220
+ if (result.status === "exited" && result.binaryStdout) {
2221
+ const verdict = binaryStreamVerdict(result.binaryStdout, result.commandId, options.model);
2222
+ if (verdict.block) output.push(verdict.block);
2223
+ if (verdict.note) output.push({
2224
+ type: "text",
2225
+ text: verdict.note
2226
+ });
2227
+ }
2222
2228
  return {
2223
2229
  output,
2224
2230
  isError: false,
@@ -2231,6 +2237,32 @@ function toShellToolResult(result, toolCallId = "", options = {}) {
2231
2237
  } : void 0
2232
2238
  };
2233
2239
  }
2240
+ /**
2241
+ * Boundary decision for a binary final stream: attach as native media when the
2242
+ * magic matches the closed model-media set, the model accepts the type, and
2243
+ * the stream was not truncated; otherwise explain why nothing was attached.
2244
+ */
2245
+ function binaryStreamVerdict(binary, commandId, model) {
2246
+ const media = sniffModelMediaType(binary.data);
2247
+ const binPath = `/@/commands/${commandId}/stdout.bin`;
2248
+ if (binary.truncated) return { note: `Binary stdout was truncated at the output limit (${binary.data.length} of ${binary.totalBytes} bytes)${media ? ` and looks like ${media.mediaType}` : ""}; it was not attached. Redirect to a file or raise the output limit and re-run.` };
2249
+ if (!media) return { note: `Binary stdout does not match any model-viewable media type; the raw bytes remain readable at ${binPath}.` };
2250
+ if (!model || !modelAcceptsMediaType(model, media.mediaType)) return { note: `Binary stdout is ${media.mediaType}, which this model does not accept natively; the raw bytes remain readable at ${binPath}.` };
2251
+ const source = {
2252
+ mediaType: media.mediaType,
2253
+ data: bytesToBase64(binary.data)
2254
+ };
2255
+ return {
2256
+ block: media.kind === "video" ? {
2257
+ type: "video",
2258
+ source
2259
+ } : {
2260
+ type: "image",
2261
+ source
2262
+ },
2263
+ note: `Attached stdout as ${media.mediaType} (${binary.totalBytes} bytes).`
2264
+ };
2265
+ }
2234
2266
  function parseShellExecInput(input) {
2235
2267
  const record = asRecord(input, "agent tool input must be an object");
2236
2268
  if (typeof record.script !== "string") throw new Error("shell_exec requires string field \"script\"");
@@ -2352,7 +2384,8 @@ async function finishShellToolResult(environment, result, ctx) {
2352
2384
  const toolResult = toShellToolResult(result, ctx.toolCallId, {
2353
2385
  includePreview: true,
2354
2386
  previewBudgetTokens,
2355
- exposeCommandHandle
2387
+ exposeCommandHandle,
2388
+ model: ctx.model.model
2356
2389
  });
2357
2390
  if (!exposeCommandHandle) await environment.releaseCommand(result.commandId);
2358
2391
  return toolResult;
@@ -2364,56 +2397,41 @@ function shellCommandHandleRequired(result, budgetTokens) {
2364
2397
  return preview.truncated || result.output.truncated || result.output.bytes > maxChars || result.stdout.truncated || result.stderr.truncated;
2365
2398
  }
2366
2399
  //#endregion
2367
- //#region src/command-tools.ts
2368
- /** Observation window for projected command invocations. Registered commands
2369
- * execute in-process and complete quickly; long work belongs to shell_exec. */
2370
- const COMMAND_TOOL_TIMEOUT_MS = 6e4;
2371
- /**
2372
- * Projects registered command leaves onto the native tool surface: one
2373
- * AgentTool per leaf, named by its path (editor create -> editor_create).
2374
- * The tool renders the same shell invocation the model could type and runs it
2375
- * through BashEnvironment.exec, so both projections share one implementation,
2376
- * one audit trail, and one artifact store.
2377
- */
2378
- function createCommandProjectionTools(environment, options = {}, reservedNames = /* @__PURE__ */ new Set()) {
2379
- const operations = listRegisteredCommandOperations(environment.registeredCommands()).filter((operation) => operationSelected(operation, options));
2380
- const seen = new Set(reservedNames);
2381
- return operations.map((operation) => {
2382
- const name = operation.path.join("_");
2383
- if (seen.has(name)) throw new Error(`Command tool projection: tool name "${name}" conflicts with an existing tool`);
2384
- seen.add(name);
2385
- return {
2386
- name,
2387
- description: operation.description,
2388
- inputSchema: operation.inputSchema,
2389
- invoke: async (ctx, input) => {
2390
- const values = asRecord(input, `${name} input must be an object`);
2391
- const result = await environment.exec({
2392
- script: operation.renderScript(values),
2393
- agentSessionId: ctx.agentSessionId,
2394
- timeoutMs: COMMAND_TOOL_TIMEOUT_MS,
2395
- signal: ctx.signal
2396
- });
2397
- ctx.emitProgress(result);
2398
- return finishShellToolResult(environment, result, ctx);
2399
- }
2400
- };
2401
- });
2402
- }
2403
- function operationSelected(operation, options) {
2404
- const path = operation.path.join(" ");
2405
- if (options.exclude?.includes(path)) return false;
2406
- if (options.include && options.include.length > 0) return options.include.includes(path);
2407
- return true;
2408
- }
2409
- //#endregion
2410
2400
  //#region src/server.ts
2401
+ var RunCommandLineSessionNotFoundError = class extends Error {
2402
+ agentSessionId;
2403
+ constructor(agentSessionId) {
2404
+ super(`runCommandLine: no session "${agentSessionId}" is open in this process`);
2405
+ this.agentSessionId = agentSessionId;
2406
+ this.name = "RunCommandLineSessionNotFoundError";
2407
+ }
2408
+ };
2409
+ var RunCommandLineCommandNotRegisteredError = class extends Error {
2410
+ commandName;
2411
+ constructor(commandName) {
2412
+ super(`runCommandLine: command "${commandName}" is not registered for this session`);
2413
+ this.commandName = commandName;
2414
+ this.name = "RunCommandLineCommandNotRegisteredError";
2415
+ }
2416
+ };
2417
+ var RunCommandLineTimeoutError = class extends Error {
2418
+ commandId;
2419
+ partialStdout;
2420
+ partialStderr;
2421
+ constructor(commandId, partialStdout, partialStderr) {
2422
+ super(`runCommandLine: command "${commandId}" exceeded the ${MAX_TIMEOUT_MS}ms ceiling and was aborted`);
2423
+ this.commandId = commandId;
2424
+ this.partialStdout = partialStdout;
2425
+ this.partialStderr = partialStderr;
2426
+ this.name = "RunCommandLineTimeoutError";
2427
+ }
2428
+ };
2411
2429
  var AgentServer = class {
2412
2430
  agent;
2413
2431
  providers;
2414
2432
  shellOptions;
2415
2433
  sessionOptions;
2416
- commandToolOptions;
2434
+ prepareSessionShell;
2417
2435
  bindings = /* @__PURE__ */ new Set();
2418
2436
  sessionOwnership = new SessionOwnershipRegistry();
2419
2437
  constructor(options) {
@@ -2421,7 +2439,7 @@ var AgentServer = class {
2421
2439
  this.providers = createProviderMap(options.providers);
2422
2440
  this.shellOptions = options.shell ?? {};
2423
2441
  this.sessionOptions = options.session ?? {};
2424
- this.commandToolOptions = options.commandTools ?? null;
2442
+ this.prepareSessionShell = options.prepareSessionShell ?? null;
2425
2443
  }
2426
2444
  client() {
2427
2445
  const transports = createInProcessTransportPair();
@@ -2435,7 +2453,7 @@ var AgentServer = class {
2435
2453
  providers: this.providers,
2436
2454
  shell: this.shellOptions,
2437
2455
  session: this.sessionOptions,
2438
- commandTools: this.commandToolOptions,
2456
+ prepareSessionShell: this.prepareSessionShell,
2439
2457
  sessions: this.sessionOwnership
2440
2458
  });
2441
2459
  this.bindings.add(binding);
@@ -2446,6 +2464,16 @@ var AgentServer = class {
2446
2464
  this.bindings.clear();
2447
2465
  await Promise.all(bindings.map((binding) => binding.close()));
2448
2466
  }
2467
+ /**
2468
+ * Runs one registered-command invocation to completion for a live session.
2469
+ * Transport-agnostic: callers (e.g. LocalHost command bridge) supply their
2470
+ * own IPC; AgentServer only knows how to exec against the open session shell.
2471
+ */
2472
+ async runCommandLine(agentSessionId, name, args, opts) {
2473
+ const binding = this.sessionOwnership.get(agentSessionId);
2474
+ if (!binding) throw new RunCommandLineSessionNotFoundError(agentSessionId);
2475
+ return binding.runCommandLine(name, args, opts);
2476
+ }
2449
2477
  };
2450
2478
  /**
2451
2479
  * Tracks which transport binding currently owns each client-provided session
@@ -2463,6 +2491,9 @@ var SessionOwnershipRegistry = class {
2463
2491
  release(sessionId, binding) {
2464
2492
  if (this.holders.get(sessionId) === binding) this.holders.delete(sessionId);
2465
2493
  }
2494
+ get(sessionId) {
2495
+ return this.holders.get(sessionId);
2496
+ }
2466
2497
  };
2467
2498
  var AgentTransportBindingImpl = class {
2468
2499
  transport;
@@ -2470,7 +2501,7 @@ var AgentTransportBindingImpl = class {
2470
2501
  providers;
2471
2502
  shellOptions;
2472
2503
  sessionOptions;
2473
- commandToolOptions;
2504
+ prepareSessionShell;
2474
2505
  sessions;
2475
2506
  session = null;
2476
2507
  currentAgent = null;
@@ -2478,6 +2509,7 @@ var AgentTransportBindingImpl = class {
2478
2509
  currentCwd = null;
2479
2510
  currentProviderId = null;
2480
2511
  currentSessionId = null;
2512
+ currentCommandNames = /* @__PURE__ */ new Set();
2481
2513
  unsubscribeSession = null;
2482
2514
  unsubscribeTransport = null;
2483
2515
  closed = false;
@@ -2487,7 +2519,7 @@ var AgentTransportBindingImpl = class {
2487
2519
  this.providers = options.providers;
2488
2520
  this.shellOptions = options.shell ?? {};
2489
2521
  this.sessionOptions = options.session ?? {};
2490
- this.commandToolOptions = options.commandTools;
2522
+ this.prepareSessionShell = options.prepareSessionShell;
2491
2523
  this.sessions = options.sessions;
2492
2524
  this.unsubscribeTransport = this.transport.onFrame((frame) => {
2493
2525
  this.handleFrame(frame);
@@ -2694,26 +2726,26 @@ var AgentTransportBindingImpl = class {
2694
2726
  const commands = agent.commands?.(harnessContext) ?? [];
2695
2727
  const commandRegistry = new CommandRegistry();
2696
2728
  for (const command of commands) commandRegistry.register(command);
2729
+ const commandNames = commandRegistry.list().map((command) => command.name);
2697
2730
  const environment = new BashEnvironment({
2698
- ...this.shellOptions,
2731
+ ...this.prepareSessionShell ? await this.prepareSessionShell({
2732
+ agentSessionId,
2733
+ host,
2734
+ commandNames,
2735
+ shell: this.shellOptions
2736
+ }) : this.shellOptions,
2699
2737
  host,
2700
2738
  commands: commandRegistry
2701
2739
  });
2702
2740
  let sessionRef = null;
2703
- const standardTools = createStandardAgentTools({
2741
+ const tools = createStandardAgentTools({
2704
2742
  environment,
2705
2743
  scheduleYield: (_ctx, durationMs) => {
2706
2744
  if (!sessionRef) throw new Error("AgentServer: session is not ready for yield scheduling");
2707
2745
  return sessionRef.scheduleYieldWakeup(durationMs);
2708
2746
  }
2709
2747
  });
2710
- const projectedTools = this.commandToolOptions ? createCommandProjectionTools(environment, this.commandToolOptions, new Set(standardTools.map((tool) => tool.name))) : [];
2711
- const tools = [...standardTools, ...projectedTools];
2712
- let commandsPrompt = commandRegistry.renderPrompt();
2713
- if (projectedTools.length > 0) {
2714
- const names = projectedTools.map((tool) => tool.name).join(", ");
2715
- commandsPrompt += `\n\nNative tool forms: ${names} — identical behavior, audit, and artifacts to the shell commands above.`;
2716
- }
2748
+ const commandsPrompt = commandRegistry.renderHelp();
2717
2749
  const runtime = {
2718
2750
  harnessName: agent.name,
2719
2751
  initialState: () => agent.initialState(),
@@ -2754,6 +2786,7 @@ var AgentTransportBindingImpl = class {
2754
2786
  this.currentEnvironment = environment;
2755
2787
  this.currentCwd = frame.cwd;
2756
2788
  this.currentProviderId = frame.provider.providerId;
2789
+ this.currentCommandNames = new Set(commandNames);
2757
2790
  if (restoring) session.updateModel(null, frame.provider.model);
2758
2791
  this.unsubscribeSession = this.session.subscribe((event) => this.handleSessionEvent(event));
2759
2792
  this.send({ type: "opened" });
@@ -2858,6 +2891,7 @@ var AgentTransportBindingImpl = class {
2858
2891
  this.currentEnvironment = null;
2859
2892
  this.currentCwd = null;
2860
2893
  this.currentProviderId = null;
2894
+ this.currentCommandNames = /* @__PURE__ */ new Set();
2861
2895
  if (this.currentSessionId) {
2862
2896
  this.sessions.release(this.currentSessionId, this);
2863
2897
  this.currentSessionId = null;
@@ -2891,6 +2925,50 @@ var AgentTransportBindingImpl = class {
2891
2925
  });
2892
2926
  this.sendShellWriteResult(frame.commandId, result);
2893
2927
  }
2928
+ /** Backs `AgentServer.runCommandLine` — see its doc comment for the contract. */
2929
+ async runCommandLine(name, args, opts) {
2930
+ const environment = this.currentEnvironment;
2931
+ const agentSessionId = this.currentSessionId;
2932
+ if (!environment || !agentSessionId) throw new Error("runCommandLine: session has no active shell environment");
2933
+ if (!this.currentCommandNames.has(name)) throw new RunCommandLineCommandNotRegisteredError(name);
2934
+ const words = [name, ...args].map(shellQuote).join(" ");
2935
+ let script = words;
2936
+ if (opts.stdin.length > 0) {
2937
+ const delimiter = heredocDelimiter(opts.stdin);
2938
+ script = `${words} <<'${delimiter}'\n${opts.stdin.endsWith("\n") ? opts.stdin : `${opts.stdin}\n`}${delimiter}`;
2939
+ }
2940
+ const result = await environment.exec({
2941
+ agentSessionId,
2942
+ script,
2943
+ timeoutMs: MAX_TIMEOUT_MS,
2944
+ signal: opts.signal,
2945
+ ephemeral: true,
2946
+ cwd: opts.cwd
2947
+ });
2948
+ try {
2949
+ if (result.status === "exited") {
2950
+ if (result.binaryStdout) {
2951
+ const truncationNote = result.binaryStdout.truncated ? `command bridge: binary stdout truncated at the output limit (${result.binaryStdout.data.length} of ${result.binaryStdout.totalBytes} bytes)\n` : "";
2952
+ return {
2953
+ exitCode: result.exitCode,
2954
+ stdout: bytesToBase64(result.binaryStdout.data),
2955
+ stdoutEncoding: "base64",
2956
+ stderr: `${result.stderr.delta}${truncationNote}`
2957
+ };
2958
+ }
2959
+ return {
2960
+ exitCode: result.exitCode,
2961
+ stdout: result.stdout.delta,
2962
+ stderr: result.stderr.delta
2963
+ };
2964
+ }
2965
+ if (result.status === "aborted") throw new Error(`runCommandLine: call for "${name}" was cancelled before it completed`);
2966
+ const aborted = await environment.abort({ commandId: result.commandId });
2967
+ throw new RunCommandLineTimeoutError(result.commandId, aborted.status === "aborted" ? aborted.stdout.delta : "", aborted.status === "aborted" ? aborted.stderr.delta : "");
2968
+ } finally {
2969
+ await environment.disposeShell(result.shellId).catch(() => {});
2970
+ }
2971
+ }
2894
2972
  sessionFor(command) {
2895
2973
  if (!this.session) {
2896
2974
  this.send({
@@ -3056,4 +3134,4 @@ function createProviderMap(providers) {
3056
3134
  return map;
3057
3135
  }
3058
3136
  //#endregion
3059
- export { AgentClient, AgentServer, AgentSession, DEFAULT_TURN_RETRY_POLICY, Transcript, applyTranscriptPatches, cloneBlocks, createCommandProjectionTools, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, finishShellToolResult, isRetryableCode, parseAgentJson, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, stringifyAgentJson, toShellToolResult };
3137
+ export { AgentClient, AgentServer, AgentSession, DEFAULT_TURN_RETRY_POLICY, RunCommandLineCommandNotRegisteredError, RunCommandLineSessionNotFoundError, RunCommandLineTimeoutError, Transcript, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, finishShellToolResult, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
@@ -1,6 +1,5 @@
1
- import { n as AgentServerTransport, t as AgentClientTransport } from "./transport-BkAbPvRc.mjs";
1
+ import { n as AgentServerTransport, t as AgentClientTransport } from "./transport-DVa_82EW.mjs";
2
2
  import { Readable, Writable } from "node:stream";
3
-
4
3
  //#region src/stdio-transport.d.ts
5
4
  declare function createStdioClientTransport(readable: Readable, writable: Writable): AgentClientTransport;
6
5
  declare function createStdioServerTransport(readable: Readable, writable: Writable): AgentServerTransport;
@@ -1,4 +1,4 @@
1
- import { n as stringifyAgentJson, t as parseAgentJson } from "./json-codec-BFLXlg3K.mjs";
1
+ import { parsePortableJson, stringifyPortableJson } from "@demicodes/utils";
2
2
  import { createInterface } from "node:readline";
3
3
  //#region src/stdio-transport.ts
4
4
  function createStdioClientTransport(readable, writable) {
@@ -19,13 +19,13 @@ var JsonLineTransport = class {
19
19
  this.readline = createInterface({ input: readable });
20
20
  this.readline.on("line", (line) => {
21
21
  if (this.closed || line.trim() === "") return;
22
- const frame = parseAgentJson(line);
22
+ const frame = parsePortableJson(line);
23
23
  for (const handler of this.handlers) handler(frame);
24
24
  });
25
25
  }
26
26
  send(frame) {
27
27
  if (this.closed) throw new Error("Agent transport is closed");
28
- this.writable.write(`${stringifyAgentJson(frame)}\n`);
28
+ this.writable.write(`${stringifyPortableJson(frame)}\n`);
29
29
  }
30
30
  onFrame(handler) {
31
31
  this.handlers.add(handler);
@@ -1,7 +1,6 @@
1
- import { BashAuditEvent, CommandSpec, Host, ShellCommandSnapshot } from "@demicodes/shell";
1
+ import { BashAuditEvent, Command, Host, ShellCommandSnapshot } from "@demicodes/shell";
2
2
  import { AgentProvider, InferenceItem, ProviderEvent, ProviderSelection, ToolDefinition } from "@demicodes/provider";
3
3
  import { Block, ModelSelection, QueuedMessage, SessionPhase, ToolResultContentBlock, Transcript, UserContentBlock } from "@demicodes/core";
4
-
5
4
  //#region src/retry-policy.d.ts
6
5
  /**
7
6
  * Retry policy for provider requests, applied by the agent runtime (turn loop
@@ -164,7 +163,7 @@ interface AgentHarness<State = unknown> {
164
163
  name: string;
165
164
  initialState(): State;
166
165
  host(ctx: AgentHarnessContext<State>): Host;
167
- commands?(ctx: AgentHarnessContext<State>): CommandSpec[];
166
+ commands?(ctx: AgentHarnessContext<State>): Command[];
168
167
  systemPrompt(ctx: AgentSystemPromptContext<State>): string;
169
168
  preamble?(ctx: AgentPromptContext<State>): string | null;
170
169
  resolveReferences?(ctx: AgentReferenceResolveContext<State>, content: UserContentBlock[]): Promise<UserContentBlock[]> | UserContentBlock[];
@@ -1,4 +1,4 @@
1
- import { n as stringifyAgentJson, t as parseAgentJson } from "./json-codec-BFLXlg3K.mjs";
1
+ import { parsePortableJson, stringifyPortableJson } from "@demicodes/utils";
2
2
  //#region src/patch.ts
3
3
  /**
4
4
  * Applies journal-produced transcript patches to a block list, returning a new
@@ -447,7 +447,7 @@ var WebSocketJsonTransport = class {
447
447
  socket;
448
448
  handlers = /* @__PURE__ */ new Set();
449
449
  onMessage = (event) => {
450
- const frame = parseAgentJson(typeof event.data === "string" ? event.data : String(event.data));
450
+ const frame = parsePortableJson(typeof event.data === "string" ? event.data : String(event.data));
451
451
  for (const handler of this.handlers) handler(frame);
452
452
  };
453
453
  constructor(socket) {
@@ -455,7 +455,7 @@ var WebSocketJsonTransport = class {
455
455
  this.socket.addEventListener("message", this.onMessage);
456
456
  }
457
457
  send(frame) {
458
- this.socket.send(stringifyAgentJson(frame));
458
+ this.socket.send(stringifyPortableJson(frame));
459
459
  }
460
460
  onFrame(handler) {
461
461
  this.handlers.add(handler);
@@ -1,7 +1,6 @@
1
- import { c as ConversationSummary, f as AbortResult, n as AgentServerTransport, s as ClientSessionEvent, t as AgentClientTransport } from "./transport-BkAbPvRc.mjs";
1
+ import { c as ConversationSummary, f as AbortResult, n as AgentServerTransport, s as ClientSessionEvent, t as AgentClientTransport } from "./transport-DVa_82EW.mjs";
2
2
  import { ProviderSelection } from "@demicodes/provider";
3
3
  import { Block, UserContentBlock } from "@demicodes/core";
4
-
5
4
  //#region src/client.d.ts
6
5
  type AgentClientListener = (event: ClientSessionEvent) => void;
7
6
  declare class AgentClient {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@demicodes/agent",
3
3
  "description": "Session runtime and transport-neutral client and server protocol for Demi.",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -22,10 +22,10 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@demicodes/core": "workspace:*",
26
- "@demicodes/provider": "workspace:*",
27
- "@demicodes/shell": "workspace:*",
28
- "@demicodes/utils": "workspace:*"
25
+ "@demicodes/core": "0.2.0",
26
+ "@demicodes/provider": "0.2.0",
27
+ "@demicodes/shell": "0.2.0",
28
+ "@demicodes/utils": "0.2.0"
29
29
  },
30
30
  "license": "Apache-2.0",
31
31
  "main": "./dist/index.mjs",
@@ -1,70 +0,0 @@
1
- //#region src/json-codec.ts
2
- const BINARY_MARKER = "__demiUint8Array";
3
- const BIGINT_MARKER = "__demiBigInt";
4
- const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5
- function stringifyAgentJson(value) {
6
- return JSON.stringify(value, (_key, nested) => {
7
- if (nested instanceof Uint8Array) return {
8
- [BINARY_MARKER]: true,
9
- base64: bytesToBase64(nested)
10
- };
11
- if (typeof nested === "bigint") return {
12
- [BIGINT_MARKER]: true,
13
- value: nested.toString()
14
- };
15
- return nested;
16
- });
17
- }
18
- function parseAgentJson(text) {
19
- return JSON.parse(text, (_key, nested) => {
20
- if (isEncodedUint8Array(nested)) return base64ToBytes(nested.base64);
21
- if (isEncodedBigInt(nested)) return BigInt(nested.value);
22
- return nested;
23
- });
24
- }
25
- function isEncodedUint8Array(value) {
26
- return value !== null && typeof value === "object" && !Array.isArray(value) && value[BINARY_MARKER] === true && typeof value.base64 === "string";
27
- }
28
- function isEncodedBigInt(value) {
29
- return value !== null && typeof value === "object" && !Array.isArray(value) && value[BIGINT_MARKER] === true && typeof value.value === "string";
30
- }
31
- function bytesToBase64(bytes) {
32
- let output = "";
33
- for (let index = 0; index < bytes.byteLength; index += 3) {
34
- const first = bytes[index];
35
- const second = bytes[index + 1];
36
- const third = bytes[index + 2];
37
- output += BASE64_ALPHABET[first >> 2];
38
- output += BASE64_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4];
39
- output += second === void 0 ? "=" : BASE64_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6];
40
- output += third === void 0 ? "=" : BASE64_ALPHABET[third & 63];
41
- }
42
- return output;
43
- }
44
- function base64ToBytes(base64) {
45
- const clean = base64.replace(/\s+/g, "");
46
- if (clean.length === 0) return /* @__PURE__ */ new Uint8Array();
47
- if (clean.length % 4 !== 0) throw new Error("Invalid base64 payload length");
48
- const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
49
- const bytes = new Uint8Array(clean.length / 4 * 3 - padding);
50
- let offset = 0;
51
- for (let index = 0; index < clean.length; index += 4) {
52
- const first = base64Value(clean[index]);
53
- const second = base64Value(clean[index + 1]);
54
- const third = clean[index + 2] === "=" ? 0 : base64Value(clean[index + 2]);
55
- const fourth = clean[index + 3] === "=" ? 0 : base64Value(clean[index + 3]);
56
- const triple = first << 18 | second << 12 | third << 6 | fourth;
57
- if (offset < bytes.byteLength) bytes[offset++] = triple >> 16 & 255;
58
- if (offset < bytes.byteLength) bytes[offset++] = triple >> 8 & 255;
59
- if (offset < bytes.byteLength) bytes[offset++] = triple & 255;
60
- }
61
- return bytes;
62
- }
63
- function base64Value(char) {
64
- if (!char || char === "=") throw new Error("Invalid base64 payload");
65
- const value = BASE64_ALPHABET.indexOf(char);
66
- if (value === -1) throw new Error("Invalid base64 payload");
67
- return value;
68
- }
69
- //#endregion
70
- export { stringifyAgentJson as n, parseAgentJson as t };