@mobrienv/autoloop-harness 0.7.4 → 0.8.0
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/dist/backend/types.d.ts +13 -0
- package/dist/backend/types.js +14 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/config-helpers.d.ts +10 -2
- package/dist/config-helpers.js +137 -28
- package/dist/config-helpers.js.map +1 -1
- package/dist/control/acp-adapter.d.ts +12 -0
- package/dist/control/acp-adapter.js +54 -0
- package/dist/control/acp-adapter.js.map +1 -0
- package/dist/control/adapter.d.ts +12 -0
- package/dist/control/adapter.js +2 -0
- package/dist/control/adapter.js.map +1 -0
- package/dist/control/capabilities.d.ts +9 -0
- package/dist/control/capabilities.js +27 -0
- package/dist/control/capabilities.js.map +1 -0
- package/dist/control/claude-sdk-adapter.d.ts +15 -0
- package/dist/control/claude-sdk-adapter.js +79 -0
- package/dist/control/claude-sdk-adapter.js.map +1 -0
- package/dist/control/dispatch.d.ts +14 -0
- package/dist/control/dispatch.js +41 -0
- package/dist/control/dispatch.js.map +1 -0
- package/dist/control/index.d.ts +11 -0
- package/dist/control/index.js +9 -0
- package/dist/control/index.js.map +1 -0
- package/dist/control/kiro-adapter.d.ts +2 -0
- package/dist/control/kiro-adapter.js +2 -0
- package/dist/control/kiro-adapter.js.map +1 -0
- package/dist/control/paths.d.ts +19 -0
- package/dist/control/paths.js +40 -0
- package/dist/control/paths.js.map +1 -0
- package/dist/control/pi-adapter.d.ts +15 -0
- package/dist/control/pi-adapter.js +76 -0
- package/dist/control/pi-adapter.js.map +1 -0
- package/dist/control/queue.d.ts +14 -0
- package/dist/control/queue.js +82 -0
- package/dist/control/queue.js.map +1 -0
- package/dist/control/render.d.ts +10 -0
- package/dist/control/render.js +53 -0
- package/dist/control/render.js.map +1 -0
- package/dist/control/types.d.ts +47 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/emit.js +6 -4
- package/dist/emit.js.map +1 -1
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +78 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.js +181 -24
- package/dist/index.js.map +1 -1
- package/dist/iteration.js +174 -20
- package/dist/iteration.js.map +1 -1
- package/dist/metareview.js +92 -6
- package/dist/metareview.js.map +1 -1
- package/dist/notify.d.ts +15 -0
- package/dist/notify.js +98 -0
- package/dist/notify.js.map +1 -0
- package/dist/parallel.d.ts +1 -0
- package/dist/parallel.js +10 -5
- package/dist/parallel.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.js +34 -0
- package/dist/prompt.js.map +1 -1
- package/dist/registry-bridge.js +7 -1
- package/dist/registry-bridge.js.map +1 -1
- package/dist/stop.d.ts +3 -0
- package/dist/stop.js +63 -0
- package/dist/stop.js.map +1 -1
- package/dist/types.d.ts +47 -1
- package/dist/wave/launch-branches.js +2 -0
- package/dist/wave/launch-branches.js.map +1 -1
- package/package.json +51 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defaultCapabilities } from "./capabilities.js";
|
|
2
|
+
/**
|
|
3
|
+
* Live-control adapter for the claude-sdk backend. The Claude Agent SDK runs
|
|
4
|
+
* each iteration as a streaming-input session: `interrupt()` cancels the
|
|
5
|
+
* in-flight turn, and a pushed user message steers it mid-turn — delivered at
|
|
6
|
+
* the next safe boundary. Guidance also stays durable via the journal, so
|
|
7
|
+
* `guide` is always applied even when live steering is unavailable.
|
|
8
|
+
*/
|
|
9
|
+
export function claudeSdkControlAdapter(runId, hooks) {
|
|
10
|
+
const caps = buildCapabilities(runId);
|
|
11
|
+
return {
|
|
12
|
+
backend: "claude-sdk",
|
|
13
|
+
capabilities() {
|
|
14
|
+
return caps;
|
|
15
|
+
},
|
|
16
|
+
onRequest(request) {
|
|
17
|
+
if (request.verb === "interrupt") {
|
|
18
|
+
return applyInterrupt(hooks);
|
|
19
|
+
}
|
|
20
|
+
if (request.verb === "guide") {
|
|
21
|
+
const payload = request.payload;
|
|
22
|
+
if (payload?.interrupt) {
|
|
23
|
+
return applyInterrupt(hooks, "guidance-driven");
|
|
24
|
+
}
|
|
25
|
+
return applySteer(hooks, payload?.message ?? "");
|
|
26
|
+
}
|
|
27
|
+
return { state: "ignored", detail: `unknown verb: ${request.verb}` };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function buildCapabilities(runId) {
|
|
32
|
+
const base = defaultCapabilities("claude-sdk", runId);
|
|
33
|
+
base.interrupt = {
|
|
34
|
+
supported: true,
|
|
35
|
+
detail: "SDK interrupt() of the in-flight turn",
|
|
36
|
+
};
|
|
37
|
+
base.guidance = {
|
|
38
|
+
supported: true,
|
|
39
|
+
detail: "journal-durable + live steer into the in-flight turn",
|
|
40
|
+
};
|
|
41
|
+
return base;
|
|
42
|
+
}
|
|
43
|
+
function applySteer(hooks, message) {
|
|
44
|
+
if (!message) {
|
|
45
|
+
return { state: "applied", detail: "guidance appended" };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
hooks.triggerSteer(message);
|
|
49
|
+
return {
|
|
50
|
+
state: "applied",
|
|
51
|
+
detail: "guidance appended + steered into live turn",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Steering is opportunistic; the journal copy still reaches the next
|
|
56
|
+
// iteration prompt, so the guidance itself is applied either way.
|
|
57
|
+
return {
|
|
58
|
+
state: "applied",
|
|
59
|
+
detail: "guidance appended (live steer unavailable)",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function applyInterrupt(hooks, detailPrefix = "interrupt") {
|
|
64
|
+
try {
|
|
65
|
+
hooks.triggerInterrupt();
|
|
66
|
+
return {
|
|
67
|
+
state: "applied",
|
|
68
|
+
detail: `${detailPrefix}: SDK interrupt sent`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
73
|
+
return {
|
|
74
|
+
state: "rejected",
|
|
75
|
+
detail: `${detailPrefix}: interrupt failed: ${msg}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=claude-sdk-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-sdk-adapter.js","sourceRoot":"","sources":["../../src/control/claude-sdk-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAexD;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,KAAa,EACb,KAA4B;IAE5B,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAEtC,OAAO;QACL,OAAO,EAAE,YAAY;QACrB,YAAY;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QACD,SAAS,CAAC,OAAuB;YAC/B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAuB,CAAC;gBAChD,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;oBACvB,OAAO,cAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;gBAClD,CAAC;gBACD,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACvE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,IAAI,GAAG,mBAAmB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACtD,IAAI,CAAC,SAAS,GAAG;QACf,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,uCAAuC;KAChD,CAAC;IACF,IAAI,CAAC,QAAQ,GAAG;QACd,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,sDAAsD;KAC/D,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,KAA4B,EAAE,OAAe;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC;QACH,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,kEAAkE;QAClE,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,KAA4B,EAC5B,YAAY,GAAG,WAAW;IAE1B,IAAI,CAAC;QACH,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACzB,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,GAAG,YAAY,sBAAsB;SAC9C,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO;YACL,KAAK,EAAE,UAAU;YACjB,MAAM,EAAE,GAAG,YAAY,uBAAuB,GAAG,EAAE;SACpD,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LiveControlAdapter } from "./adapter.js";
|
|
2
|
+
import type { ControlRequest } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Publish an adapter's capabilities to the run-scoped control directory.
|
|
5
|
+
* Called at loop start and any time the adapter's capabilities change.
|
|
6
|
+
*/
|
|
7
|
+
export declare function publishCapabilities(runStateDir: string, adapter: LiveControlAdapter): void;
|
|
8
|
+
/**
|
|
9
|
+
* Drain all pending control requests for this run, handing each to the
|
|
10
|
+
* adapter and recording its ack to the status log.
|
|
11
|
+
*
|
|
12
|
+
* Returns the list of requests that were acknowledged on this drain pass.
|
|
13
|
+
*/
|
|
14
|
+
export declare function drainControlRequests(runStateDir: string, adapter: LiveControlAdapter): ControlRequest[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { appendStatus, pendingRequests, writeCapabilities } from "./queue.js";
|
|
2
|
+
/**
|
|
3
|
+
* Publish an adapter's capabilities to the run-scoped control directory.
|
|
4
|
+
* Called at loop start and any time the adapter's capabilities change.
|
|
5
|
+
*/
|
|
6
|
+
export function publishCapabilities(runStateDir, adapter) {
|
|
7
|
+
writeCapabilities(runStateDir, adapter.capabilities());
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Drain all pending control requests for this run, handing each to the
|
|
11
|
+
* adapter and recording its ack to the status log.
|
|
12
|
+
*
|
|
13
|
+
* Returns the list of requests that were acknowledged on this drain pass.
|
|
14
|
+
*/
|
|
15
|
+
export function drainControlRequests(runStateDir, adapter) {
|
|
16
|
+
const requests = pendingRequests(runStateDir);
|
|
17
|
+
for (const request of requests) {
|
|
18
|
+
ackRequest(runStateDir, adapter, request);
|
|
19
|
+
}
|
|
20
|
+
return requests;
|
|
21
|
+
}
|
|
22
|
+
function ackRequest(runStateDir, adapter, request) {
|
|
23
|
+
let ack;
|
|
24
|
+
try {
|
|
25
|
+
ack = adapter.onRequest(request);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
29
|
+
ack = { state: "rejected", detail: `adapter threw: ${msg}` };
|
|
30
|
+
}
|
|
31
|
+
const status = {
|
|
32
|
+
id: request.id,
|
|
33
|
+
runId: request.runId,
|
|
34
|
+
verb: request.verb,
|
|
35
|
+
state: ack.state,
|
|
36
|
+
at: new Date().toISOString(),
|
|
37
|
+
detail: ack.detail,
|
|
38
|
+
};
|
|
39
|
+
appendStatus(runStateDir, status);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/control/dispatch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG9E;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,OAA2B;IAE3B,iBAAiB,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAmB,EACnB,OAA2B;IAE3B,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC9C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,UAAU,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,UAAU,CACjB,WAAmB,EACnB,OAA2B,EAC3B,OAAuB;IAEvB,IAAI,GAAe,CAAC;IACpB,IAAI,CAAC;QACH,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,GAAG,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,GAAG,EAAE,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,MAAM,GAAkB;QAC5B,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;KACnB,CAAC;IACF,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { acpControlAdapter, kiroControlAdapter } from "./acp-adapter.js";
|
|
2
|
+
export type { LiveControlAdapter } from "./adapter.js";
|
|
3
|
+
export { CAPABILITY_VERBS, defaultCapabilities, supportsInterrupt, } from "./capabilities.js";
|
|
4
|
+
export { claudeSdkControlAdapter } from "./claude-sdk-adapter.js";
|
|
5
|
+
export { drainControlRequests, publishCapabilities } from "./dispatch.js";
|
|
6
|
+
export { baseStateDirFromRunState, controlCapabilitiesFile, controlDir, controlRequestsFile, controlStatusFile, } from "./paths.js";
|
|
7
|
+
export { piControlAdapter } from "./pi-adapter.js";
|
|
8
|
+
export { appendRequest, appendStatus, buildRequest, newRequestId, pendingRequests, readCapabilities, readRequests, readStatuses, writeCapabilities, } from "./queue.js";
|
|
9
|
+
export type { ControlSnapshot } from "./render.js";
|
|
10
|
+
export { renderCapabilities, renderShow } from "./render.js";
|
|
11
|
+
export type { CapabilityVerb, ControlAck, ControlCapabilities, ControlCapability, ControlPayload, ControlRequest, ControlStatus, ControlStatusState, ControlVerb, GuidePayload, InterruptPayload, } from "./types.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { acpControlAdapter, kiroControlAdapter } from "./acp-adapter.js";
|
|
2
|
+
export { CAPABILITY_VERBS, defaultCapabilities, supportsInterrupt, } from "./capabilities.js";
|
|
3
|
+
export { claudeSdkControlAdapter } from "./claude-sdk-adapter.js";
|
|
4
|
+
export { drainControlRequests, publishCapabilities } from "./dispatch.js";
|
|
5
|
+
export { baseStateDirFromRunState, controlCapabilitiesFile, controlDir, controlRequestsFile, controlStatusFile, } from "./paths.js";
|
|
6
|
+
export { piControlAdapter } from "./pi-adapter.js";
|
|
7
|
+
export { appendRequest, appendStatus, buildRequest, newRequestId, pendingRequests, readCapabilities, readRequests, readStatuses, writeCapabilities, } from "./queue.js";
|
|
8
|
+
export { renderCapabilities, renderShow } from "./render.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/control/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEzE,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,EACV,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kiro-adapter.js","sourceRoot":"","sources":["../../src/control/kiro-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the run-scoped control directory.
|
|
3
|
+
*
|
|
4
|
+
* `runStateDir` is the per-run state tree:
|
|
5
|
+
* - run-scoped mode: <base>/.autoloop/runs/<run-id>/
|
|
6
|
+
* - worktree mode: <worktree>/.autoloop/
|
|
7
|
+
* - shared mode: <base>/.autoloop/runs/<run-id>/ (same as run-scoped)
|
|
8
|
+
*
|
|
9
|
+
* The registry record's `state_dir` is always the right input.
|
|
10
|
+
*/
|
|
11
|
+
export declare function controlDir(runStateDir: string): string;
|
|
12
|
+
export declare function controlRequestsFile(runStateDir: string): string;
|
|
13
|
+
export declare function controlStatusFile(runStateDir: string): string;
|
|
14
|
+
export declare function controlCapabilitiesFile(runStateDir: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Infer the base state dir given a run's recorded state_dir. Used to find the
|
|
17
|
+
* parent `.autoloop/` registry when the run dir is a per-run or worktree tree.
|
|
18
|
+
*/
|
|
19
|
+
export declare function baseStateDirFromRunState(runStateDir: string): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the run-scoped control directory.
|
|
4
|
+
*
|
|
5
|
+
* `runStateDir` is the per-run state tree:
|
|
6
|
+
* - run-scoped mode: <base>/.autoloop/runs/<run-id>/
|
|
7
|
+
* - worktree mode: <worktree>/.autoloop/
|
|
8
|
+
* - shared mode: <base>/.autoloop/runs/<run-id>/ (same as run-scoped)
|
|
9
|
+
*
|
|
10
|
+
* The registry record's `state_dir` is always the right input.
|
|
11
|
+
*/
|
|
12
|
+
export function controlDir(runStateDir) {
|
|
13
|
+
return join(runStateDir, "control");
|
|
14
|
+
}
|
|
15
|
+
export function controlRequestsFile(runStateDir) {
|
|
16
|
+
return join(controlDir(runStateDir), "requests.jsonl");
|
|
17
|
+
}
|
|
18
|
+
export function controlStatusFile(runStateDir) {
|
|
19
|
+
return join(controlDir(runStateDir), "status.jsonl");
|
|
20
|
+
}
|
|
21
|
+
export function controlCapabilitiesFile(runStateDir) {
|
|
22
|
+
return join(controlDir(runStateDir), "capabilities.json");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Infer the base state dir given a run's recorded state_dir. Used to find the
|
|
26
|
+
* parent `.autoloop/` registry when the run dir is a per-run or worktree tree.
|
|
27
|
+
*/
|
|
28
|
+
export function baseStateDirFromRunState(runStateDir) {
|
|
29
|
+
// run-scoped: <base>/runs/<run-id>/ -> parent of parent
|
|
30
|
+
// worktree: <work>/.autoloop/ -> runStateDir itself is the base
|
|
31
|
+
// shared: same as run-scoped
|
|
32
|
+
const parentName = basename(runStateDir);
|
|
33
|
+
if (parentName === ".autoloop" || parentName === ".miniloop") {
|
|
34
|
+
return runStateDir;
|
|
35
|
+
}
|
|
36
|
+
// runs/<run-id>/ path — peel two levels
|
|
37
|
+
const runs = join(runStateDir, "..");
|
|
38
|
+
return join(runs, "..");
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/control/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,WAAmB;IAC5C,OAAO,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,yDAAyD;IACzD,uEAAuE;IACvE,+BAA+B;IAC/B,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;QAC7D,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,wCAAwC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LiveControlAdapter } from "./adapter.js";
|
|
2
|
+
export interface PiAdapterHooks {
|
|
3
|
+
/** Called when the supervisor asks the backend to cancel its in-flight turn. */
|
|
4
|
+
triggerInterrupt: () => void;
|
|
5
|
+
/** Called to queue guidance into the in-flight turn via pi's `steer` command. */
|
|
6
|
+
triggerSteer: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Live-control adapter for the pi backend. Pi runs as a persistent RPC
|
|
10
|
+
* session (`pi --mode rpc`): `abort` cancels the in-flight turn, and `steer`
|
|
11
|
+
* queues guidance into it mid-turn — delivered before the next LLM call.
|
|
12
|
+
* Guidance also stays durable via the journal, so `guide` is always applied
|
|
13
|
+
* even when live steering is unavailable.
|
|
14
|
+
*/
|
|
15
|
+
export declare function piControlAdapter(runId: string, hooks: PiAdapterHooks): LiveControlAdapter;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { defaultCapabilities } from "./capabilities.js";
|
|
2
|
+
/**
|
|
3
|
+
* Live-control adapter for the pi backend. Pi runs as a persistent RPC
|
|
4
|
+
* session (`pi --mode rpc`): `abort` cancels the in-flight turn, and `steer`
|
|
5
|
+
* queues guidance into it mid-turn — delivered before the next LLM call.
|
|
6
|
+
* Guidance also stays durable via the journal, so `guide` is always applied
|
|
7
|
+
* even when live steering is unavailable.
|
|
8
|
+
*/
|
|
9
|
+
export function piControlAdapter(runId, hooks) {
|
|
10
|
+
const caps = buildCapabilities(runId);
|
|
11
|
+
return {
|
|
12
|
+
backend: "pi",
|
|
13
|
+
capabilities() {
|
|
14
|
+
return caps;
|
|
15
|
+
},
|
|
16
|
+
onRequest(request) {
|
|
17
|
+
if (request.verb === "interrupt") {
|
|
18
|
+
return applyInterrupt(hooks);
|
|
19
|
+
}
|
|
20
|
+
if (request.verb === "guide") {
|
|
21
|
+
const payload = request.payload;
|
|
22
|
+
if (payload?.interrupt) {
|
|
23
|
+
return applyInterrupt(hooks, "guidance-driven");
|
|
24
|
+
}
|
|
25
|
+
return applySteer(hooks, payload?.message ?? "");
|
|
26
|
+
}
|
|
27
|
+
return { state: "ignored", detail: `unknown verb: ${request.verb}` };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function buildCapabilities(runId) {
|
|
32
|
+
const base = defaultCapabilities("pi", runId);
|
|
33
|
+
base.interrupt = {
|
|
34
|
+
supported: true,
|
|
35
|
+
detail: "pi RPC abort of the in-flight turn",
|
|
36
|
+
};
|
|
37
|
+
base.guidance = {
|
|
38
|
+
supported: true,
|
|
39
|
+
detail: "journal-durable + live steer into the in-flight turn",
|
|
40
|
+
};
|
|
41
|
+
return base;
|
|
42
|
+
}
|
|
43
|
+
function applySteer(hooks, message) {
|
|
44
|
+
if (!message) {
|
|
45
|
+
return { state: "applied", detail: "guidance appended" };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
hooks.triggerSteer(message);
|
|
49
|
+
return {
|
|
50
|
+
state: "applied",
|
|
51
|
+
detail: "guidance appended + steered into live turn",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Steering is opportunistic; the journal copy still reaches the next
|
|
56
|
+
// iteration prompt, so the guidance itself is applied either way.
|
|
57
|
+
return {
|
|
58
|
+
state: "applied",
|
|
59
|
+
detail: "guidance appended (live steer unavailable)",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function applyInterrupt(hooks, detailPrefix = "interrupt") {
|
|
64
|
+
try {
|
|
65
|
+
hooks.triggerInterrupt();
|
|
66
|
+
return { state: "applied", detail: `${detailPrefix}: pi abort sent` };
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
70
|
+
return {
|
|
71
|
+
state: "rejected",
|
|
72
|
+
detail: `${detailPrefix}: abort failed: ${msg}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=pi-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-adapter.js","sourceRoot":"","sources":["../../src/control/pi-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAexD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAa,EACb,KAAqB;IAErB,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAEtC,OAAO;QACL,OAAO,EAAE,IAAI;QACb,YAAY;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QACD,SAAS,CAAC,OAAuB;YAC/B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAuB,CAAC;gBAChD,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;oBACvB,OAAO,cAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;gBAClD,CAAC;gBACD,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACvE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,CAAC,SAAS,GAAG;QACf,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,oCAAoC;KAC7C,CAAC;IACF,IAAI,CAAC,QAAQ,GAAG;QACd,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,sDAAsD;KAC/D,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,KAAqB,EAAE,OAAe;IACxD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC;QACH,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,kEAAkE;QAClE,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,KAAqB,EACrB,YAAY,GAAG,WAAW;IAE1B,IAAI,CAAC;QACH,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,YAAY,iBAAiB,EAAE,CAAC;IACxE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO;YACL,KAAK,EAAE,UAAU;YACjB,MAAM,EAAE,GAAG,YAAY,mBAAmB,GAAG,EAAE;SAChD,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ControlCapabilities, ControlPayload, ControlRequest, ControlStatus, ControlVerb } from "./types.js";
|
|
2
|
+
export declare function appendRequest(runStateDir: string, request: ControlRequest): void;
|
|
3
|
+
export declare function readRequests(runStateDir: string): ControlRequest[];
|
|
4
|
+
export declare function appendStatus(runStateDir: string, status: ControlStatus): void;
|
|
5
|
+
export declare function readStatuses(runStateDir: string): ControlStatus[];
|
|
6
|
+
export declare function writeCapabilities(runStateDir: string, caps: ControlCapabilities): void;
|
|
7
|
+
export declare function readCapabilities(runStateDir: string): ControlCapabilities | null;
|
|
8
|
+
/**
|
|
9
|
+
* Pending requests are requests with no status line yet — i.e. control the
|
|
10
|
+
* harness has not yet acknowledged.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pendingRequests(runStateDir: string): ControlRequest[];
|
|
13
|
+
export declare function newRequestId(): string;
|
|
14
|
+
export declare function buildRequest(runId: string, verb: ControlVerb, payload: ControlPayload, reason?: string): ControlRequest;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { controlCapabilitiesFile, controlRequestsFile, controlStatusFile, } from "./paths.js";
|
|
4
|
+
function ensureDir(path) {
|
|
5
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
function readJsonlLines(path) {
|
|
8
|
+
if (!existsSync(path))
|
|
9
|
+
return [];
|
|
10
|
+
const text = readFileSync(path, "utf-8");
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const line of text.split("\n")) {
|
|
13
|
+
const trimmed = line.trim();
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
out.push(JSON.parse(trimmed));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
/* skip malformed */
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
export function appendRequest(runStateDir, request) {
|
|
26
|
+
const path = controlRequestsFile(runStateDir);
|
|
27
|
+
ensureDir(path);
|
|
28
|
+
appendFileSync(path, `${JSON.stringify(request)}\n`, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
export function readRequests(runStateDir) {
|
|
31
|
+
return readJsonlLines(controlRequestsFile(runStateDir));
|
|
32
|
+
}
|
|
33
|
+
export function appendStatus(runStateDir, status) {
|
|
34
|
+
const path = controlStatusFile(runStateDir);
|
|
35
|
+
ensureDir(path);
|
|
36
|
+
appendFileSync(path, `${JSON.stringify(status)}\n`, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
export function readStatuses(runStateDir) {
|
|
39
|
+
return readJsonlLines(controlStatusFile(runStateDir));
|
|
40
|
+
}
|
|
41
|
+
export function writeCapabilities(runStateDir, caps) {
|
|
42
|
+
const path = controlCapabilitiesFile(runStateDir);
|
|
43
|
+
ensureDir(path);
|
|
44
|
+
writeFileSync(path, `${JSON.stringify(caps, null, 2)}\n`, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
export function readCapabilities(runStateDir) {
|
|
47
|
+
const path = controlCapabilitiesFile(runStateDir);
|
|
48
|
+
if (!existsSync(path))
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pending requests are requests with no status line yet — i.e. control the
|
|
59
|
+
* harness has not yet acknowledged.
|
|
60
|
+
*/
|
|
61
|
+
export function pendingRequests(runStateDir) {
|
|
62
|
+
const requests = readRequests(runStateDir);
|
|
63
|
+
const acked = new Set(readStatuses(runStateDir).map((s) => s.id));
|
|
64
|
+
return requests.filter((r) => !acked.has(r.id));
|
|
65
|
+
}
|
|
66
|
+
function randomSuffix() {
|
|
67
|
+
return Math.random().toString(36).slice(2, 8);
|
|
68
|
+
}
|
|
69
|
+
export function newRequestId() {
|
|
70
|
+
return `ctl_${Date.now().toString(36)}_${randomSuffix()}`;
|
|
71
|
+
}
|
|
72
|
+
export function buildRequest(runId, verb, payload, reason = "") {
|
|
73
|
+
return {
|
|
74
|
+
id: newRequestId(),
|
|
75
|
+
runId,
|
|
76
|
+
requestedAt: new Date().toISOString(),
|
|
77
|
+
verb,
|
|
78
|
+
reason,
|
|
79
|
+
payload,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/control/queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RunRecord } from "@mobrienv/autoloop-core/registry/types";
|
|
2
|
+
import type { ControlCapabilities, ControlRequest, ControlStatus } from "./types.js";
|
|
3
|
+
export interface ControlSnapshot {
|
|
4
|
+
run: RunRecord;
|
|
5
|
+
capabilities: ControlCapabilities | null;
|
|
6
|
+
pendingRequests: ControlRequest[];
|
|
7
|
+
recentStatuses: ControlStatus[];
|
|
8
|
+
}
|
|
9
|
+
export declare function renderShow(snap: ControlSnapshot): string;
|
|
10
|
+
export declare function renderCapabilities(caps: ControlCapabilities | null): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CAPABILITY_VERBS } from "./capabilities.js";
|
|
2
|
+
export function renderShow(snap) {
|
|
3
|
+
const lines = [];
|
|
4
|
+
lines.push(`Run: ${snap.run.run_id}`);
|
|
5
|
+
lines.push(`Status: ${snap.run.status}`);
|
|
6
|
+
lines.push(`Preset: ${snap.run.preset}`);
|
|
7
|
+
lines.push(`Backend: ${snap.run.backend || "(unknown)"}`);
|
|
8
|
+
lines.push(`Iteration: ${snap.run.iteration}`);
|
|
9
|
+
if (snap.run.stop_reason)
|
|
10
|
+
lines.push(`Stop: ${snap.run.stop_reason}`);
|
|
11
|
+
if (snap.run.pid)
|
|
12
|
+
lines.push(`PID: ${snap.run.pid}`);
|
|
13
|
+
lines.push("");
|
|
14
|
+
lines.push(renderCapabilities(snap.capabilities));
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push("Recent control activity:");
|
|
17
|
+
if (snap.recentStatuses.length === 0 && snap.pendingRequests.length === 0) {
|
|
18
|
+
lines.push(" (none)");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
for (const r of snap.pendingRequests) {
|
|
22
|
+
lines.push(` pending ${r.verb.padEnd(10)} ${r.id} ${r.reason || ""}`);
|
|
23
|
+
}
|
|
24
|
+
for (const s of snap.recentStatuses.slice(-5)) {
|
|
25
|
+
const detail = s.detail ? ` — ${s.detail}` : "";
|
|
26
|
+
lines.push(` ${s.state.padEnd(8)} ${s.verb.padEnd(10)} ${s.id}${detail}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
31
|
+
export function renderCapabilities(caps) {
|
|
32
|
+
if (!caps) {
|
|
33
|
+
return "Capabilities: (none published yet — run may not be live)";
|
|
34
|
+
}
|
|
35
|
+
const lines = [];
|
|
36
|
+
lines.push(`Capabilities (backend: ${caps.backend}):`);
|
|
37
|
+
for (const verb of CAPABILITY_VERBS) {
|
|
38
|
+
const cap = caps[verb];
|
|
39
|
+
const mark = cap.supported ? "yes" : "no ";
|
|
40
|
+
const detail = cap.detail ? ` — ${cap.detail}` : "";
|
|
41
|
+
lines.push(` ${verb.padEnd(10)} ${mark}${detail}`);
|
|
42
|
+
}
|
|
43
|
+
if (caps.extras) {
|
|
44
|
+
for (const [name, cap] of Object.entries(caps.extras)) {
|
|
45
|
+
const mark = cap.supported ? "yes" : "no ";
|
|
46
|
+
const detail = cap.detail ? ` — ${cap.detail}` : "";
|
|
47
|
+
lines.push(` ${name.padEnd(10)} ${mark}${detail}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
lines.push(` published ${caps.publishedAt}`);
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/control/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-neutral live-control types.
|
|
3
|
+
*
|
|
4
|
+
* The supervisor-facing contract. Adapters (Kiro ACP, Pi, future Claude live
|
|
5
|
+
* sessions) map these verbs to backend-specific behavior; the journal remains
|
|
6
|
+
* canonical for what actually happened.
|
|
7
|
+
*/
|
|
8
|
+
export type ControlVerb = "interrupt" | "guide";
|
|
9
|
+
export type CapabilityVerb = "guidance" | "interrupt" | "inspect";
|
|
10
|
+
export interface GuidePayload {
|
|
11
|
+
message: string;
|
|
12
|
+
/** If true, operator wants the current turn aborted so guidance is picked up now. */
|
|
13
|
+
interrupt: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type InterruptPayload = Record<string, never>;
|
|
16
|
+
export type ControlPayload = GuidePayload | InterruptPayload;
|
|
17
|
+
export interface ControlRequest {
|
|
18
|
+
id: string;
|
|
19
|
+
runId: string;
|
|
20
|
+
requestedAt: string;
|
|
21
|
+
verb: ControlVerb;
|
|
22
|
+
reason: string;
|
|
23
|
+
payload: ControlPayload;
|
|
24
|
+
}
|
|
25
|
+
export interface ControlCapability {
|
|
26
|
+
supported: boolean;
|
|
27
|
+
detail?: string;
|
|
28
|
+
}
|
|
29
|
+
export type ControlCapabilities = Record<CapabilityVerb, ControlCapability> & {
|
|
30
|
+
backend: string;
|
|
31
|
+
publishedAt: string;
|
|
32
|
+
runId: string;
|
|
33
|
+
extras?: Record<string, ControlCapability>;
|
|
34
|
+
};
|
|
35
|
+
export type ControlStatusState = "received" | "applied" | "rejected" | "ignored";
|
|
36
|
+
export interface ControlStatus {
|
|
37
|
+
id: string;
|
|
38
|
+
runId: string;
|
|
39
|
+
verb: ControlVerb;
|
|
40
|
+
state: ControlStatusState;
|
|
41
|
+
at: string;
|
|
42
|
+
detail?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ControlAck {
|
|
45
|
+
state: ControlStatusState;
|
|
46
|
+
detail?: string;
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-neutral live-control types.
|
|
3
|
+
*
|
|
4
|
+
* The supervisor-facing contract. Adapters (Kiro ACP, Pi, future Claude live
|
|
5
|
+
* sessions) map these verbs to backend-specific behavior; the journal remains
|
|
6
|
+
* canonical for what actually happened.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/control/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/dist/emit.js
CHANGED
|
@@ -34,6 +34,7 @@ const CORE_SYSTEM_TOPICS = new Set([
|
|
|
34
34
|
"review.finish",
|
|
35
35
|
"backend.start",
|
|
36
36
|
"backend.finish",
|
|
37
|
+
"backend.usage",
|
|
37
38
|
"event.invalid",
|
|
38
39
|
]);
|
|
39
40
|
export function coreSystemTopic(topic) {
|
|
@@ -70,12 +71,13 @@ export function emit(projectDir, topic, payload) {
|
|
|
70
71
|
if (operatorTopic(topic)) {
|
|
71
72
|
return acceptEmit(journalFile, topic, payload, validation);
|
|
72
73
|
}
|
|
73
|
-
// Task completion gate: block completion if open tasks remain
|
|
74
|
+
// Task completion gate: block completion if open blocking tasks remain.
|
|
75
|
+
// Soft tasks (soft === true) are advisory and never block completion.
|
|
74
76
|
if (topic === validation.completionEvent) {
|
|
75
77
|
const tasksFile = config.resolveTasksFile(projectDir);
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
return rejectTaskGate(journalFile, topic,
|
|
78
|
+
const blockingTasks = materializeOpenFrom(tasksFile).filter((t) => t.soft !== true);
|
|
79
|
+
if (blockingTasks.length > 0) {
|
|
80
|
+
return rejectTaskGate(journalFile, topic, blockingTasks, validation);
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
if (invalidEvent(topic, validation.allowedEvents, validation.parallelEnabled, validation.completionEvent)) {
|