From 77c4add62ae5ac9ccfb1452f5a99b10017039c12 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 16 Sep 2026 23:52:34 +0900 Subject: [PATCH 1/2] fix(coding-agent): type ExtensionKernelTools as the shipped invoke scope surface PR #1765 shipped kernelTools.invoke(request, { signal?, scope? }) and capabilities.invokeScope, but ExtensionContext.kernelTools still declared invoke(request, signal?: AbortSignal) with no capabilities. Alias ExtensionKernelTools to senpi-codemode KernelToolsCapability so the public typed surface cannot drift. Refs #1731 --- packages/coding-agent/CHANGELOG.md | 2 ++ .../core/extensions/kernel-tools-context.ts | 21 +++++++----------- .../coding-agent/src/core/extensions/types.ts | 15 ++----------- packages/coding-agent/src/index.ts | 9 +++++++- .../test/extension-kernel-tools-types.ts | 22 +++++++++++++++++++ 5 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 packages/coding-agent/test/extension-kernel-tools-types.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2d19e8482d..61fb298f8e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -8,6 +8,8 @@ ### Changed +- `ExtensionContext.kernelTools` is typed as the shipped kernel-tools surface: `invoke(request, options?)` accepts `{ signal?, scope? }` (a bare `AbortSignal` still works) and `capabilities.invokeScope` is present. `ExtensionKernelTools` is senpi-codemode's `KernelToolsCapability`, so the two cannot drift ([#1731](https://github.com/code-yeongyu/senpi/issues/1731)). + ### Fixed ### Removed diff --git a/packages/coding-agent/src/core/extensions/kernel-tools-context.ts b/packages/coding-agent/src/core/extensions/kernel-tools-context.ts index aa17fc739b..910d4173e4 100644 --- a/packages/coding-agent/src/core/extensions/kernel-tools-context.ts +++ b/packages/coding-agent/src/core/extensions/kernel-tools-context.ts @@ -1,17 +1,12 @@ import { AsyncLocalStorage } from "node:async_hooks"; +import type { KernelToolsCapability } from "@code-yeongyu/senpi-codemode"; -export type ExtensionKernelTools = { - describe(names: readonly string[]): Promise; - invoke( - request: { - name: string; - kernel_generation: number; - definition_revision: number; - args: unknown; - call_id: string; - }, - signal?: AbortSignal, - ): Promise; -}; +export type { KernelToolsCapability }; +export type ExtensionKernelTools = KernelToolsCapability; +export type { + KernelToolsCapabilities, + KernelToolsInvokeOptions, + KernelToolsInvokeRequest, +} from "@code-yeongyu/senpi-codemode"; export const kernelToolsStorage = new AsyncLocalStorage(); diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index 1abf913129..046630304a 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -89,6 +89,7 @@ import type { } from "../tools/index.ts"; import type { ReadClassifier } from "../tools/read-classifiers.ts"; import type { McpServerDeclaration } from "./builtin/mcp/config-schema.ts"; +import type { ExtensionKernelTools } from "./kernel-tools-context.ts"; export type { ExecOptions, ExecResult } from "../exec.ts"; export type { AppKeybinding, KeybindingsManager } from "../keybindings.ts"; @@ -489,19 +490,7 @@ export interface ExtensionContext { * Transient parent JS kernel-tool capability. Present only while a supported * JavaScript eval owns the host-tool context; absent on older runtimes. */ - readonly kernelTools?: { - describe(names: readonly string[]): Promise; - invoke( - request: { - name: string; - kernel_generation: number; - definition_revision: number; - args: unknown; - call_id: string; - }, - signal?: AbortSignal, - ): Promise; - }; + readonly kernelTools?: ExtensionKernelTools; /** Abort the current agent operation */ abort(source?: "user" | "system"): void; /** Whether there are queued messages waiting */ diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index 8fed7689c9..f66c98dca1 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -179,7 +179,14 @@ export { wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts"; -export { type ExtensionKernelTools, kernelToolsStorage } from "./core/extensions/kernel-tools-context.ts"; +export { + type ExtensionKernelTools, + type KernelToolsCapabilities, + type KernelToolsCapability, + type KernelToolsInvokeOptions, + type KernelToolsInvokeRequest, + kernelToolsStorage, +} from "./core/extensions/kernel-tools-context.ts"; // Notice primitives export { buildNoticeBox, diff --git a/packages/coding-agent/test/extension-kernel-tools-types.ts b/packages/coding-agent/test/extension-kernel-tools-types.ts new file mode 100644 index 0000000000..4aa9290df5 --- /dev/null +++ b/packages/coding-agent/test/extension-kernel-tools-types.ts @@ -0,0 +1,22 @@ +/** + * Compile-only fixture: `ExtensionContext.kernelTools` must match the shipped + * kernel-tools surface (invoke scope options + capabilities.invokeScope). + */ +import type { ExtensionContext } from "../src/core/extensions/types.ts"; + +const request = { + name: "fn", + kernel_generation: 0, + definition_revision: 0, + args: {}, + call_id: "call", +}; + +export function assertShippedKernelToolsSurface(ctx: ExtensionContext): void { + const kernelTools = ctx.kernelTools; + if (!kernelTools) return; + void kernelTools.invoke(request, { scope: { tools: { deny: ["write"] } } }); + void kernelTools.invoke(request, AbortSignal.abort()); + const invokeScope: true = kernelTools.capabilities.invokeScope; + void invokeScope; +} From 978e1450c9a1e7ff0c3247ad21a004f4d5878dc2 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 17 Sep 2026 00:18:19 +0900 Subject: [PATCH 2/2] fix(coding-agent): own the shipped kernelTools invoke-scope types Coding-agent declares ExtensionKernelTools, KernelToolInvokeOptions, and KernelToolInvokeScope. senpi-codemode aliases those types and satisfies ExtensionKernelTools so the implementation cannot drift. Drop the reverse import that created a workspace build cycle. Refs #1731 --- packages/coding-agent/CHANGELOG.md | 2 +- packages/coding-agent/src/changes.md | 18 +++++++++ .../src/core/extensions/changes.md | 19 +++++++++ .../core/extensions/kernel-tools-context.ts | 39 ++++++++++++++----- packages/coding-agent/src/index.ts | 6 +-- .../test/extension-kernel-tools-types.ts | 2 +- packages/senpi-codemode/CHANGELOG.md | 2 + packages/senpi-codemode/changes.md | 19 +++++++++ .../src/kernels/js/kernel-tools-types.ts | 35 ++++++----------- .../senpi-codemode/src/tool/run-eval-cell.ts | 19 ++++++--- 10 files changed, 117 insertions(+), 44 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 61fb298f8e..1c3cf30bf3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -8,7 +8,7 @@ ### Changed -- `ExtensionContext.kernelTools` is typed as the shipped kernel-tools surface: `invoke(request, options?)` accepts `{ signal?, scope? }` (a bare `AbortSignal` still works) and `capabilities.invokeScope` is present. `ExtensionKernelTools` is senpi-codemode's `KernelToolsCapability`, so the two cannot drift ([#1731](https://github.com/code-yeongyu/senpi/issues/1731)). +- `ExtensionContext.kernelTools` is typed as the shipped kernel-tools surface: `invoke(request, options?)` accepts `{ signal?, scope? }` (a bare `AbortSignal` still works) and `capabilities.invokeScope` is present. Coding-agent owns `ExtensionKernelTools`, `KernelToolInvokeOptions`, and `KernelToolInvokeScope`; senpi-codemode binds its implementation to those types so they cannot drift ([#1731](https://github.com/code-yeongyu/senpi/issues/1731)). ### Fixed diff --git a/packages/coding-agent/src/changes.md b/packages/coding-agent/src/changes.md index 5b5354c4ac..c238784067 100644 --- a/packages/coding-agent/src/changes.md +++ b/packages/coding-agent/src/changes.md @@ -1,5 +1,23 @@ # changes +## 2026-09-16 - Type kernelTools as the shipped invoke-scope surface (senpi#1731) + +### What changed + +- `packages/coding-agent/src/index.ts` exports `ExtensionKernelTools`, `KernelToolInvokeOptions`, and `KernelToolInvokeScope` as the public shipped kernel-tools surface. + +### Why + +- `packages/coding-agent/src/index.ts` is the public `@code-yeongyu/senpi` surface; typed consumers of `kernelTools` were still on the pre-#1765 `AbortSignal`-only declaration. + +### Why an extension could not handle it + +- Package index re-exports are owned by coding-agent; an extension cannot change the published host type. + +### Expected merge conflict zones + +- `packages/coding-agent/src/index.ts` adjacent to the `kernelToolsStorage` export. + ## 2026-09-16 - Answer `--help` without booting the engine (oh-my-openagent#8371) ### What changed diff --git a/packages/coding-agent/src/core/extensions/changes.md b/packages/coding-agent/src/core/extensions/changes.md index c1c2ff0c74..5559ff1152 100644 --- a/packages/coding-agent/src/core/extensions/changes.md +++ b/packages/coding-agent/src/core/extensions/changes.md @@ -1,5 +1,24 @@ # Core Extensions Changes +## 2026-09-16 - Type kernelTools as the shipped invoke-scope surface (senpi#1731) + +### What changed + +- `packages/coding-agent/src/core/extensions/types.ts` types `ExtensionContext.kernelTools` as `ExtensionKernelTools` instead of a hand-written `invoke(request, signal?: AbortSignal)` copy. +- `packages/coding-agent/src/core/extensions/kernel-tools-context.ts` owns `ExtensionKernelTools`, `KernelToolInvokeOptions`, and `KernelToolInvokeScope`: `invoke` accepts `{ signal?, scope? }` (bare `AbortSignal` still typed) and `capabilities.invokeScope` is present. + +### Why + +- `packages/coding-agent/src/core/extensions/types.ts` is the public `ExtensionContext` contract; coding-agent is the lower layer and must declare the shipped kernel-tools surface rather than import it from senpi-codemode. + +### Why an extension could not handle it + +- `packages/coding-agent/src/core/extensions/types.ts` owns `ExtensionContext`; an extension cannot replace the host's published type. + +### Expected merge conflict zones + +- `packages/coding-agent/src/core/extensions/types.ts` after `steeringSignal`; `packages/coding-agent/src/core/extensions/kernel-tools-context.ts` `ExtensionKernelTools` declaration. + ## 2026-09-16 - Host budget for session_shutdown handlers (senpi#1732) ### What changed diff --git a/packages/coding-agent/src/core/extensions/kernel-tools-context.ts b/packages/coding-agent/src/core/extensions/kernel-tools-context.ts index 910d4173e4..26b6715bc8 100644 --- a/packages/coding-agent/src/core/extensions/kernel-tools-context.ts +++ b/packages/coding-agent/src/core/extensions/kernel-tools-context.ts @@ -1,12 +1,33 @@ import { AsyncLocalStorage } from "node:async_hooks"; -import type { KernelToolsCapability } from "@code-yeongyu/senpi-codemode"; - -export type { KernelToolsCapability }; -export type ExtensionKernelTools = KernelToolsCapability; -export type { - KernelToolsCapabilities, - KernelToolsInvokeOptions, - KernelToolsInvokeRequest, -} from "@code-yeongyu/senpi-codemode"; + +/** Host tools a kernel-tool invocation's nested calls may reach. `deny` wins when both name the same tool. */ +export type KernelToolInvokeScope = { + tools?: { + allow?: string[]; + deny?: string[]; + }; +}; + +export type KernelToolInvokeOptions = { + signal?: AbortSignal; + scope?: KernelToolInvokeScope; +}; + +export type ExtensionKernelTools = { + readonly capabilities: { + readonly invokeScope: boolean; + }; + describe(names: readonly string[]): Promise; + invoke( + request: { + name: string; + kernel_generation: number; + definition_revision: number; + args: unknown; + call_id: string; + }, + options?: AbortSignal | KernelToolInvokeOptions, + ): Promise; +}; export const kernelToolsStorage = new AsyncLocalStorage(); diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index f66c98dca1..ff01f3bd47 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -181,10 +181,8 @@ export { } from "./core/extensions/index.ts"; export { type ExtensionKernelTools, - type KernelToolsCapabilities, - type KernelToolsCapability, - type KernelToolsInvokeOptions, - type KernelToolsInvokeRequest, + type KernelToolInvokeOptions, + type KernelToolInvokeScope, kernelToolsStorage, } from "./core/extensions/kernel-tools-context.ts"; // Notice primitives diff --git a/packages/coding-agent/test/extension-kernel-tools-types.ts b/packages/coding-agent/test/extension-kernel-tools-types.ts index 4aa9290df5..9a3c306684 100644 --- a/packages/coding-agent/test/extension-kernel-tools-types.ts +++ b/packages/coding-agent/test/extension-kernel-tools-types.ts @@ -17,6 +17,6 @@ export function assertShippedKernelToolsSurface(ctx: ExtensionContext): void { if (!kernelTools) return; void kernelTools.invoke(request, { scope: { tools: { deny: ["write"] } } }); void kernelTools.invoke(request, AbortSignal.abort()); - const invokeScope: true = kernelTools.capabilities.invokeScope; + const invokeScope: boolean = kernelTools.capabilities.invokeScope; void invokeScope; } diff --git a/packages/senpi-codemode/CHANGELOG.md b/packages/senpi-codemode/CHANGELOG.md index 8eaffe86ed..9e22dc71cc 100644 --- a/packages/senpi-codemode/CHANGELOG.md +++ b/packages/senpi-codemode/CHANGELOG.md @@ -10,6 +10,8 @@ ### Changed +- 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)). + ### Fixed ### Removed diff --git a/packages/senpi-codemode/changes.md b/packages/senpi-codemode/changes.md index 7aa73f81ad..6161c95cf4 100644 --- a/packages/senpi-codemode/changes.md +++ b/packages/senpi-codemode/changes.md @@ -1,5 +1,24 @@ # senpi-codemode fork changes +## 2026-09-16 - Bind kernel-tools types to the host declaration (senpi#1731) + +### What changed + +- `src/kernels/js/kernel-tools-types.ts` aliases `KernelToolsInvokeOptions` / `KernelToolsInvokeScope` / `KernelToolsHostScope` from `@code-yeongyu/senpi`'s `KernelToolInvokeOptions` / `KernelToolInvokeScope`, types `KernelToolsCapability` as `ExtensionKernelTools`, and `KERNEL_TOOLS_CAPABILITIES satisfies ExtensionKernelTools["capabilities"]`. +- `src/tool/run-eval-cell.ts` types the cell capability object with `satisfies ExtensionKernelTools`. + +### Why + +- Coding-agent owns the public `ExtensionContext.kernelTools` declaration; this package implements it. Importing the host types here is the drift check (#1731). + +### Why an extension could not handle it + +- The capability object is constructed by the codemode kernel and published onto the host `kernelToolsStorage`; only this package can bind that object to the host type. + +### Expected merge conflict zones + +- LOW: `src/kernels/js/kernel-tools-types.ts`, `src/tool/run-eval-cell.ts`. + ## 2026-09-16 - Call-scoped host-tool policy for kernel-tool invoke (#1731) ### What changed diff --git a/packages/senpi-codemode/src/kernels/js/kernel-tools-types.ts b/packages/senpi-codemode/src/kernels/js/kernel-tools-types.ts index 6b2cf21405..d96936bc58 100644 --- a/packages/senpi-codemode/src/kernels/js/kernel-tools-types.ts +++ b/packages/senpi-codemode/src/kernels/js/kernel-tools-types.ts @@ -1,3 +1,4 @@ +import type { ExtensionKernelTools, KernelToolInvokeOptions, KernelToolInvokeScope } from "@code-yeongyu/senpi"; import type { KernelToolErrorCode, KernelToolHostDenial, KernelToolHostDenialReason } from "./kernel-tools-errors.ts"; export type { KernelToolErrorCode, KernelToolHostDenial, KernelToolHostDenialReason }; @@ -31,36 +32,22 @@ export type KernelToolsDescribeResult = { readonly results: readonly KernelToolsDescribeEntry[]; }; -/** - * Host tools a kernel-tool invocation's nested calls may reach: `allow` narrows to exactly those - * names, `deny` refuses the named ones, and `deny` wins where both name the same tool. - */ -export type KernelToolsHostScope = { - readonly allow?: readonly string[]; - readonly deny?: readonly string[]; -}; - -/** Execution scope for one `invoke`; never persisted, dropped when that call settles (#1731). */ -export type KernelToolsInvokeScope = { - readonly tools?: KernelToolsHostScope; -}; - -export type KernelToolsInvokeOptions = { - readonly signal?: AbortSignal; - readonly scope?: KernelToolsInvokeScope; -}; +/** Host declaration (#1731); aliases so this package cannot drift from `@code-yeongyu/senpi`. */ +export type KernelToolsHostScope = NonNullable; +export type KernelToolsInvokeScope = KernelToolInvokeScope; +export type KernelToolsInvokeOptions = KernelToolInvokeOptions; /** Stable capability markers a consumer gates on before sending an option this runtime may not know. */ -export type KernelToolsCapabilities = { - readonly invokeScope: true; -}; +export type KernelToolsCapabilities = ExtensionKernelTools["capabilities"]; -export const KERNEL_TOOLS_CAPABILITIES: KernelToolsCapabilities = Object.freeze({ invokeScope: true }); +export const KERNEL_TOOLS_CAPABILITIES = Object.freeze({ + invokeScope: true as const, +}) satisfies ExtensionKernelTools["capabilities"]; export type KernelToolsCapability = { - readonly capabilities: KernelToolsCapabilities; + readonly capabilities: ExtensionKernelTools["capabilities"]; describe(names: readonly string[]): Promise; - invoke(request: KernelToolsInvokeRequest, options?: AbortSignal | KernelToolsInvokeOptions): Promise; + invoke: ExtensionKernelTools["invoke"]; }; export const KERNEL_TOOLS_UNSUPPORTED = { diff --git a/packages/senpi-codemode/src/tool/run-eval-cell.ts b/packages/senpi-codemode/src/tool/run-eval-cell.ts index bbbe1c273f..b4d380057c 100644 --- a/packages/senpi-codemode/src/tool/run-eval-cell.ts +++ b/packages/senpi-codemode/src/tool/run-eval-cell.ts @@ -1,8 +1,17 @@ import { randomUUID } from "node:crypto"; import { join } from "node:path"; -import { type AgentToolResult, type ExtensionContext, kernelToolsStorage } from "@code-yeongyu/senpi"; +import { + type AgentToolResult, + type ExtensionContext, + type ExtensionKernelTools, + kernelToolsStorage, +} from "@code-yeongyu/senpi"; import { DEFAULT_FOREGROUND_WINDOW_SECONDS, defaultCodemodeSettings } from "../config/settings.ts"; -import { KERNEL_TOOLS_CAPABILITIES, type KernelToolsCapability } from "../kernels/js/kernel-tools-types.ts"; +import { + KERNEL_TOOLS_CAPABILITIES, + type KernelToolsCapability, + type KernelToolsDescribeResult, +} from "../kernels/js/kernel-tools-types.ts"; import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts"; import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts"; import { CellHandler, type CellState } from "./cell-handler.ts"; @@ -220,12 +229,12 @@ function jsKernelTools(kernel: EvalKernel, language: string): KernelToolsCapabil if (language !== "js") return undefined; if (!("describeKernelTools" in kernel) || typeof kernel.describeKernelTools !== "function") return undefined; const js = kernel as EvalKernel & { - describeKernelTools: KernelToolsCapability["describe"]; - invokeKernelTool: KernelToolsCapability["invoke"]; + describeKernelTools: (names: readonly string[]) => Promise; + invokeKernelTool: ExtensionKernelTools["invoke"]; }; return { capabilities: KERNEL_TOOLS_CAPABILITIES, describe: (names) => js.describeKernelTools(names), invoke: (request, options) => js.invokeKernelTool(request, options), - }; + } satisfies ExtensionKernelTools; }