@astrosheep/keiyaku 4.1.1 → 4.1.2
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/build/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-handle.d.ts +39 -0
- package/build/src/akuma/akuma-handle.js +263 -0
- package/build/src/akuma/akuma-observe.d.ts +19 -0
- package/build/src/akuma/akuma-observe.js +74 -0
- package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
- package/build/src/akuma/akuma-product-symbols.js +1 -0
- package/build/src/akuma/akuma-product.d.ts +18 -0
- package/build/src/akuma/akuma-product.js +154 -0
- package/build/src/akuma/akuma.d.ts +23 -70
- package/build/src/akuma/akuma.js +17 -433
- package/build/src/akuma/body-supervisor.d.ts +26 -0
- package/build/src/akuma/body-supervisor.js +89 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +3 -2
- package/build/src/akuma/projection-read.d.ts +17 -0
- package/build/src/akuma/projection-read.js +135 -0
- package/build/src/akuma/projection.d.ts +1 -18
- package/build/src/akuma/projection.js +21 -155
- package/build/src/akuma/request-execution.d.ts +6 -0
- package/build/src/akuma/request-execution.js +86 -0
- package/build/src/akuma/request-serve.d.ts +12 -1
- package/build/src/akuma/request-serve.js +2 -102
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
- package/build/src/akuma/{body-turn.js → turn-drive.js} +4 -99
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +1 -11
- package/build/src/cli/commands/contract-invoke.d.ts +25 -0
- package/build/src/cli/commands/contract-invoke.js +193 -0
- package/build/src/cli/commands/contract.d.ts +12 -0
- package/build/src/cli/commands/contract.js +229 -0
- package/build/src/cli/invoke.js +24 -184
- package/build/src/cli/parse.d.ts +1 -1
- package/build/src/cli/parse.js +8 -222
- package/build/src/cli/render/akuma-activity.d.ts +38 -0
- package/build/src/cli/render/akuma-activity.js +362 -0
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +14 -371
- package/build/src/cli/render/contract-history.d.ts +2 -0
- package/build/src/cli/render/contract-history.js +83 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +1 -83
- package/build/src/git/reconcile.d.ts +8 -1
- package/build/src/git/reconcile.js +13 -301
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/library/contract-bind.d.ts +26 -0
- package/build/src/library/contract-bind.js +106 -0
- package/build/src/library/contract-operations.d.ts +77 -0
- package/build/src/library/contract-operations.js +130 -0
- package/build/src/library/contract-types.d.ts +95 -0
- package/build/src/library/contract-types.js +1 -0
- package/build/src/library/contract.d.ts +11 -136
- package/build/src/library/contract.js +9 -222
- package/package.json +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { abortableDelay } from "./abort.js";
|
|
2
|
+
import { heartExists, readHeart } from "./heart/index.js";
|
|
3
|
+
const BODY_CONTROL_OBSERVATION_MS = 100;
|
|
4
|
+
export const CONTROL_RESPONSE_MS = 1_000;
|
|
5
|
+
export class BodySupervisor {
|
|
6
|
+
paths;
|
|
7
|
+
bodySequence;
|
|
8
|
+
leash;
|
|
9
|
+
signal;
|
|
10
|
+
controller = new AbortController();
|
|
11
|
+
finished = new AbortController();
|
|
12
|
+
observer;
|
|
13
|
+
waiter;
|
|
14
|
+
observation;
|
|
15
|
+
stopping;
|
|
16
|
+
constructor(paths, bodySequence, leash, observation) {
|
|
17
|
+
this.paths = paths;
|
|
18
|
+
this.bodySequence = bodySequence;
|
|
19
|
+
this.leash = leash;
|
|
20
|
+
this.signal = this.controller.signal;
|
|
21
|
+
this.observation = observation;
|
|
22
|
+
this.observer = this.observe();
|
|
23
|
+
}
|
|
24
|
+
static async open(paths, bodySequence, leash) {
|
|
25
|
+
return new BodySupervisor(paths, bodySequence, leash, await readHeart(paths));
|
|
26
|
+
}
|
|
27
|
+
get reason() {
|
|
28
|
+
return this.stopping;
|
|
29
|
+
}
|
|
30
|
+
current() {
|
|
31
|
+
return this.observation;
|
|
32
|
+
}
|
|
33
|
+
async recordHung(diagnostic, at) {
|
|
34
|
+
await this.leash.recordBodyHung(this.paths, { sequence: this.bodySequence, diagnostic, at });
|
|
35
|
+
}
|
|
36
|
+
async refresh() {
|
|
37
|
+
this.publish(await readHeart(this.paths));
|
|
38
|
+
return this.observation;
|
|
39
|
+
}
|
|
40
|
+
next(after) {
|
|
41
|
+
if (this.observation !== after)
|
|
42
|
+
return Promise.resolve(this.observation);
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
this.waiter = resolve;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
cancel(reason) {
|
|
48
|
+
if (this.stopping !== undefined)
|
|
49
|
+
return;
|
|
50
|
+
this.stopping = reason;
|
|
51
|
+
this.controller.abort(new Error(reason === "control" ? "Akuma Body interrupted by durable control" : "Akuma Heart disappeared"));
|
|
52
|
+
this.waiter?.(this.observation);
|
|
53
|
+
this.waiter = undefined;
|
|
54
|
+
}
|
|
55
|
+
async close() {
|
|
56
|
+
this.finished.abort();
|
|
57
|
+
await this.observer;
|
|
58
|
+
this.waiter = undefined;
|
|
59
|
+
}
|
|
60
|
+
publish(snapshot) {
|
|
61
|
+
this.observation = snapshot;
|
|
62
|
+
this.waiter?.(snapshot);
|
|
63
|
+
this.waiter = undefined;
|
|
64
|
+
}
|
|
65
|
+
async observe() {
|
|
66
|
+
for (;;) {
|
|
67
|
+
const snapshot = this.observation;
|
|
68
|
+
if (!(await heartExists(this.paths)))
|
|
69
|
+
return this.cancel("heart-gone");
|
|
70
|
+
if (snapshot.stop?.bodySequence === this.bodySequence || snapshot.pause?.bodySequence === this.bodySequence) {
|
|
71
|
+
return this.cancel("control");
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await abortableDelay(BODY_CONTROL_OBSERVATION_MS, this.finished.signal);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await this.refresh();
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (await heartExists(this.paths))
|
|
84
|
+
throw error;
|
|
85
|
+
return this.cancel("heart-gone");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -4,7 +4,7 @@ import type { ProviderAdapter } from "./provider.js";
|
|
|
4
4
|
import { type UpstreamExecutionPort, type RequestChildLaunch } from "./request-serve.js";
|
|
5
5
|
import { type OwnedProcess, type RunLogReference } from "../runtime/proc/run.js";
|
|
6
6
|
export declare const LEASH_HELD_EXIT = 75;
|
|
7
|
-
export { CONTROL_RESPONSE_MS } from "./body-
|
|
7
|
+
export { CONTROL_RESPONSE_MS } from "./body-supervisor.js";
|
|
8
8
|
export type BodyLaunch = Readonly<{
|
|
9
9
|
paths: AkumaPaths;
|
|
10
10
|
seed?: Omit<Soul, "createdAt">;
|
package/build/src/akuma/body.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import { abortableDelay } from "./abort.js";
|
|
3
|
-
import { BodySupervisor
|
|
3
|
+
import { BodySupervisor } from "./body-supervisor.js";
|
|
4
|
+
import { driveTurn, turnRecipe } from "./turn-drive.js";
|
|
4
5
|
import { HeldAkumaLeash, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, readHeart, readNonterminalRequests, watchHeart, } from "./heart/index.js";
|
|
5
6
|
import { resolveProviderExecution } from "./providers/index.js";
|
|
6
7
|
import { clearBodyRequestTransport, settleBodyRequests, } from "./request-serve.js";
|
|
7
8
|
import { handoffProcess, spawnDetachedProcess, } from "../runtime/proc/run.js";
|
|
8
9
|
const LEASH_RETRY_MS = 100;
|
|
9
10
|
export const LEASH_HELD_EXIT = 75;
|
|
10
|
-
export { CONTROL_RESPONSE_MS } from "./body-
|
|
11
|
+
export { CONTROL_RESPONSE_MS } from "./body-supervisor.js";
|
|
11
12
|
function missing(error) {
|
|
12
13
|
return isHeartAbsent(error) || error.code === "ENOENT";
|
|
13
14
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ActivitySnapshot, TurnLedger } from "./projection.js";
|
|
2
|
+
import type { HistoryCursor, HistoryPage } from "./projection.js";
|
|
3
|
+
export declare function ordinarySnapshotBudget(ordinaryBudget?: number): Readonly<{
|
|
4
|
+
tail: number;
|
|
5
|
+
voice: number;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function selectSnapshot(ledger: TurnLedger, input: Readonly<{
|
|
8
|
+
aperture: "monitoring" | "receipt";
|
|
9
|
+
budget?: Readonly<{
|
|
10
|
+
tail: number;
|
|
11
|
+
voice?: number;
|
|
12
|
+
}>;
|
|
13
|
+
}>): Readonly<{
|
|
14
|
+
snapshot: ActivitySnapshot;
|
|
15
|
+
ordinaryCount: number;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function selectHistory(ledger: TurnLedger, cursor: HistoryCursor): HistoryPage;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const DEFAULT_TAIL = 3;
|
|
2
|
+
const DEFAULT_VOICE = 3;
|
|
3
|
+
const REPORTED_CHANGE_LIMIT = 5;
|
|
4
|
+
const EMPTY_REPORTED = { reportedChanges: [], reportedChangesOmitted: 0 };
|
|
5
|
+
function assembleEntries(ledger, window, selected) {
|
|
6
|
+
const windowRows = new Set(window);
|
|
7
|
+
const result = [];
|
|
8
|
+
let hidden = 0;
|
|
9
|
+
const flushGap = () => {
|
|
10
|
+
if (hidden === 0)
|
|
11
|
+
return;
|
|
12
|
+
result.push({ kind: "gap", count: hidden });
|
|
13
|
+
hidden = 0;
|
|
14
|
+
};
|
|
15
|
+
for (const row of ledger.rows) {
|
|
16
|
+
if (windowRows.has(row) && !selected.has(row)) {
|
|
17
|
+
hidden += 1;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!selected.has(row))
|
|
21
|
+
continue;
|
|
22
|
+
flushGap();
|
|
23
|
+
if (row.kind !== "turn" && row.kind !== "outcome" && !(row.kind === "tool" && row.state === "unsettled")) {
|
|
24
|
+
result.push({ kind: "row", row: row });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
flushGap();
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
const DEFAULT_ORDINARY = DEFAULT_TAIL + DEFAULT_VOICE;
|
|
31
|
+
export function ordinarySnapshotBudget(ordinaryBudget = DEFAULT_ORDINARY) {
|
|
32
|
+
const tail = Math.min(DEFAULT_TAIL, Math.max(0, ordinaryBudget));
|
|
33
|
+
return { tail, voice: Math.min(DEFAULT_VOICE, Math.max(0, ordinaryBudget) - tail) };
|
|
34
|
+
}
|
|
35
|
+
function isSuccessfulFileChange(row) {
|
|
36
|
+
return (row.kind === "tool" && typeof row.state === "object" && row.state.status === "ok" && row.call.kind === "fileChange");
|
|
37
|
+
}
|
|
38
|
+
function flattenReportedChanges(rows) {
|
|
39
|
+
return rows.flatMap((row) => {
|
|
40
|
+
if (!isSuccessfulFileChange(row))
|
|
41
|
+
return [];
|
|
42
|
+
return row.call.changes.map((change) => ({
|
|
43
|
+
sequence: row.sequence,
|
|
44
|
+
at: row.at,
|
|
45
|
+
op: change.op,
|
|
46
|
+
path: change.path,
|
|
47
|
+
...(change.diffstat === undefined ? {} : { diffstat: change.diffstat }),
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function summarizeReportedChanges(rows) {
|
|
52
|
+
const eligible = flattenReportedChanges(rows);
|
|
53
|
+
return {
|
|
54
|
+
reportedChanges: eligible.slice(-REPORTED_CHANGE_LIMIT),
|
|
55
|
+
reportedChangesOmitted: Math.max(0, eligible.length - REPORTED_CHANGE_LIMIT),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function frontierReportedChanges(ledger) {
|
|
59
|
+
if (ledger.openTurn !== undefined)
|
|
60
|
+
return summarizeReportedChanges(ledger.openTurn.rows);
|
|
61
|
+
const closed = ledger.turns.findLast((turn) => turn.kind === "closed");
|
|
62
|
+
return closed === undefined ? EMPTY_REPORTED : summarizeReportedChanges(closed.rows);
|
|
63
|
+
}
|
|
64
|
+
function isPendingTell(row) {
|
|
65
|
+
return row.kind === "tell" && row.state === "pending";
|
|
66
|
+
}
|
|
67
|
+
function isToldTell(row) {
|
|
68
|
+
return row.kind === "tell" && row.state === "told";
|
|
69
|
+
}
|
|
70
|
+
function isActiveTool(row) {
|
|
71
|
+
return row.kind === "tool" && row.state === "active";
|
|
72
|
+
}
|
|
73
|
+
export function selectSnapshot(ledger, input) {
|
|
74
|
+
const budget = input.budget ?? { tail: DEFAULT_TAIL, voice: DEFAULT_VOICE };
|
|
75
|
+
if (ledger.turns.length === 0 && !ledger.rows.some((row) => row.kind === "tell"))
|
|
76
|
+
return { snapshot: { kind: "unborn", entries: [], omitted: 0, ...EMPTY_REPORTED }, ordinaryCount: 0 };
|
|
77
|
+
const pending = ledger.rows.filter(isPendingTell);
|
|
78
|
+
const latestTold = input.aperture === "monitoring" ? ledger.rows.findLast(isToldTell) : undefined;
|
|
79
|
+
const pins = latestTold === undefined ? pending : [...pending, latestTold];
|
|
80
|
+
const reported = frontierReportedChanges(ledger);
|
|
81
|
+
if (ledger.openTurn !== undefined) {
|
|
82
|
+
const window = ledger.openTurn.rows;
|
|
83
|
+
const pinSet = new Set([...window.filter(isActiveTool), ...pins]);
|
|
84
|
+
const ordinary = window.filter((row) => !pinSet.has(row));
|
|
85
|
+
const tailCount = Math.max(0, budget.tail);
|
|
86
|
+
const tail = tailCount === 0 ? [] : ordinary.slice(-tailCount);
|
|
87
|
+
const tailSet = new Set(tail);
|
|
88
|
+
const voiceCount = Math.max(0, budget.voice ?? DEFAULT_VOICE);
|
|
89
|
+
const voiceCandidates = ordinary.filter((row) => !tailSet.has(row) && (row.kind === "said" || row.kind === "thought"));
|
|
90
|
+
const voice = voiceCount === 0 ? [] : voiceCandidates.slice(-voiceCount);
|
|
91
|
+
const selected = new Set([...pinSet, ...tail, ...voice]);
|
|
92
|
+
const omitted = window.filter((row) => !selected.has(row)).length;
|
|
93
|
+
return {
|
|
94
|
+
snapshot: {
|
|
95
|
+
kind: "open",
|
|
96
|
+
turn: ledger.openTurn.turn,
|
|
97
|
+
entries: assembleEntries(ledger, window, selected),
|
|
98
|
+
omitted,
|
|
99
|
+
...reported,
|
|
100
|
+
},
|
|
101
|
+
ordinaryCount: tail.length + voice.length,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
snapshot: {
|
|
106
|
+
kind: "idle",
|
|
107
|
+
...(ledger.latestOutcome === undefined ? {} : { outcome: ledger.latestOutcome }),
|
|
108
|
+
entries: assembleEntries(ledger, [], new Set(pins)),
|
|
109
|
+
omitted: 0,
|
|
110
|
+
...reported,
|
|
111
|
+
},
|
|
112
|
+
ordinaryCount: 0,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function selectHistory(ledger, cursor) {
|
|
116
|
+
const eligible = ledger.rows.filter((row) => cursor.before !== undefined
|
|
117
|
+
? row.sequence < cursor.before
|
|
118
|
+
: cursor.since !== undefined
|
|
119
|
+
? row.sequence > cursor.since
|
|
120
|
+
: true);
|
|
121
|
+
const rows = cursor.since !== undefined ? eligible.slice(0, cursor.limit) : eligible.slice(-cursor.limit);
|
|
122
|
+
const omitted = Math.max(0, eligible.length - rows.length);
|
|
123
|
+
const historyLost = cursor.since !== undefined
|
|
124
|
+
? ledger.rows[0] !== undefined && ledger.rows[0].sequence > cursor.since + 1
|
|
125
|
+
: ledger.retained.lowestRetained !== null && ledger.retained.lowestRetained > 1 && omitted === 0;
|
|
126
|
+
return {
|
|
127
|
+
rows,
|
|
128
|
+
omitted,
|
|
129
|
+
hasEarlier: cursor.since === undefined && eligible.length > rows.length,
|
|
130
|
+
hasLater: cursor.since !== undefined && eligible.length > rows.length,
|
|
131
|
+
historyLost,
|
|
132
|
+
lowestRetained: ledger.retained.lowestRetained,
|
|
133
|
+
highest: ledger.retained.highest,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -172,21 +172,4 @@ export type TurnLedger = Readonly<{
|
|
|
172
172
|
}>;
|
|
173
173
|
/** Fold the retained fact timeline into Turn-owned rows without inventing provider facts. */
|
|
174
174
|
export declare function projectTurns(facts: readonly TimelineFact[], retained?: RetainedWindow): TurnLedger;
|
|
175
|
-
|
|
176
|
-
export declare function ordinarySnapshotBudget(ordinaryBudget?: number): Readonly<{
|
|
177
|
-
tail: number;
|
|
178
|
-
voice: number;
|
|
179
|
-
}>;
|
|
180
|
-
/** Select one current Turn, one latest outcome, or no focus; pending tells stay actionable. */
|
|
181
|
-
export declare function selectSnapshot(ledger: TurnLedger, input: Readonly<{
|
|
182
|
-
aperture: "monitoring" | "receipt";
|
|
183
|
-
budget?: Readonly<{
|
|
184
|
-
tail: number;
|
|
185
|
-
voice?: number;
|
|
186
|
-
}>;
|
|
187
|
-
}>): Readonly<{
|
|
188
|
-
snapshot: ActivitySnapshot;
|
|
189
|
-
ordinaryCount: number;
|
|
190
|
-
}>;
|
|
191
|
-
export declare function selectHistory(ledger: TurnLedger, cursor: HistoryCursor): HistoryPage;
|
|
192
|
-
export {};
|
|
175
|
+
export { ordinarySnapshotBudget, selectHistory, selectSnapshot } from "./projection-read.js";
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { decodeAgentEvent } from "./provider.js";
|
|
2
|
-
const DEFAULT_TAIL = 3;
|
|
3
|
-
const DEFAULT_VOICE = 3;
|
|
4
|
-
const REPORTED_CHANGE_LIMIT = 5;
|
|
5
|
-
const EMPTY_REPORTED = { reportedChanges: [], reportedChangesOmitted: 0 };
|
|
6
2
|
function duration(startedAt, completedAt) {
|
|
7
3
|
const start = Date.parse(startedAt);
|
|
8
4
|
const end = Date.parse(completedAt);
|
|
@@ -124,6 +120,25 @@ function finishTurn(turn) {
|
|
|
124
120
|
}
|
|
125
121
|
return { kind: "open", turn: turn.turn, rows: rows };
|
|
126
122
|
}
|
|
123
|
+
function finishLedger(state, retained) {
|
|
124
|
+
const turns = state.turns.map(finishTurn);
|
|
125
|
+
const rows = [
|
|
126
|
+
...turns.flatMap((turn) => [turn.turn, ...turn.rows, ...(turn.kind === "closed" ? [turn.outcome] : [])]),
|
|
127
|
+
...state.tells,
|
|
128
|
+
...state.orphanRows,
|
|
129
|
+
...state.orphanOutcomes,
|
|
130
|
+
].sort((left, right) => left.sequence - right.sequence);
|
|
131
|
+
const frontier = turns.at(-1);
|
|
132
|
+
const openTurn = frontier?.kind === "open" ? frontier : undefined;
|
|
133
|
+
const latestOutcome = rows.findLast((row) => row.kind === "outcome");
|
|
134
|
+
return {
|
|
135
|
+
rows,
|
|
136
|
+
turns,
|
|
137
|
+
retained,
|
|
138
|
+
...(openTurn === undefined ? {} : { openTurn }),
|
|
139
|
+
...(latestOutcome === undefined ? {} : { latestOutcome }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
127
142
|
/** Fold the retained fact timeline into Turn-owned rows without inventing provider facts. */
|
|
128
143
|
export function projectTurns(facts, retained = {
|
|
129
144
|
lowestRetained: facts[0]?.sequence ?? null,
|
|
@@ -196,155 +211,6 @@ export function projectTurns(facts, retained = {
|
|
|
196
211
|
projectActivityEvent(state, fact);
|
|
197
212
|
}
|
|
198
213
|
}
|
|
199
|
-
|
|
200
|
-
const rows = [
|
|
201
|
-
...turns.flatMap((turn) => [turn.turn, ...turn.rows, ...(turn.kind === "closed" ? [turn.outcome] : [])]),
|
|
202
|
-
...state.tells,
|
|
203
|
-
...state.orphanRows,
|
|
204
|
-
...state.orphanOutcomes,
|
|
205
|
-
].sort((left, right) => left.sequence - right.sequence);
|
|
206
|
-
const frontier = turns.at(-1);
|
|
207
|
-
const openTurn = frontier?.kind === "open" ? frontier : undefined;
|
|
208
|
-
const latestOutcome = rows.findLast((row) => row.kind === "outcome");
|
|
209
|
-
return {
|
|
210
|
-
rows,
|
|
211
|
-
turns,
|
|
212
|
-
retained,
|
|
213
|
-
...(openTurn === undefined ? {} : { openTurn }),
|
|
214
|
-
...(latestOutcome === undefined ? {} : { latestOutcome }),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function assembleEntries(ledger, window, selected) {
|
|
218
|
-
const windowRows = new Set(window);
|
|
219
|
-
const result = [];
|
|
220
|
-
let hidden = 0;
|
|
221
|
-
const flushGap = () => {
|
|
222
|
-
if (hidden === 0)
|
|
223
|
-
return;
|
|
224
|
-
result.push({ kind: "gap", count: hidden });
|
|
225
|
-
hidden = 0;
|
|
226
|
-
};
|
|
227
|
-
for (const row of ledger.rows) {
|
|
228
|
-
if (windowRows.has(row) && !selected.has(row)) {
|
|
229
|
-
hidden += 1;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (!selected.has(row))
|
|
233
|
-
continue;
|
|
234
|
-
flushGap();
|
|
235
|
-
if (row.kind !== "turn" && row.kind !== "outcome" && !(row.kind === "tool" && row.state === "unsettled")) {
|
|
236
|
-
result.push({ kind: "row", row: row });
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
flushGap();
|
|
240
|
-
return result;
|
|
241
|
-
}
|
|
242
|
-
const DEFAULT_ORDINARY = DEFAULT_TAIL + DEFAULT_VOICE;
|
|
243
|
-
/** Split one nonnegative ordinary allowance into newest tail first, then pre-tail voice. */
|
|
244
|
-
export function ordinarySnapshotBudget(ordinaryBudget = DEFAULT_ORDINARY) {
|
|
245
|
-
const tail = Math.min(DEFAULT_TAIL, Math.max(0, ordinaryBudget));
|
|
246
|
-
return { tail, voice: Math.min(DEFAULT_VOICE, Math.max(0, ordinaryBudget) - tail) };
|
|
247
|
-
}
|
|
248
|
-
function isSuccessfulFileChange(row) {
|
|
249
|
-
return (row.kind === "tool" && typeof row.state === "object" && row.state.status === "ok" && row.call.kind === "fileChange");
|
|
250
|
-
}
|
|
251
|
-
function flattenReportedChanges(rows) {
|
|
252
|
-
return rows.flatMap((row) => {
|
|
253
|
-
if (!isSuccessfulFileChange(row))
|
|
254
|
-
return [];
|
|
255
|
-
return row.call.changes.map((change) => ({
|
|
256
|
-
sequence: row.sequence,
|
|
257
|
-
at: row.at,
|
|
258
|
-
op: change.op,
|
|
259
|
-
path: change.path,
|
|
260
|
-
...(change.diffstat === undefined ? {} : { diffstat: change.diffstat }),
|
|
261
|
-
}));
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
function summarizeReportedChanges(rows) {
|
|
265
|
-
const eligible = flattenReportedChanges(rows);
|
|
266
|
-
return {
|
|
267
|
-
reportedChanges: eligible.slice(-REPORTED_CHANGE_LIMIT),
|
|
268
|
-
reportedChangesOmitted: Math.max(0, eligible.length - REPORTED_CHANGE_LIMIT),
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function frontierReportedChanges(ledger) {
|
|
272
|
-
if (ledger.openTurn !== undefined)
|
|
273
|
-
return summarizeReportedChanges(ledger.openTurn.rows);
|
|
274
|
-
const closed = ledger.turns.findLast((turn) => turn.kind === "closed");
|
|
275
|
-
return closed === undefined ? EMPTY_REPORTED : summarizeReportedChanges(closed.rows);
|
|
276
|
-
}
|
|
277
|
-
function isPendingTell(row) {
|
|
278
|
-
return row.kind === "tell" && row.state === "pending";
|
|
279
|
-
}
|
|
280
|
-
function isToldTell(row) {
|
|
281
|
-
return row.kind === "tell" && row.state === "told";
|
|
282
|
-
}
|
|
283
|
-
function isActiveTool(row) {
|
|
284
|
-
return row.kind === "tool" && row.state === "active";
|
|
285
|
-
}
|
|
286
|
-
/** Select one current Turn, one latest outcome, or no focus; pending tells stay actionable. */
|
|
287
|
-
export function selectSnapshot(ledger, input) {
|
|
288
|
-
const budget = input.budget ?? { tail: DEFAULT_TAIL, voice: DEFAULT_VOICE };
|
|
289
|
-
if (ledger.turns.length === 0 && !ledger.rows.some((row) => row.kind === "tell")) {
|
|
290
|
-
return { snapshot: { kind: "unborn", entries: [], omitted: 0, ...EMPTY_REPORTED }, ordinaryCount: 0 };
|
|
291
|
-
}
|
|
292
|
-
const pending = ledger.rows.filter(isPendingTell);
|
|
293
|
-
const latestTold = input.aperture === "monitoring" ? ledger.rows.findLast(isToldTell) : undefined;
|
|
294
|
-
const pins = latestTold === undefined ? pending : [...pending, latestTold];
|
|
295
|
-
const reported = frontierReportedChanges(ledger);
|
|
296
|
-
if (ledger.openTurn !== undefined) {
|
|
297
|
-
const window = ledger.openTurn.rows;
|
|
298
|
-
const pinSet = new Set([...window.filter(isActiveTool), ...pins]);
|
|
299
|
-
const ordinary = window.filter((row) => !pinSet.has(row));
|
|
300
|
-
const tailCount = Math.max(0, budget.tail);
|
|
301
|
-
const tail = tailCount === 0 ? [] : ordinary.slice(-tailCount);
|
|
302
|
-
const tailSet = new Set(tail);
|
|
303
|
-
const voiceCount = Math.max(0, budget.voice ?? DEFAULT_VOICE);
|
|
304
|
-
const voiceCandidates = ordinary.filter((row) => !tailSet.has(row) && (row.kind === "said" || row.kind === "thought"));
|
|
305
|
-
const voice = voiceCount === 0 ? [] : voiceCandidates.slice(-voiceCount);
|
|
306
|
-
const selected = new Set([...pinSet, ...tail, ...voice]);
|
|
307
|
-
const omitted = window.filter((row) => !selected.has(row)).length;
|
|
308
|
-
return {
|
|
309
|
-
snapshot: {
|
|
310
|
-
kind: "open",
|
|
311
|
-
turn: ledger.openTurn.turn,
|
|
312
|
-
entries: assembleEntries(ledger, window, selected),
|
|
313
|
-
omitted,
|
|
314
|
-
...reported,
|
|
315
|
-
},
|
|
316
|
-
ordinaryCount: tail.length + voice.length,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
return {
|
|
320
|
-
snapshot: {
|
|
321
|
-
kind: "idle",
|
|
322
|
-
...(ledger.latestOutcome === undefined ? {} : { outcome: ledger.latestOutcome }),
|
|
323
|
-
entries: assembleEntries(ledger, [], new Set(pins)),
|
|
324
|
-
omitted: 0,
|
|
325
|
-
...reported,
|
|
326
|
-
},
|
|
327
|
-
ordinaryCount: 0,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
export function selectHistory(ledger, cursor) {
|
|
331
|
-
const eligible = ledger.rows.filter((row) => cursor.before !== undefined
|
|
332
|
-
? row.sequence < cursor.before
|
|
333
|
-
: cursor.since !== undefined
|
|
334
|
-
? row.sequence > cursor.since
|
|
335
|
-
: true);
|
|
336
|
-
const rows = cursor.since !== undefined ? eligible.slice(0, cursor.limit) : eligible.slice(-cursor.limit);
|
|
337
|
-
const omitted = Math.max(0, eligible.length - rows.length);
|
|
338
|
-
const historyLost = cursor.since !== undefined
|
|
339
|
-
? ledger.rows[0] !== undefined && ledger.rows[0].sequence > cursor.since + 1
|
|
340
|
-
: ledger.retained.lowestRetained !== null && ledger.retained.lowestRetained > 1 && omitted === 0;
|
|
341
|
-
return {
|
|
342
|
-
rows,
|
|
343
|
-
omitted,
|
|
344
|
-
hasEarlier: cursor.since === undefined && eligible.length > rows.length,
|
|
345
|
-
hasLater: cursor.since !== undefined && eligible.length > rows.length,
|
|
346
|
-
historyLost,
|
|
347
|
-
lowestRetained: ledger.retained.lowestRetained,
|
|
348
|
-
highest: ledger.retained.highest,
|
|
349
|
-
};
|
|
214
|
+
return finishLedger(state, retained);
|
|
350
215
|
}
|
|
216
|
+
export { ordinarySnapshotBudget, selectHistory, selectSnapshot } from "./projection-read.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UpstreamRequestService } from "./heart/index.js";
|
|
2
|
+
import type { ServeInput, UpstreamFact } from "./request-serve.js";
|
|
3
|
+
export declare function executeRequest(input: ServeInput, request: UpstreamFact): Promise<Readonly<{
|
|
4
|
+
result: unknown;
|
|
5
|
+
service?: UpstreamRequestService;
|
|
6
|
+
}>>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export async function executeRequest(input, request) {
|
|
2
|
+
if (input.upstream === undefined)
|
|
3
|
+
throw new Error("upstream execution port is unavailable");
|
|
4
|
+
if (request.action === "akuma.wait")
|
|
5
|
+
return {
|
|
6
|
+
result: await input.upstream.wait({
|
|
7
|
+
targets: request.targets,
|
|
8
|
+
completion: request.completion,
|
|
9
|
+
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
10
|
+
signal: input.signal,
|
|
11
|
+
}),
|
|
12
|
+
service: { action: request.action },
|
|
13
|
+
};
|
|
14
|
+
if (request.action === "akuma.tell")
|
|
15
|
+
return {
|
|
16
|
+
result: await input.upstream.tell({
|
|
17
|
+
target: request.target,
|
|
18
|
+
body: request.body,
|
|
19
|
+
tellId: request.id,
|
|
20
|
+
recordedAt: request.admittedAt,
|
|
21
|
+
signal: input.signal,
|
|
22
|
+
}),
|
|
23
|
+
service: { action: request.action, target: request.target, tellId: request.id },
|
|
24
|
+
};
|
|
25
|
+
if (request.action === "akuma.kill") {
|
|
26
|
+
const served = await input.upstream.kill({ targets: request.targets, signal: input.signal });
|
|
27
|
+
return { result: served.result, service: { action: request.action, results: served.service } };
|
|
28
|
+
}
|
|
29
|
+
if (request.action === "contract.deliver") {
|
|
30
|
+
const served = await input.upstream.deliver({
|
|
31
|
+
repoRoot: request.repoRoot,
|
|
32
|
+
contractId: request.contractId,
|
|
33
|
+
...(request.message === undefined ? {} : { message: request.message }),
|
|
34
|
+
includeDirty: request.includeDirty,
|
|
35
|
+
materializeConflict: request.materializeConflict,
|
|
36
|
+
requester: request.requester,
|
|
37
|
+
signal: input.signal,
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
result: served.result,
|
|
41
|
+
...(served.deliveryFactId === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: {
|
|
44
|
+
service: {
|
|
45
|
+
action: request.action,
|
|
46
|
+
repoRoot: request.repoRoot,
|
|
47
|
+
contractId: request.contractId,
|
|
48
|
+
deliveryFactId: served.deliveryFactId,
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (request.action === "contract.review") {
|
|
54
|
+
const served = await input.upstream.review({
|
|
55
|
+
repoRoot: request.repoRoot,
|
|
56
|
+
contractId: request.contractId,
|
|
57
|
+
verdict: request.verdict,
|
|
58
|
+
...(request.summary === undefined ? {} : { summary: request.summary }),
|
|
59
|
+
requester: request.requester,
|
|
60
|
+
signal: input.signal,
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
result: served.result,
|
|
64
|
+
...(served.reviewFactId === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: {
|
|
67
|
+
service: {
|
|
68
|
+
action: request.action,
|
|
69
|
+
repoRoot: request.repoRoot,
|
|
70
|
+
contractId: request.contractId,
|
|
71
|
+
reviewFactId: served.reviewFactId,
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const task = request;
|
|
77
|
+
return {
|
|
78
|
+
result: await input.upstream.task({
|
|
79
|
+
world: task.world,
|
|
80
|
+
request: task.request,
|
|
81
|
+
requester: task.requester,
|
|
82
|
+
signal: input.signal,
|
|
83
|
+
}),
|
|
84
|
+
service: { action: task.action },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type KillEvidence, type Soul } from "./heart/index.js";
|
|
1
|
+
import { type KillEvidence, type RequestFact, type Soul } from "./heart/index.js";
|
|
2
2
|
import { type AkuId, type AkumaPaths } from "./identity.js";
|
|
3
|
+
import { type StructuralRequestClaim } from "./request-wire.js";
|
|
3
4
|
import { type TaskMutationRequest } from "../task/mutation.js";
|
|
4
5
|
export type RequestChildLaunch = Readonly<{
|
|
5
6
|
paths: AkumaPaths;
|
|
@@ -67,6 +68,16 @@ type PumpInput = Readonly<{
|
|
|
67
68
|
upstream?: UpstreamExecutionPort;
|
|
68
69
|
signal: AbortSignal;
|
|
69
70
|
}>;
|
|
71
|
+
export type ServeInput = Omit<PumpInput, "bodySequence"> & Readonly<{
|
|
72
|
+
directory: string;
|
|
73
|
+
claim: StructuralRequestClaim;
|
|
74
|
+
admissionOpen(): boolean;
|
|
75
|
+
}>;
|
|
76
|
+
export type UpstreamFact = Exclude<Extract<RequestFact, {
|
|
77
|
+
state: "admitted";
|
|
78
|
+
}>, Extract<RequestFact, {
|
|
79
|
+
action: "akuma.call";
|
|
80
|
+
}>>;
|
|
70
81
|
export declare class BodyRequestPump {
|
|
71
82
|
private readonly input;
|
|
72
83
|
readonly directory: string;
|