@code-yeongyu/senpi 2026.9.15-2 → 2026.9.16
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/CHANGELOG.md +21 -0
- package/dist/core/extensions/kernel-tools-context.d.ts +13 -0
- package/dist/core/extensions/kernel-tools-context.d.ts.map +1 -0
- package/dist/core/extensions/kernel-tools-context.js +3 -0
- package/dist/core/extensions/kernel-tools-context.js.map +1 -0
- package/dist/core/extensions/runner.d.ts +19 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +103 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +21 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/settings-manager.d.ts +23 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +41 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +4 -3
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +3 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +16 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/docs/extensions.md +3 -0
- package/docs/settings.md +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +21 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +24 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/http-server.ts +7 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/kernel-tools-protocol.ts +85 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +6 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +30 -11
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +11 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +46 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +29 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.d.ts +13 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.js +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.d.ts +10 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.js +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.js +22 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.ts +26 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-host.ts +134 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-naming.js +28 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.d.ts +5 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.js +255 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-pump.js +105 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.d.ts +27 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.js +127 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-schema.js +69 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-types.ts +42 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +26 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +20 -8
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +6 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +9 -7
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/workpool.js +37 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/kernel-tools-unavailable.ts +5 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +13 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +57 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +13 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/workpool.rb +38 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +13 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +4 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +8 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +49 -30
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +51 -12
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +3 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +18 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts +4 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js +268 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts +18 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js +69 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts +27 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js +118 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts +21 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js +24 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts +12 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js +92 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts +47 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js +8 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts +52 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js +155 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +10 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { kernelToolError } from "./kernel-tools-errors.js";
|
|
2
|
+
|
|
3
|
+
export function defaultInputSchema(params) {
|
|
4
|
+
return {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: Object.fromEntries(params.map((name) => [name, {}])),
|
|
7
|
+
required: [...params],
|
|
8
|
+
additionalProperties: false,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveToolMetadata(metadata, params) {
|
|
13
|
+
if (metadata === undefined) return { description: "", input_schema: defaultInputSchema(params) };
|
|
14
|
+
if (!isPlainJsonObject(metadata)) {
|
|
15
|
+
throw kernelToolError("invalid_tool_definition", "tool() metadata must be plain JSON");
|
|
16
|
+
}
|
|
17
|
+
const description = metadata.description === undefined ? "" : metadata.description;
|
|
18
|
+
if (typeof description !== "string") {
|
|
19
|
+
throw kernelToolError("invalid_tool_definition", "tool() description must be a string");
|
|
20
|
+
}
|
|
21
|
+
if (metadata.schema === undefined) return { description, input_schema: defaultInputSchema(params) };
|
|
22
|
+
const schema = metadata.schema;
|
|
23
|
+
if (!isPlainJsonObject(schema) || schema.type !== "object" || !isPlainJsonObject(schema.properties)) {
|
|
24
|
+
throw kernelToolError("invalid_tool_definition", "tool() metadata schema must be a JSON object schema");
|
|
25
|
+
}
|
|
26
|
+
const propertyKeys = Object.keys(schema.properties);
|
|
27
|
+
if (propertyKeys.length !== params.length || params.some((name) => !Object.hasOwn(schema.properties, name))) {
|
|
28
|
+
throw kernelToolError("invalid_tool_definition", "tool() metadata schema properties must match parameters");
|
|
29
|
+
}
|
|
30
|
+
return { description, input_schema: schema };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function validateInvokeArgs(schema, args) {
|
|
34
|
+
if (!isPlainJsonObject(args)) throw kernelToolError("invalid_tool_definition", "kernel tool args must be a JSON object");
|
|
35
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
36
|
+
for (const key of required) {
|
|
37
|
+
if (!Object.hasOwn(args, key)) {
|
|
38
|
+
throw kernelToolError("invalid_tool_definition", `missing kernel tool argument: ${key}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (schema.additionalProperties === false) {
|
|
42
|
+
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
43
|
+
for (const key of Object.keys(args)) {
|
|
44
|
+
if (!allowed.has(key)) {
|
|
45
|
+
throw kernelToolError("invalid_tool_definition", `unexpected kernel tool argument: ${key}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isPlainJsonObject(value) {
|
|
52
|
+
return isJsonValue(value) && value !== null && typeof value === "object" && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isJsonValue(value) {
|
|
56
|
+
if (value === null) return true;
|
|
57
|
+
const type = typeof value;
|
|
58
|
+
if (type === "string" || type === "boolean") return true;
|
|
59
|
+
if (type === "number") return Number.isFinite(value);
|
|
60
|
+
if (type !== "object") return false;
|
|
61
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
62
|
+
const proto = Object.getPrototypeOf(value);
|
|
63
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
64
|
+
return Object.values(value).every(isJsonValue);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function orderedArgs(params, args) {
|
|
68
|
+
return params.map((name) => args[name]);
|
|
69
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { KernelToolErrorCode } from "./kernel-tools-errors.ts";
|
|
2
|
+
|
|
3
|
+
export type { KernelToolErrorCode };
|
|
4
|
+
|
|
5
|
+
export type KernelToolDescriptor = {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly input_schema: unknown;
|
|
9
|
+
readonly language: "js";
|
|
10
|
+
readonly kernel_generation: number;
|
|
11
|
+
readonly definition_revision: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type KernelToolsInvokeRequest = {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly kernel_generation: number;
|
|
17
|
+
readonly definition_revision: number;
|
|
18
|
+
readonly args: unknown;
|
|
19
|
+
readonly call_id: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type KernelToolsDescribeEntry =
|
|
23
|
+
| { readonly name: string; readonly ok: true; readonly descriptor: KernelToolDescriptor }
|
|
24
|
+
| {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly ok: false;
|
|
27
|
+
readonly error: { readonly code: KernelToolErrorCode; readonly message: string };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type KernelToolsDescribeResult = {
|
|
31
|
+
readonly results: readonly KernelToolsDescribeEntry[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type KernelToolsCapability = {
|
|
35
|
+
describe(names: readonly string[]): Promise<KernelToolsDescribeResult>;
|
|
36
|
+
invoke(request: KernelToolsInvokeRequest, signal?: AbortSignal): Promise<unknown>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const KERNEL_TOOLS_UNSUPPORTED = {
|
|
40
|
+
code: "tools_unavailable" as const,
|
|
41
|
+
message: "Kernel tools require a live JavaScript worker context",
|
|
42
|
+
};
|
|
@@ -7,9 +7,11 @@ export const JAVASCRIPT_KERNEL_PRELUDE = [
|
|
|
7
7
|
"read(path, options?): read UTF-8 text; plain paths use cwd and local:// uses the session local root.",
|
|
8
8
|
"write(path, content): write UTF-8 or binary data and return the resolved path.",
|
|
9
9
|
"tool.<name>(args): request a host tool call through the bridge; resolves to { text, images?, details?, hasError? }.",
|
|
10
|
+
"tool(fn, metadata?): register a named JavaScript function as a fenced kernel tool for in-process children.",
|
|
10
11
|
"completion(prompt, options?): request a host completion bridge call.",
|
|
11
12
|
"output(...ids, options?): retrieve task output through the reserved output bridge.",
|
|
12
13
|
"agent(prompt, options?): delegate work through the reserved agent bridge.",
|
|
14
|
+
"workpool(agent, name, options?): await a host-owned pool adapter with pool_id, push, close, inspect, and cancel; options only accept mode.",
|
|
13
15
|
"parallel(thunks): run async thunks through the configured bounded pool; preserves order and rethrows the lowest-index error after all settle.",
|
|
14
16
|
"pipeline(items, ...stages): map items through staged async transforms with a barrier between stages.",
|
|
15
17
|
].join("\n");
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { kernelToolCallContext } from "./kernel-tools-context.js";
|
|
2
|
+
import { kernelToolError } from "./kernel-tools-errors.js";
|
|
3
|
+
import { createKernelToolPump } from "./kernel-tools-pump.js";
|
|
1
4
|
import { JsWorkerRuntime } from "./worker-runtime.js";
|
|
2
5
|
|
|
3
6
|
// Mirrors INTERRUPT_ACK_OP and CHILD_LIFECYCLE_OP in src/bridge/reserved.ts (this worker file cannot import TypeScript).
|
|
@@ -21,6 +24,12 @@ export function createWorkerCore(transport, options) {
|
|
|
21
24
|
let runtime = null;
|
|
22
25
|
let activeCell = null;
|
|
23
26
|
const pendingTools = new Map();
|
|
27
|
+
const nestedInvokes = new Map();
|
|
28
|
+
const kernelTools = createKernelToolPump({
|
|
29
|
+
getRuntime: () => runtime,
|
|
30
|
+
emit: (message) => transport.send(message),
|
|
31
|
+
nestedInvokes,
|
|
32
|
+
});
|
|
24
33
|
|
|
25
34
|
function emit(message) {
|
|
26
35
|
transport.send(message);
|
|
@@ -47,9 +56,12 @@ export function createWorkerCore(transport, options) {
|
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
async function callTool(toolName, args) {
|
|
50
|
-
|
|
59
|
+
const nested = kernelToolCallContext.getStore();
|
|
60
|
+
if (!nested && activeCell?.interruption) throw activeCell.interruption;
|
|
61
|
+
if (nested?.signal.aborted) throw nested.signal.reason;
|
|
62
|
+
const bag = nested?.pendingTools ?? pendingTools;
|
|
51
63
|
const callId = `js-${crypto.randomUUID()}`;
|
|
52
|
-
const promise = new Promise((resolve, reject) =>
|
|
64
|
+
const promise = new Promise((resolve, reject) => bag.set(callId, { resolve, reject }));
|
|
53
65
|
emit({ type: "tool-call", callId, toolName, args });
|
|
54
66
|
return await promise;
|
|
55
67
|
}
|
|
@@ -63,10 +75,16 @@ export function createWorkerCore(transport, options) {
|
|
|
63
75
|
pendingTools.delete(callId);
|
|
64
76
|
pending.reject(interruption);
|
|
65
77
|
}
|
|
78
|
+
kernelTools.abortAll(kernelToolError("kernel_tool_stale", interruption.message));
|
|
66
79
|
runtime.interrupt();
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
function onMessage(message) {
|
|
83
|
+
if (kernelTools.handle(message)) return;
|
|
84
|
+
if (message.type === "kernel-tools-names") {
|
|
85
|
+
runtime?.kernelTools.setCollisionNames(message.hostToolNames ?? [], message.foreignLanguageNames ?? []);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
70
88
|
if (message.type === "init") {
|
|
71
89
|
applySessionEnvironment(message.sessionEnv);
|
|
72
90
|
runtime = new JsWorkerRuntime({
|
|
@@ -74,6 +92,9 @@ export function createWorkerCore(transport, options) {
|
|
|
74
92
|
parallelPoolWidth: options.parallelPoolWidth,
|
|
75
93
|
localRoots: message.connection.localRoots,
|
|
76
94
|
artifactsDir: message.connection.artifactsDir,
|
|
95
|
+
kernelGeneration: message.kernelGeneration ?? 1,
|
|
96
|
+
hostToolNames: message.hostToolNames ?? [],
|
|
97
|
+
foreignLanguageNames: message.foreignLanguageNames ?? [],
|
|
77
98
|
onChildEvent: (event) => emit({ type: "status", event: { op: CHILD_LIFECYCLE_OP, ...event } }),
|
|
78
99
|
});
|
|
79
100
|
emit({ type: "ready" });
|
|
@@ -84,6 +105,7 @@ export function createWorkerCore(transport, options) {
|
|
|
84
105
|
return;
|
|
85
106
|
}
|
|
86
107
|
if (message.type === "tool-reply") {
|
|
108
|
+
if (kernelTools.settleToolReply(message)) return;
|
|
87
109
|
const pending = pendingTools.get(message.callId);
|
|
88
110
|
if (!pending) return;
|
|
89
111
|
pendingTools.delete(message.callId);
|
|
@@ -144,7 +166,7 @@ function cellInterruptedError(reason) {
|
|
|
144
166
|
|
|
145
167
|
function bridgeError(error) {
|
|
146
168
|
if (error instanceof Error) {
|
|
147
|
-
return { name: error.name, message: error.message, stack: error.stack };
|
|
169
|
+
return { name: error.name, message: error.message, stack: error.stack, ...(typeof error.code === "string" ? { code: error.code } : {}) };
|
|
148
170
|
}
|
|
149
171
|
return { message: String(error) };
|
|
150
172
|
}
|
|
@@ -153,5 +175,6 @@ function errorFromBridge(error) {
|
|
|
153
175
|
const result = new Error(error.message);
|
|
154
176
|
if (error.name) result.name = error.name;
|
|
155
177
|
if (error.stack) result.stack = error.stack;
|
|
178
|
+
if (typeof error.code === "string") result.code = error.code;
|
|
156
179
|
return result;
|
|
157
180
|
}
|
|
@@ -6,6 +6,10 @@ import { encodeDisplayImage, resolveDisplayOps } from "./display-image.js";
|
|
|
6
6
|
import { terminateProcessTrees } from "./process-tree.js";
|
|
7
7
|
import { awaitMaybePromise, indirectEval, wrapUserCode } from "./worker-indirect-eval.js";
|
|
8
8
|
import { installShellCapture } from "./worker-shell-capture.js";
|
|
9
|
+
import { createWorkpool } from "./workpool.js";
|
|
10
|
+
import { inKernelToolInvoke } from "./kernel-tools-context.js";
|
|
11
|
+
import { kernelToolError } from "./kernel-tools-errors.js";
|
|
12
|
+
import { createKernelToolRegistry, createToolNamespace } from "./kernel-tools-registry.js";
|
|
9
13
|
|
|
10
14
|
const PREPARED_CELL_PREFIX = "/*senpi:prepared-cell*/";
|
|
11
15
|
// How long a child gets to honour SIGTERM before SIGKILL. Short, because the
|
|
@@ -22,6 +26,7 @@ export class JsWorkerRuntime {
|
|
|
22
26
|
#pendingDisplays = [];
|
|
23
27
|
#children = new Set();
|
|
24
28
|
#onChildEvent;
|
|
29
|
+
#tools;
|
|
25
30
|
|
|
26
31
|
constructor(options) {
|
|
27
32
|
this.#cwd = options.cwd;
|
|
@@ -29,9 +34,18 @@ export class JsWorkerRuntime {
|
|
|
29
34
|
this.#onChildEvent = typeof options.onChildEvent === "function" ? options.onChildEvent : null;
|
|
30
35
|
this.#localRoots = { ...(options.localRoots ?? {}) };
|
|
31
36
|
if (options.artifactsDir && !this.#localRoots.local) this.#localRoots.local = join(options.artifactsDir, "local");
|
|
37
|
+
this.#tools = createKernelToolRegistry({
|
|
38
|
+
generation: options.kernelGeneration ?? 1,
|
|
39
|
+
hostToolNames: options.hostToolNames ?? [],
|
|
40
|
+
foreignLanguageNames: options.foreignLanguageNames ?? [],
|
|
41
|
+
});
|
|
32
42
|
this.#installGlobals();
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
get kernelTools() {
|
|
46
|
+
return this.#tools;
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
async run(code, cellId, hooks) {
|
|
36
50
|
this.#hooks = hooks;
|
|
37
51
|
try {
|
|
@@ -107,17 +121,13 @@ export class JsWorkerRuntime {
|
|
|
107
121
|
globalThis.output = async (...args) => await this.#output(args);
|
|
108
122
|
globalThis.tool_schema = async name => await this.#toolSchema(name);
|
|
109
123
|
globalThis.agent = async (prompt, options, ...rest) => await this.#agent(prompt, options, rest);
|
|
124
|
+
globalThis.workpool = (agent, name, options) => createWorkpool((toolName, args) => this.#callTool(toolName, args), agent, name, options);
|
|
110
125
|
globalThis.parallel = async thunks => await this.#parallel(thunks);
|
|
111
126
|
globalThis.pipeline = async (items, ...stages) => await this.#pipeline(items, stages);
|
|
112
127
|
globalThis.completion = async (prompt, opts) => await this.#callTool("completion", { prompt, opts });
|
|
113
|
-
globalThis.tool =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
get: (_target, prop) => {
|
|
117
|
-
if (typeof prop !== "string") return undefined;
|
|
118
|
-
return async args => await this.#callTool(prop, args ?? {});
|
|
119
|
-
},
|
|
120
|
-
},
|
|
128
|
+
globalThis.tool = createToolNamespace(
|
|
129
|
+
(fn, metadata) => this.#tools.define(fn, metadata),
|
|
130
|
+
async (name, args) => await this.#callTool(name, args),
|
|
121
131
|
);
|
|
122
132
|
globalThis.tools = globalThis.tool;
|
|
123
133
|
const originalLog = console.log.bind(console);
|
|
@@ -273,6 +283,7 @@ export class JsWorkerRuntime {
|
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
async #agent(prompt, options, rest) {
|
|
286
|
+
if (inKernelToolInvoke()) throw kernelToolError("kernel_tool_recursion", "kernel tools may not invoke agent()");
|
|
276
287
|
const parsed = optionsArg({
|
|
277
288
|
name: "agent",
|
|
278
289
|
value: options,
|
|
@@ -302,6 +313,7 @@ export class JsWorkerRuntime {
|
|
|
302
313
|
output: text,
|
|
303
314
|
handle: details.handle ?? `agent://${id}`,
|
|
304
315
|
id,
|
|
316
|
+
run_epoch: details.run_epoch,
|
|
305
317
|
agent: details.agent ?? callArgs.agent ?? null,
|
|
306
318
|
};
|
|
307
319
|
if (Object.hasOwn(callArgs, "schema")) node.data = output;
|
|
@@ -39,18 +39,23 @@ export class WorkerSlot {
|
|
|
39
39
|
return this.#startupAbort !== null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
get generation(): number {
|
|
43
|
+
return this.#generation;
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
postMessage(message: HostToKernelMessage): void {
|
|
43
47
|
this.#worker?.postMessage(message);
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
async ensureReady(): Promise<void> {
|
|
47
51
|
if (!this.#ready) {
|
|
48
|
-
const generation = ++this.#generation;
|
|
52
|
+
const generation = this.#generation === 0 ? ++this.#generation : this.#generation;
|
|
49
53
|
const controller = new AbortController();
|
|
50
54
|
this.#startupAbort = controller;
|
|
51
55
|
const ready = startWorkerWithInlineFallback(
|
|
52
56
|
{
|
|
53
57
|
options: this.#options,
|
|
58
|
+
kernelGeneration: generation,
|
|
54
59
|
publish: (worker) => this.#publish(worker, generation),
|
|
55
60
|
isCurrent: (worker) => this.#isCurrent(worker, generation),
|
|
56
61
|
retire: (worker) => {
|
|
@@ -2,6 +2,7 @@ import { dirname, join } from "node:path";
|
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
3
|
import { type CodemodeRuntimeAssetEnvironment, requireCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
|
|
4
4
|
import { createInlineWorker, type WorkerLike } from "./inline-worker.ts";
|
|
5
|
+
import { resolveKernelToolNameSource } from "./kernel-contract.ts";
|
|
5
6
|
import { type JavaScriptKernelOptions, localBridgeConnection } from "./local-module-loader.ts";
|
|
6
7
|
import { spawnNodeWorker, WorkerStartupCancelledError, waitForReady } from "./worker-host.ts";
|
|
7
8
|
|
|
@@ -16,6 +17,7 @@ export function resolveJsWorkerEntryUrl(options: JavaScriptWorkerEntryUrlOptions
|
|
|
16
17
|
|
|
17
18
|
export interface WorkerStartupHooks {
|
|
18
19
|
readonly options: JavaScriptKernelOptions;
|
|
20
|
+
readonly kernelGeneration: number;
|
|
19
21
|
/** Wires the worker into the kernel; throws `WorkerStartupCancelledError` once the generation is stale. */
|
|
20
22
|
publish(worker: WorkerLike): void;
|
|
21
23
|
isCurrent(worker: WorkerLike): boolean;
|
|
@@ -27,7 +29,7 @@ export async function startWorkerWithInlineFallback(hooks: WorkerStartupHooks, s
|
|
|
27
29
|
let worker = spawnWorker(hooks.options);
|
|
28
30
|
hooks.publish(worker);
|
|
29
31
|
try {
|
|
30
|
-
await initializeWorker(worker, hooks
|
|
32
|
+
await initializeWorker(worker, hooks, signal);
|
|
31
33
|
return;
|
|
32
34
|
} catch (error) {
|
|
33
35
|
if (!hooks.isCurrent(worker) || error instanceof WorkerStartupCancelledError) {
|
|
@@ -41,7 +43,7 @@ export async function startWorkerWithInlineFallback(hooks: WorkerStartupHooks, s
|
|
|
41
43
|
if (!hooks.canFallBackInline()) throw new WorkerStartupCancelledError();
|
|
42
44
|
worker = createInlineWorker(hooks.options.cwd, hooks.options.parallelPoolWidth);
|
|
43
45
|
hooks.publish(worker);
|
|
44
|
-
await initializeWorker(worker, hooks
|
|
46
|
+
await initializeWorker(worker, hooks, signal);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function spawnWorker(options: JavaScriptKernelOptions): WorkerLike {
|
|
@@ -54,16 +56,16 @@ function spawnWorker(options: JavaScriptKernelOptions): WorkerLike {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
async function initializeWorker(
|
|
58
|
-
worker: WorkerLike,
|
|
59
|
-
options: JavaScriptKernelOptions,
|
|
60
|
-
signal: AbortSignal,
|
|
61
|
-
): Promise<void> {
|
|
59
|
+
async function initializeWorker(worker: WorkerLike, hooks: WorkerStartupHooks, signal: AbortSignal): Promise<void> {
|
|
62
60
|
const ready = waitForReady(worker, signal);
|
|
61
|
+
const options = hooks.options;
|
|
63
62
|
worker.postMessage({
|
|
64
63
|
type: "init",
|
|
65
64
|
sessionId: options.sessionId,
|
|
66
65
|
connection: localBridgeConnection(options),
|
|
66
|
+
kernelGeneration: hooks.kernelGeneration,
|
|
67
|
+
hostToolNames: resolveKernelToolNameSource(options.hostToolNames),
|
|
68
|
+
foreignLanguageNames: resolveKernelToolNameSource(options.foreignLanguageNames),
|
|
67
69
|
...(options.sessionEnv === undefined ? {} : { sessionEnv: options.sessionEnv }),
|
|
68
70
|
});
|
|
69
71
|
await ready;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { inKernelToolInvoke } from "./kernel-tools-context.js";
|
|
2
|
+
import { kernelToolError } from "./kernel-tools-errors.js";
|
|
3
|
+
|
|
4
|
+
// Only host-tool sugar: no admission, worker, or queue state belongs to the kernel.
|
|
5
|
+
export async function createWorkpool(callTool, agent, name, options = {}) {
|
|
6
|
+
if (inKernelToolInvoke()) throw kernelToolError("kernel_tool_recursion", "kernel tools may not invoke workpool()");
|
|
7
|
+
if (options === null || typeof options !== "object" || Array.isArray(options) || Object.keys(options).some(key => key !== "mode")) {
|
|
8
|
+
throw new TypeError("workpool() options only accept mode");
|
|
9
|
+
}
|
|
10
|
+
const call = async args => {
|
|
11
|
+
try {
|
|
12
|
+
return await callTool("workpool", args);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error?.code === "unknown_tool" || error?.code === "inactive_tool") {
|
|
15
|
+
throw workpoolError("workpool_unavailable", "No active host workpool tool", error);
|
|
16
|
+
}
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const result = await call({ op: "create", agent, name, ...options });
|
|
21
|
+
if (result.details?.error) throw workpoolError(result.details.error.code, result.details.error.message);
|
|
22
|
+
const pool_id = result.details?.pool_id;
|
|
23
|
+
if (result.hasError || typeof pool_id !== "string" || !/^wp_[0-9a-f]{32}$/.test(pool_id)) {
|
|
24
|
+
throw workpoolError("workpool_unavailable", "Host did not return a workpool identity");
|
|
25
|
+
}
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
pool_id,
|
|
28
|
+
push: items => call({ op: "push", pool_id, items }),
|
|
29
|
+
close: () => call({ op: "close", pool_id }),
|
|
30
|
+
inspect: () => call({ op: "inspect", pool_id }),
|
|
31
|
+
cancel: () => call({ op: "cancel", pool_id }),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function workpoolError(code, message, cause) {
|
|
36
|
+
return Object.assign(new Error(message, { cause }), { code });
|
|
37
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KernelInterruptHandle } from "../../tool/types.ts";
|
|
2
|
+
import { rejectKernelToolsUnavailable } from "../kernel-tools-unavailable.ts";
|
|
2
3
|
import type { PendingRun, PythonKernelRunOptions, PythonKernelStartOptions, ResultMessage } from "./kernel-contract.ts";
|
|
3
4
|
import { failedPythonResult, PythonKernelTransport } from "./transport.ts";
|
|
4
5
|
|
|
@@ -31,6 +32,18 @@ export class PythonKernel {
|
|
|
31
32
|
return kernel;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
listKernelToolNames(): readonly string[] {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describeKernelTools(_names: readonly string[]): Promise<never> {
|
|
40
|
+
return rejectKernelToolsUnavailable();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
invokeKernelTool(_request: unknown, _signal?: AbortSignal): Promise<never> {
|
|
44
|
+
return rejectKernelToolsUnavailable();
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
run(input: PythonKernelRunOptions): Promise<ResultMessage> {
|
|
35
48
|
if (this.#failure) return Promise.reject(this.#failure);
|
|
36
49
|
if (this.#closed) return Promise.reject(new Error("Python kernel is closed"));
|
|
@@ -24,7 +24,7 @@ from collections.abc import Iterable
|
|
|
24
24
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from threading import Lock, Thread
|
|
27
|
-
from typing import Any, Callable
|
|
27
|
+
from typing import Any, Callable, Union
|
|
28
28
|
from urllib.parse import unquote
|
|
29
29
|
|
|
30
30
|
SESSION_ID = ""
|
|
@@ -43,7 +43,11 @@ TIMEOUT_RESUME_OP = "timeout-resume"
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class PreludeRuntimeError(RuntimeError):
|
|
46
|
-
"""Host bridge or magic execution failed."""
|
|
46
|
+
"""Host bridge or magic execution failed, retaining its machine code."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, message: str, code: str | None = None) -> None:
|
|
49
|
+
super().__init__(message)
|
|
50
|
+
self.code = code
|
|
47
51
|
|
|
48
52
|
|
|
49
53
|
class PreludeValueError(ValueError):
|
|
@@ -370,7 +374,7 @@ def bridge_post(path: str, payload: dict[str, Any]) -> Any:
|
|
|
370
374
|
return body.get("value")
|
|
371
375
|
error = body.get("error") if isinstance(body, dict) else body
|
|
372
376
|
if isinstance(error, dict):
|
|
373
|
-
raise PreludeRuntimeError(str(error.get("message", error)))
|
|
377
|
+
raise PreludeRuntimeError(str(error.get("message", error)), error.get("code"))
|
|
374
378
|
raise PreludeRuntimeError(str(error))
|
|
375
379
|
|
|
376
380
|
|
|
@@ -416,6 +420,54 @@ class ToolProxy:
|
|
|
416
420
|
|
|
417
421
|
tool = ToolProxy()
|
|
418
422
|
|
|
423
|
+
JsonValue = Union[str, int, float, bool, None, list["JsonValue"], dict[str, "JsonValue"]]
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _workpool_call(args: dict[str, JsonValue]) -> dict[str, JsonValue]:
|
|
427
|
+
try:
|
|
428
|
+
return tool.workpool(args)
|
|
429
|
+
except PreludeRuntimeError as error:
|
|
430
|
+
if error.code in ("unknown_tool", "inactive_tool"):
|
|
431
|
+
raise PreludeRuntimeError("No active host workpool tool", "workpool_unavailable") from error
|
|
432
|
+
raise
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class Workpool:
|
|
436
|
+
"""An opaque host identity, not a worker queue."""
|
|
437
|
+
|
|
438
|
+
__slots__ = ("pool_id",)
|
|
439
|
+
|
|
440
|
+
def __init__(self, pool_id: str) -> None:
|
|
441
|
+
self.pool_id = pool_id
|
|
442
|
+
|
|
443
|
+
def push(self, items: list[JsonValue]) -> dict[str, JsonValue]:
|
|
444
|
+
return _workpool_call({"op": "push", "pool_id": self.pool_id, "items": items})
|
|
445
|
+
|
|
446
|
+
def close(self) -> dict[str, JsonValue]:
|
|
447
|
+
return _workpool_call({"op": "close", "pool_id": self.pool_id})
|
|
448
|
+
|
|
449
|
+
def inspect(self) -> dict[str, JsonValue]:
|
|
450
|
+
return _workpool_call({"op": "inspect", "pool_id": self.pool_id})
|
|
451
|
+
|
|
452
|
+
def cancel(self) -> dict[str, JsonValue]:
|
|
453
|
+
return _workpool_call({"op": "cancel", "pool_id": self.pool_id})
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def workpool(agent: dict[str, JsonValue], name: str, *, mode: str | None = None) -> Workpool:
|
|
457
|
+
args: dict[str, JsonValue] = {"op": "create", "agent": agent, "name": name}
|
|
458
|
+
if mode is not None:
|
|
459
|
+
args["mode"] = mode
|
|
460
|
+
result = _workpool_call(args)
|
|
461
|
+
details = result.get("details")
|
|
462
|
+
if isinstance(details, dict):
|
|
463
|
+
error = details.get("error")
|
|
464
|
+
if isinstance(error, dict):
|
|
465
|
+
raise PreludeRuntimeError(str(error["message"]), str(error["code"]))
|
|
466
|
+
pool_id = details.get("pool_id")
|
|
467
|
+
if not result.get("hasError") and isinstance(pool_id, str) and re.fullmatch(r"wp_[0-9a-f]{32}", pool_id):
|
|
468
|
+
return Workpool(pool_id)
|
|
469
|
+
raise PreludeRuntimeError("Host did not return a workpool identity", "workpool_unavailable")
|
|
470
|
+
|
|
419
471
|
|
|
420
472
|
def completion(
|
|
421
473
|
prompt: str,
|
|
@@ -522,6 +574,7 @@ def agent(
|
|
|
522
574
|
"output": text_value,
|
|
523
575
|
"handle": handle_value,
|
|
524
576
|
"id": agent_id,
|
|
577
|
+
"run_epoch": response_record.get("run_epoch"),
|
|
525
578
|
"agent": response_record.get("agent", agent),
|
|
526
579
|
}
|
|
527
580
|
if schema is not None:
|
|
@@ -917,6 +970,7 @@ USER_NS.update(
|
|
|
917
970
|
"tool": tool,
|
|
918
971
|
"completion": completion,
|
|
919
972
|
"agent": agent,
|
|
973
|
+
"workpool": workpool,
|
|
920
974
|
"output": output,
|
|
921
975
|
"tool_schema": tool_schema,
|
|
922
976
|
"__senpi_magic": _magic,
|
|
@@ -140,13 +140,22 @@ def __senpi_bridge_request(path, payload)
|
|
|
140
140
|
return body["value"] if body.is_a?(Hash) && body["ok"] == true
|
|
141
141
|
|
|
142
142
|
error = body.is_a?(Hash) ? body["error"] : body
|
|
143
|
-
raise(error.is_a?(Hash) ? error["message"].to_s : error.to_s)
|
|
143
|
+
raise SenpiBridgeError.new(error.is_a?(Hash) ? error["message"].to_s : error.to_s, error.is_a?(Hash) ? error["code"] : nil)
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def __senpi_call_tool(name, args)
|
|
147
147
|
__senpi_bridge_request("/call", { "callId" => "rb-#{Process.pid}-#{rand(1_000_000)}", "toolName" => name, "args" => args })
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
+
class SenpiBridgeError < StandardError
|
|
151
|
+
attr_reader :code
|
|
152
|
+
|
|
153
|
+
def initialize(message, code = nil)
|
|
154
|
+
super(message)
|
|
155
|
+
@code = code
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
150
159
|
class SenpiToolCallable
|
|
151
160
|
def initialize(name)
|
|
152
161
|
@name = name
|
|
@@ -177,6 +186,8 @@ def tool
|
|
|
177
186
|
$__senpi_tool_proxy ||= SenpiToolProxy.new
|
|
178
187
|
end
|
|
179
188
|
|
|
189
|
+
require_relative "workpool"
|
|
190
|
+
|
|
180
191
|
def completion(prompt, model: "default", system: nil, schema: nil, **kwargs)
|
|
181
192
|
options = { "model" => model }.merge(kwargs.transform_keys(&:to_s))
|
|
182
193
|
options["system"] = system unless system.nil?
|
|
@@ -213,7 +224,7 @@ def agent(prompt, agent: "task", model: nil, label: nil, schema: nil, isolated:
|
|
|
213
224
|
text_value = record.fetch("text", response)
|
|
214
225
|
result = schema.nil? ? text_value : record.key?("data") ? record["data"] : JSON.parse(text_value.to_s)
|
|
215
226
|
return result unless handle
|
|
216
|
-
{ "text" => text_value, "output" => text_value, "handle" => record["handle"] || (record["id"] && "agent://#{record["id"]}"), "id" => record["id"], "agent" => record.fetch("agent", agent) }.tap do |node|
|
|
227
|
+
{ "text" => text_value, "output" => text_value, "handle" => record["handle"] || (record["id"] && "agent://#{record["id"]}"), "id" => record["id"], "run_epoch" => record["run_epoch"], "agent" => record.fetch("agent", agent) }.tap do |node|
|
|
217
228
|
node["data"] = result unless schema.nil?
|
|
218
229
|
end
|
|
219
230
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Only an opaque host identity is retained; all work remains engine-owned.
|
|
2
|
+
def __senpi_workpool_call(args)
|
|
3
|
+
tool.workpool(args)
|
|
4
|
+
rescue SenpiBridgeError => error
|
|
5
|
+
raise SenpiBridgeError.new("No active host workpool tool", "workpool_unavailable") if ["unknown_tool", "inactive_tool"].include?(error.code)
|
|
6
|
+
raise
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class SenpiWorkpool
|
|
10
|
+
attr_reader :pool_id
|
|
11
|
+
|
|
12
|
+
def initialize(pool_id)
|
|
13
|
+
@pool_id = pool_id
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def push(items)
|
|
18
|
+
__senpi_workpool_call({ "op" => "push", "pool_id" => @pool_id, "items" => items })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
[:close, :inspect, :cancel].each do |op|
|
|
22
|
+
define_method(op) { __senpi_workpool_call({ "op" => op.to_s, "pool_id" => @pool_id }) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def workpool(agent, name, mode: nil)
|
|
27
|
+
args = { "op" => "create", "agent" => agent, "name" => name }
|
|
28
|
+
args["mode"] = mode unless mode.nil?
|
|
29
|
+
result = __senpi_workpool_call(args)
|
|
30
|
+
details = result["details"]
|
|
31
|
+
if details.is_a?(Hash)
|
|
32
|
+
error = details["error"]
|
|
33
|
+
raise SenpiBridgeError.new(error["message"], error["code"]) if error.is_a?(Hash)
|
|
34
|
+
pool_id = details["pool_id"]
|
|
35
|
+
return SenpiWorkpool.new(pool_id) if !result["hasError"] && pool_id.is_a?(String) && /\Awp_[0-9a-f]{32}\z/.match?(pool_id)
|
|
36
|
+
end
|
|
37
|
+
raise SenpiBridgeError.new("Host did not return a workpool identity", "workpool_unavailable")
|
|
38
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
2
|
import { decodeBridgeFrame, encodeBridgeFrame, isKernelToHostMessage } from "../../bridge/protocol.ts";
|
|
3
3
|
import type { KernelInterruptHandle } from "../../tool/types.ts";
|
|
4
|
+
import { rejectKernelToolsUnavailable } from "../kernel-tools-unavailable.ts";
|
|
4
5
|
import { applySessionEnvironment } from "../session-env.ts";
|
|
5
6
|
import type { KernelResult, KernelRunInput, SubprocessKernelOptions, ToolCallMessage } from "./subprocess-contract.ts";
|
|
6
7
|
import { type SubprocessLike, SubprocessProcess, type SubprocessSpawn, spawnSubprocess } from "./subprocess-process.ts";
|
|
@@ -69,6 +70,18 @@ export class SubprocessKernel {
|
|
|
69
70
|
return { stateRetained: Promise.resolve(false) };
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
listKernelToolNames(): readonly string[] {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describeKernelTools(_names: readonly string[]): Promise<never> {
|
|
78
|
+
return rejectKernelToolsUnavailable();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
invokeKernelTool(_request: unknown, _signal?: AbortSignal): Promise<never> {
|
|
82
|
+
return rejectKernelToolsUnavailable();
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
nextToolCall(): Promise<ToolCallMessage> {
|
|
73
86
|
return this.runs.nextToolCall();
|
|
74
87
|
}
|