refactor(acp): simplify pass — wire exit-hook, drop dead idle timer
Post-implementation /simplify cleanup. Registers the plan-specified
process.on('exit', killAllProcesses) safety hook in index.ts (was missing —
closes an orphan-subprocess gap on hard exit). Removes the unwired idle-timer
(engine StuckTaskDetector + dispose()/registry teardown is authoritative per
KTD4a) and its tests. Fixes a stale dispositionFor doc comment, removes a
redundant identifier re-normalization in the event bridge, and clarifies why
the FusionCategory type keeps git_write/task_agent_mutation. Behavior-
preserving; 177 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
forceKill,
|
||||
spawnAgent,
|
||||
activeProcessCount,
|
||||
createIdleTimer,
|
||||
} from "../process-manager.js";
|
||||
|
||||
const spawned: ChildProcess[] = [];
|
||||
@@ -160,24 +159,3 @@ describe("spawnAgent", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createIdleTimer", () => {
|
||||
it("fires onIdle after the interval and reset re-arms", async () => {
|
||||
let fired = 0;
|
||||
const timer = createIdleTimer(30, () => {
|
||||
fired += 1;
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
expect(fired).toBe(1);
|
||||
timer.clear();
|
||||
});
|
||||
|
||||
it("clear prevents firing", async () => {
|
||||
let fired = 0;
|
||||
const timer = createIdleTimer(20, () => {
|
||||
fired += 1;
|
||||
});
|
||||
timer.clear();
|
||||
await new Promise((r) => setTimeout(r, 40));
|
||||
expect(fired).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,11 +98,10 @@ function buildResponse(sel: {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the per-category disposition from the live policy (exempt → allow).
|
||||
*
|
||||
* Exported so the fs-capabilities write path (U7) can reuse the exact same
|
||||
* per-category gate-reading logic for `file_write_delete` instead of duplicating
|
||||
* it (and risking drift from the U5 security floor).
|
||||
* Read the raw per-category disposition from the live policy (exempt → allow),
|
||||
* before the Risk S1 acknowledgement escalation. Callers that gate untrusted
|
||||
* actions should use `effectiveDisposition` (which applies the escalation); this
|
||||
* is the unescalated primitive it builds on.
|
||||
*/
|
||||
export function dispositionFor(
|
||||
category: FusionCategory | "exempt",
|
||||
|
||||
@@ -207,7 +207,8 @@ export function createEventBridge(callbacks: AcpCallbacks): EventBridge {
|
||||
// them (a partial update may only set status/output).
|
||||
if (update.title != null) tracked.title = safeTitle(update.title);
|
||||
if (update.kind != null) tracked.kind = update.kind;
|
||||
setTracked(update.toolCallId, tracked);
|
||||
// `id` is already bounded above; setTracked re-keys with the same value.
|
||||
setTracked(id, tracked);
|
||||
|
||||
const status = update.status;
|
||||
if (status !== "completed" && status !== "failed") {
|
||||
|
||||
@@ -2,6 +2,12 @@ import { definePlugin } from "@fusion/plugin-sdk";
|
||||
import type { FusionPlugin, PluginRuntimeFactory, PluginRuntimeManifestMetadata } from "@fusion/plugin-sdk";
|
||||
import { resolveCliSettings } from "./cli-spawn.js";
|
||||
import { AcpRuntimeAdapter } from "./runtime-adapter.js";
|
||||
import { killAllProcesses } from "./process-manager.js";
|
||||
|
||||
// Reap any live agent subprocesses on hard process exit so none are orphaned
|
||||
// (KTD4 — the registry SIGKILL is the authoritative no-orphan guarantee). Scoped
|
||||
// to tracked agent subprocesses only; never touches other processes/ports.
|
||||
process.on("exit", killAllProcesses);
|
||||
|
||||
export const ACP_RUNTIME_ID = "acp";
|
||||
const ACP_RUNTIME_VERSION = "0.1.0";
|
||||
|
||||
@@ -152,38 +152,3 @@ export function captureStderr(child: ChildProcess): () => string {
|
||||
});
|
||||
return () => buffer;
|
||||
}
|
||||
|
||||
// --- inactivity timer (KTD4: high ceiling, engine is the authority) --------
|
||||
|
||||
/** Default idle ceiling. The engine's StuckTaskDetector is authoritative. */
|
||||
export const DEFAULT_IDLE_CEILING_MS = 30 * 60_000;
|
||||
|
||||
export interface IdleTimer {
|
||||
reset(): void;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an inactivity timer that fires `onIdle` after `ms` of no `reset()`.
|
||||
* The default ceiling is intentionally high (KTD4) — this is a backstop, not
|
||||
* the primary aborter.
|
||||
*/
|
||||
export function createIdleTimer(ms: number, onIdle: () => void): IdleTimer {
|
||||
let handle: NodeJS.Timeout | undefined;
|
||||
const arm = () => {
|
||||
handle = setTimeout(onIdle, ms);
|
||||
// Don't keep the event loop alive solely for the backstop timer.
|
||||
handle.unref?.();
|
||||
};
|
||||
arm();
|
||||
return {
|
||||
reset() {
|
||||
if (handle) clearTimeout(handle);
|
||||
arm();
|
||||
},
|
||||
clear() {
|
||||
if (handle) clearTimeout(handle);
|
||||
handle = undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,8 +24,16 @@ export interface AcpCallbacks {
|
||||
export type GateDisposition = "allow" | "block" | "require-approval";
|
||||
|
||||
/**
|
||||
* Fusion action-gate categories the ACP `toolCall.kind` is classified into
|
||||
* (KTD3a). `"exempt"` is implicit (read-only / benign) and always allows.
|
||||
* Fusion action-gate categories — the full policy-rule keyspace, used to read
|
||||
* `permissionPolicy.rules[category]`. `"exempt"` is implicit (read-only / benign)
|
||||
* and always allows.
|
||||
*
|
||||
* Note: ACP's `ToolKind` has no git/task discriminator, so `classifyToolKind`
|
||||
* only ever produces `file_write_delete` / `command_execution` / `network_api`
|
||||
* (+ exempt). `git_write` and `task_agent_mutation` remain part of the category
|
||||
* type because the policy rules are keyed by all categories — git writes in
|
||||
* particular route through `file_write_delete` gating PLUS the path-jail's hard
|
||||
* `.git/**` reject (KTD6a), not a dedicated `git_write` classification.
|
||||
*/
|
||||
export type FusionCategory =
|
||||
| "git_write"
|
||||
|
||||
Reference in New Issue
Block a user