@code-yeongyu/senpi-codemode 2026.9.16-3 → 2026.9.17
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 +16 -0
- package/package.json +4 -4
- package/src/bridge/kernel-tools-protocol.ts +19 -0
- package/src/index.ts +7 -0
- package/src/kernels/js/context-manager.ts +10 -3
- package/src/kernels/js/kernel-tools-context.d.ts +4 -0
- package/src/kernels/js/kernel-tools-errors.js +6 -3
- package/src/kernels/js/kernel-tools-errors.ts +21 -3
- package/src/kernels/js/kernel-tools-host.ts +44 -3
- package/src/kernels/js/kernel-tools-pump.js +15 -1
- package/src/kernels/js/kernel-tools-scope.d.ts +9 -0
- package/src/kernels/js/kernel-tools-scope.js +34 -0
- package/src/kernels/js/kernel-tools-types.ts +17 -3
- package/src/kernels/js/worker-core.js +7 -0
- package/src/kernels/py/kernel.ts +2 -1
- package/src/kernels/shared/subprocess-kernel.ts +2 -1
- package/src/tool/run-eval-cell.ts +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,22 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.9.17] - 2026-09-17
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `kernelTools.invoke(request, options?)` accepts a per-call execution scope for the nested host calls the invoked closure makes: `{ scope: { tools: { allow?: string[], deny?: string[] } } }`. While that invocation is active, a `tool.<name>()` outside the scope is refused inside the worker with `kernel_tool_host_denied` carrying `{ tool, call_id, reason: "allow" | "deny" }`: the closure sees a rejected promise, the refusal never reaches the host bridge, and the parent's own cells and queue keep the parent's full tool surface. `deny` wins over `allow`, an `allow` list refuses every host tool it does not name, a malformed list fails closed, and the scope lives only for that call — it is dropped when the call settles (including interrupt and reset) and is never persisted. The second argument still accepts a bare `AbortSignal`, and a call without a scope posts exactly the message it always did. Consumers detect the feature through `kernelTools.capabilities.invokeScope === true`; `KERNEL_TOOLS_CAPABILITIES`, `KernelToolsCapabilities`, `KernelToolsInvokeOptions`, `KernelToolsInvokeScope`, `KernelToolsHostScope`, `KernelToolHostDenial` and `KernelToolHostDenialReason` are exported ([#1731](https://github.com/code-yeongyu/senpi/issues/1731)).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Kernel-tools types bind to coding-agent's `ExtensionKernelTools` / `KernelToolInvokeOptions` / `KernelToolInvokeScope` (`KERNEL_TOOLS_CAPABILITIES satisfies ExtensionKernelTools["capabilities"]`) so the implementation cannot drift from the host declaration ([#1731](https://github.com/code-yeongyu/senpi/issues/1731)).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
### Removed
|
|
30
|
+
|
|
15
31
|
## [2026.9.16-3] - 2026-09-16
|
|
16
32
|
|
|
17
33
|
### Breaking Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.17",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@babel/parser": "8.0.4",
|
|
33
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.
|
|
33
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.17",
|
|
34
34
|
"typebox": "1.3.27"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
37
|
+
"@code-yeongyu/senpi": "2026.9.17"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@code-yeongyu/senpi": "2026.9.
|
|
40
|
+
"@code-yeongyu/senpi": "2026.9.17"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"senpi",
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { type Static, Type } from "typebox";
|
|
2
2
|
|
|
3
|
+
/** Payload of a `kernel_tool_host_denied` refusal: the host tool, the invoking call, the reason (#1731). */
|
|
4
|
+
const kernelToolHostDenialSchema = Type.Object({
|
|
5
|
+
tool: Type.String({ minLength: 1 }),
|
|
6
|
+
call_id: Type.String({ minLength: 1 }),
|
|
7
|
+
reason: Type.Union([Type.Literal("allow"), Type.Literal("deny")]),
|
|
8
|
+
});
|
|
9
|
+
|
|
3
10
|
const kernelToolErrorSchema = Type.Object({
|
|
4
11
|
message: Type.String(),
|
|
5
12
|
name: Type.Optional(Type.String()),
|
|
6
13
|
stack: Type.Optional(Type.String()),
|
|
7
14
|
code: Type.Optional(Type.String()),
|
|
15
|
+
details: Type.Optional(kernelToolHostDenialSchema),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** Per-call execution scope for the nested host calls the invoked closure makes (#1731). */
|
|
19
|
+
export const kernelToolInvokeScopeSchema = Type.Object({
|
|
20
|
+
tools: Type.Optional(
|
|
21
|
+
Type.Object({
|
|
22
|
+
allow: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
23
|
+
deny: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
24
|
+
}),
|
|
25
|
+
),
|
|
8
26
|
});
|
|
9
27
|
|
|
10
28
|
export const kernelToolDescriptorSchema = Type.Object({
|
|
@@ -43,6 +61,7 @@ export const kernelToolHostToKernelSchemas = [
|
|
|
43
61
|
definition_revision: Type.Integer({ minimum: 1 }),
|
|
44
62
|
args: Type.Unknown(),
|
|
45
63
|
call_id: Type.String({ minLength: 1 }),
|
|
64
|
+
scope: Type.Optional(kernelToolInvokeScopeSchema),
|
|
46
65
|
}),
|
|
47
66
|
Type.Object({
|
|
48
67
|
type: Type.Literal("kernel-tool-cancel"),
|
package/src/index.ts
CHANGED
|
@@ -263,10 +263,17 @@ function modelIdFrom(event: unknown): string | undefined {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
export {
|
|
266
|
+
KERNEL_TOOLS_CAPABILITIES,
|
|
266
267
|
KERNEL_TOOLS_UNSUPPORTED,
|
|
267
268
|
type KernelToolDescriptor,
|
|
269
|
+
type KernelToolHostDenial,
|
|
270
|
+
type KernelToolHostDenialReason,
|
|
271
|
+
type KernelToolsCapabilities,
|
|
268
272
|
type KernelToolsCapability,
|
|
269
273
|
type KernelToolsDescribeResult,
|
|
274
|
+
type KernelToolsHostScope,
|
|
275
|
+
type KernelToolsInvokeOptions,
|
|
270
276
|
type KernelToolsInvokeRequest,
|
|
277
|
+
type KernelToolsInvokeScope,
|
|
271
278
|
} from "./kernels/js/kernel-tools-types.ts";
|
|
272
279
|
export { enabledLanguagesFrom };
|
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
} from "./kernel-contract.ts";
|
|
14
14
|
import { kernelToolError } from "./kernel-tools-errors.ts";
|
|
15
15
|
import { KernelToolHostPump } from "./kernel-tools-host.ts";
|
|
16
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
KernelToolsDescribeResult,
|
|
18
|
+
KernelToolsInvokeOptions,
|
|
19
|
+
KernelToolsInvokeRequest,
|
|
20
|
+
} from "./kernel-tools-types.ts";
|
|
17
21
|
import { type JavaScriptKernelOptions, LocalModuleLoader } from "./local-module-loader.ts";
|
|
18
22
|
import { terminateProcessTrees } from "./process-tree-host.ts";
|
|
19
23
|
import { JavaScriptRunQueue, type PendingJavaScriptRun, stoppedResult } from "./run-queue.ts";
|
|
@@ -72,8 +76,11 @@ export class JavaScriptKernel {
|
|
|
72
76
|
return this.#kernelTools.describe(names);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
invokeKernelTool(
|
|
76
|
-
|
|
79
|
+
invokeKernelTool(
|
|
80
|
+
request: KernelToolsInvokeRequest,
|
|
81
|
+
options?: AbortSignal | KernelToolsInvokeOptions,
|
|
82
|
+
): Promise<unknown> {
|
|
83
|
+
return this.#kernelTools.invoke(request, options);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
async run(input: JavaScriptRunInput): Promise<ResultMessage> {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { KernelToolsInvokeScope } from "./kernel-tools-types.ts";
|
|
2
|
+
|
|
1
3
|
export const kernelToolCallContext: {
|
|
2
4
|
run<T>(store: KernelToolCallStore, fn: () => T): T;
|
|
3
5
|
getStore(): KernelToolCallStore | undefined;
|
|
@@ -8,6 +10,8 @@ export type KernelToolCallStore = {
|
|
|
8
10
|
readonly callId: string;
|
|
9
11
|
readonly generation: number;
|
|
10
12
|
readonly signal: AbortSignal;
|
|
13
|
+
/** Host tools this call's nested bridge calls may reach; absent means the parent's full surface. */
|
|
14
|
+
readonly scope?: KernelToolsInvokeScope;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
17
|
export function inKernelToolInvoke(): boolean;
|
|
@@ -7,16 +7,19 @@ export const KERNEL_TOOL_ERROR_CODES = Object.freeze([
|
|
|
7
7
|
"kernel_tool_missing",
|
|
8
8
|
"kernel_tool_failed",
|
|
9
9
|
"kernel_tool_recursion",
|
|
10
|
+
"kernel_tool_host_denied",
|
|
10
11
|
]);
|
|
11
12
|
|
|
12
13
|
export class KernelToolError extends Error {
|
|
13
|
-
constructor(code, message) {
|
|
14
|
+
constructor(code, message, details) {
|
|
14
15
|
super(message);
|
|
15
16
|
this.name = "KernelToolError";
|
|
16
17
|
this.code = code;
|
|
18
|
+
// Only `kernel_tool_host_denied` carries one today: { tool, call_id, reason }.
|
|
19
|
+
if (details !== undefined) this.details = details;
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export function kernelToolError(code, message) {
|
|
21
|
-
return new KernelToolError(code, message);
|
|
23
|
+
export function kernelToolError(code, message, details) {
|
|
24
|
+
return new KernelToolError(code, message, details);
|
|
22
25
|
}
|
|
@@ -7,20 +7,38 @@ export const KERNEL_TOOL_ERROR_CODES = [
|
|
|
7
7
|
"kernel_tool_missing",
|
|
8
8
|
"kernel_tool_failed",
|
|
9
9
|
"kernel_tool_recursion",
|
|
10
|
+
"kernel_tool_host_denied",
|
|
10
11
|
] as const;
|
|
11
12
|
|
|
12
13
|
export type KernelToolErrorCode = (typeof KERNEL_TOOL_ERROR_CODES)[number];
|
|
13
14
|
|
|
15
|
+
/** Why a nested host call was refused: it is outside the call's allow list, or named by its deny list. */
|
|
16
|
+
export type KernelToolHostDenialReason = "allow" | "deny";
|
|
17
|
+
|
|
18
|
+
/** Payload carried by `kernel_tool_host_denied`: the host tool, the invoking kernel-tool call, the reason. */
|
|
19
|
+
export type KernelToolHostDenial = {
|
|
20
|
+
readonly tool: string;
|
|
21
|
+
readonly call_id: string;
|
|
22
|
+
readonly reason: KernelToolHostDenialReason;
|
|
23
|
+
};
|
|
24
|
+
|
|
14
25
|
export class KernelToolError extends Error {
|
|
15
26
|
readonly name = "KernelToolError";
|
|
16
27
|
readonly code: KernelToolErrorCode;
|
|
28
|
+
/** Structured payload for the codes that carry one; only `kernel_tool_host_denied` does today. */
|
|
29
|
+
readonly details?: KernelToolHostDenial;
|
|
17
30
|
|
|
18
|
-
constructor(code: KernelToolErrorCode, message: string) {
|
|
31
|
+
constructor(code: KernelToolErrorCode, message: string, details?: KernelToolHostDenial) {
|
|
19
32
|
super(message);
|
|
20
33
|
this.code = code;
|
|
34
|
+
if (details !== undefined) this.details = details;
|
|
21
35
|
}
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
export function kernelToolError(
|
|
25
|
-
|
|
38
|
+
export function kernelToolError(
|
|
39
|
+
code: KernelToolErrorCode,
|
|
40
|
+
message: string,
|
|
41
|
+
details?: KernelToolHostDenial,
|
|
42
|
+
): KernelToolError {
|
|
43
|
+
return new KernelToolError(code, message, details);
|
|
26
44
|
}
|
|
@@ -2,7 +2,12 @@ import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/prot
|
|
|
2
2
|
import { generateCorrelationId } from "../../bridge/protocol.ts";
|
|
3
3
|
import { RESERVED_AGENT_TOOL } from "../../bridge/reserved.ts";
|
|
4
4
|
import { kernelToolError } from "./kernel-tools-errors.ts";
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
KernelToolsDescribeResult,
|
|
7
|
+
KernelToolsInvokeOptions,
|
|
8
|
+
KernelToolsInvokeRequest,
|
|
9
|
+
KernelToolsInvokeScope,
|
|
10
|
+
} from "./kernel-tools-types.ts";
|
|
6
11
|
|
|
7
12
|
type KernelToolReply = Extract<
|
|
8
13
|
KernelToHostMessage,
|
|
@@ -59,7 +64,13 @@ export class KernelToolHostPump {
|
|
|
59
64
|
return { results: reply.results as KernelToolsDescribeResult["results"] };
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
/**
|
|
68
|
+
* `options` is the caller's abort signal, or `{ signal?, scope? }` where `scope` bounds the host
|
|
69
|
+
* tools the invoked closure may reach during this call only. A call without a scope posts exactly
|
|
70
|
+
* the message it always did (#1731).
|
|
71
|
+
*/
|
|
72
|
+
async invoke(request: KernelToolsInvokeRequest, options?: AbortSignal | KernelToolsInvokeOptions): Promise<unknown> {
|
|
73
|
+
const { signal, scope } = normalizeInvokeOptions(options);
|
|
63
74
|
this.events.dispatchEvent(new Event("nestedInvoke"));
|
|
64
75
|
const reply = await this.#request(
|
|
65
76
|
{
|
|
@@ -70,13 +81,14 @@ export class KernelToolHostPump {
|
|
|
70
81
|
definition_revision: request.definition_revision,
|
|
71
82
|
args: request.args,
|
|
72
83
|
call_id: request.call_id,
|
|
84
|
+
...wireScope(scope),
|
|
73
85
|
},
|
|
74
86
|
signal,
|
|
75
87
|
);
|
|
76
88
|
if (reply.type !== "kernel-tool-invoke-reply") {
|
|
77
89
|
throw kernelToolError("kernel_tool_failed", "unexpected kernel-tool invoke reply");
|
|
78
90
|
}
|
|
79
|
-
if (!reply.ok) throw kernelToolError(codeOf(reply.error.code), reply.error.message);
|
|
91
|
+
if (!reply.ok) throw kernelToolError(codeOf(reply.error.code), reply.error.message, reply.error.details);
|
|
80
92
|
return reply.value;
|
|
81
93
|
}
|
|
82
94
|
|
|
@@ -112,6 +124,33 @@ export class KernelToolHostPump {
|
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
/**
|
|
128
|
+
* The scope as the protocol carries it: own copies of the caller's lists, and nothing at all when the
|
|
129
|
+
* caller named no host tools, so an unscoped call posts exactly the message it always did.
|
|
130
|
+
*/
|
|
131
|
+
function wireScope(scope?: KernelToolsInvokeScope): { scope?: { tools: { allow?: string[]; deny?: string[] } } } {
|
|
132
|
+
const tools = scope?.tools;
|
|
133
|
+
if (tools === undefined) return {};
|
|
134
|
+
if (tools.allow === undefined && tools.deny === undefined) return {};
|
|
135
|
+
return {
|
|
136
|
+
scope: {
|
|
137
|
+
tools: {
|
|
138
|
+
...(tools.allow === undefined ? {} : { allow: [...tools.allow] }),
|
|
139
|
+
...(tools.deny === undefined ? {} : { deny: [...tools.deny] }),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeInvokeOptions(options?: AbortSignal | KernelToolsInvokeOptions): KernelToolsInvokeOptions {
|
|
146
|
+
if (options === undefined) return {};
|
|
147
|
+
return isAbortSignal(options) ? { signal: options } : options;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isAbortSignal(options: AbortSignal | KernelToolsInvokeOptions): options is AbortSignal {
|
|
151
|
+
return options instanceof AbortSignal || "aborted" in options;
|
|
152
|
+
}
|
|
153
|
+
|
|
115
154
|
function codeOf(
|
|
116
155
|
code: string | undefined,
|
|
117
156
|
):
|
|
@@ -119,12 +158,14 @@ function codeOf(
|
|
|
119
158
|
| "kernel_tool_stale"
|
|
120
159
|
| "kernel_tool_missing"
|
|
121
160
|
| "kernel_tool_recursion"
|
|
161
|
+
| "kernel_tool_host_denied"
|
|
122
162
|
| "tools_unavailable"
|
|
123
163
|
| "invalid_tool_definition" {
|
|
124
164
|
if (
|
|
125
165
|
code === "kernel_tool_stale" ||
|
|
126
166
|
code === "kernel_tool_missing" ||
|
|
127
167
|
code === "kernel_tool_recursion" ||
|
|
168
|
+
code === "kernel_tool_host_denied" ||
|
|
128
169
|
code === "tools_unavailable" ||
|
|
129
170
|
code === "invalid_tool_definition"
|
|
130
171
|
) {
|
|
@@ -21,7 +21,15 @@ export function createKernelToolPump({ getRuntime, emit, nestedInvokes }) {
|
|
|
21
21
|
const runtime = getRuntime();
|
|
22
22
|
if (!runtime?.kernelTools) throw kernelToolError("tools_unavailable", "JS runtime not initialized");
|
|
23
23
|
const value = await kernelToolCallContext.run(
|
|
24
|
-
|
|
24
|
+
// The scope lives only in this store: it is gone once the call settles, and it never
|
|
25
|
+
// reaches the top-level cell context or the next invocation (#1731).
|
|
26
|
+
{
|
|
27
|
+
pendingTools,
|
|
28
|
+
callId: message.call_id,
|
|
29
|
+
generation: message.kernel_generation,
|
|
30
|
+
signal: controller.signal,
|
|
31
|
+
scope: message.scope,
|
|
32
|
+
},
|
|
25
33
|
() =>
|
|
26
34
|
runtime.kernelTools.invoke(
|
|
27
35
|
{
|
|
@@ -91,15 +99,21 @@ function pumpError(error) {
|
|
|
91
99
|
message: error.message,
|
|
92
100
|
stack: error.stack,
|
|
93
101
|
...(typeof error.code === "string" ? { code: error.code } : {}),
|
|
102
|
+
...(isPlainRecord(error.details) ? { details: error.details } : {}),
|
|
94
103
|
};
|
|
95
104
|
}
|
|
96
105
|
return { message: String(error) };
|
|
97
106
|
}
|
|
98
107
|
|
|
108
|
+
function isPlainRecord(value) {
|
|
109
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
99
112
|
function errorFrom(error) {
|
|
100
113
|
const result = new Error(error.message);
|
|
101
114
|
if (error.name) result.name = error.name;
|
|
102
115
|
if (error.stack) result.stack = error.stack;
|
|
103
116
|
if (typeof error.code === "string") result.code = error.code;
|
|
117
|
+
if (isPlainRecord(error.details)) result.details = error.details;
|
|
104
118
|
return result;
|
|
105
119
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KernelToolError, KernelToolHostDenialReason } from "./kernel-tools-errors.ts";
|
|
2
|
+
import type { KernelToolsInvokeScope } from "./kernel-tools-types.ts";
|
|
3
|
+
|
|
4
|
+
export function hostToolRefusal(
|
|
5
|
+
scope: KernelToolsInvokeScope | undefined,
|
|
6
|
+
toolName: string,
|
|
7
|
+
): KernelToolHostDenialReason | null;
|
|
8
|
+
|
|
9
|
+
export function hostDeniedError(toolName: string, callId: string, reason: KernelToolHostDenialReason): KernelToolError;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { kernelToolError } from "./kernel-tools-errors.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Call-scoped host-tool policy for one kernel-tool invocation (#1731). A call without a tool scope
|
|
5
|
+
* refuses nothing (today's behavior); `deny` wins over `allow`; an `allow` list refuses every host
|
|
6
|
+
* tool it does not name; a malformed list fails closed instead of widening the call's reach.
|
|
7
|
+
*/
|
|
8
|
+
export function hostToolRefusal(scope, toolName) {
|
|
9
|
+
const tools = scope?.tools;
|
|
10
|
+
if (tools === null || typeof tools !== "object") return null;
|
|
11
|
+
if (tools.deny !== undefined) {
|
|
12
|
+
const deny = nameList(tools.deny);
|
|
13
|
+
if (deny === null || deny.includes(toolName)) return "deny";
|
|
14
|
+
}
|
|
15
|
+
if (tools.allow !== undefined) {
|
|
16
|
+
const allow = nameList(tools.allow);
|
|
17
|
+
if (allow === null || !allow.includes(toolName)) return "allow";
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The typed refusal the closure sees and the invoking call reports. */
|
|
23
|
+
export function hostDeniedError(toolName, callId, reason) {
|
|
24
|
+
return kernelToolError(
|
|
25
|
+
"kernel_tool_host_denied",
|
|
26
|
+
`Host tool is outside this kernel tool call's scope: ${toolName} (${reason})`,
|
|
27
|
+
{ tool: toolName, call_id: callId, reason },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function nameList(value) {
|
|
32
|
+
if (!Array.isArray(value)) return null;
|
|
33
|
+
return value.every((name) => typeof name === "string") ? value : null;
|
|
34
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtensionKernelTools, KernelToolInvokeOptions, KernelToolInvokeScope } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { KernelToolErrorCode, KernelToolHostDenial, KernelToolHostDenialReason } from "./kernel-tools-errors.ts";
|
|
2
3
|
|
|
3
|
-
export type { KernelToolErrorCode };
|
|
4
|
+
export type { KernelToolErrorCode, KernelToolHostDenial, KernelToolHostDenialReason };
|
|
4
5
|
|
|
5
6
|
export type KernelToolDescriptor = {
|
|
6
7
|
readonly name: string;
|
|
@@ -31,9 +32,22 @@ export type KernelToolsDescribeResult = {
|
|
|
31
32
|
readonly results: readonly KernelToolsDescribeEntry[];
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
/** Host declaration (#1731); aliases so this package cannot drift from `@code-yeongyu/senpi`. */
|
|
36
|
+
export type KernelToolsHostScope = NonNullable<KernelToolInvokeScope["tools"]>;
|
|
37
|
+
export type KernelToolsInvokeScope = KernelToolInvokeScope;
|
|
38
|
+
export type KernelToolsInvokeOptions = KernelToolInvokeOptions;
|
|
39
|
+
|
|
40
|
+
/** Stable capability markers a consumer gates on before sending an option this runtime may not know. */
|
|
41
|
+
export type KernelToolsCapabilities = ExtensionKernelTools["capabilities"];
|
|
42
|
+
|
|
43
|
+
export const KERNEL_TOOLS_CAPABILITIES = Object.freeze({
|
|
44
|
+
invokeScope: true as const,
|
|
45
|
+
}) satisfies ExtensionKernelTools["capabilities"];
|
|
46
|
+
|
|
34
47
|
export type KernelToolsCapability = {
|
|
48
|
+
readonly capabilities: ExtensionKernelTools["capabilities"];
|
|
35
49
|
describe(names: readonly string[]): Promise<KernelToolsDescribeResult>;
|
|
36
|
-
invoke
|
|
50
|
+
invoke: ExtensionKernelTools["invoke"];
|
|
37
51
|
};
|
|
38
52
|
|
|
39
53
|
export const KERNEL_TOOLS_UNSUPPORTED = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { kernelToolCallContext } from "./kernel-tools-context.js";
|
|
2
2
|
import { kernelToolError } from "./kernel-tools-errors.js";
|
|
3
3
|
import { createKernelToolPump } from "./kernel-tools-pump.js";
|
|
4
|
+
import { hostDeniedError, hostToolRefusal } from "./kernel-tools-scope.js";
|
|
4
5
|
import { JsWorkerRuntime } from "./worker-runtime.js";
|
|
5
6
|
|
|
6
7
|
// Mirrors INTERRUPT_ACK_OP and CHILD_LIFECYCLE_OP in src/bridge/reserved.ts (this worker file cannot import TypeScript).
|
|
@@ -59,6 +60,12 @@ export function createWorkerCore(transport, options) {
|
|
|
59
60
|
const nested = kernelToolCallContext.getStore();
|
|
60
61
|
if (!nested && activeCell?.interruption) throw activeCell.interruption;
|
|
61
62
|
if (nested?.signal.aborted) throw nested.signal.reason;
|
|
63
|
+
// A scoped kernel-tool call is refused here, before anything reaches the host bridge, so the
|
|
64
|
+
// closure sees a rejected promise and the parent's own cells keep their full tool surface (#1731).
|
|
65
|
+
if (nested) {
|
|
66
|
+
const refusal = hostToolRefusal(nested.scope, toolName);
|
|
67
|
+
if (refusal) throw hostDeniedError(toolName, nested.callId, refusal);
|
|
68
|
+
}
|
|
62
69
|
const bag = nested?.pendingTools ?? pendingTools;
|
|
63
70
|
const callId = `js-${crypto.randomUUID()}`;
|
|
64
71
|
const promise = new Promise((resolve, reject) => bag.set(callId, { resolve, reject }));
|
package/src/kernels/py/kernel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KernelInterruptHandle } from "../../tool/types.ts";
|
|
2
|
+
import type { KernelToolsInvokeOptions } from "../js/kernel-tools-types.ts";
|
|
2
3
|
import { rejectKernelToolsUnavailable } from "../kernel-tools-unavailable.ts";
|
|
3
4
|
import type { PendingRun, PythonKernelRunOptions, PythonKernelStartOptions, ResultMessage } from "./kernel-contract.ts";
|
|
4
5
|
import { failedPythonResult, PythonKernelTransport } from "./transport.ts";
|
|
@@ -40,7 +41,7 @@ export class PythonKernel {
|
|
|
40
41
|
return rejectKernelToolsUnavailable();
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
invokeKernelTool(_request: unknown,
|
|
44
|
+
invokeKernelTool(_request: unknown, _options?: AbortSignal | KernelToolsInvokeOptions): Promise<never> {
|
|
44
45
|
return rejectKernelToolsUnavailable();
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -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 type { KernelToolsInvokeOptions } from "../js/kernel-tools-types.ts";
|
|
4
5
|
import { rejectKernelToolsUnavailable } from "../kernel-tools-unavailable.ts";
|
|
5
6
|
import { applySessionEnvironment } from "../session-env.ts";
|
|
6
7
|
import type { KernelResult, KernelRunInput, SubprocessKernelOptions, ToolCallMessage } from "./subprocess-contract.ts";
|
|
@@ -78,7 +79,7 @@ export class SubprocessKernel {
|
|
|
78
79
|
return rejectKernelToolsUnavailable();
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
invokeKernelTool(_request: unknown,
|
|
82
|
+
invokeKernelTool(_request: unknown, _options?: AbortSignal | KernelToolsInvokeOptions): Promise<never> {
|
|
82
83
|
return rejectKernelToolsUnavailable();
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AgentToolResult,
|
|
5
|
+
type ExtensionContext,
|
|
6
|
+
type ExtensionKernelTools,
|
|
7
|
+
kernelToolsStorage,
|
|
8
|
+
} from "@code-yeongyu/senpi";
|
|
4
9
|
import { DEFAULT_FOREGROUND_WINDOW_SECONDS, defaultCodemodeSettings } from "../config/settings.ts";
|
|
5
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
KERNEL_TOOLS_CAPABILITIES,
|
|
12
|
+
type KernelToolsCapability,
|
|
13
|
+
type KernelToolsDescribeResult,
|
|
14
|
+
} from "../kernels/js/kernel-tools-types.ts";
|
|
6
15
|
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
7
16
|
import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
|
|
8
17
|
import { CellHandler, type CellState } from "./cell-handler.ts";
|
|
@@ -220,11 +229,12 @@ function jsKernelTools(kernel: EvalKernel, language: string): KernelToolsCapabil
|
|
|
220
229
|
if (language !== "js") return undefined;
|
|
221
230
|
if (!("describeKernelTools" in kernel) || typeof kernel.describeKernelTools !== "function") return undefined;
|
|
222
231
|
const js = kernel as EvalKernel & {
|
|
223
|
-
describeKernelTools:
|
|
224
|
-
invokeKernelTool:
|
|
232
|
+
describeKernelTools: (names: readonly string[]) => Promise<KernelToolsDescribeResult>;
|
|
233
|
+
invokeKernelTool: ExtensionKernelTools["invoke"];
|
|
225
234
|
};
|
|
226
235
|
return {
|
|
236
|
+
capabilities: KERNEL_TOOLS_CAPABILITIES,
|
|
227
237
|
describe: (names) => js.describeKernelTools(names),
|
|
228
|
-
invoke: (request,
|
|
229
|
-
};
|
|
238
|
+
invoke: (request, options) => js.invokeKernelTool(request, options),
|
|
239
|
+
} satisfies ExtensionKernelTools;
|
|
230
240
|
}
|