@akagilnc/pi-workflow-roles 0.1.2133 → 0.1.2139
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/compliance-transport.js +19 -24
- package/dist/engine-detour-tool.js +5 -15
- package/dist/engine-detour.js +3 -11
- package/dist/evidence-child-executor.js +3 -4
- package/dist/navigator-attendance.js +2 -3
- package/dist/public-cli/main.js +7 -61
- package/package.json +1 -1
- package/resources/engines/codex.md +3 -4
- package/resources/engines/grok-4.6.md +10 -9
- package/resources/engines/kimi.md +2 -2
- package/resources/engines/opus.md +2 -2
- package/src/collector-role.ts +5 -33
- package/src/compliance-transport.ts +19 -24
- package/src/engine-detour-tool.ts +6 -21
- package/src/engine-detour.ts +3 -15
- package/src/evidence-child-executor.ts +3 -4
- package/src/navigator-attendance.ts +2 -3
- package/src/role-runtime.ts +0 -3
- package/dist/package-owned-tool-idle.js +0 -223
- package/src/package-owned-tool-idle.ts +0 -257
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ ak-role config unset-engine judge
|
|
|
40
40
|
|
|
41
41
|
Receipts are typed, so callers compose roles without parsing prose; ordering and stopping stay caller-owned. Programmatic consumers derive contracts from the exported schemas in `src/package-contracts/`, not from this guide.
|
|
42
42
|
|
|
43
|
-
When a labor-engine detour fails and the seat continues the labor on the main road, the typed receipt may carry a mechanical `engineLaborFallback` field: `{ engine, failure, laborBy: "seat" }`. It appears only after a real detour failure that fell back to seat labor
|
|
43
|
+
When a labor-engine detour fails and the seat continues the labor on the main road, the typed receipt may carry a mechanical `engineLaborFallback` field: `{ engine, failure, laborBy: "seat" }`. It appears only after a real detour failure that fell back to seat labor—not on detour success or caller cancel. First failure wins for the activation; model-forged `engineLaborFallback` keys are stripped unless the package latch recorded one. Sole producer: `src/engine-labor-fallback.ts`; decision record: [ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md). This README only projects that contract.
|
|
44
44
|
|
|
45
45
|
## Call the roles
|
|
46
46
|
|
package/README.zh-CN.md
CHANGED
|
@@ -40,7 +40,7 @@ ak-role config unset-engine judge
|
|
|
40
40
|
|
|
41
41
|
回执是 typed 的,调用者不必解析散文即可组合角色;顺序与停止归调用者。编程消费者从 `src/package-contracts/` 导出推导契约,不从本文。
|
|
42
42
|
|
|
43
|
-
当劳务引擎绕行失败、座席回到主路继续劳务时,typed 回执可带机械字段 `engineLaborFallback`:`{ engine, failure, laborBy: "seat" }
|
|
43
|
+
当劳务引擎绕行失败、座席回到主路继续劳务时,typed 回执可带机械字段 `engineLaborFallback`:`{ engine, failure, laborBy: "seat" }`。仅在真实绕行失败并座席顶班后出现——成功绕行或调用方 cancel 不出现。同一次 activation 内先到先得;无包内 latch 时剥离模型伪造的 `engineLaborFallback` 键。唯一构造点:`src/engine-labor-fallback.ts`;决策记录:[ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md)。本文只投影该契约。
|
|
44
44
|
|
|
45
45
|
## 调用百官
|
|
46
46
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { executeAuditorChild, } from "./evidence-child-executor.js";
|
|
3
3
|
import { createAuditorDossierTool } from "./auditor-dossier-tool.js";
|
|
4
|
-
import { withPackageOwnedToolIdleSuspended } from "./package-owned-tool-idle.js";
|
|
5
4
|
/** Zero-projection kickoff — soul already carries dossier-fetch duty; no hand-delivered materials. */
|
|
6
5
|
export const AUDITOR_DOSSIER_PROMPT = "Audit the current run dossier.";
|
|
7
6
|
const nonblank = Type.String({ minLength: 1, pattern: "\\S" });
|
|
@@ -62,28 +61,24 @@ export function readComplianceCandidate(arguments_, usage) {
|
|
|
62
61
|
return { status: "audit-incomplete", observation: { kind: "object-status-unreadable", status: status === undefined ? "missing" : "unknown" }, candidate: arguments_, ...(usage === undefined ? {} : { usage }) };
|
|
63
62
|
}
|
|
64
63
|
export async function runComplianceAudit(options) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
77
|
-
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
78
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
79
|
-
});
|
|
80
|
-
if (receipt.noReceiptLifecycle !== undefined) {
|
|
81
|
-
return {
|
|
82
|
-
status: "no-receipt",
|
|
83
|
-
...receipt.noReceiptLifecycle,
|
|
84
|
-
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
64
|
+
const prompt = options.serializedInput ?? AUDITOR_DOSSIER_PROMPT;
|
|
65
|
+
const receipt = await executeAuditorChild({
|
|
66
|
+
tool: options.tool,
|
|
67
|
+
dossierTool: createAuditorDossierTool(options.runDirectory),
|
|
68
|
+
systemPrompt: options.systemPrompt,
|
|
69
|
+
prompt,
|
|
70
|
+
roleLabel: options.roleLabel,
|
|
71
|
+
context: options.context,
|
|
72
|
+
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
73
|
+
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
74
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
88
75
|
});
|
|
76
|
+
if (receipt.noReceiptLifecycle !== undefined) {
|
|
77
|
+
return {
|
|
78
|
+
status: "no-receipt",
|
|
79
|
+
...receipt.noReceiptLifecycle,
|
|
80
|
+
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
89
84
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { ENGINE_DETOUR_ALREADY_USED_DIAGNOSTIC, ENGINE_DETOUR_TOOL_NAME, engineDetourFailureDiagnostic, engineNameFromEnv, isEngineDetourFailure, runEngineDetourOnce, } from "./engine-detour.js";
|
|
3
3
|
import { activationEngineLaborFallbackLatch, recordEngineLaborFallback, } from "./engine-labor-fallback.js";
|
|
4
|
-
import { isPackageOwnedToolIdleTimeoutError, pokePackageOwnedToolIdle, wrapPackageOwnedToolDefinition, } from "./package-owned-tool-idle.js";
|
|
5
4
|
const engineDetourArgsSchema = Type.Object({
|
|
6
5
|
argv: Type.Array(Type.String({ minLength: 1 }), {
|
|
7
6
|
minItems: 1,
|
|
@@ -23,14 +22,8 @@ function seatFallbackToolResult(field, failure) {
|
|
|
23
22
|
},
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
|
-
/** Caller/upper-layer cancel must propagate;
|
|
25
|
+
/** Caller/upper-layer cancel must propagate; process failure is seat-fallback, not cancel. */
|
|
27
26
|
function isCallerCancellation(error, signal) {
|
|
28
|
-
if (isPackageOwnedToolIdleTimeoutError(error))
|
|
29
|
-
return false;
|
|
30
|
-
if (signal !== undefined &&
|
|
31
|
-
isPackageOwnedToolIdleTimeoutError(signal.reason)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
27
|
if (signal?.aborted === true)
|
|
35
28
|
return true;
|
|
36
29
|
if (typeof error === "object" &&
|
|
@@ -44,13 +37,13 @@ function isCallerCancellation(error, signal) {
|
|
|
44
37
|
* Build one once-latch detour tool definition for a configured engine name.
|
|
45
38
|
* `latch` is shared so parent registration can reset between activations.
|
|
46
39
|
* `fail` owns host abort (parent) vs throw (evidence child) for tool misuse only.
|
|
47
|
-
* Engine process failure (nonzero/empty/spawn
|
|
40
|
+
* Engine process failure (nonzero/empty/spawn) soft-returns seat fallback (#380).
|
|
48
41
|
* Caller AbortSignal cancel propagates without writing fallback.
|
|
49
42
|
*/
|
|
50
43
|
export function createEngineDetourToolDefinition(input) {
|
|
51
44
|
const latch = input.latch ?? { used: false };
|
|
52
45
|
const engineName = input.engineName;
|
|
53
|
-
return
|
|
46
|
+
return {
|
|
54
47
|
name: ENGINE_DETOUR_TOOL_NAME,
|
|
55
48
|
label: "Engine Detour",
|
|
56
49
|
description: `Run one labor-engine subprocess (engine=${engineName}) and return its stdout to this session. Call at most once per activation. Build argv from the host CLI actual interface for this engine name; when optional packaged notes are present in the session prompt, follow those bytes too.`,
|
|
@@ -83,17 +76,14 @@ export function createEngineDetourToolDefinition(input) {
|
|
|
83
76
|
};
|
|
84
77
|
let result;
|
|
85
78
|
try {
|
|
86
|
-
// Byte activity on stdout/stderr touches the outer package-owned idle clock
|
|
87
|
-
// (183s silence law unchanged). True hangs still die; slow streaming engines live.
|
|
88
79
|
result = await runEngineDetourOnce({
|
|
89
80
|
argv,
|
|
90
81
|
cwd: ctx.cwd,
|
|
91
82
|
...(signal === undefined ? {} : { signal }),
|
|
92
|
-
onOutputActivity: pokePackageOwnedToolIdle,
|
|
93
83
|
});
|
|
94
84
|
}
|
|
95
85
|
catch (error) {
|
|
96
|
-
// Caller cancel: propagate.
|
|
86
|
+
// Caller cancel: propagate. Spawn/engine failure: seat fallback.
|
|
97
87
|
if (isCallerCancellation(error, signal)) {
|
|
98
88
|
throw error;
|
|
99
89
|
}
|
|
@@ -111,7 +101,7 @@ export function createEngineDetourToolDefinition(input) {
|
|
|
111
101
|
},
|
|
112
102
|
};
|
|
113
103
|
},
|
|
114
|
-
}
|
|
104
|
+
};
|
|
115
105
|
}
|
|
116
106
|
/**
|
|
117
107
|
* Register the engine-generic detour tool once for this process when any role has
|
package/dist/engine-detour.js
CHANGED
|
@@ -48,8 +48,8 @@ export async function runEngineDetourOnce(input) {
|
|
|
48
48
|
return await new Promise((resolve, reject) => {
|
|
49
49
|
let settled = false;
|
|
50
50
|
const signal = input.signal;
|
|
51
|
-
// Own abort→kill explicitly so rejection preserves signal.reason (caller cancel
|
|
52
|
-
//
|
|
51
|
+
// Own abort→kill explicitly so rejection preserves signal.reason (caller cancel).
|
|
52
|
+
// Do not pass `signal` to spawn (Node replaces reason).
|
|
53
53
|
const child = spawn(command, args, {
|
|
54
54
|
cwd: input.cwd,
|
|
55
55
|
env: input.env ?? process.env,
|
|
@@ -57,18 +57,11 @@ export async function runEngineDetourOnce(input) {
|
|
|
57
57
|
});
|
|
58
58
|
let stdout = "";
|
|
59
59
|
let stderr = "";
|
|
60
|
-
const noteActivity = (chunk) => {
|
|
61
|
-
if (chunk.length === 0)
|
|
62
|
-
return;
|
|
63
|
-
input.onOutputActivity?.();
|
|
64
|
-
};
|
|
65
60
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
66
61
|
stdout += chunk;
|
|
67
|
-
noteActivity(chunk);
|
|
68
62
|
});
|
|
69
63
|
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
70
64
|
stderr += chunk;
|
|
71
|
-
noteActivity(chunk);
|
|
72
65
|
});
|
|
73
66
|
const fail = (error) => {
|
|
74
67
|
if (settled)
|
|
@@ -89,8 +82,7 @@ export async function runEngineDetourOnce(input) {
|
|
|
89
82
|
resolve(result);
|
|
90
83
|
};
|
|
91
84
|
const onAbort = () => {
|
|
92
|
-
// Fail synchronously so
|
|
93
|
-
// before the outer package-owned idle hard-reject drain.
|
|
85
|
+
// Fail synchronously so caller-cancel soft-settle preserves signal.reason.
|
|
94
86
|
fail(signal !== undefined ? abortReasonError(signal) : new Error("aborted"));
|
|
95
87
|
try {
|
|
96
88
|
child.kill("SIGTERM");
|
|
@@ -11,7 +11,6 @@ import { AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE, AUDITOR_PARENT_ATTEMPT_BINDING_E
|
|
|
11
11
|
import { createEngineDetourToolDefinition } from "./engine-detour-tool.js";
|
|
12
12
|
import { engineNameFromEnv } from "./engine-detour.js";
|
|
13
13
|
import { appendEngineSessionMaterial, engineSessionMaterialFromOptions, } from "./package-resources/engine-material.js";
|
|
14
|
-
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
|
|
15
14
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
|
|
16
15
|
import { REVIEWER_VERIFICATION_BOUNDARY } from "./reviewer-construction.js";
|
|
17
16
|
import { createStreamIdleGuard, isStreamIdleTimeoutError } from "./stream-idle-guard.js";
|
|
@@ -599,7 +598,7 @@ export async function executeAuditorChild(options) {
|
|
|
599
598
|
let decisionToolFailure;
|
|
600
599
|
const decisionToolFailures = new Map();
|
|
601
600
|
const delivery = createReceiptDeliveryPolicy();
|
|
602
|
-
const tool =
|
|
601
|
+
const tool = {
|
|
603
602
|
...options.tool,
|
|
604
603
|
label: options.roleLabel,
|
|
605
604
|
async execute(...args) {
|
|
@@ -625,7 +624,7 @@ export async function executeAuditorChild(options) {
|
|
|
625
624
|
throw error;
|
|
626
625
|
}
|
|
627
626
|
},
|
|
628
|
-
}
|
|
627
|
+
};
|
|
629
628
|
const parentSessionManager = options.context.sessionManager;
|
|
630
629
|
const parentHeader = parentSessionManager?.getHeader?.();
|
|
631
630
|
const parentSessionFile = parentSessionManager?.getSessionFile?.();
|
|
@@ -644,7 +643,7 @@ export async function executeAuditorChild(options) {
|
|
|
644
643
|
thinkingLevel: options.context.thinkingLevel ?? "off",
|
|
645
644
|
modelRuntime: inherited.runtime,
|
|
646
645
|
systemPrompt: options.systemPrompt,
|
|
647
|
-
customTools: [
|
|
646
|
+
customTools: [{ ...options.dossierTool, label: options.roleLabel }, tool],
|
|
648
647
|
sessionManager: auditorSessionManager,
|
|
649
648
|
});
|
|
650
649
|
const binding = {
|
|
@@ -14,7 +14,6 @@ import { PACKAGED_ROLE_REGISTRY, packagedRoleMetadata } from "./packaged-role-re
|
|
|
14
14
|
import { openInProcessAgentSession } from "./in-process-session.js";
|
|
15
15
|
import { renderPublicAkRoleCommand } from "./public-command-renderer.js";
|
|
16
16
|
import { issueRoot, subjectPath } from "./work-subject-identity.js";
|
|
17
|
-
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.js";
|
|
18
17
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.js";
|
|
19
18
|
import { recordTypedProviderHttpStatus } from "./typed-provider-http.js";
|
|
20
19
|
import {
|
|
@@ -247,7 +246,7 @@ function parseNavigatorModelSetting(value) {
|
|
|
247
246
|
return { provider, model, thinkingLevel: suffix === "max" ? "max" : "off" };
|
|
248
247
|
}
|
|
249
248
|
function createNavigatorPrepareTool(onOutput) {
|
|
250
|
-
return
|
|
249
|
+
return {
|
|
251
250
|
name: NAVIGATOR_PREPARE_TOOL_NAME,
|
|
252
251
|
label: "Navigator preparation",
|
|
253
252
|
description: "Submit Navigator direction advice. Provide candidates with next.role (phase when meaningful). route/matches/reason/command are optional context, not acceptance gates.",
|
|
@@ -256,7 +255,7 @@ function createNavigatorPrepareTool(onOutput) {
|
|
|
256
255
|
onOutput(value);
|
|
257
256
|
return { content: [{ type: "text", text: "Navigator preparation accepted" }], details: value, terminate: true };
|
|
258
257
|
}
|
|
259
|
-
}
|
|
258
|
+
};
|
|
260
259
|
}
|
|
261
260
|
function selectNavigatorCandidate(candidates, settlement) {
|
|
262
261
|
if (settlement.kind !== "accepted") return void 0;
|
package/dist/public-cli/main.js
CHANGED
|
@@ -19302,64 +19302,6 @@ var init_engine_labor_fallback = __esm({
|
|
|
19302
19302
|
}
|
|
19303
19303
|
});
|
|
19304
19304
|
|
|
19305
|
-
// src/stream-idle-guard.ts
|
|
19306
|
-
var init_stream_idle_guard = __esm({
|
|
19307
|
-
"src/stream-idle-guard.ts"() {
|
|
19308
|
-
"use strict";
|
|
19309
|
-
}
|
|
19310
|
-
});
|
|
19311
|
-
|
|
19312
|
-
// src/stderr-jsonl.ts
|
|
19313
|
-
var init_stderr_jsonl = __esm({
|
|
19314
|
-
"src/stderr-jsonl.ts"() {
|
|
19315
|
-
"use strict";
|
|
19316
|
-
}
|
|
19317
|
-
});
|
|
19318
|
-
|
|
19319
|
-
// src/tool-execution-observation.ts
|
|
19320
|
-
var observationBase, toolExecutionObservationRecordSchema;
|
|
19321
|
-
var init_tool_execution_observation = __esm({
|
|
19322
|
-
"src/tool-execution-observation.ts"() {
|
|
19323
|
-
"use strict";
|
|
19324
|
-
init_build();
|
|
19325
|
-
init_value2();
|
|
19326
|
-
init_stderr_jsonl();
|
|
19327
|
-
observationBase = {
|
|
19328
|
-
role: typebox_exports.String({ minLength: 1 }),
|
|
19329
|
-
toolCallId: typebox_exports.String({ minLength: 1 }),
|
|
19330
|
-
toolName: typebox_exports.String({ minLength: 1 }),
|
|
19331
|
-
timestamp: typebox_exports.String({ format: "date-time" })
|
|
19332
|
-
};
|
|
19333
|
-
toolExecutionObservationRecordSchema = typebox_exports.Union([
|
|
19334
|
-
typebox_exports.Object({
|
|
19335
|
-
...observationBase,
|
|
19336
|
-
event: typebox_exports.Literal("tool_execution_start")
|
|
19337
|
-
}, { additionalProperties: true }),
|
|
19338
|
-
typebox_exports.Object({
|
|
19339
|
-
...observationBase,
|
|
19340
|
-
event: typebox_exports.Literal("tool_execution_update")
|
|
19341
|
-
}, { additionalProperties: true }),
|
|
19342
|
-
typebox_exports.Object({
|
|
19343
|
-
...observationBase,
|
|
19344
|
-
event: typebox_exports.Literal("tool_execution_end"),
|
|
19345
|
-
isError: typebox_exports.Boolean()
|
|
19346
|
-
}, { additionalProperties: true })
|
|
19347
|
-
]);
|
|
19348
|
-
}
|
|
19349
|
-
});
|
|
19350
|
-
|
|
19351
|
-
// src/package-owned-tool-idle.ts
|
|
19352
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19353
|
-
var packageOwnedToolIdleScope;
|
|
19354
|
-
var init_package_owned_tool_idle = __esm({
|
|
19355
|
-
"src/package-owned-tool-idle.ts"() {
|
|
19356
|
-
"use strict";
|
|
19357
|
-
init_stream_idle_guard();
|
|
19358
|
-
init_tool_execution_observation();
|
|
19359
|
-
packageOwnedToolIdleScope = new AsyncLocalStorage();
|
|
19360
|
-
}
|
|
19361
|
-
});
|
|
19362
|
-
|
|
19363
19305
|
// src/engine-detour-tool.ts
|
|
19364
19306
|
var engineDetourArgsSchema;
|
|
19365
19307
|
var init_engine_detour_tool = __esm({
|
|
@@ -19368,7 +19310,6 @@ var init_engine_detour_tool = __esm({
|
|
|
19368
19310
|
init_build();
|
|
19369
19311
|
init_engine_detour();
|
|
19370
19312
|
init_engine_labor_fallback();
|
|
19371
|
-
init_package_owned_tool_idle();
|
|
19372
19313
|
engineDetourArgsSchema = typebox_exports.Object(
|
|
19373
19314
|
{
|
|
19374
19315
|
argv: typebox_exports.Array(typebox_exports.String({ minLength: 1 }), {
|
|
@@ -19411,6 +19352,13 @@ var init_receipt_delivery_policy = __esm({
|
|
|
19411
19352
|
}
|
|
19412
19353
|
});
|
|
19413
19354
|
|
|
19355
|
+
// src/stream-idle-guard.ts
|
|
19356
|
+
var init_stream_idle_guard = __esm({
|
|
19357
|
+
"src/stream-idle-guard.ts"() {
|
|
19358
|
+
"use strict";
|
|
19359
|
+
}
|
|
19360
|
+
});
|
|
19361
|
+
|
|
19414
19362
|
// src/evidence-child-executor.ts
|
|
19415
19363
|
var init_evidence_child_executor = __esm({
|
|
19416
19364
|
"src/evidence-child-executor.ts"() {
|
|
@@ -19419,7 +19367,6 @@ var init_evidence_child_executor = __esm({
|
|
|
19419
19367
|
init_engine_detour_tool();
|
|
19420
19368
|
init_engine_detour();
|
|
19421
19369
|
init_engine_material();
|
|
19422
|
-
init_package_owned_tool_idle();
|
|
19423
19370
|
init_receipt_delivery_policy();
|
|
19424
19371
|
init_reviewer_construction();
|
|
19425
19372
|
init_stream_idle_guard();
|
|
@@ -19452,7 +19399,6 @@ var init_compliance_transport = __esm({
|
|
|
19452
19399
|
init_build();
|
|
19453
19400
|
init_evidence_child_executor();
|
|
19454
19401
|
init_auditor_dossier_tool();
|
|
19455
|
-
init_package_owned_tool_idle();
|
|
19456
19402
|
nonblank2 = typebox_exports.String({ minLength: 1, pattern: "\\S" });
|
|
19457
19403
|
decisionGateSchema = typebox_exports.Object({ question: nonblank2, options: typebox_exports.Array(nonblank2, { minItems: 1 }) }, { additionalProperties: false });
|
|
19458
19404
|
complianceDecisionSchema = typebox_exports.Object({ status: typebox_exports.Unknown({ description: "Auditor decision status." }), violations: typebox_exports.Array(nonblank2, { description: "Observed compliance violations." }), conflicts: typebox_exports.Array(nonblank2, { description: "Unresolved authority or execution conflicts." }), decisionGate: typebox_exports.Union([decisionGateSchema, typebox_exports.Null()], { description: "Escalation question and available options." }) }, { additionalProperties: true, required: [] });
|
package/package.json
CHANGED
|
@@ -33,10 +33,9 @@ codex exec --skip-git-repo-check --ephemeral -C "$PROJECT_ROOT" "YOUR_LABOR_PROM
|
|
|
33
33
|
|
|
34
34
|
Default (non-`--json`) mode prints the session banner and progress on stderr
|
|
35
35
|
(measured on this host). Collect the labor body from stdout so that stderr log
|
|
36
|
-
noise is not mixed into the returned body
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
types include `thread.started`, `turn.started`, `item.completed`, `error`,
|
|
36
|
+
noise is not mixed into the returned body. Use `--json` only when the seat itself
|
|
37
|
+
needs machine-readable event rows (those rows land on stdout as JSONL; measured
|
|
38
|
+
event types include `thread.started`, `turn.started`, `item.completed`, `error`,
|
|
40
39
|
`turn.failed`):
|
|
41
40
|
|
|
42
41
|
```bash
|
|
@@ -27,15 +27,16 @@ grok --prompt-file /path/to/labor-prompt.md -m grok-4.6 --always-approve --outpu
|
|
|
27
27
|
- `--output-format plain` keeps stdout clean for capture — but it stays
|
|
28
28
|
silent until the run finishes. **For labor longer than ~2 minutes use
|
|
29
29
|
`--output-format streaming-json` instead**: it emits NDJSON events
|
|
30
|
-
(thought/text deltas) continuously from the first second,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
(thought/text deltas) continuously from the first second, so long runs stay
|
|
31
|
+
observable instead of appearing hung. Reconstruct the final answer by
|
|
32
|
+
concatenating each NDJSON object's `data` where `type == "text"`, in stream
|
|
33
|
+
order; `type == "end"` (stopReason end_turn) marks completion. Do not treat
|
|
34
|
+
`thought` events as the answer (live-verified stream shape 2026-08-21).
|
|
35
|
+
- **Always pass `--reasoning-effort <low|medium|high>`** matching the effort
|
|
36
|
+
tier ordered in the labor mandate (verified live 2026-08-21: flag exists,
|
|
37
|
+
alias `--effort`; a low-tier run completed correctly). If the mandate names
|
|
38
|
+
no tier, use the seat's ordered thinking tier; never omit the flag — the
|
|
39
|
+
CLI default is not guaranteed to match the ordered tier.
|
|
39
40
|
|
|
40
41
|
## Failure handling
|
|
41
42
|
|
|
@@ -31,8 +31,8 @@ kimi -m <model-alias> -p "YOUR_LABOR_PROMPT"
|
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Use `--output-format stream-json` (choices measured on this host: `text`,
|
|
34
|
-
`stream-json`; default is `text`)
|
|
35
|
-
|
|
34
|
+
`stream-json`; default is `text`) when long labor needs progressive observability
|
|
35
|
+
while the engine works. Take the labor body from
|
|
36
36
|
`{"role":"assistant","content":...}` rows, not from `role:meta` rows:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
@@ -33,8 +33,8 @@ claude -p --model opus --verbose --output-format=stream-json "YOUR_LABOR_PROMPT"
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Use `--output-format=stream-json` (choices measured on this host: `text`, `json`,
|
|
36
|
-
`stream-json`)
|
|
37
|
-
|
|
36
|
+
`stream-json`) when long labor needs progressive observability while the engine
|
|
37
|
+
works; take the labor body from the final `result` event's `result` field,
|
|
38
38
|
not from intermediate stream rows.
|
|
39
39
|
|
|
40
40
|
Prefer `claude --help` on the host over any remembered flag set. Do not wrap this
|
package/src/collector-role.ts
CHANGED
|
@@ -61,10 +61,6 @@ const requestSchema = collectorRequestArgsSchema;
|
|
|
61
61
|
const waitSchema = collectorWaitArgsSchema;
|
|
62
62
|
const outputSchema = collectorOutputArgsSchema;
|
|
63
63
|
|
|
64
|
-
// Well below the fixed 183s package-tool silence clock while avoiding noisy UI
|
|
65
|
-
// churn during Collector's legal five-minute wait.
|
|
66
|
-
const COLLECTOR_WAIT_PROGRESS_INTERVAL_MS = 60_000;
|
|
67
|
-
|
|
68
64
|
type RequestParams = Static<typeof requestSchema>;
|
|
69
65
|
type WaitParams = Static<typeof waitSchema>;
|
|
70
66
|
type OutputParams = Static<typeof outputSchema>;
|
|
@@ -377,40 +373,18 @@ export function createCollectorRoleRuntime(
|
|
|
377
373
|
"Call ak_collector_wait with a positive durationMs; runtime caps each wait to five minutes and to remaining eligibility.",
|
|
378
374
|
],
|
|
379
375
|
parameters: waitSchema,
|
|
380
|
-
async execute(toolCallId, params: WaitParams, signal,
|
|
376
|
+
async execute(toolCallId, params: WaitParams, signal, _onUpdate, ctx) {
|
|
381
377
|
if (activation === undefined) {
|
|
382
378
|
throw new Error("Collector is not activated");
|
|
383
379
|
}
|
|
384
|
-
const currentActivation = activation;
|
|
385
|
-
let progressTimer: ReturnType<typeof setTimeout> | undefined;
|
|
386
380
|
try {
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
let lastElapsedMs = 0;
|
|
390
|
-
const scheduleProgress = (): void => {
|
|
391
|
-
if (onUpdate === undefined) return;
|
|
392
|
-
progressTimer = setTimeout(() => {
|
|
393
|
-
const elapsedMs = Math.max(
|
|
394
|
-
0,
|
|
395
|
-
Math.floor(currentActivation.clock.monoNow() - waitStartedMono),
|
|
396
|
-
);
|
|
397
|
-
if (elapsedMs > lastElapsedMs) {
|
|
398
|
-
lastElapsedMs = elapsedMs;
|
|
399
|
-
onUpdate({
|
|
400
|
-
content: [],
|
|
401
|
-
details: { kind: "collector_wait_progress", elapsedMs },
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
scheduleProgress();
|
|
405
|
-
}, COLLECTOR_WAIT_PROGRESS_INTERVAL_MS);
|
|
406
|
-
};
|
|
407
|
-
scheduleProgress();
|
|
408
|
-
const details = await currentActivation.ledger.wait(
|
|
381
|
+
activation.ledger.beginOperational(COLLECTOR_WAIT_TOOL, toolCallId);
|
|
382
|
+
const details = await activation.ledger.wait(
|
|
409
383
|
params,
|
|
410
|
-
|
|
384
|
+
activation.clock,
|
|
411
385
|
signal,
|
|
412
386
|
);
|
|
413
|
-
|
|
387
|
+
activation.ledger.completeOperational(toolCallId);
|
|
414
388
|
return {
|
|
415
389
|
content: [{
|
|
416
390
|
type: "text" as const,
|
|
@@ -420,8 +394,6 @@ export function createCollectorRoleRuntime(
|
|
|
420
394
|
};
|
|
421
395
|
} catch (error) {
|
|
422
396
|
hostActions.failInfrastructure(error, ctx);
|
|
423
|
-
} finally {
|
|
424
|
-
if (progressTimer !== undefined) clearTimeout(progressTimer);
|
|
425
397
|
}
|
|
426
398
|
},
|
|
427
399
|
});
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "./evidence-child-executor.ts";
|
|
8
8
|
import { createAuditorDossierTool } from "./auditor-dossier-tool.ts";
|
|
9
9
|
import type { DossierObservation } from "./dossier-resolution.ts";
|
|
10
|
-
import { withPackageOwnedToolIdleSuspended } from "./package-owned-tool-idle.ts";
|
|
11
10
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
12
11
|
|
|
13
12
|
export type ComplianceCompletion = AuditorCompletion;
|
|
@@ -117,28 +116,24 @@ export type RunComplianceAuditOptions = {
|
|
|
117
116
|
};
|
|
118
117
|
|
|
119
118
|
export async function runComplianceAudit(options: RunComplianceAuditOptions): Promise<ComplianceDecision> {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
132
|
-
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
133
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
134
|
-
});
|
|
135
|
-
if (receipt.noReceiptLifecycle !== undefined) {
|
|
136
|
-
return {
|
|
137
|
-
status: "no-receipt",
|
|
138
|
-
...receipt.noReceiptLifecycle,
|
|
139
|
-
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
119
|
+
const prompt = options.serializedInput ?? AUDITOR_DOSSIER_PROMPT;
|
|
120
|
+
const receipt = await executeAuditorChild({
|
|
121
|
+
tool: options.tool,
|
|
122
|
+
dossierTool: createAuditorDossierTool(options.runDirectory),
|
|
123
|
+
systemPrompt: options.systemPrompt,
|
|
124
|
+
prompt,
|
|
125
|
+
roleLabel: options.roleLabel,
|
|
126
|
+
context: options.context,
|
|
127
|
+
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
128
|
+
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
129
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
143
130
|
});
|
|
131
|
+
if (receipt.noReceiptLifecycle !== undefined) {
|
|
132
|
+
return {
|
|
133
|
+
status: "no-receipt",
|
|
134
|
+
...receipt.noReceiptLifecycle,
|
|
135
|
+
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
144
139
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Evidence-child legs install the same definition via customTools (no spawn in role modules).
|
|
5
5
|
* #380: engine process failure soft-returns so the seat rejoins the main road; declaration
|
|
6
6
|
* is recorded once via engine-labor-fallback (no fail-closed reject-leg).
|
|
7
|
-
* Caller AbortSignal cancel propagates without fallback
|
|
7
|
+
* Caller AbortSignal cancel propagates without fallback.
|
|
8
8
|
*/
|
|
9
9
|
import type {
|
|
10
10
|
AgentToolResult,
|
|
@@ -27,11 +27,6 @@ import {
|
|
|
27
27
|
recordEngineLaborFallback,
|
|
28
28
|
type EngineLaborFallbackField,
|
|
29
29
|
} from "./engine-labor-fallback.ts";
|
|
30
|
-
import {
|
|
31
|
-
isPackageOwnedToolIdleTimeoutError,
|
|
32
|
-
pokePackageOwnedToolIdle,
|
|
33
|
-
wrapPackageOwnedToolDefinition,
|
|
34
|
-
} from "./package-owned-tool-idle.ts";
|
|
35
30
|
|
|
36
31
|
const engineDetourArgsSchema = Type.Object(
|
|
37
32
|
{
|
|
@@ -81,18 +76,11 @@ function seatFallbackToolResult(
|
|
|
81
76
|
};
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
/** Caller/upper-layer cancel must propagate;
|
|
79
|
+
/** Caller/upper-layer cancel must propagate; process failure is seat-fallback, not cancel. */
|
|
85
80
|
function isCallerCancellation(
|
|
86
81
|
error: unknown,
|
|
87
82
|
signal: AbortSignal | undefined,
|
|
88
83
|
): boolean {
|
|
89
|
-
if (isPackageOwnedToolIdleTimeoutError(error)) return false;
|
|
90
|
-
if (
|
|
91
|
-
signal !== undefined &&
|
|
92
|
-
isPackageOwnedToolIdleTimeoutError(signal.reason)
|
|
93
|
-
) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
84
|
if (signal?.aborted === true) return true;
|
|
97
85
|
if (
|
|
98
86
|
typeof error === "object" &&
|
|
@@ -108,7 +96,7 @@ function isCallerCancellation(
|
|
|
108
96
|
* Build one once-latch detour tool definition for a configured engine name.
|
|
109
97
|
* `latch` is shared so parent registration can reset between activations.
|
|
110
98
|
* `fail` owns host abort (parent) vs throw (evidence child) for tool misuse only.
|
|
111
|
-
* Engine process failure (nonzero/empty/spawn
|
|
99
|
+
* Engine process failure (nonzero/empty/spawn) soft-returns seat fallback (#380).
|
|
112
100
|
* Caller AbortSignal cancel propagates without writing fallback.
|
|
113
101
|
*/
|
|
114
102
|
export function createEngineDetourToolDefinition(input: {
|
|
@@ -118,7 +106,7 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
118
106
|
}): ToolDefinition {
|
|
119
107
|
const latch = input.latch ?? { used: false };
|
|
120
108
|
const engineName = input.engineName;
|
|
121
|
-
return
|
|
109
|
+
return {
|
|
122
110
|
name: ENGINE_DETOUR_TOOL_NAME,
|
|
123
111
|
label: "Engine Detour",
|
|
124
112
|
description:
|
|
@@ -170,16 +158,13 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
170
158
|
|
|
171
159
|
let result: Awaited<ReturnType<typeof runEngineDetourOnce>>;
|
|
172
160
|
try {
|
|
173
|
-
// Byte activity on stdout/stderr touches the outer package-owned idle clock
|
|
174
|
-
// (183s silence law unchanged). True hangs still die; slow streaming engines live.
|
|
175
161
|
result = await runEngineDetourOnce({
|
|
176
162
|
argv,
|
|
177
163
|
cwd: ctx.cwd,
|
|
178
164
|
...(signal === undefined ? {} : { signal }),
|
|
179
|
-
onOutputActivity: pokePackageOwnedToolIdle,
|
|
180
165
|
});
|
|
181
166
|
} catch (error) {
|
|
182
|
-
// Caller cancel: propagate.
|
|
167
|
+
// Caller cancel: propagate. Spawn/engine failure: seat fallback.
|
|
183
168
|
if (isCallerCancellation(error, signal)) {
|
|
184
169
|
throw error;
|
|
185
170
|
}
|
|
@@ -200,7 +185,7 @@ export function createEngineDetourToolDefinition(input: {
|
|
|
200
185
|
},
|
|
201
186
|
};
|
|
202
187
|
},
|
|
203
|
-
}
|
|
188
|
+
} as ToolDefinition;
|
|
204
189
|
}
|
|
205
190
|
|
|
206
191
|
/**
|
package/src/engine-detour.ts
CHANGED
|
@@ -44,11 +44,6 @@ export type EngineDetourRunInput = Readonly<{
|
|
|
44
44
|
cwd: string;
|
|
45
45
|
env?: NodeJS.ProcessEnv;
|
|
46
46
|
signal?: AbortSignal;
|
|
47
|
-
/**
|
|
48
|
-
* Called when the child emits at least one byte on stdout or stderr.
|
|
49
|
-
* Host wires this to the package-owned idle clock (activity = touch); no timer here.
|
|
50
|
-
*/
|
|
51
|
-
onOutputActivity?: () => void;
|
|
52
47
|
}>;
|
|
53
48
|
|
|
54
49
|
function abortReasonError(signal: AbortSignal): Error {
|
|
@@ -78,8 +73,8 @@ export async function runEngineDetourOnce(
|
|
|
78
73
|
return await new Promise<EngineDetourResult>((resolve, reject) => {
|
|
79
74
|
let settled = false;
|
|
80
75
|
const signal = input.signal;
|
|
81
|
-
// Own abort→kill explicitly so rejection preserves signal.reason (caller cancel
|
|
82
|
-
//
|
|
76
|
+
// Own abort→kill explicitly so rejection preserves signal.reason (caller cancel).
|
|
77
|
+
// Do not pass `signal` to spawn (Node replaces reason).
|
|
83
78
|
const child = spawn(command, args, {
|
|
84
79
|
cwd: input.cwd,
|
|
85
80
|
env: input.env ?? process.env,
|
|
@@ -87,17 +82,11 @@ export async function runEngineDetourOnce(
|
|
|
87
82
|
});
|
|
88
83
|
let stdout = "";
|
|
89
84
|
let stderr = "";
|
|
90
|
-
const noteActivity = (chunk: string): void => {
|
|
91
|
-
if (chunk.length === 0) return;
|
|
92
|
-
input.onOutputActivity?.();
|
|
93
|
-
};
|
|
94
85
|
child.stdout.setEncoding("utf8").on("data", (chunk: string) => {
|
|
95
86
|
stdout += chunk;
|
|
96
|
-
noteActivity(chunk);
|
|
97
87
|
});
|
|
98
88
|
child.stderr.setEncoding("utf8").on("data", (chunk: string) => {
|
|
99
89
|
stderr += chunk;
|
|
100
|
-
noteActivity(chunk);
|
|
101
90
|
});
|
|
102
91
|
const fail = (error: unknown): void => {
|
|
103
92
|
if (settled) return;
|
|
@@ -116,8 +105,7 @@ export async function runEngineDetourOnce(
|
|
|
116
105
|
resolve(result);
|
|
117
106
|
};
|
|
118
107
|
const onAbort = (): void => {
|
|
119
|
-
// Fail synchronously so
|
|
120
|
-
// before the outer package-owned idle hard-reject drain.
|
|
108
|
+
// Fail synchronously so caller-cancel soft-settle preserves signal.reason.
|
|
121
109
|
fail(signal !== undefined ? abortReasonError(signal) : new Error("aborted"));
|
|
122
110
|
try {
|
|
123
111
|
child.kill("SIGTERM");
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
engineSessionMaterialFromOptions,
|
|
37
37
|
type EngineSessionMaterial,
|
|
38
38
|
} from "./package-resources/engine-material.ts";
|
|
39
|
-
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.ts";
|
|
40
39
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT, type NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
41
40
|
import { REVIEWER_VERIFICATION_BOUNDARY } from "./reviewer-construction.ts";
|
|
42
41
|
import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
|
|
@@ -750,7 +749,7 @@ export async function executeAuditorChild(
|
|
|
750
749
|
let decisionToolFailure: unknown;
|
|
751
750
|
const decisionToolFailures = new Map<string, unknown>();
|
|
752
751
|
const delivery = createReceiptDeliveryPolicy();
|
|
753
|
-
const tool =
|
|
752
|
+
const tool = {
|
|
754
753
|
...options.tool,
|
|
755
754
|
label: options.roleLabel,
|
|
756
755
|
async execute(...args: any[]) {
|
|
@@ -775,7 +774,7 @@ export async function executeAuditorChild(
|
|
|
775
774
|
throw error;
|
|
776
775
|
}
|
|
777
776
|
},
|
|
778
|
-
}
|
|
777
|
+
};
|
|
779
778
|
|
|
780
779
|
const parentSessionManager = options.context.sessionManager;
|
|
781
780
|
const parentHeader = parentSessionManager?.getHeader?.();
|
|
@@ -796,7 +795,7 @@ export async function executeAuditorChild(
|
|
|
796
795
|
thinkingLevel: options.context.thinkingLevel ?? "off",
|
|
797
796
|
modelRuntime: inherited.runtime,
|
|
798
797
|
systemPrompt: options.systemPrompt,
|
|
799
|
-
customTools: [
|
|
798
|
+
customTools: [{ ...options.dossierTool, label: options.roleLabel }, tool],
|
|
800
799
|
sessionManager: auditorSessionManager,
|
|
801
800
|
});
|
|
802
801
|
|
|
@@ -16,7 +16,6 @@ import { PACKAGED_ROLE_REGISTRY, type PackagedRole, packagedRoleMetadata } from
|
|
|
16
16
|
import { openInProcessAgentSession } from "./in-process-session.ts";
|
|
17
17
|
import { renderPublicAkRoleCommand } from "./public-command-renderer.ts";
|
|
18
18
|
import { issueRoot, subjectPath } from "./work-subject-identity.ts";
|
|
19
|
-
import { wrapPackageOwnedToolDefinition } from "./package-owned-tool-idle.ts";
|
|
20
19
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
21
20
|
import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
|
|
22
21
|
import {
|
|
@@ -437,7 +436,7 @@ export function parseNavigatorModelSetting(value: string): { provider: string; m
|
|
|
437
436
|
}
|
|
438
437
|
|
|
439
438
|
export function createNavigatorPrepareTool(onOutput: (value: PrepareOutput) => void): ToolDefinition {
|
|
440
|
-
return
|
|
439
|
+
return {
|
|
441
440
|
name: NAVIGATOR_PREPARE_TOOL_NAME,
|
|
442
441
|
label: "Navigator preparation",
|
|
443
442
|
description: "Submit Navigator direction advice. Provide candidates with next.role (phase when meaningful). route/matches/reason/command are optional context, not acceptance gates.",
|
|
@@ -448,7 +447,7 @@ export function createNavigatorPrepareTool(onOutput: (value: PrepareOutput) => v
|
|
|
448
447
|
onOutput(value as PrepareOutput);
|
|
449
448
|
return { content: [{ type: "text" as const, text: "Navigator preparation accepted" }], details: value, terminate: true as const };
|
|
450
449
|
},
|
|
451
|
-
}
|
|
450
|
+
};
|
|
452
451
|
}
|
|
453
452
|
|
|
454
453
|
/**
|
package/src/role-runtime.ts
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
installActivationEngineLaborFallbackLatch,
|
|
29
29
|
restoreEngineLaborFallbackFromSessionEntries,
|
|
30
30
|
} from "./engine-labor-fallback.ts";
|
|
31
|
-
import { installPackageOwnedToolRegistration } from "./package-owned-tool-idle.ts";
|
|
32
31
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
33
32
|
import { createOAuthKeepalive, type OAuthKeepaliveOptions } from "./oauth-keepalive.ts";
|
|
34
33
|
|
|
@@ -575,8 +574,6 @@ export function createRoleRuntimeExtension(
|
|
|
575
574
|
dependencies: RoleRuntimeDependencies,
|
|
576
575
|
): (pi: ExtensionAPI) => void {
|
|
577
576
|
return (pi) => {
|
|
578
|
-
// #102: one shared package-owned tool registration surface for all role runtimes.
|
|
579
|
-
installPackageOwnedToolRegistration(pi);
|
|
580
577
|
pi.registerFlag(ROLE_FLAG.name, ROLE_FLAG.definition);
|
|
581
578
|
// Reviewer transport flags: shared envelope owns registration (ADR 0018).
|
|
582
579
|
for (const flag of REVIEWER_TRANSPORT_FLAGS) {
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #102 package-owned tool idle backstop.
|
|
3
|
-
*
|
|
4
|
-
* Fixed 183000ms silence clock on package-owned tool execute only.
|
|
5
|
-
* Real producing onUpdate resets; final resolve/reject clears; timeout throws so
|
|
6
|
-
* Pi settles the current call as an LLM-visible isError tool result. No retry,
|
|
7
|
-
* role failure, process termination, config, or Pi built-in coverage.
|
|
8
|
-
*
|
|
9
|
-
* #339: do not name-exempt whole terminating tools. Outer idle stays armed for
|
|
10
|
-
* pre/post-audit work. Only the real compliance-audit await suspends this single
|
|
11
|
-
* layer (ADR 0059 owns that interval); resume re-arms the same outer backstop.
|
|
12
|
-
*
|
|
13
|
-
* #380: on idle, abort a derived tool signal (not the caller signal) so detour
|
|
14
|
-
* spawn cleanup and softFail can run through the existing AbortSignal path.
|
|
15
|
-
* Cooperative soft-settle may win; non-listeners still hard-reject after a
|
|
16
|
-
* microtask drain (no second timer).
|
|
17
|
-
*/
|
|
18
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19
|
-
import { DEFAULT_STREAM_IDLE_TIMEOUT_MS, createStreamIdleGuard, } from "./stream-idle-guard.js";
|
|
20
|
-
import { isProducingToolUpdate } from "./tool-execution-observation.js";
|
|
21
|
-
export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS = DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
22
|
-
export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE = "AK_PACKAGE_OWNED_TOOL_IDLE_TIMEOUT";
|
|
23
|
-
const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
|
|
24
|
-
/**
|
|
25
|
-
* Active outer package-owned execute idle, if any. Nested tool executes install
|
|
26
|
-
* their own store; compliance audit only suspends the store visible at await time.
|
|
27
|
-
*/
|
|
28
|
-
const packageOwnedToolIdleScope = new AsyncLocalStorage();
|
|
29
|
-
export class PackageOwnedToolIdleTimeoutError extends Error {
|
|
30
|
-
code = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE;
|
|
31
|
-
idleTimeoutMs = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS;
|
|
32
|
-
constructor() {
|
|
33
|
-
super(`package-owned tool idle timeout after ${PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS}ms`);
|
|
34
|
-
this.name = "PackageOwnedToolIdleTimeoutError";
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export function isPackageOwnedToolIdleTimeoutError(value) {
|
|
38
|
-
return (value instanceof PackageOwnedToolIdleTimeoutError ||
|
|
39
|
-
(typeof value === "object" &&
|
|
40
|
-
value !== null &&
|
|
41
|
-
value.name === "PackageOwnedToolIdleTimeoutError" &&
|
|
42
|
-
value.code === PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE));
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Package-tool activity includes content production and host-only details
|
|
46
|
-
* progress. Keep the observation-plane oracle separate: its stderr heartbeat
|
|
47
|
-
* contract remains content-driven. Pi's known execute-entry placeholder
|
|
48
|
-
* (`content: [], details: undefined`) is not activity.
|
|
49
|
-
*/
|
|
50
|
-
function isPackageOwnedToolActivityUpdate(partialResult) {
|
|
51
|
-
if (isProducingToolUpdate(partialResult))
|
|
52
|
-
return true;
|
|
53
|
-
if (typeof partialResult !== "object" || partialResult === null)
|
|
54
|
-
return false;
|
|
55
|
-
const details = partialResult.details;
|
|
56
|
-
if (details === undefined || details === null)
|
|
57
|
-
return false;
|
|
58
|
-
if (typeof details === "string")
|
|
59
|
-
return details.length > 0;
|
|
60
|
-
if (Array.isArray(details))
|
|
61
|
-
return details.length > 0;
|
|
62
|
-
if (typeof details === "object")
|
|
63
|
-
return Reflect.ownKeys(details).length > 0;
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* #339: suspend the active package-owned execute idle for one real compliance
|
|
68
|
-
* audit await. Single-layer only — production has one runComplianceAudit seam.
|
|
69
|
-
* No-op outside a wrapped package-owned execute. No second timeout or retry.
|
|
70
|
-
*/
|
|
71
|
-
export async function withPackageOwnedToolIdleSuspended(run) {
|
|
72
|
-
const scope = packageOwnedToolIdleScope.getStore();
|
|
73
|
-
if (scope === undefined)
|
|
74
|
-
return run();
|
|
75
|
-
scope.suspend();
|
|
76
|
-
try {
|
|
77
|
-
return await run();
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
scope.resume();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Touch the active package-owned execute idle clock (reuse stream-idle-guard poke).
|
|
85
|
-
* No-op outside a wrapped package-owned execute or while that layer is suspended.
|
|
86
|
-
* Used by long-running tools (engine detour) that see real subprocess output bytes.
|
|
87
|
-
*/
|
|
88
|
-
export function pokePackageOwnedToolIdle() {
|
|
89
|
-
packageOwnedToolIdleScope.getStore()?.poke();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Single shared execute wrapper for package-owned tool definitions.
|
|
93
|
-
* Idempotent: wrapping twice returns the same protected definition.
|
|
94
|
-
*/
|
|
95
|
-
export function wrapPackageOwnedToolDefinition(tool) {
|
|
96
|
-
// Mark the execute function, not the tool object — callers may spread tool fields
|
|
97
|
-
// onto a new definition with a different execute (e.g. auditor customTools).
|
|
98
|
-
if (tool.execute[WRAPPED] === true)
|
|
99
|
-
return tool;
|
|
100
|
-
const originalExecute = tool.execute.bind(tool);
|
|
101
|
-
const wrappedExecute = function packageOwnedToolIdleExecute(...args) {
|
|
102
|
-
const parentSignal = args[2];
|
|
103
|
-
const onUpdate = args[3];
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
let settled = false;
|
|
106
|
-
let suspended = false;
|
|
107
|
-
let idle = createStreamIdleGuard({
|
|
108
|
-
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
109
|
-
});
|
|
110
|
-
// Derived tool signal: caller cancel forwards; idle aborts this only (not parent).
|
|
111
|
-
const toolController = new AbortController();
|
|
112
|
-
const onParentAbort = () => {
|
|
113
|
-
if (toolController.signal.aborted)
|
|
114
|
-
return;
|
|
115
|
-
toolController.abort(parentSignal?.reason);
|
|
116
|
-
};
|
|
117
|
-
if (parentSignal !== undefined) {
|
|
118
|
-
if (parentSignal.aborted) {
|
|
119
|
-
toolController.abort(parentSignal.reason);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
parentSignal.addEventListener("abort", onParentAbort);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const cleanup = () => {
|
|
126
|
-
idle.signal.removeEventListener("abort", onIdle);
|
|
127
|
-
idle.dispose();
|
|
128
|
-
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
129
|
-
};
|
|
130
|
-
const settle = (deliver) => {
|
|
131
|
-
if (settled)
|
|
132
|
-
return;
|
|
133
|
-
settled = true;
|
|
134
|
-
cleanup();
|
|
135
|
-
deliver();
|
|
136
|
-
};
|
|
137
|
-
const onIdle = () => {
|
|
138
|
-
const idleError = new PackageOwnedToolIdleTimeoutError();
|
|
139
|
-
if (!toolController.signal.aborted) {
|
|
140
|
-
toolController.abort(idleError);
|
|
141
|
-
}
|
|
142
|
-
// Cooperative tools (detour) soft-settle via the aborted signal in the
|
|
143
|
-
// next microtasks. Non-listeners still hard-reject after a short drain
|
|
144
|
-
// of those microtasks — no second wall-clock timer.
|
|
145
|
-
void Promise.resolve()
|
|
146
|
-
.then(() => { })
|
|
147
|
-
.then(() => { })
|
|
148
|
-
.then(() => {
|
|
149
|
-
settle(() => reject(idleError));
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
153
|
-
const suspension = {
|
|
154
|
-
suspend() {
|
|
155
|
-
if (settled || suspended)
|
|
156
|
-
return;
|
|
157
|
-
suspended = true;
|
|
158
|
-
// ADR 0059 owns the audit interval — drop this layer until audit returns.
|
|
159
|
-
idle.signal.removeEventListener("abort", onIdle);
|
|
160
|
-
idle.dispose();
|
|
161
|
-
},
|
|
162
|
-
resume() {
|
|
163
|
-
if (settled || !suspended)
|
|
164
|
-
return;
|
|
165
|
-
suspended = false;
|
|
166
|
-
// Fresh single-layer silence window for post-audit work (e.g. cleanup).
|
|
167
|
-
idle = createStreamIdleGuard({
|
|
168
|
-
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
169
|
-
});
|
|
170
|
-
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
171
|
-
},
|
|
172
|
-
poke() {
|
|
173
|
-
if (settled || suspended)
|
|
174
|
-
return;
|
|
175
|
-
idle.poke();
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
const guardedOnUpdate = onUpdate === undefined
|
|
179
|
-
? undefined
|
|
180
|
-
: (partialResult) => {
|
|
181
|
-
if (settled)
|
|
182
|
-
return;
|
|
183
|
-
if (isPackageOwnedToolActivityUpdate(partialResult))
|
|
184
|
-
idle.poke();
|
|
185
|
-
onUpdate(partialResult);
|
|
186
|
-
};
|
|
187
|
-
const callArgs = args.slice();
|
|
188
|
-
// Pass derived signal so idle/cancel can clean up subprocesses without
|
|
189
|
-
// aborting the caller/parent agent signal identity.
|
|
190
|
-
callArgs[2] = toolController.signal;
|
|
191
|
-
callArgs[3] = guardedOnUpdate;
|
|
192
|
-
void packageOwnedToolIdleScope.run(suspension, async () => {
|
|
193
|
-
try {
|
|
194
|
-
const result = await originalExecute(...callArgs);
|
|
195
|
-
settle(() => resolve(result));
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
settle(() => reject(error));
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
wrappedExecute[WRAPPED] = true;
|
|
204
|
-
return {
|
|
205
|
-
...tool,
|
|
206
|
-
execute: wrappedExecute,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Install the shared registration surface on an ExtensionAPI once.
|
|
211
|
-
* All subsequent pi.registerTool calls for package-owned tools are wrapped.
|
|
212
|
-
*/
|
|
213
|
-
export function installPackageOwnedToolRegistration(pi) {
|
|
214
|
-
const current = pi.registerTool;
|
|
215
|
-
if (current[WRAPPED] === true)
|
|
216
|
-
return;
|
|
217
|
-
const original = current.bind(pi);
|
|
218
|
-
const installed = ((tool) => {
|
|
219
|
-
original(wrapPackageOwnedToolDefinition(tool));
|
|
220
|
-
});
|
|
221
|
-
installed[WRAPPED] = true;
|
|
222
|
-
pi.registerTool = installed;
|
|
223
|
-
}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #102 package-owned tool idle backstop.
|
|
3
|
-
*
|
|
4
|
-
* Fixed 183000ms silence clock on package-owned tool execute only.
|
|
5
|
-
* Real producing onUpdate resets; final resolve/reject clears; timeout throws so
|
|
6
|
-
* Pi settles the current call as an LLM-visible isError tool result. No retry,
|
|
7
|
-
* role failure, process termination, config, or Pi built-in coverage.
|
|
8
|
-
*
|
|
9
|
-
* #339: do not name-exempt whole terminating tools. Outer idle stays armed for
|
|
10
|
-
* pre/post-audit work. Only the real compliance-audit await suspends this single
|
|
11
|
-
* layer (ADR 0059 owns that interval); resume re-arms the same outer backstop.
|
|
12
|
-
*
|
|
13
|
-
* #380: on idle, abort a derived tool signal (not the caller signal) so detour
|
|
14
|
-
* spawn cleanup and softFail can run through the existing AbortSignal path.
|
|
15
|
-
* Cooperative soft-settle may win; non-listeners still hard-reject after a
|
|
16
|
-
* microtask drain (no second timer).
|
|
17
|
-
*/
|
|
18
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19
|
-
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
23
|
-
createStreamIdleGuard,
|
|
24
|
-
type StreamIdleGuard,
|
|
25
|
-
} from "./stream-idle-guard.ts";
|
|
26
|
-
import { isProducingToolUpdate } from "./tool-execution-observation.ts";
|
|
27
|
-
|
|
28
|
-
export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS = DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
29
|
-
export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE = "AK_PACKAGE_OWNED_TOOL_IDLE_TIMEOUT" as const;
|
|
30
|
-
|
|
31
|
-
const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
|
|
32
|
-
|
|
33
|
-
type PackageOwnedToolIdleSuspension = {
|
|
34
|
-
suspend(): void;
|
|
35
|
-
resume(): void;
|
|
36
|
-
/** Reset the silence window (byte/activity signal from the active execute). */
|
|
37
|
-
poke(): void;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Active outer package-owned execute idle, if any. Nested tool executes install
|
|
42
|
-
* their own store; compliance audit only suspends the store visible at await time.
|
|
43
|
-
*/
|
|
44
|
-
const packageOwnedToolIdleScope = new AsyncLocalStorage<PackageOwnedToolIdleSuspension>();
|
|
45
|
-
|
|
46
|
-
export class PackageOwnedToolIdleTimeoutError extends Error {
|
|
47
|
-
readonly code = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE;
|
|
48
|
-
readonly idleTimeoutMs = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS;
|
|
49
|
-
|
|
50
|
-
constructor() {
|
|
51
|
-
super(`package-owned tool idle timeout after ${PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS}ms`);
|
|
52
|
-
this.name = "PackageOwnedToolIdleTimeoutError";
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function isPackageOwnedToolIdleTimeoutError(
|
|
57
|
-
value: unknown,
|
|
58
|
-
): value is PackageOwnedToolIdleTimeoutError {
|
|
59
|
-
return (
|
|
60
|
-
value instanceof PackageOwnedToolIdleTimeoutError ||
|
|
61
|
-
(
|
|
62
|
-
typeof value === "object" &&
|
|
63
|
-
value !== null &&
|
|
64
|
-
(value as { name?: unknown }).name === "PackageOwnedToolIdleTimeoutError" &&
|
|
65
|
-
(value as { code?: unknown }).code === PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE
|
|
66
|
-
)
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Minimal executable tool shape accepted by the shared idle wrapper. */
|
|
71
|
-
export type PackageOwnedToolLike = {
|
|
72
|
-
readonly name: string;
|
|
73
|
-
execute: (...args: never[]) => Promise<unknown>;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Package-tool activity includes content production and host-only details
|
|
78
|
-
* progress. Keep the observation-plane oracle separate: its stderr heartbeat
|
|
79
|
-
* contract remains content-driven. Pi's known execute-entry placeholder
|
|
80
|
-
* (`content: [], details: undefined`) is not activity.
|
|
81
|
-
*/
|
|
82
|
-
function isPackageOwnedToolActivityUpdate(partialResult: unknown): boolean {
|
|
83
|
-
if (isProducingToolUpdate(partialResult)) return true;
|
|
84
|
-
if (typeof partialResult !== "object" || partialResult === null) return false;
|
|
85
|
-
const details = (partialResult as { details?: unknown }).details;
|
|
86
|
-
if (details === undefined || details === null) return false;
|
|
87
|
-
if (typeof details === "string") return details.length > 0;
|
|
88
|
-
if (Array.isArray(details)) return details.length > 0;
|
|
89
|
-
if (typeof details === "object") return Reflect.ownKeys(details).length > 0;
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* #339: suspend the active package-owned execute idle for one real compliance
|
|
95
|
-
* audit await. Single-layer only — production has one runComplianceAudit seam.
|
|
96
|
-
* No-op outside a wrapped package-owned execute. No second timeout or retry.
|
|
97
|
-
*/
|
|
98
|
-
export async function withPackageOwnedToolIdleSuspended<T>(
|
|
99
|
-
run: () => Promise<T>,
|
|
100
|
-
): Promise<T> {
|
|
101
|
-
const scope = packageOwnedToolIdleScope.getStore();
|
|
102
|
-
if (scope === undefined) return run();
|
|
103
|
-
scope.suspend();
|
|
104
|
-
try {
|
|
105
|
-
return await run();
|
|
106
|
-
} finally {
|
|
107
|
-
scope.resume();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Touch the active package-owned execute idle clock (reuse stream-idle-guard poke).
|
|
113
|
-
* No-op outside a wrapped package-owned execute or while that layer is suspended.
|
|
114
|
-
* Used by long-running tools (engine detour) that see real subprocess output bytes.
|
|
115
|
-
*/
|
|
116
|
-
export function pokePackageOwnedToolIdle(): void {
|
|
117
|
-
packageOwnedToolIdleScope.getStore()?.poke();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Single shared execute wrapper for package-owned tool definitions.
|
|
122
|
-
* Idempotent: wrapping twice returns the same protected definition.
|
|
123
|
-
*/
|
|
124
|
-
export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(tool: T): T {
|
|
125
|
-
// Mark the execute function, not the tool object — callers may spread tool fields
|
|
126
|
-
// onto a new definition with a different execute (e.g. auditor customTools).
|
|
127
|
-
if ((tool.execute as { [WRAPPED]?: boolean })[WRAPPED] === true) return tool;
|
|
128
|
-
|
|
129
|
-
const originalExecute = tool.execute.bind(tool) as (
|
|
130
|
-
...args: unknown[]
|
|
131
|
-
) => Promise<unknown>;
|
|
132
|
-
|
|
133
|
-
const wrappedExecute = function packageOwnedToolIdleExecute(
|
|
134
|
-
...args: unknown[]
|
|
135
|
-
): Promise<unknown> {
|
|
136
|
-
const parentSignal = args[2] as AbortSignal | undefined;
|
|
137
|
-
const onUpdate = args[3] as ((partialResult: unknown) => void) | undefined;
|
|
138
|
-
return new Promise((resolve, reject) => {
|
|
139
|
-
let settled = false;
|
|
140
|
-
let suspended = false;
|
|
141
|
-
let idle: StreamIdleGuard = createStreamIdleGuard({
|
|
142
|
-
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Derived tool signal: caller cancel forwards; idle aborts this only (not parent).
|
|
146
|
-
const toolController = new AbortController();
|
|
147
|
-
const onParentAbort = (): void => {
|
|
148
|
-
if (toolController.signal.aborted) return;
|
|
149
|
-
toolController.abort(parentSignal?.reason);
|
|
150
|
-
};
|
|
151
|
-
if (parentSignal !== undefined) {
|
|
152
|
-
if (parentSignal.aborted) {
|
|
153
|
-
toolController.abort(parentSignal.reason);
|
|
154
|
-
} else {
|
|
155
|
-
parentSignal.addEventListener("abort", onParentAbort);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const cleanup = (): void => {
|
|
160
|
-
idle.signal.removeEventListener("abort", onIdle);
|
|
161
|
-
idle.dispose();
|
|
162
|
-
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const settle = (deliver: () => void): void => {
|
|
166
|
-
if (settled) return;
|
|
167
|
-
settled = true;
|
|
168
|
-
cleanup();
|
|
169
|
-
deliver();
|
|
170
|
-
};
|
|
171
|
-
const onIdle = (): void => {
|
|
172
|
-
const idleError = new PackageOwnedToolIdleTimeoutError();
|
|
173
|
-
if (!toolController.signal.aborted) {
|
|
174
|
-
toolController.abort(idleError);
|
|
175
|
-
}
|
|
176
|
-
// Cooperative tools (detour) soft-settle via the aborted signal in the
|
|
177
|
-
// next microtasks. Non-listeners still hard-reject after a short drain
|
|
178
|
-
// of those microtasks — no second wall-clock timer.
|
|
179
|
-
void Promise.resolve()
|
|
180
|
-
.then(() => {})
|
|
181
|
-
.then(() => {})
|
|
182
|
-
.then(() => {
|
|
183
|
-
settle(() => reject(idleError));
|
|
184
|
-
});
|
|
185
|
-
};
|
|
186
|
-
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
187
|
-
|
|
188
|
-
const suspension: PackageOwnedToolIdleSuspension = {
|
|
189
|
-
suspend(): void {
|
|
190
|
-
if (settled || suspended) return;
|
|
191
|
-
suspended = true;
|
|
192
|
-
// ADR 0059 owns the audit interval — drop this layer until audit returns.
|
|
193
|
-
idle.signal.removeEventListener("abort", onIdle);
|
|
194
|
-
idle.dispose();
|
|
195
|
-
},
|
|
196
|
-
resume(): void {
|
|
197
|
-
if (settled || !suspended) return;
|
|
198
|
-
suspended = false;
|
|
199
|
-
// Fresh single-layer silence window for post-audit work (e.g. cleanup).
|
|
200
|
-
idle = createStreamIdleGuard({
|
|
201
|
-
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
202
|
-
});
|
|
203
|
-
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
204
|
-
},
|
|
205
|
-
poke(): void {
|
|
206
|
-
if (settled || suspended) return;
|
|
207
|
-
idle.poke();
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const guardedOnUpdate = onUpdate === undefined
|
|
212
|
-
? undefined
|
|
213
|
-
: (partialResult: unknown) => {
|
|
214
|
-
if (settled) return;
|
|
215
|
-
if (isPackageOwnedToolActivityUpdate(partialResult)) idle.poke();
|
|
216
|
-
onUpdate(partialResult);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const callArgs = args.slice();
|
|
220
|
-
// Pass derived signal so idle/cancel can clean up subprocesses without
|
|
221
|
-
// aborting the caller/parent agent signal identity.
|
|
222
|
-
callArgs[2] = toolController.signal;
|
|
223
|
-
callArgs[3] = guardedOnUpdate;
|
|
224
|
-
|
|
225
|
-
void packageOwnedToolIdleScope.run(suspension, async () => {
|
|
226
|
-
try {
|
|
227
|
-
const result = await originalExecute(...callArgs);
|
|
228
|
-
settle(() => resolve(result));
|
|
229
|
-
} catch (error) {
|
|
230
|
-
settle(() => reject(error));
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
};
|
|
235
|
-
(wrappedExecute as { [WRAPPED]?: boolean })[WRAPPED] = true;
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
...tool,
|
|
239
|
-
execute: wrappedExecute as T["execute"],
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Install the shared registration surface on an ExtensionAPI once.
|
|
245
|
-
* All subsequent pi.registerTool calls for package-owned tools are wrapped.
|
|
246
|
-
*/
|
|
247
|
-
export function installPackageOwnedToolRegistration(pi: ExtensionAPI): void {
|
|
248
|
-
const current = pi.registerTool;
|
|
249
|
-
if ((current as { [WRAPPED]?: boolean })[WRAPPED] === true) return;
|
|
250
|
-
|
|
251
|
-
const original = current.bind(pi);
|
|
252
|
-
const installed = ((tool: ToolDefinition<any, any, any>) => {
|
|
253
|
-
original(wrapPackageOwnedToolDefinition(tool) as ToolDefinition<any, any, any>);
|
|
254
|
-
}) as typeof pi.registerTool;
|
|
255
|
-
(installed as { [WRAPPED]?: boolean })[WRAPPED] = true;
|
|
256
|
-
pi.registerTool = installed;
|
|
257
|
-
}
|