@livx.cc/agentx 0.99.40 → 0.99.42
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/README.md +1 -0
- package/dist/{Agent-D4-CLnxC.d.ts → Agent-DydgLoT-.d.ts} +5 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +156 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +177 -56
- package/dist/index.js.map +1 -1
- package/dist/{mcp-aONOXRNk.d.ts → mcp-D5eN0JyQ.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/models.d.ts +15 -1
- package/dist/models.js +7 -0
- package/dist/models.js.map +1 -1
- package/dist/{tools-BWzPQVy0.d.ts → tools-S-ArIR3o.d.ts} +14 -2
- package/dist/tools.shell.d.ts +14 -2
- package/dist/tools.shell.js +80 -2
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp.client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as McpToolSearchOptions, f as McpToolSpec } from './mcp-
|
|
2
|
-
import { A as AgentTool } from './tools-
|
|
1
|
+
import { e as McpToolSearchOptions, f as McpToolSpec } from './mcp-D5eN0JyQ.js';
|
|
2
|
+
import { A as AgentTool } from './tools-S-ArIR3o.js';
|
|
3
3
|
import '@livx.cc/wcli/core';
|
|
4
4
|
|
|
5
5
|
/** A JSON-RPC 2.0 transport: request (awaits a result), notify (fire-and-forget), close. */
|
package/dist/models.d.ts
CHANGED
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
declare const MODEL_ALIASES: Record<string, string>;
|
|
11
11
|
/** Resolve an alias to a concrete id. A provider prefix ("anthropic/") is preserved; unknown ids pass through. */
|
|
12
12
|
declare function resolveModelAlias(input: string): string;
|
|
13
|
+
/** The provider of a model id ("cursor/x" -> "cursor"). Provider-specific `providerOptions` are only
|
|
14
|
+
* portable within the same provider — leaking them across is a hard 400.
|
|
15
|
+
*
|
|
16
|
+
* A BARE id is the common case, not the exception: `resolveModelAlias('haiku')` yields
|
|
17
|
+
* `claude-haiku-4-5-20251001` with no prefix (ai.libx.js resolves it fuzzily downstream), so reading
|
|
18
|
+
* only the literal "/" made every aliased child look like a provider switch and silently dropped the
|
|
19
|
+
* parent's options. The fix belongs HERE — the alias table must keep emitting the ids ai.libx.js
|
|
20
|
+
* expects, and the comparison is provider identity, not string identity.
|
|
21
|
+
*
|
|
22
|
+
* A bare id we can't attribute is UNKNOWN, not a shared provider: it gets a per-id marker so two
|
|
23
|
+
* different unknown ids never compare equal (returning "" for both made them look like one provider
|
|
24
|
+
* and would have leaked one host's options onto the other's model). An unknown id still compares
|
|
25
|
+
* equal to ITSELF, so a child staying on the parent's exact model keeps the parent's options. */
|
|
26
|
+
declare function modelProvider(model: string): string;
|
|
13
27
|
/** Short, human-friendly name for a resolved model id (e.g. "anthropic/claude-opus-4-8" → "opus"). */
|
|
14
28
|
declare function modelShortLabel(model?: string): string;
|
|
15
29
|
interface ResolveModelSwitchOpts {
|
|
@@ -39,4 +53,4 @@ interface ModelSwitch {
|
|
|
39
53
|
*/
|
|
40
54
|
declare function resolveModelSwitch(opts: ResolveModelSwitchOpts): ModelSwitch;
|
|
41
55
|
|
|
42
|
-
export { MODEL_ALIASES, type ModelSwitch, type ResolveModelSwitchOpts, modelShortLabel, resolveModelAlias, resolveModelSwitch };
|
|
56
|
+
export { MODEL_ALIASES, type ModelSwitch, type ResolveModelSwitchOpts, modelProvider, modelShortLabel, resolveModelAlias, resolveModelSwitch };
|
package/dist/models.js
CHANGED
|
@@ -15,6 +15,12 @@ function resolveModelAlias(input) {
|
|
|
15
15
|
const rest = slash === -1 ? input : input.slice(slash + 1);
|
|
16
16
|
return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
|
|
17
17
|
}
|
|
18
|
+
function modelProvider(model) {
|
|
19
|
+
const i = model.indexOf("/");
|
|
20
|
+
if (i !== -1) return model.slice(0, i);
|
|
21
|
+
if (/^claude-/i.test(model)) return "anthropic";
|
|
22
|
+
return `?${model}`;
|
|
23
|
+
}
|
|
18
24
|
function modelShortLabel(model) {
|
|
19
25
|
if (!model) return "default";
|
|
20
26
|
const id = model.split("/").pop();
|
|
@@ -38,6 +44,7 @@ function resolveModelSwitch(opts) {
|
|
|
38
44
|
}
|
|
39
45
|
export {
|
|
40
46
|
MODEL_ALIASES,
|
|
47
|
+
modelProvider,
|
|
41
48
|
modelShortLabel,
|
|
42
49
|
resolveModelAlias,
|
|
43
50
|
resolveModelSwitch
|
package/dist/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/models.ts"],"sourcesContent":["/**\n * Model aliasing + mid-conversation switch resolution — pure, dependency-free.\n *\n * Harness-level so every agentx consumer (unclaw, para-chat, …) resolves `[opus]`-style\n * aliases, gates by an allow-list, and announces a switch the same way. Policy (which models\n * a caller may use) and session state (the prior model) stay with the consumer; this module\n * is the shared mechanism.\n */\n\n/** Canonical short aliases → concrete Anthropic model ids. */\nexport const MODEL_ALIASES: Record<string, string> = {\n fable: 'claude-fable-5',\n 'fable-5': 'claude-fable-5',\n opus: 'claude-opus-4-8',\n 'opus-4-8': 'claude-opus-4-8',\n 'opus-4-7': 'claude-opus-4-7',\n 'opus-4-6': 'claude-opus-4-6',\n sonnet: 'claude-sonnet-4-6',\n haiku: 'claude-haiku-4-5-20251001',\n};\n\n/** Resolve an alias to a concrete id. A provider prefix (\"anthropic/\") is preserved; unknown ids pass through. */\nexport function resolveModelAlias(input: string): string {\n const slash = input.indexOf('/');\n const prefix = slash === -1 ? '' : input.slice(0, slash + 1);\n const rest = slash === -1 ? input : input.slice(slash + 1);\n return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);\n}\n\n/** Short, human-friendly name for a resolved model id (e.g. \"anthropic/claude-opus-4-8\" → \"opus\"). */\nexport function modelShortLabel(model?: string): string {\n if (!model) return 'default';\n const id = model.split('/').pop()!;\n return id.replace(/^claude-/, '').replace(/-\\d.*$/, '');\n}\n\nexport interface ResolveModelSwitchOpts {\n /** Model requested for this turn (e.g. from an inline directive). Undefined = keep `current`. */\n requested?: string;\n /** The turn's default/current model when nothing is requested. */\n current: string;\n /** The session's prior resolved model — drives change detection + the notice. */\n prior?: string;\n /** If set, `requested` must match one of these (by short label) or it's denied and `current` is kept. */\n allowed?: string[];\n /** Announcement formatter. Default: `_[switching from x to y]_\\n\\n`. */\n format?: (from: string, to: string) => string;\n}\n\nexport interface ModelSwitch {\n /** The model to run this turn. */\n model: string;\n /** True when `requested` was rejected by `allowed` and `model` fell back to `current`. */\n denied: boolean;\n /** Inline notice to surface when the model actually changed vs `prior`, else undefined. */\n notice?: string;\n}\n\n/**\n * Resolve which model a turn runs, given an optional request, an allow-list, and the prior model.\n * Comparisons are by short label so provider-prefixed and dated ids unify\n * (\"anthropic/claude-opus-4-8\" ≡ \"claude-opus-4-8\" ≡ \"opus\").\n */\nexport function resolveModelSwitch(opts: ResolveModelSwitchOpts): ModelSwitch {\n const { requested, current, prior, allowed } = opts;\n if (!requested) return { model: current, denied: false };\n if (allowed && !allowed.some((a) => modelShortLabel(a) === modelShortLabel(requested))) {\n return { model: current, denied: true };\n }\n const fmt = opts.format ?? ((from: string, to: string) => `_[switching from ${from} to ${to}]_\\n\\n`);\n const changed = prior !== undefined && modelShortLabel(prior) !== modelShortLabel(requested);\n return {\n model: requested,\n denied: false,\n notice: changed ? fmt(modelShortLabel(prior!), modelShortLabel(requested)) : undefined,\n };\n}\n"],"mappings":";AAUO,IAAM,gBAAwC;AAAA,EACnD,OAAO;AAAA,EACP,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,OAAO;AACT;AAGO,SAAS,kBAAkB,OAAuB;AACvD,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,QAAM,SAAS,UAAU,KAAK,KAAK,MAAM,MAAM,GAAG,QAAQ,CAAC;AAC3D,QAAM,OAAO,UAAU,KAAK,QAAQ,MAAM,MAAM,QAAQ,CAAC;AACzD,SAAO,UAAU,cAAc,KAAK,YAAY,CAAC,KAAK;AACxD;AAGO,SAAS,gBAAgB,OAAwB;AACtD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,KAAK,MAAM,MAAM,GAAG,EAAE,IAAI;AAChC,SAAO,GAAG,QAAQ,YAAY,EAAE,EAAE,QAAQ,UAAU,EAAE;AACxD;AA6BO,SAAS,mBAAmB,MAA2C;AAC5E,QAAM,EAAE,WAAW,SAAS,OAAO,QAAQ,IAAI;AAC/C,MAAI,CAAC,UAAW,QAAO,EAAE,OAAO,SAAS,QAAQ,MAAM;AACvD,MAAI,WAAW,CAAC,QAAQ,KAAK,CAAC,MAAM,gBAAgB,CAAC,MAAM,gBAAgB,SAAS,CAAC,GAAG;AACtF,WAAO,EAAE,OAAO,SAAS,QAAQ,KAAK;AAAA,EACxC;AACA,QAAM,MAAM,KAAK,WAAW,CAAC,MAAc,OAAe,oBAAoB,IAAI,OAAO,EAAE;AAAA;AAAA;AAC3F,QAAM,UAAU,UAAU,UAAa,gBAAgB,KAAK,MAAM,gBAAgB,SAAS;AAC3F,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ,UAAU,IAAI,gBAAgB,KAAM,GAAG,gBAAgB,SAAS,CAAC,IAAI;AAAA,EAC/E;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/models.ts"],"sourcesContent":["/**\n * Model aliasing + mid-conversation switch resolution — pure, dependency-free.\n *\n * Harness-level so every agentx consumer (unclaw, para-chat, …) resolves `[opus]`-style\n * aliases, gates by an allow-list, and announces a switch the same way. Policy (which models\n * a caller may use) and session state (the prior model) stay with the consumer; this module\n * is the shared mechanism.\n */\n\n/** Canonical short aliases → concrete Anthropic model ids. */\nexport const MODEL_ALIASES: Record<string, string> = {\n fable: 'claude-fable-5',\n 'fable-5': 'claude-fable-5',\n opus: 'claude-opus-4-8',\n 'opus-4-8': 'claude-opus-4-8',\n 'opus-4-7': 'claude-opus-4-7',\n 'opus-4-6': 'claude-opus-4-6',\n sonnet: 'claude-sonnet-4-6',\n haiku: 'claude-haiku-4-5-20251001',\n};\n\n/** Resolve an alias to a concrete id. A provider prefix (\"anthropic/\") is preserved; unknown ids pass through. */\nexport function resolveModelAlias(input: string): string {\n const slash = input.indexOf('/');\n const prefix = slash === -1 ? '' : input.slice(0, slash + 1);\n const rest = slash === -1 ? input : input.slice(slash + 1);\n return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);\n}\n\n/** The provider of a model id (\"cursor/x\" -> \"cursor\"). Provider-specific `providerOptions` are only\n * portable within the same provider — leaking them across is a hard 400.\n *\n * A BARE id is the common case, not the exception: `resolveModelAlias('haiku')` yields\n * `claude-haiku-4-5-20251001` with no prefix (ai.libx.js resolves it fuzzily downstream), so reading\n * only the literal \"/\" made every aliased child look like a provider switch and silently dropped the\n * parent's options. The fix belongs HERE — the alias table must keep emitting the ids ai.libx.js\n * expects, and the comparison is provider identity, not string identity.\n *\n * A bare id we can't attribute is UNKNOWN, not a shared provider: it gets a per-id marker so two\n * different unknown ids never compare equal (returning \"\" for both made them look like one provider\n * and would have leaked one host's options onto the other's model). An unknown id still compares\n * equal to ITSELF, so a child staying on the parent's exact model keeps the parent's options. */\nexport function modelProvider(model: string): string {\n const i = model.indexOf('/');\n if (i !== -1) return model.slice(0, i);\n if (/^claude-/i.test(model)) return 'anthropic';\n return `?${model}`; // unknown: identity-scoped, never equal to a different unknown id\n}\n\n/** Short, human-friendly name for a resolved model id (e.g. \"anthropic/claude-opus-4-8\" → \"opus\"). */\nexport function modelShortLabel(model?: string): string {\n if (!model) return 'default';\n const id = model.split('/').pop()!;\n return id.replace(/^claude-/, '').replace(/-\\d.*$/, '');\n}\n\nexport interface ResolveModelSwitchOpts {\n /** Model requested for this turn (e.g. from an inline directive). Undefined = keep `current`. */\n requested?: string;\n /** The turn's default/current model when nothing is requested. */\n current: string;\n /** The session's prior resolved model — drives change detection + the notice. */\n prior?: string;\n /** If set, `requested` must match one of these (by short label) or it's denied and `current` is kept. */\n allowed?: string[];\n /** Announcement formatter. Default: `_[switching from x to y]_\\n\\n`. */\n format?: (from: string, to: string) => string;\n}\n\nexport interface ModelSwitch {\n /** The model to run this turn. */\n model: string;\n /** True when `requested` was rejected by `allowed` and `model` fell back to `current`. */\n denied: boolean;\n /** Inline notice to surface when the model actually changed vs `prior`, else undefined. */\n notice?: string;\n}\n\n/**\n * Resolve which model a turn runs, given an optional request, an allow-list, and the prior model.\n * Comparisons are by short label so provider-prefixed and dated ids unify\n * (\"anthropic/claude-opus-4-8\" ≡ \"claude-opus-4-8\" ≡ \"opus\").\n */\nexport function resolveModelSwitch(opts: ResolveModelSwitchOpts): ModelSwitch {\n const { requested, current, prior, allowed } = opts;\n if (!requested) return { model: current, denied: false };\n if (allowed && !allowed.some((a) => modelShortLabel(a) === modelShortLabel(requested))) {\n return { model: current, denied: true };\n }\n const fmt = opts.format ?? ((from: string, to: string) => `_[switching from ${from} to ${to}]_\\n\\n`);\n const changed = prior !== undefined && modelShortLabel(prior) !== modelShortLabel(requested);\n return {\n model: requested,\n denied: false,\n notice: changed ? fmt(modelShortLabel(prior!), modelShortLabel(requested)) : undefined,\n };\n}\n"],"mappings":";AAUO,IAAM,gBAAwC;AAAA,EACnD,OAAO;AAAA,EACP,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,OAAO;AACT;AAGO,SAAS,kBAAkB,OAAuB;AACvD,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,QAAM,SAAS,UAAU,KAAK,KAAK,MAAM,MAAM,GAAG,QAAQ,CAAC;AAC3D,QAAM,OAAO,UAAU,KAAK,QAAQ,MAAM,MAAM,QAAQ,CAAC;AACzD,SAAO,UAAU,cAAc,KAAK,YAAY,CAAC,KAAK;AACxD;AAeO,SAAS,cAAc,OAAuB;AACnD,QAAM,IAAI,MAAM,QAAQ,GAAG;AAC3B,MAAI,MAAM,GAAI,QAAO,MAAM,MAAM,GAAG,CAAC;AACrC,MAAI,YAAY,KAAK,KAAK,EAAG,QAAO;AACpC,SAAO,IAAI,KAAK;AAClB;AAGO,SAAS,gBAAgB,OAAwB;AACtD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,KAAK,MAAM,MAAM,GAAG,EAAE,IAAI;AAChC,SAAO,GAAG,QAAQ,YAAY,EAAE,EAAE,QAAQ,UAAU,EAAE;AACxD;AA6BO,SAAS,mBAAmB,MAA2C;AAC5E,QAAM,EAAE,WAAW,SAAS,OAAO,QAAQ,IAAI;AAC/C,MAAI,CAAC,UAAW,QAAO,EAAE,OAAO,SAAS,QAAQ,MAAM;AACvD,MAAI,WAAW,CAAC,QAAQ,KAAK,CAAC,MAAM,gBAAgB,CAAC,MAAM,gBAAgB,SAAS,CAAC,GAAG;AACtF,WAAO,EAAE,OAAO,SAAS,QAAQ,KAAK;AAAA,EACxC;AACA,QAAM,MAAM,KAAK,WAAW,CAAC,MAAc,OAAe,oBAAoB,IAAI,OAAO,EAAE;AAAA;AAAA;AAC3F,QAAM,UAAU,UAAU,UAAa,gBAAgB,KAAK,MAAM,gBAAgB,SAAS;AAC3F,SAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ,UAAU,IAAI,gBAAgB,KAAM,GAAG,gBAAgB,SAAS,CAAC,IAAI;AAAA,EAC/E;AACF;","names":[]}
|
|
@@ -407,6 +407,15 @@ interface AgentTool {
|
|
|
407
407
|
data: string;
|
|
408
408
|
}[];
|
|
409
409
|
}>;
|
|
410
|
+
/** Optional: return a copy of this tool bound to a different real-disk working directory.
|
|
411
|
+
* Implemented by tools that escape the VFS (the real `Shell`), which are cwd-bound at construction
|
|
412
|
+
* and would otherwise keep pointing at the PARENT's tree when the tool instance is inherited by an
|
|
413
|
+
* isolated child agent. `subagent.ts` calls this for a worktree child; tools without it pass through.
|
|
414
|
+
* Return `undefined` for a capability that CANNOT follow the child (the background-job companions,
|
|
415
|
+
* bound to the parent's job registry) — the tool is then dropped from the child's toolbelt.
|
|
416
|
+
* An implementation must carry over any post-construction mutation the host made to the instance
|
|
417
|
+
* (a renamed `name`/`description`) — rebuilding from the factory's options alone loses it. */
|
|
418
|
+
withCwd?(cwd: string): AgentTool | undefined;
|
|
410
419
|
}
|
|
411
420
|
/** Build a tool context bound to a filesystem backend (Mem / Disk / …) and an optional host. */
|
|
412
421
|
declare function makeContext(fs: IFilesystem, host?: HostBridge): ToolContext;
|
|
@@ -427,7 +436,10 @@ declare function defaultTools(): AgentTool[];
|
|
|
427
436
|
* surface picks from this registry; embedders can build a custom tool set by name.
|
|
428
437
|
*/
|
|
429
438
|
declare function toolRegistry(): Record<string, AgentTool>;
|
|
430
|
-
/** Resolve a list of tool names against
|
|
431
|
-
|
|
439
|
+
/** Resolve a list of tool names against `available` (host-supplied tools, e.g. a parent agent's
|
|
440
|
+
* toolbelt or mounted MCP tools) first, then the built-in registry. Unknown names throw.
|
|
441
|
+
* Without `available` an allowlist could only ever name a built-in — so a def could not scope a
|
|
442
|
+
* child to a tool its parent injected (`Bash`, `ToolSearch`, …). */
|
|
443
|
+
declare function toolsByName(names: string[], available?: AgentTool[]): AgentTool[];
|
|
432
444
|
|
|
433
445
|
export { type AgentTool as A, makeJobTools as B, type ChatLike as C, messageHasImage as D, messageHasImageRef as E, messageHasInlineImage as F, mintImageRef as G, type HostBridge as H, IMAGE_ELIDE_STUB as I, parseImageRef as J, readTool as K, remintImageRefs as L, type Message as M, sendBytes as N, sendBytesPer as O, toWireTools as P, todoWriteTool as Q, type Role as R, SandboxJobRegistry as S, type TodoItem as T, type UserQuestion as U, toolImageRef as V, toolRegistry as W, toolsByName as X, type ChatOptions as a, type ChatResponse as b, type ContentPart as c, type HostEvent as d, IMAGE_REF_SCHEME as e, type ImageRef as f, type MessageContent as g, type StreamChunk as h, type Tool as i, type ToolCall as j, type ToolContext as k, bashTool as l, clearImageRefs as m, contentBytes as n, contentText as o, defaultTools as p, editTool as q, elideImages as r, elideInlineImages as s, elideStaleImages as t, exitSessionTool as u, expandImagesForSend as v, imagePart as w, imageRefPart as x, imageRefResult as y, makeContext as z };
|
package/dist/tools.shell.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AgentTool } from './tools-
|
|
1
|
+
import { A as AgentTool } from './tools-S-ArIR3o.js';
|
|
2
2
|
import '@livx.cc/wcli/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -141,12 +141,24 @@ declare class ShellJobRegistry {
|
|
|
141
141
|
command: string;
|
|
142
142
|
status: JobStatus;
|
|
143
143
|
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Take over an ALREADY-RUNNING child as a background job. A foreground command that outruns its
|
|
146
|
+
* timeout is not necessarily a hung command — killing it throws away work that was nearly done and,
|
|
147
|
+
* worse, a launcher that spawned its own detached worker leaves that worker running with nothing
|
|
148
|
+
* tracking it. Adopting hands the model a handle instead: the command keeps going, its completion
|
|
149
|
+
* is reported like any other job, and `seed` carries the output produced before the handoff.
|
|
150
|
+
*/
|
|
151
|
+
adopt(command: string, proc: SpawnedProcess, seed?: string): string;
|
|
144
152
|
kill(id: string): boolean;
|
|
145
153
|
killAll(): void;
|
|
146
154
|
}
|
|
147
155
|
/** Build an opt-in real-shell tool bound to `options.cwd`. */
|
|
148
156
|
declare function makeRealShellTool(options: RealShellOptions): AgentTool;
|
|
149
|
-
/** Build the background-job companion tools (ShellOutput / ShellStatus / ShellKill) over a registry.
|
|
157
|
+
/** Build the background-job companion tools (ShellOutput / ShellStatus / ShellKill) over a registry.
|
|
158
|
+
* Each closes over THIS registry, i.e. this agent's jobs in this agent's cwd, so none of them can be
|
|
159
|
+
* rebound to an isolated child's root: `withCwd` returns `undefined` and they are dropped from the
|
|
160
|
+
* child's toolbelt. That keeps the capability coherent — the child's `Shell` cannot START a background
|
|
161
|
+
* job (its registry is dropped), so it must not be able to inspect or SIGTERM the parent's either. */
|
|
150
162
|
declare function makeShellJobTools(registry: ShellJobRegistry): AgentTool[];
|
|
151
163
|
|
|
152
164
|
export { type JobExitHandler, type JobExitNotice, type JobStatus, type RealShellOptions, type ShellJobConfig, ShellJobRegistry, type SpawnFn, type SpawnedProcess, formatJobExit, makeRealShellTool, makeShellJobTools };
|
package/dist/tools.shell.js
CHANGED
|
@@ -650,6 +650,41 @@ var ShellJobRegistry = class {
|
|
|
650
650
|
list() {
|
|
651
651
|
return [...this.jobs].map(([id, j]) => ({ id, command: j.command, status: j.status }));
|
|
652
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Take over an ALREADY-RUNNING child as a background job. A foreground command that outruns its
|
|
655
|
+
* timeout is not necessarily a hung command — killing it throws away work that was nearly done and,
|
|
656
|
+
* worse, a launcher that spawned its own detached worker leaves that worker running with nothing
|
|
657
|
+
* tracking it. Adopting hands the model a handle instead: the command keeps going, its completion
|
|
658
|
+
* is reported like any other job, and `seed` carries the output produced before the handoff.
|
|
659
|
+
*/
|
|
660
|
+
adopt(command, proc, seed = "") {
|
|
661
|
+
const id = `job-${++this.seq}`;
|
|
662
|
+
const max = this.cfg.maxBuffer ?? 256 * 1024;
|
|
663
|
+
const job = { command, buf: seed.slice(-max), status: "running", proc };
|
|
664
|
+
const append = (chunk) => {
|
|
665
|
+
const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
|
|
666
|
+
job.buf = (job.buf + s).slice(-max);
|
|
667
|
+
};
|
|
668
|
+
proc.stdout?.on("data", append);
|
|
669
|
+
proc.stderr?.on("data", append);
|
|
670
|
+
proc.on("error", (err) => {
|
|
671
|
+
if (job.status === "running") {
|
|
672
|
+
job.status = "error";
|
|
673
|
+
append(`
|
|
674
|
+
[error] ${err?.message ?? err}`);
|
|
675
|
+
this.notifyExit(id, job);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
proc.on("close", (code) => {
|
|
679
|
+
if (job.status === "running") {
|
|
680
|
+
job.status = "exited";
|
|
681
|
+
job.exitCode = code ?? void 0;
|
|
682
|
+
this.notifyExit(id, job);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
this.jobs.set(id, job);
|
|
686
|
+
return id;
|
|
687
|
+
}
|
|
653
688
|
kill(id) {
|
|
654
689
|
const j = this.jobs.get(id);
|
|
655
690
|
if (!j) return false;
|
|
@@ -679,8 +714,26 @@ function makeRealShellTool(options) {
|
|
|
679
714
|
};
|
|
680
715
|
return {
|
|
681
716
|
name: "Shell",
|
|
717
|
+
// Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
|
|
718
|
+
// `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
|
|
719
|
+
// shraga-ee, to the parent's session), so a background job started from the child would run
|
|
720
|
+
// outside the child's isolation and report into the parent. Background stays a parent capability
|
|
721
|
+
// (`makeShellJobTools`' companions drop out of an isolated child entirely — see their `withCwd`).
|
|
722
|
+
// Only `run` is rebuilt: everything else is carried over from THIS instance, because a host may
|
|
723
|
+
// have mutated the tool AFTER construction (shraga-ee renames `Shell` -> `Bash` and its system
|
|
724
|
+
// prompt says `Bash` everywhere). Rebuilding from `options` alone silently dropped that rename,
|
|
725
|
+
// so a worktree child advertised `Shell` while being told to call `Bash` — an unknown-tool error
|
|
726
|
+
// on its first call, and a hard failure for an agentType def whose allowlist names `Bash`.
|
|
727
|
+
// `description` is the ONE thing taken from the rebound tool instead: it is derived from the
|
|
728
|
+
// options and states whether `background:true` works — the rebind drops the registry, so carrying
|
|
729
|
+
// the parent's text over would advertise a background capability the child does not have. (The
|
|
730
|
+
// spread also collapses the getter below to a plain value, which is why this override is explicit.)
|
|
731
|
+
withCwd(cwd) {
|
|
732
|
+
const rebound = makeRealShellTool({ ...options, cwd, registry: void 0 });
|
|
733
|
+
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
734
|
+
},
|
|
682
735
|
get description() {
|
|
683
|
-
return
|
|
736
|
+
return "Run a shell command via /bin/sh in the working directory. Executes any installed binary \u2014 ls, cat, grep, git, bun, node, curl, scripts, etc. Returns combined stdout+stderr; non-zero exits are prefixed `[exit N]`. Runs non-interactively with no terminal (stdin is /dev/null): commands that prompt for input fail fast rather than hang \u2014 for privileged actions use a non-interactive flag (e.g. `sudo -n`), or ask the user to run the command themselves. " + (options.registry?.adopt ? `A command still running after ${defaultTimeoutMs}ms is NOT killed \u2014 it is handed to a background job and you get its id, so you can keep checking on it (\`[still running]\`). Pass \`timeoutMs\` (max ${maxTimeoutMs}) to wait longer in the foreground, and always bound network commands yourself (e.g. \`curl -m 10\`). ` : `Each command is killed after ${defaultTimeoutMs}ms (result \`[exit 124]\` with whatever output it produced) \u2014 pass \`timeoutMs\` (max ${maxTimeoutMs}) for a legitimately slower command, and always bound network commands yourself (e.g. \`curl -m 10\`). `) + backgroundDoc();
|
|
684
737
|
},
|
|
685
738
|
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
686
739
|
maxDurationMs: maxTimeoutMs,
|
|
@@ -719,9 +772,12 @@ function makeRealShellTool(options) {
|
|
|
719
772
|
else ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
720
773
|
}
|
|
721
774
|
let timedOut = false;
|
|
722
|
-
|
|
775
|
+
let onDeadline = () => {
|
|
723
776
|
timedOut = true;
|
|
724
777
|
ctl.abort();
|
|
778
|
+
};
|
|
779
|
+
const timer = setTimeout(() => {
|
|
780
|
+
onDeadline();
|
|
725
781
|
}, timeoutMs);
|
|
726
782
|
let pend = "";
|
|
727
783
|
let flushTimer = null;
|
|
@@ -752,6 +808,18 @@ function makeRealShellTool(options) {
|
|
|
752
808
|
}
|
|
753
809
|
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
754
810
|
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
811
|
+
const registry = options.registry;
|
|
812
|
+
if (registry?.adopt) {
|
|
813
|
+
onDeadline = () => {
|
|
814
|
+
timedOut = true;
|
|
815
|
+
if (settled) return;
|
|
816
|
+
flushEmit(ctx);
|
|
817
|
+
proc.stdout?.off?.("data", collect);
|
|
818
|
+
proc.stderr?.off?.("data", collect);
|
|
819
|
+
const id = registry.adopt(cmd, proc, out);
|
|
820
|
+
finish(handoffFor(timeoutMs, id, clean(out), !!registry.notifiesOnExit));
|
|
821
|
+
};
|
|
822
|
+
}
|
|
755
823
|
const collect = (chunk) => {
|
|
756
824
|
const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
|
|
757
825
|
out += s;
|
|
@@ -784,6 +852,12 @@ function makeRealShellTool(options) {
|
|
|
784
852
|
}
|
|
785
853
|
};
|
|
786
854
|
}
|
|
855
|
+
function handoffFor(timeoutMs, id, body, notifies) {
|
|
856
|
+
const head = `[still running] exceeded ${timeoutMs}ms, so it was handed to background job ${id} \u2014 NOT killed, it is still going. Check on it with ShellOutput({id:"${id}"}) / ShellStatus({id:"${id}"}), stop it with ShellKill({id:"${id}"}). ` + (notifies ? "Its completion will be reported to you, so you may continue with other work meanwhile." : "Nothing will tell you when it finishes \u2014 poll it yourself before you rely on its result.");
|
|
857
|
+
return body ? `${head}
|
|
858
|
+
Output so far:
|
|
859
|
+
${body}` : head;
|
|
860
|
+
}
|
|
787
861
|
function reasonFor(timedOut, timeoutMs, body) {
|
|
788
862
|
const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed). Re-running this command unchanged will time out again. Either re-run it with background:true (returns a job id immediately; poll with ShellOutput/ShellStatus, stop with ShellKill) or narrow it so it can finish \u2014 bound it, scope it, or ask for less.` : "[exit 130] cancelled (killed)";
|
|
789
863
|
return body ? `${head}
|
|
@@ -793,8 +867,10 @@ ${body}` : head;
|
|
|
793
867
|
var NO_JOB = (id) => `Error: no background job '${id}'. Use ShellStatus with no id to list jobs, or start one with Shell({background:true}).`;
|
|
794
868
|
function makeShellJobTools(registry) {
|
|
795
869
|
const idParam = { type: "object", properties: { id: { type: "string", description: "the job id from Shell({background:true})" } } };
|
|
870
|
+
const dropOnRebind = { withCwd: () => void 0 };
|
|
796
871
|
return [
|
|
797
872
|
{
|
|
873
|
+
...dropOnRebind,
|
|
798
874
|
name: "ShellOutput",
|
|
799
875
|
description: "Read the accumulated output (tail) of a background Shell job by id.",
|
|
800
876
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
@@ -807,6 +883,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
807
883
|
}
|
|
808
884
|
},
|
|
809
885
|
{
|
|
886
|
+
...dropOnRebind,
|
|
810
887
|
name: "ShellStatus",
|
|
811
888
|
description: "Status of a background Shell job (running/exited/killed + exit code). Omit `id` to list all jobs.",
|
|
812
889
|
parameters: idParam,
|
|
@@ -820,6 +897,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
820
897
|
}
|
|
821
898
|
},
|
|
822
899
|
{
|
|
900
|
+
...dropOnRebind,
|
|
823
901
|
name: "ShellKill",
|
|
824
902
|
description: "Stop a running background Shell job by id (SIGTERM).",
|
|
825
903
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|