diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2d19e8482d..1c3cf30bf3 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. 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 ### Removed 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 aa17fc739b..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,6 +1,22 @@ import { AsyncLocalStorage } from "node:async_hooks"; +/** 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: { @@ -10,7 +26,7 @@ export type ExtensionKernelTools = { args: unknown; call_id: string; }, - signal?: AbortSignal, + options?: AbortSignal | KernelToolInvokeOptions, ): Promise; }; 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..ff01f3bd47 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -179,7 +179,12 @@ export { wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts"; -export { type ExtensionKernelTools, kernelToolsStorage } from "./core/extensions/kernel-tools-context.ts"; +export { + type ExtensionKernelTools, + type KernelToolInvokeOptions, + type KernelToolInvokeScope, + 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..9a3c306684 --- /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: 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; }