@astrosheep/keiyaku 4.6.8 → 4.6.9
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 +40 -14
- package/build/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/build/plugins/claude/skills/keiyaku/SKILL.md +5 -6
- package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/plugins/claude/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/plugins/claude/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/claude/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/plugins/claude/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/plugins/claude/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/build/plugins/codex/skills/keiyaku/SKILL.md +5 -6
- package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/plugins/codex/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/plugins/codex/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/plugins/opencode/index.js +1 -1
- package/build/plugins/opencode/skills/keiyaku/SKILL.md +5 -6
- package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/plugins/opencode/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/plugins/opencode/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/skills/keiyaku/SKILL.md +5 -6
- package/build/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/skills/keiyaku-akuma/references/automation.md +8 -8
- package/build/skills/keiyaku-babysit/SKILL.md +1 -1
- package/build/skills/keiyaku-bind/SKILL.md +9 -9
- package/build/skills/keiyaku-debt/SKILL.md +91 -0
- package/build/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/src/akuma/akuma-instance.d.ts +2 -0
- package/build/src/akuma/akuma-instance.js +10 -0
- package/build/src/akuma/akuma-observe.d.ts +33 -8
- package/build/src/akuma/akuma-observe.js +88 -64
- package/build/src/akuma/akuma-owner.d.ts +11 -2
- package/build/src/akuma/akuma-owner.js +45 -35
- package/build/src/akuma/akuma-selection-execution.js +9 -1
- package/build/src/akuma/akuma-stream.d.ts +22 -0
- package/build/src/akuma/akuma-stream.js +116 -0
- package/build/src/akuma/akuma-wait.d.ts +1 -40
- package/build/src/akuma/akuma-wait.js +23 -84
- package/build/src/akuma/akuma.d.ts +17 -11
- package/build/src/akuma/akuma.js +2 -0
- package/build/src/akuma/allowed.d.ts +7 -12
- package/build/src/akuma/allowed.js +13 -4
- package/build/src/akuma/body-supervisor.d.ts +5 -5
- package/build/src/akuma/body-supervisor.js +22 -20
- package/build/src/akuma/body.js +28 -1
- package/build/src/akuma/call-request.d.ts +12 -22
- package/build/src/akuma/heart/activity-schema.d.ts +12 -0
- package/build/src/akuma/heart/activity-schema.js +6 -0
- package/build/src/akuma/heart/index.d.ts +59 -2
- package/build/src/akuma/heart/index.js +87 -8
- package/build/src/akuma/heart/rows.js +5 -3
- package/build/src/akuma/heart/soul.d.ts +9 -0
- package/build/src/akuma/heart/soul.js +29 -0
- package/build/src/akuma/heart/storage.d.ts +15 -0
- package/build/src/akuma/heart/storage.js +55 -0
- package/build/src/akuma/heart/timeline.d.ts +7 -0
- package/build/src/akuma/heart/timeline.js +10 -0
- package/build/src/akuma/index.d.ts +1 -0
- package/build/src/akuma/projection.d.ts +25 -0
- package/build/src/akuma/projection.js +24 -7
- package/build/src/akuma/provider-recipe.d.ts +11 -0
- package/build/src/akuma/provider-recipe.js +23 -0
- package/build/src/akuma/provider.d.ts +25 -0
- package/build/src/akuma/provider.js +99 -7
- package/build/src/akuma/providers/acp/events.js +25 -2
- package/build/src/akuma/providers/claude/events.js +76 -10
- package/build/src/akuma/providers/claude/index.js +2 -5
- package/build/src/akuma/providers/codex-app-server/events.js +17 -2
- package/build/src/akuma/providers/codex-app-server/index.js +15 -9
- package/build/src/akuma/providers/decode.d.ts +8 -0
- package/build/src/akuma/providers/decode.js +21 -0
- package/build/src/akuma/providers/grok-build/index.js +10 -26
- package/build/src/akuma/providers/opencode-sdk/event-stream.d.ts +3 -3
- package/build/src/akuma/providers/opencode-sdk/event-stream.js +2 -25
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +7 -43
- package/build/src/akuma/providers/opencode-sdk/events.js +147 -284
- package/build/src/akuma/providers/opencode-sdk/index.js +267 -421
- package/build/src/akuma/providers/opencode-sdk/native-http.d.ts +9 -0
- package/build/src/akuma/providers/opencode-sdk/native-http.js +69 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +42 -36
- package/build/src/akuma/providers/opencode-sdk/session.js +75 -82
- package/build/src/akuma/providers/paseo/events.d.ts +8 -0
- package/build/src/akuma/providers/paseo/events.js +58 -11
- package/build/src/akuma/providers/paseo/index.js +11 -7
- package/build/src/akuma/providers/pi/events.d.ts +2 -0
- package/build/src/akuma/providers/pi/events.js +32 -24
- package/build/src/akuma/providers/pi/index.js +21 -35
- package/build/src/akuma/request-lifecycle.d.ts +1 -2
- package/build/src/akuma/request-lifecycle.js +2 -8
- package/build/src/akuma/request-rendezvous.js +38 -55
- package/build/src/akuma/request-serve.d.ts +1 -1
- package/build/src/akuma/request-serve.js +77 -113
- package/build/src/akuma/selection-observation.d.ts +57 -51
- package/build/src/akuma/selection-observation.js +3 -3
- package/build/src/akuma/turn-drive.d.ts +3 -0
- package/build/src/akuma/turn-drive.js +26 -5
- package/build/src/akuma-body.js +12 -22
- package/build/src/cli/akuma-caller.js +10 -0
- package/build/src/cli/commands/akuma-grammar.js +3 -4
- package/build/src/cli/commands/akuma.js +13 -51
- package/build/src/cli/commands/contract-grammar.d.ts +7 -6
- package/build/src/cli/commands/contract-grammar.js +27 -32
- package/build/src/cli/commands/contract-help.d.ts +16 -7
- package/build/src/cli/commands/contract-help.js +20 -11
- package/build/src/cli/commands/contract.js +11 -11
- package/build/src/cli/commands/debt-grammar.d.ts +19 -0
- package/build/src/cli/commands/debt-grammar.js +323 -0
- package/build/src/cli/commands/debt-query.d.ts +5 -0
- package/build/src/cli/commands/{task-query.js → debt-query.js} +9 -9
- package/build/src/cli/commands/{task.d.ts → debt.d.ts} +2 -2
- package/build/src/cli/commands/{task.js → debt.js} +83 -143
- package/build/src/cli/coordinates.d.ts +1 -1
- package/build/src/cli/coordinates.js +1 -1
- package/build/src/cli/parse.d.ts +4 -4
- package/build/src/cli/parse.js +17 -16
- package/build/src/cli/render/akuma-activity.d.ts +11 -83
- package/build/src/cli/render/akuma-activity.js +20 -382
- package/build/src/cli/render/catalog.d.ts +2 -2
- package/build/src/cli/render/catalog.js +5 -5
- package/build/src/cli/render/contract.js +2 -2
- package/build/src/cli/render/debt.d.ts +28 -0
- package/build/src/cli/render/{task.js → debt.js} +51 -112
- package/build/src/cli/render/kanshi.js +28 -28
- package/build/src/cli/render/marks.d.ts +5 -5
- package/build/src/cli/render/marks.js +4 -10
- package/build/src/cli/render/nuke.js +1 -1
- package/build/src/cli/runtime.js +3 -3
- package/build/src/cli/scan.d.ts +1 -1
- package/build/src/cli/usage.d.ts +1 -1
- package/build/src/cli/usage.js +4 -4
- package/build/src/contract-worktree.js +3 -3
- package/build/src/debt/board.d.ts +160 -0
- package/build/src/{task → debt}/board.js +89 -89
- package/build/src/debt/catalog.d.ts +11 -0
- package/build/src/debt/catalog.js +59 -0
- package/build/src/{task → debt}/context.d.ts +1 -1
- package/build/src/{task → debt}/context.js +1 -1
- package/build/src/{task → debt}/created-observation.d.ts +6 -10
- package/build/src/{task → debt}/created-observation.js +7 -7
- package/build/src/debt/document.d.ts +37 -0
- package/build/src/{task → debt}/document.js +37 -37
- package/build/src/debt/identity.d.ts +21 -0
- package/build/src/{task → debt}/identity.js +35 -35
- package/build/src/debt/index.d.ts +111 -0
- package/build/src/debt/index.js +203 -0
- package/build/src/{task → debt}/input.d.ts +8 -8
- package/build/src/{task → debt}/input.js +21 -21
- package/build/src/debt/lifecycle-operations.d.ts +6 -0
- package/build/src/{task → debt}/lifecycle-operations.js +27 -31
- package/build/src/debt/mutation-result.d.ts +981 -0
- package/build/src/debt/mutation-result.js +109 -0
- package/build/src/debt/mutation.d.ts +424 -0
- package/build/src/debt/mutation.js +223 -0
- package/build/src/debt/operation-types.d.ts +71 -0
- package/build/src/debt/operations.d.ts +28 -0
- package/build/src/{task → debt}/operations.js +59 -60
- package/build/src/debt/query.d.ts +76 -0
- package/build/src/{task → debt}/query.js +55 -55
- package/build/src/{task → debt}/store.d.ts +13 -13
- package/build/src/{task → debt}/store.js +58 -49
- package/build/src/debt/view.d.ts +4 -0
- package/build/src/debt/view.js +7 -0
- package/build/src/identity/coordinates.d.ts +1 -1
- package/build/src/index.d.ts +13 -13
- package/build/src/index.js +9 -9
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +33 -33
- package/build/src/kanshi/report.d.ts +7 -7
- package/build/src/kanshi/select.js +4 -4
- package/build/src/library/akumas.d.ts +2 -3
- package/build/src/library/bind.d.ts +3 -3
- package/build/src/library/bind.js +5 -5
- package/build/src/library/contract-creation.js +5 -5
- package/build/src/library/contract-execution.js +4 -4
- package/build/src/library/contract-types.d.ts +2 -2
- package/build/src/library/input.d.ts +2 -2
- package/build/src/library/input.js +5 -5
- package/build/src/library/invocation.js +2 -2
- package/build/src/library/nuke.js +3 -3
- package/build/src/library/outcome.d.ts +126 -126
- package/build/src/library/outcome.js +3 -3
- package/build/src/library/selection.d.ts +1 -4
- package/build/src/library/selection.js +11 -62
- package/build/src/plugin/public.d.ts +5 -0
- package/build/src/plugin/runtime.js +3 -1
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settlement/fence.d.ts +3 -3
- package/build/src/settlement/fence.js +4 -4
- package/build/src/settlement/holder.d.ts +16 -16
- package/build/src/settlement/holder.js +69 -63
- package/build/src/settlement/settle.d.ts +5 -5
- package/build/src/settlement/settle.js +53 -53
- package/package.json +12 -6
- package/plugins/paseo/plugin/index.server.ts +17 -0
- package/plugins/paseo/plugin/paseo-plugin.json +4 -0
- package/plugins/paseo/plugin/server/attach.ts +52 -0
- package/plugins/paseo/plugin/server/keiyaku.ts +44 -0
- package/plugins/paseo/plugin/server/message.ts +60 -0
- package/plugins/paseo/plugin/server/mirror.ts +193 -0
- package/plugins/paseo/plugin/server/model.ts +14 -0
- package/plugins/paseo/plugin/server/provider.ts +276 -0
- package/plugins/paseo/plugin/server/sdk-worker.ts +237 -0
- package/plugins/paseo/plugin/server/sdk.ts +289 -0
- package/plugins/paseo/plugin/server/stop.ts +26 -0
- package/plugins/paseo/plugin/server/transport.ts +35 -0
- package/plugins/paseo/plugin/shared/attach-rpc.ts +10 -0
- package/plugins/paseo/plugin/shared/refresh-rpc.ts +6 -0
- package/plugins/paseo/plugin/tsconfig.json +6 -0
- package/skills/keiyaku/SKILL.md +5 -6
- package/skills/keiyaku-akuma/SKILL.md +1 -1
- package/skills/keiyaku-akuma/references/automation.md +8 -8
- package/skills/keiyaku-babysit/SKILL.md +1 -1
- package/skills/keiyaku-bind/SKILL.md +9 -9
- package/skills/keiyaku-debt/SKILL.md +91 -0
- package/skills/keiyaku-workflow/SKILL.md +2 -2
- package/build/plugins/claude/skills/keiyaku-task/SKILL.md +0 -104
- package/build/plugins/codex/skills/keiyaku-task/SKILL.md +0 -104
- package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +0 -104
- package/build/skills/keiyaku-task/SKILL.md +0 -104
- package/build/src/cli/commands/task-grammar.d.ts +0 -19
- package/build/src/cli/commands/task-grammar.js +0 -384
- package/build/src/cli/commands/task-query.d.ts +0 -5
- package/build/src/cli/render/task.d.ts +0 -39
- package/build/src/task/board.d.ts +0 -168
- package/build/src/task/catalog.d.ts +0 -11
- package/build/src/task/catalog.js +0 -59
- package/build/src/task/compose-language.d.ts +0 -57
- package/build/src/task/compose-language.js +0 -364
- package/build/src/task/compose-parser.d.ts +0 -32
- package/build/src/task/compose-parser.js +0 -180
- package/build/src/task/compose.d.ts +0 -16
- package/build/src/task/compose.js +0 -189
- package/build/src/task/document.d.ts +0 -39
- package/build/src/task/identity.d.ts +0 -21
- package/build/src/task/index.d.ts +0 -138
- package/build/src/task/index.js +0 -269
- package/build/src/task/lifecycle-operations.d.ts +0 -6
- package/build/src/task/mutation-result.d.ts +0 -1726
- package/build/src/task/mutation-result.js +0 -198
- package/build/src/task/mutation.d.ts +0 -590
- package/build/src/task/mutation.js +0 -262
- package/build/src/task/operation-types.d.ts +0 -71
- package/build/src/task/operations.d.ts +0 -28
- package/build/src/task/query.d.ts +0 -76
- package/build/src/task/view.d.ts +0 -4
- package/build/src/task/view.js +0 -7
- package/skills/keiyaku-task/SKILL.md +0 -104
- /package/build/src/{task → debt}/operation-types.js +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ProviderEvent, ProviderTimelineItem } from "@getpaseo/plugin/server/provider";
|
|
3
|
+
import type { History, Row } from "./keiyaku.js";
|
|
4
|
+
import type { AdmittedMessage } from "./message.js";
|
|
5
|
+
|
|
6
|
+
type Emit = (event: ProviderEvent) => void;
|
|
7
|
+
type Turn = { sequence: number; closed: boolean };
|
|
8
|
+
export function createMirror(sessionId: string, emit: Emit) {
|
|
9
|
+
let covered: { high: number; signatures: Map<number, string> } | undefined;
|
|
10
|
+
let turn: Turn | undefined;
|
|
11
|
+
const openTurns = new Map<number, Turn>();
|
|
12
|
+
let unhealthy = false;
|
|
13
|
+
let coverageNotice = "";
|
|
14
|
+
let submissions = 0;
|
|
15
|
+
let deferred: History | undefined;
|
|
16
|
+
let deferredReannounce = false;
|
|
17
|
+
const displayedTells = new Map<string, number>();
|
|
18
|
+
const item = (value: ProviderTimelineItem, timestamp?: string) => emit({
|
|
19
|
+
type: "timeline.item", sessionId, item: value, ...(timestamp === undefined ? {} : { timestamp }),
|
|
20
|
+
});
|
|
21
|
+
const finish = (target: Turn | undefined, state: "completed" | "failed" | "canceled", message?: string) => {
|
|
22
|
+
if (!target || target.closed) return;
|
|
23
|
+
target.closed = true;
|
|
24
|
+
openTurns.delete(target.sequence);
|
|
25
|
+
emit({ type: "session.turn", sessionId, turnId: `turn/${target.sequence}`, state,
|
|
26
|
+
...(message === undefined ? {} : { error: { message } }) });
|
|
27
|
+
};
|
|
28
|
+
const userMessage = (row: Extract<Row, { kind: "tell" }>, clientMessageId?: string) => {
|
|
29
|
+
if (displayedTells.has(row.tellId)) return;
|
|
30
|
+
displayedTells.set(row.tellId, row.sequence);
|
|
31
|
+
item({ type: "user_message", id: `keiyaku:tell/${row.tellId}`, text: row.text,
|
|
32
|
+
...(clientMessageId === undefined ? {} : { clientMessageId }) }, row.at);
|
|
33
|
+
};
|
|
34
|
+
const mirror = {
|
|
35
|
+
seed(history: History) {
|
|
36
|
+
const { rows } = history;
|
|
37
|
+
covered = { high: history.highest ?? 0,
|
|
38
|
+
signatures: new Map(rows.map((row) => [row.sequence, JSON.stringify(row)])) };
|
|
39
|
+
for (const row of rows) if (row.kind === "tell") displayedTells.set(row.tellId, row.sequence);
|
|
40
|
+
const latest = rows.filter((row) => row.kind === "turn").sort((a, b) => a.sequence - b.sequence).at(-1);
|
|
41
|
+
if (latest?.kind === "turn") {
|
|
42
|
+
turn = { sequence: latest.turnSequence,
|
|
43
|
+
closed: rows.some((row) => row.kind === "outcome" && row.turnSequence === latest.turnSequence) };
|
|
44
|
+
if (!turn.closed) {
|
|
45
|
+
openTurns.set(turn.sequence, turn);
|
|
46
|
+
emit({ type: "session.turn", sessionId, turnId: `turn/${turn.sequence}`, state: "started" });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
hold() {
|
|
51
|
+
submissions++;
|
|
52
|
+
return (publish = true) => {
|
|
53
|
+
submissions--;
|
|
54
|
+
if (submissions === 0) {
|
|
55
|
+
const rows = deferred;
|
|
56
|
+
const reannounce = deferredReannounce;
|
|
57
|
+
deferred = undefined;
|
|
58
|
+
deferredReannounce = false;
|
|
59
|
+
if (rows && publish) mirror.publish(rows, reannounce);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
currentTurnId: () => turn && !turn.closed ? `turn/${turn.sequence}` : undefined,
|
|
64
|
+
refreshedTurnId() {
|
|
65
|
+
const latest = deferred?.rows.filter((row) => row.kind === "turn").at(-1);
|
|
66
|
+
if (latest?.kind !== "turn") return mirror.currentTurnId();
|
|
67
|
+
if (deferred?.rows.some((row) => row.kind === "outcome" && row.turnSequence === latest.turnSequence)) return undefined;
|
|
68
|
+
if (turn?.sequence === latest.turnSequence && turn.closed) return undefined;
|
|
69
|
+
return `turn/${latest.turnSequence}`;
|
|
70
|
+
},
|
|
71
|
+
admit(row: AdmittedMessage, clientMessageId: string) {
|
|
72
|
+
userMessage(row, clientMessageId);
|
|
73
|
+
},
|
|
74
|
+
cancel(reason: string) {
|
|
75
|
+
for (const target of openTurns.values()) finish(target, "canceled", reason);
|
|
76
|
+
},
|
|
77
|
+
observedTurn(): Readonly<{ cancel(reason: string): void }> | undefined {
|
|
78
|
+
if (!turn || turn.closed) return undefined;
|
|
79
|
+
const observed = turn;
|
|
80
|
+
return { cancel(reason: string) {
|
|
81
|
+
if (deferred?.rows.some((row) => row.kind === "outcome" && row.turnSequence === observed.sequence)) return;
|
|
82
|
+
finish(observed, "canceled", reason);
|
|
83
|
+
} };
|
|
84
|
+
},
|
|
85
|
+
unreadable() {
|
|
86
|
+
deferredReannounce = false;
|
|
87
|
+
if (!unhealthy) item({ type: "notification", level: "warning",
|
|
88
|
+
id: `keiyaku:unreadable/${covered ? Math.min(...covered.signatures.keys()) : 0}`,
|
|
89
|
+
message: "Keiyaku read surface unavailable; retaining the previous mirror." });
|
|
90
|
+
unhealthy = true;
|
|
91
|
+
},
|
|
92
|
+
publish(input: History, reannounce = false) {
|
|
93
|
+
// Hold racing reads until native Tell identity can replace the optimistic item.
|
|
94
|
+
if (submissions > 0) { deferred = input; deferredReannounce ||= reannounce; return; }
|
|
95
|
+
const rows = [...input.rows].sort((a, b) => a.sequence - b.sequence);
|
|
96
|
+
const lowest = rows[0]?.sequence;
|
|
97
|
+
// Public history metadata, not projected sequence holes, owns coverage.
|
|
98
|
+
// Neither missing retention nor a bounded read is an execution outcome.
|
|
99
|
+
const uncoveredEarlier = input.hasEarlier && (!covered || lowest === undefined || lowest > covered.high);
|
|
100
|
+
const notice = input.historyLost ? "retention" : uncoveredEarlier ? "bounded" : "";
|
|
101
|
+
const noticeKey = notice ? `${notice}/${input.lowestRetained ?? "empty"}` : "";
|
|
102
|
+
if (noticeKey && noticeKey !== coverageNotice) {
|
|
103
|
+
item({ type: "notification", level: "warning", id: `keiyaku:window/${lowest ?? "empty"}`,
|
|
104
|
+
message: input.historyLost
|
|
105
|
+
? "Keiyaku reports history outside retention; retained records remain available. Execution state is unchanged."
|
|
106
|
+
: "Earlier retained history is outside this bounded mirror read. Execution state is unchanged." });
|
|
107
|
+
}
|
|
108
|
+
coverageNotice = noticeKey;
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
if (covered?.signatures.get(row.sequence) === JSON.stringify(row)) continue;
|
|
111
|
+
if (row.kind === "tell") {
|
|
112
|
+
userMessage(row);
|
|
113
|
+
} else if (row.kind === "call") {
|
|
114
|
+
item({ type: "user_message", id: `keiyaku:call/${row.sequence}`, text: row.text }, row.at);
|
|
115
|
+
} else if (row.kind === "turn") {
|
|
116
|
+
// Publish retained starts even when their outcomes arrive in this read.
|
|
117
|
+
// Older starts must not reopen a sealed or superseded Turn.
|
|
118
|
+
if (!turn || row.turnSequence > turn.sequence) {
|
|
119
|
+
turn = { sequence: row.turnSequence, closed: false };
|
|
120
|
+
openTurns.set(turn.sequence, turn);
|
|
121
|
+
emit({ type: "session.turn", sessionId, turnId: `turn/${turn.sequence}`, state: "started" });
|
|
122
|
+
}
|
|
123
|
+
} else if (row.kind === "tool") {
|
|
124
|
+
// Tool identity comes from the producer, scoped to its Turn. An older
|
|
125
|
+
// CLI without it can show settled records, never guessed live pairing.
|
|
126
|
+
const state = row.state;
|
|
127
|
+
const toolIdentity = row.toolId === undefined ? `record/${row.sequence}` : `${row.turnSequence}/${row.toolId}`;
|
|
128
|
+
if (typeof state === "string" && row.toolId === undefined) {
|
|
129
|
+
item({ type: "notification", id: `keiyaku:tool/${row.sequence}`, level: "info",
|
|
130
|
+
message: `${row.name}: ${state === "active" ? "start observed" : "result not witnessed"}. ${JSON.stringify(row.call)}` }, row.at);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const result = typeof state === "object" ? state : undefined;
|
|
134
|
+
// The result's own output is the observed text; its diagnostic message is
|
|
135
|
+
// only the stand-in an older producer wrote instead, so an honestly empty
|
|
136
|
+
// output is never replaced by its diagnostic.
|
|
137
|
+
const output = result === undefined ? undefined : result.output === undefined ? result.message : result.output;
|
|
138
|
+
const single = row.call.kind === "fileChange" && row.call.changes.length === 1 ? row.call.changes[0] : undefined;
|
|
139
|
+
const detail: Extract<ProviderTimelineItem, { type: "tool_call" }>["detail"] = row.call.kind === "run"
|
|
140
|
+
? { type: "shell", command: row.call.command,
|
|
141
|
+
...(output === undefined ? {} : { output }),
|
|
142
|
+
...(result?.exitCode === undefined ? {} : { exitCode: result.exitCode }) }
|
|
143
|
+
: row.call.kind === "read"
|
|
144
|
+
? { type: "read", filePath: row.call.path,
|
|
145
|
+
...(row.call.offset === undefined ? {} : { offset: row.call.offset }),
|
|
146
|
+
...(row.call.limit === undefined ? {} : { limit: row.call.limit }),
|
|
147
|
+
...(output === undefined ? {} : { content: output }) }
|
|
148
|
+
: row.call.kind === "search"
|
|
149
|
+
? { type: "search", query: row.call.query,
|
|
150
|
+
...(output === undefined ? {} : { content: output }),
|
|
151
|
+
...(row.durationMs === undefined ? {} : { durationMs: row.durationMs }),
|
|
152
|
+
...(row.truncated === undefined ? {} : { truncated: row.truncated }) }
|
|
153
|
+
// One observed file patch is the whole operation; anything else keeps
|
|
154
|
+
// the complete change list so no single file stands in for the rest.
|
|
155
|
+
: row.call.kind === "fileChange" && single?.patch !== undefined
|
|
156
|
+
? { type: "edit", filePath: single.path, unifiedDiff: single.patch }
|
|
157
|
+
: { type: "unknown", input: z.json().parse(row.call), output: result === undefined ? null : z.json().parse(result) };
|
|
158
|
+
item({ type: "tool_call", id: `keiyaku:tool/${toolIdentity}`, callId: `tool/${toolIdentity}`, name: row.name,
|
|
159
|
+
detail, metadata: { turnSequence: row.turnSequence, sequence: row.sequence,
|
|
160
|
+
...(row.durationMs === undefined ? {} : { durationMs: row.durationMs }),
|
|
161
|
+
...(row.truncated === undefined ? {} : { truncated: row.truncated }),
|
|
162
|
+
call: z.json().parse(row.call), state: z.json().parse(state) },
|
|
163
|
+
...(typeof state === "object" && state.status === "error"
|
|
164
|
+
? { status: "failed", error: z.json().parse(state) }
|
|
165
|
+
: { status: state === "active" ? "running" : state === "unsettled" ? "canceled" : "completed", error: null }) }, row.at);
|
|
166
|
+
} else if (row.kind === "said") {
|
|
167
|
+
item({ type: "assistant_message", id: `keiyaku:said/${row.sequence}`, text: row.text }, row.at);
|
|
168
|
+
} else if (row.kind === "thought") {
|
|
169
|
+
item({ type: "reasoning", id: `keiyaku:thought/${row.sequence}`, text: row.text }, row.at);
|
|
170
|
+
} else if (row.kind === "note") {
|
|
171
|
+
item({ type: "notification", id: `keiyaku:note/${row.sequence}`, level: "info", message: row.text }, row.at);
|
|
172
|
+
} else if (row.kind === "outcome") {
|
|
173
|
+
item(row.outcome.kind === "answered"
|
|
174
|
+
? { type: "assistant_message", id: row.outcome.foldedSaidSequence === undefined
|
|
175
|
+
? `keiyaku:turn/${row.turnSequence}` : `keiyaku:said/${row.outcome.foldedSaidSequence}`, text: row.outcome.answer }
|
|
176
|
+
: { type: "error", id: `keiyaku:turn/${row.turnSequence}`, message: row.outcome.diagnostic }, row.at);
|
|
177
|
+
finish(openTurns.get(row.turnSequence), row.outcome.kind === "answered" ? "completed" : "failed",
|
|
178
|
+
row.outcome.kind === "failed" ? row.outcome.diagnostic : undefined);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
covered = { high: Math.max(covered?.high ?? 0, input.highest ?? 0),
|
|
182
|
+
signatures: new Map(rows.map((row) => [row.sequence, JSON.stringify(row)])) };
|
|
183
|
+
if (lowest !== undefined) for (const [id, sequence] of displayedTells) {
|
|
184
|
+
if (sequence < lowest) displayedTells.delete(id);
|
|
185
|
+
}
|
|
186
|
+
unhealthy = false;
|
|
187
|
+
const current = turn;
|
|
188
|
+
if (reannounce && current && !current.closed && rows.some((row) => row.kind === "turn" && row.turnSequence === current.sequence))
|
|
189
|
+
emit({ type: "session.turn", sessionId, turnId: `turn/${current.sequence}`, state: "started" });
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
return mirror;
|
|
193
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ProviderConfigState } from "@getpaseo/plugin/server/provider";
|
|
2
|
+
|
|
3
|
+
/** A mirror reports the public Akuma configuration; it cannot change it. */
|
|
4
|
+
export function modelConfig(status: { provider?: string; model?: string }): ProviderConfigState {
|
|
5
|
+
return {
|
|
6
|
+
...(status.model === undefined ? {} : { model: status.model }),
|
|
7
|
+
models: status.model === undefined ? [] : [{
|
|
8
|
+
id: status.model,
|
|
9
|
+
label: status.provider ? `${status.provider} / ${status.model}` : status.model,
|
|
10
|
+
isSelectable: false,
|
|
11
|
+
}],
|
|
12
|
+
modes: [], thinkingOptions: [], settings: [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { negotiateProviderCapabilities, type ProviderConnection, type ProviderEvent, type ProviderPrompt, type ProviderRegistration } from "@getpaseo/plugin/server/provider";
|
|
4
|
+
import type { History } from "./keiyaku.js";
|
|
5
|
+
import type { AkumaTransport } from "./transport.js";
|
|
6
|
+
import { messageText, tellPresentation } from "./message.js";
|
|
7
|
+
import { createMirror } from "./mirror.js";
|
|
8
|
+
import { modelConfig } from "./model.js";
|
|
9
|
+
import type { StopAttempt } from "./stop.js";
|
|
10
|
+
|
|
11
|
+
const identity = z.object({ world: z.string().min(1), akuId: z.string().regex(/^aku\//u) });
|
|
12
|
+
export function createProvider(transport: AkumaTransport) {
|
|
13
|
+
const sessions = new Set<{ world: string; akuId: string; observe(reannounce?: boolean): Promise<void>; stop(): Promise<void> }>();
|
|
14
|
+
const provider: ProviderRegistration = {
|
|
15
|
+
id: "keiyaku", label: "Keiyaku", description: "Akuma runtime mirror",
|
|
16
|
+
async connect(request): Promise<ProviderConnection> {
|
|
17
|
+
if (!request.versions.includes(1)) throw new Error("Provider protocol version 1 is required");
|
|
18
|
+
const capabilities = negotiateProviderCapabilities(request.capabilities, ["session.persistence", "prompt.message", "prompt.steer"]);
|
|
19
|
+
const listeners = new Set<(event: ProviderEvent) => void>();
|
|
20
|
+
const local = new Map<string, { stop(): Promise<void>; stopExecution(requestId: string): Promise<void>; prompt(prompt: ProviderPrompt): Promise<void> }>();
|
|
21
|
+
let closed = false;
|
|
22
|
+
const emit = (event: ProviderEvent) => { if (!closed) for (const listener of listeners) listener(event); };
|
|
23
|
+
return {
|
|
24
|
+
version: 1, capabilities,
|
|
25
|
+
onEvent(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; },
|
|
26
|
+
async send(input) {
|
|
27
|
+
if (closed) throw new Error("Provider connection is closed");
|
|
28
|
+
if (input.type === "catalog") {
|
|
29
|
+
emit({ type: "catalog", requestId: input.requestId, catalog: {
|
|
30
|
+
models: [{ id: "mirror", label: "Akuma mirror" }], defaultModel: "mirror", modes: [],
|
|
31
|
+
} });
|
|
32
|
+
} else if (input.type === "session.open") {
|
|
33
|
+
if (local.has(input.sessionId)) throw new Error("Session already open");
|
|
34
|
+
if (input.persistence && input.persistence.version !== 1) throw new Error("Unsupported persistence version");
|
|
35
|
+
const target = identity.parse(input.persistence?.data ?? input.config.providerOptions);
|
|
36
|
+
const world = await realpath(target.world);
|
|
37
|
+
const mirror = createMirror(input.sessionId, emit);
|
|
38
|
+
const cancellation = new AbortController();
|
|
39
|
+
// The opening window seeds retained state instead of announcing it.
|
|
40
|
+
const skipRetained = input.history === "skip";
|
|
41
|
+
let opening = true;
|
|
42
|
+
let pending = false;
|
|
43
|
+
let pendingReannounce = false;
|
|
44
|
+
let running: Promise<void> | undefined;
|
|
45
|
+
let observed: Readonly<{ cancel(): void; pulling: Promise<void> }> | undefined;
|
|
46
|
+
const submissions = new Set<Promise<void>>();
|
|
47
|
+
let stopping: Promise<StopAttempt> | undefined;
|
|
48
|
+
// Release the live subscription. Its pending frame settles as
|
|
49
|
+
// released and is never published into the next observation.
|
|
50
|
+
const releaseObservation = (): void => {
|
|
51
|
+
const current = observed;
|
|
52
|
+
observed = undefined;
|
|
53
|
+
current?.cancel();
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Observe the current window once, then keep pulling its changes.
|
|
57
|
+
* A slow pull only bounds the next frame: no producer queue exists
|
|
58
|
+
* here and no timer retries a failure. A failed or ended
|
|
59
|
+
* subscription keeps the previous display and waits for the next
|
|
60
|
+
* explicit refresh, which opens a fresh observation.
|
|
61
|
+
*/
|
|
62
|
+
async function observeWindow(announce: boolean): Promise<void> {
|
|
63
|
+
releaseObservation();
|
|
64
|
+
if (cancellation.signal.aborted) return;
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const relay = () => controller.abort(cancellation.signal.reason);
|
|
67
|
+
cancellation.signal.addEventListener("abort", relay, { once: true });
|
|
68
|
+
const iterator = transport.stream(world, target.akuId, controller.signal)[Symbol.asyncIterator]();
|
|
69
|
+
const entry = { cancel: () => {
|
|
70
|
+
cancellation.signal.removeEventListener("abort", relay);
|
|
71
|
+
controller.abort();
|
|
72
|
+
}, pulling: Promise.resolve() };
|
|
73
|
+
observed = entry;
|
|
74
|
+
const publish = (history: History, reannounce: boolean): void => {
|
|
75
|
+
if (cancellation.signal.aborted || observed !== entry) return;
|
|
76
|
+
if (opening && skipRetained) mirror.seed(history);
|
|
77
|
+
else mirror.publish(history, reannounce);
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const first = await iterator.next();
|
|
81
|
+
if (first.done) {
|
|
82
|
+
if (observed === entry) mirror.unreadable();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
publish(first.value, announce);
|
|
86
|
+
} catch {
|
|
87
|
+
if (observed === entry && !cancellation.signal.aborted) mirror.unreadable();
|
|
88
|
+
return;
|
|
89
|
+
} finally {
|
|
90
|
+
opening = false;
|
|
91
|
+
}
|
|
92
|
+
entry.pulling = (async () => {
|
|
93
|
+
for (;;) {
|
|
94
|
+
let frame: IteratorResult<History>;
|
|
95
|
+
try {
|
|
96
|
+
frame = await iterator.next();
|
|
97
|
+
} catch {
|
|
98
|
+
// A released or failed observation never reports itself over
|
|
99
|
+
// the one that replaced it.
|
|
100
|
+
if (observed === entry && !cancellation.signal.aborted) mirror.unreadable();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (observed !== entry) return;
|
|
104
|
+
if (frame.done) {
|
|
105
|
+
mirror.unreadable();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
publish(frame.value, false);
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
}
|
|
112
|
+
// Every explicit refresh, Tell and Stop synchronization reopens the
|
|
113
|
+
// one observation instead of reading beside it, so a late result can
|
|
114
|
+
// never overwrite a newer snapshot, and hints coalesce into one.
|
|
115
|
+
function observe(reannounce = false): Promise<void> {
|
|
116
|
+
if (cancellation.signal.aborted) return Promise.resolve();
|
|
117
|
+
pending = true;
|
|
118
|
+
pendingReannounce ||= reannounce;
|
|
119
|
+
running ??= (async () => {
|
|
120
|
+
while (pending && !cancellation.signal.aborted) {
|
|
121
|
+
pending = false;
|
|
122
|
+
const announce = pendingReannounce;
|
|
123
|
+
pendingReannounce = false;
|
|
124
|
+
await observeWindow(announce);
|
|
125
|
+
}
|
|
126
|
+
})().finally(() => { running = undefined; });
|
|
127
|
+
return running;
|
|
128
|
+
}
|
|
129
|
+
const session = {
|
|
130
|
+
world, akuId: target.akuId,
|
|
131
|
+
observe,
|
|
132
|
+
async prompt(prompt: ProviderPrompt) {
|
|
133
|
+
const respond = (result: Extract<ProviderEvent, { type: "session.prompt_result" }>["result"]) => emit({
|
|
134
|
+
type: "session.prompt_result", sessionId: input.sessionId, clientMessageId: prompt.clientMessageId, result,
|
|
135
|
+
});
|
|
136
|
+
let text: string;
|
|
137
|
+
try {
|
|
138
|
+
cancellation.signal.throwIfAborted();
|
|
139
|
+
text = messageText(prompt);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
142
|
+
respond({ type: "failed", error: { message: failure.message } });
|
|
143
|
+
throw failure;
|
|
144
|
+
}
|
|
145
|
+
const steerTurnId = prompt.delivery === "steer" ? mirror.currentTurnId() : undefined;
|
|
146
|
+
if (prompt.delivery === "steer" && steerTurnId === undefined) {
|
|
147
|
+
const failure = new Error("No observed Paseo Turn for steering; Tell was not submitted");
|
|
148
|
+
respond({ type: "failed", error: { message: failure.message } });
|
|
149
|
+
throw failure;
|
|
150
|
+
}
|
|
151
|
+
const release = mirror.hold();
|
|
152
|
+
const submission = (async () => {
|
|
153
|
+
try {
|
|
154
|
+
const admitted = tellPresentation(await transport.tell(world, target.akuId, cancellation.signal, text), steerTurnId);
|
|
155
|
+
// Observe once while publication is held. An idle send may have
|
|
156
|
+
// started real work even when its receipt still says pending.
|
|
157
|
+
await session.observe();
|
|
158
|
+
mirror.admit(admitted.row, prompt.clientMessageId);
|
|
159
|
+
const observed = mirror.refreshedTurnId();
|
|
160
|
+
respond(admitted.result.type === "completed" && observed !== undefined
|
|
161
|
+
? { type: "turn", turnId: observed } : admitted.result);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const failure = new Error(`${error instanceof Error ? error.message : String(error)}. Input may already be recorded; not retried.`);
|
|
164
|
+
respond({ type: "failed", error: { message: failure.message } });
|
|
165
|
+
await session.observe();
|
|
166
|
+
// Do not let the host treat an uncertain write as "unavailable"
|
|
167
|
+
// and replace execution before resubmitting the same text.
|
|
168
|
+
throw failure;
|
|
169
|
+
} finally {
|
|
170
|
+
release(!cancellation.signal.aborted);
|
|
171
|
+
}
|
|
172
|
+
})();
|
|
173
|
+
submissions.add(submission);
|
|
174
|
+
try { await submission; } finally { submissions.delete(submission); }
|
|
175
|
+
},
|
|
176
|
+
async stop() {
|
|
177
|
+
mirror.cancel("Observation closed; Akuma was not interrupted");
|
|
178
|
+
cancellation.abort();
|
|
179
|
+
const closing = observed;
|
|
180
|
+
releaseObservation();
|
|
181
|
+
sessions.delete(session);
|
|
182
|
+
local.delete(input.sessionId);
|
|
183
|
+
await Promise.allSettled([...submissions]);
|
|
184
|
+
await stopping;
|
|
185
|
+
await running;
|
|
186
|
+
await closing?.pulling;
|
|
187
|
+
emit({ type: "session.closed", sessionId: input.sessionId });
|
|
188
|
+
},
|
|
189
|
+
stopExecution,
|
|
190
|
+
};
|
|
191
|
+
async function attemptStop(): Promise<StopAttempt> {
|
|
192
|
+
if (cancellation.signal.aborted)
|
|
193
|
+
return { kind: "uncertain", message: "Keiyaku could not prove the stop: the mirror closed before it was attempted" };
|
|
194
|
+
const observed = mirror.observedTurn();
|
|
195
|
+
let attempt: StopAttempt;
|
|
196
|
+
try {
|
|
197
|
+
attempt = await transport.stop(world, target.akuId, cancellation.signal);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
attempt = { kind: "uncertain", message: `Keiyaku could not prove the stop: ${error instanceof Error ? error.message : String(error)}` };
|
|
200
|
+
}
|
|
201
|
+
await session.observe();
|
|
202
|
+
if (attempt.kind === "stopped")
|
|
203
|
+
observed?.cancel("Akuma execution was stopped from Paseo; the Akuma is preserved for a later Tell");
|
|
204
|
+
return attempt;
|
|
205
|
+
}
|
|
206
|
+
async function stopExecution(requestId: string): Promise<void> {
|
|
207
|
+
stopping ??= attemptStop().finally(() => { stopping = undefined; });
|
|
208
|
+
const attempt = await stopping;
|
|
209
|
+
if (attempt.kind === "stopped") emit({ type: "request.completed", requestId });
|
|
210
|
+
else emit({ type: "request.failed", requestId, error: { message: attempt.message } });
|
|
211
|
+
}
|
|
212
|
+
sessions.add(session);
|
|
213
|
+
local.set(input.sessionId, session);
|
|
214
|
+
emit({ type: "session.opened", requestId: input.requestId, sessionId: input.sessionId,
|
|
215
|
+
capabilities, restoration: "core", cwd: input.config.cwd,
|
|
216
|
+
persistence: { version: 1, data: { world, akuId: target.akuId } } });
|
|
217
|
+
{
|
|
218
|
+
try {
|
|
219
|
+
const observed = await transport.status(world, target.akuId, cancellation.signal);
|
|
220
|
+
if (!cancellation.signal.aborted) emit({ type: "session.config", sessionId: input.sessionId, config: modelConfig(observed) });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (!cancellation.signal.aborted) emit({ type: "session.notice", sessionId: input.sessionId, notice: {
|
|
223
|
+
id: "keiyaku:model", severity: "warning", title: "Akuma model unavailable",
|
|
224
|
+
description: error instanceof Error ? error.message : String(error),
|
|
225
|
+
} });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Ready names the first observed window, or the explicit report that
|
|
229
|
+
// none could be read. It never waits for the observation to end.
|
|
230
|
+
await session.observe();
|
|
231
|
+
if (!cancellation.signal.aborted) emit({ type: "session.ready", requestId: input.requestId, sessionId: input.sessionId });
|
|
232
|
+
} else if (input.type === "session.interrupt" || input.type === "session.close") {
|
|
233
|
+
const session = local.get(input.sessionId);
|
|
234
|
+
if (input.type === "session.close") {
|
|
235
|
+
await session?.stop();
|
|
236
|
+
emit({ type: "request.completed", requestId: input.requestId });
|
|
237
|
+
} else if (session === undefined) {
|
|
238
|
+
emit({ type: "request.failed", requestId: input.requestId,
|
|
239
|
+
error: { message: "Keiyaku mirror session is not open" } });
|
|
240
|
+
} else await session.stopExecution(input.requestId);
|
|
241
|
+
} else if (input.type === "session.prompt") {
|
|
242
|
+
const session = local.get(input.sessionId);
|
|
243
|
+
if (session) await session.prompt(input.prompt);
|
|
244
|
+
else emit({ type: "session.prompt_result", sessionId: input.sessionId, clientMessageId: input.prompt.clientMessageId,
|
|
245
|
+
result: { type: "failed", error: { message: "Keiyaku mirror session is not open" } } });
|
|
246
|
+
} else if ("requestId" in input) {
|
|
247
|
+
emit({ type: "request.failed", requestId: input.requestId, error: { message: "Unsupported Keiyaku mirror operation" } });
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
async close() {
|
|
251
|
+
await Promise.all([...local.values()].map((session) => session.stop()));
|
|
252
|
+
closed = true;
|
|
253
|
+
listeners.clear();
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
provider,
|
|
260
|
+
async refresh(input: { world: string; akuId: string }) {
|
|
261
|
+
const world = await realpath(input.world);
|
|
262
|
+
let woken = 0;
|
|
263
|
+
for (const session of sessions) if (session.world === world && session.akuId === input.akuId) {
|
|
264
|
+
void session.observe();
|
|
265
|
+
woken++;
|
|
266
|
+
}
|
|
267
|
+
return { woken };
|
|
268
|
+
},
|
|
269
|
+
async sync(input: { world: string; akuId: string }) {
|
|
270
|
+
const world = await realpath(input.world);
|
|
271
|
+
await Promise.all([...sessions].filter((session) => session.world === world && session.akuId === input.akuId)
|
|
272
|
+
.map((session) => session.observe(true)));
|
|
273
|
+
},
|
|
274
|
+
async close() { await Promise.all([...sessions].map((session) => session.stop())); },
|
|
275
|
+
};
|
|
276
|
+
}
|