@astrosheep/keiyaku 4.4.0 → 4.5.1
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/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +4 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +9 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +16 -5
- package/build/integrations/pi/keiyaku.ts +159 -0
- package/build/src/akuma/abort.js +9 -9
- package/build/src/akuma/akuma-handle.d.ts +4 -2
- package/build/src/akuma/akuma-handle.js +14 -2
- package/build/src/akuma/akuma-product.d.ts +32 -3
- package/build/src/akuma/akuma-product.js +44 -20
- package/build/src/akuma/akuma.d.ts +0 -1
- package/build/src/akuma/body.d.ts +1 -7
- package/build/src/akuma/body.js +35 -13
- package/build/src/akuma/heart/index.js +7 -3
- package/build/src/akuma/heart/rows.d.ts +1 -1
- package/build/src/akuma/heart/rows.js +3 -3
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +28 -6
- package/build/src/akuma/identity.d.ts +1 -0
- package/build/src/akuma/identity.js +7 -0
- package/build/src/akuma/index.d.ts +1 -2
- package/build/src/akuma/projection.d.ts +18 -2
- package/build/src/akuma/projection.js +11 -1
- package/build/src/akuma/providers/claude/index.js +68 -37
- package/build/src/akuma/publication.d.ts +9 -3
- package/build/src/akuma/publication.js +20 -3
- package/build/src/akuma/request-lifecycle.d.ts +45 -0
- package/build/src/akuma/request-lifecycle.js +171 -0
- package/build/src/akuma/request-serve.d.ts +9 -32
- package/build/src/akuma/request-serve.js +48 -178
- package/build/src/akuma/request-wire.js +1 -1
- package/build/src/akuma/requests.js +3 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +11 -2
- package/build/src/cli/commands/akuma-invoke.js +29 -7
- package/build/src/cli/commands/akuma.d.ts +1 -0
- package/build/src/cli/commands/akuma.js +16 -6
- package/build/src/cli/commands/contract.d.ts +3 -2
- package/build/src/cli/commands/contract.js +5 -2
- package/build/src/cli/commands/status-set.d.ts +4 -0
- package/build/src/cli/commands/status-set.js +61 -0
- package/build/src/cli/invoke.js +6 -3
- package/build/src/cli/parse.js +1 -1
- package/build/src/cli/render/akuma-activity.js +11 -0
- package/build/src/cli/render/akuma.js +7 -0
- package/build/src/cli/render/status-set.d.ts +5 -0
- package/build/src/cli/render/status-set.js +14 -0
- package/build/src/cli/render/text.js +3 -0
- package/build/src/cli/result.d.ts +15 -1
- package/build/src/cli/runtime.js +2 -0
- package/build/src/cli/square-edge.d.ts +6 -0
- package/build/src/cli/square-edge.js +74 -0
- package/build/src/contract-worktree.js +18 -5
- package/build/src/library/akuma-creation.d.ts +14 -3
- package/build/src/library/akuma-creation.js +31 -18
- package/build/src/library/fleet.d.ts +12 -1
- package/build/src/library/fleet.js +21 -5
- package/build/src/library/repo.js +3 -1
- package/build/src/runtime/proc/process-exit.js +6 -1
- package/build/src/runtime/proc/run.js +6 -10
- package/build/src/runtime/proc/termination.d.ts +1 -0
- package/build/src/runtime/proc/termination.js +22 -2
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/runtime/proc/windows-run.js +10 -7
- package/build/src/world.d.ts +2 -0
- package/build/src/world.js +4 -0
- package/package.json +8 -3
- package/build/src/akuma/result-route.d.ts +0 -2
- package/build/src/akuma/result-route.js +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CliUsageError } from "../parse.js";
|
|
2
|
+
import { canonicalContractSelector } from "../selectors.js";
|
|
3
|
+
async function statusSetAkuma(selector, id, alias, world, repo) {
|
|
4
|
+
if (world === null)
|
|
5
|
+
throw new CliUsageError("no Keiyaku world contains the Akuma selector");
|
|
6
|
+
const result = await (await import("./akuma-invoke.js")).invokeAkumaStatus(world, id, alias, repo);
|
|
7
|
+
if (result.action !== "status")
|
|
8
|
+
throw new Error("status invocation returned a non-status Akuma result");
|
|
9
|
+
return {
|
|
10
|
+
selector,
|
|
11
|
+
kind: "akuma",
|
|
12
|
+
status: result.status,
|
|
13
|
+
...(result.alias === undefined ? {} : { alias: result.alias }),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function statusSetEntry(selector, world, repo, observed) {
|
|
17
|
+
let resolved = selector;
|
|
18
|
+
let alias;
|
|
19
|
+
if (selector.startsWith("@")) {
|
|
20
|
+
if (observed === undefined)
|
|
21
|
+
throw new CliUsageError("cannot resolve a named status selector");
|
|
22
|
+
const { resolveNamedAddress } = await import("../../library/address.js");
|
|
23
|
+
const address = resolveNamedAddress({ selector, report: observed.report, aliases: observed.aliases });
|
|
24
|
+
if (address.kind === "akuma")
|
|
25
|
+
return await statusSetAkuma(selector, address.id, selector, world, repo);
|
|
26
|
+
resolved = address.id;
|
|
27
|
+
alias = selector;
|
|
28
|
+
}
|
|
29
|
+
if (resolved.startsWith("aku/"))
|
|
30
|
+
return await statusSetAkuma(selector, resolved, alias, world, repo);
|
|
31
|
+
if (repo === undefined)
|
|
32
|
+
throw new CliUsageError("cannot select a contract while the Contract world is absent");
|
|
33
|
+
if (observed === undefined)
|
|
34
|
+
throw new CliUsageError("cannot observe Contract status without a Kanshi report");
|
|
35
|
+
return {
|
|
36
|
+
selector,
|
|
37
|
+
kind: "contract",
|
|
38
|
+
report: (await import("../../kanshi/index.js")).selectKanshi({
|
|
39
|
+
report: observed.report,
|
|
40
|
+
contract: canonicalContractSelector(resolved),
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export async function invokeStatusSet(selectors, world, repo) {
|
|
45
|
+
if (selectors.length < 2)
|
|
46
|
+
throw new CliUsageError("status requires at least two selectors");
|
|
47
|
+
if (selectors.some((selector) => !selector.startsWith("aku/") && !selector.startsWith("@")) && repo === undefined) {
|
|
48
|
+
throw new CliUsageError("cannot select a contract while the Contract world is absent");
|
|
49
|
+
}
|
|
50
|
+
const needsKanshi = selectors.some((selector) => !selector.startsWith("aku/"));
|
|
51
|
+
const observed = needsKanshi
|
|
52
|
+
? await (await import("../../kanshi/index.js")).observeKanshi({
|
|
53
|
+
world,
|
|
54
|
+
...(repo === undefined ? {} : { repo }),
|
|
55
|
+
})
|
|
56
|
+
: undefined;
|
|
57
|
+
const entries = [];
|
|
58
|
+
for (const selector of selectors)
|
|
59
|
+
entries.push(await statusSetEntry(selector, world, repo, observed));
|
|
60
|
+
return { kind: "status-set", entries };
|
|
61
|
+
}
|
package/build/src/cli/invoke.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { captureRoute } from "@astrosheep/square";
|
|
2
1
|
import { resolveActor } from "./actor.js";
|
|
3
2
|
import { isParsedAkumaCommand } from "./commands/akuma.js";
|
|
4
3
|
import { invokeContractMutation } from "./commands/contract-invoke.js";
|
|
@@ -106,14 +105,13 @@ async function invokeAkumaFromEdge(parsed, input) {
|
|
|
106
105
|
const contract = parsed.command === "call" && parsed.contract !== undefined
|
|
107
106
|
? (await import("./selectors.js")).contractFromInput(repo, parsed.contract).contract
|
|
108
107
|
: undefined;
|
|
109
|
-
const resultRoute = parsed.command === "call" ? captureRoute({ cwd: input.invocationCwd, env: edge.environment }) : null;
|
|
110
108
|
return await invokeAkuma(parsed, {
|
|
111
109
|
path,
|
|
112
110
|
...(statedCwd === undefined ? {} : { statedCwd }),
|
|
113
111
|
...(home === undefined ? {} : { home }),
|
|
114
112
|
...(configuration === undefined ? {} : { settings: configuration }),
|
|
115
113
|
...(contract === undefined ? (repo === undefined ? {} : { repo }) : { contract }),
|
|
116
|
-
|
|
114
|
+
environment: edge.environment,
|
|
117
115
|
readStdin: edge.readStdin,
|
|
118
116
|
});
|
|
119
117
|
}
|
|
@@ -160,6 +158,11 @@ async function invokeCatalog(parsed, world, repo, home) {
|
|
|
160
158
|
}
|
|
161
159
|
}
|
|
162
160
|
async function invokeStatus(parsed, world, repo) {
|
|
161
|
+
const selectors = "selectors" in parsed ? parsed.selectors : undefined;
|
|
162
|
+
if (selectors !== undefined) {
|
|
163
|
+
const { invokeStatusSet } = await import("./commands/status-set.js");
|
|
164
|
+
return await invokeStatusSet(selectors, world, repo);
|
|
165
|
+
}
|
|
163
166
|
if (parsed.akuma === true) {
|
|
164
167
|
if (world === null)
|
|
165
168
|
throw new CliUsageError("no Keiyaku world contains the invocation cwd");
|
package/build/src/cli/parse.js
CHANGED
|
@@ -164,7 +164,7 @@ function scanArgv(argv) {
|
|
|
164
164
|
if (spec.positional === "none" && state.positionals.length > 0) {
|
|
165
165
|
refuse(command, `${command} accepts no contract`);
|
|
166
166
|
}
|
|
167
|
-
if (spec.positional === "optional" && state.positionals.length > 1) {
|
|
167
|
+
if (spec.positional === "optional" && state.positionals.length > 1 && command !== "status") {
|
|
168
168
|
refuse(command, `${command} accepts at most one contract`);
|
|
169
169
|
}
|
|
170
170
|
if (spec.stdin === "required" && !state.stdin) {
|
|
@@ -316,6 +316,11 @@ export function akumaRawAnswer(result) {
|
|
|
316
316
|
}
|
|
317
317
|
if (result.action === "wait" && result.result.observations.length === 1)
|
|
318
318
|
return statusAnswer(result.result.observations[0]);
|
|
319
|
+
if (result.action === "history" && result.mode === "exact" && result.historyResult.kind === "exact") {
|
|
320
|
+
return result.historyResult.outcome.outcome.kind === "answered"
|
|
321
|
+
? result.historyResult.outcome.outcome.answer
|
|
322
|
+
: result.historyResult.outcome.outcome.diagnostic;
|
|
323
|
+
}
|
|
319
324
|
return undefined;
|
|
320
325
|
}
|
|
321
326
|
export function waitText(result, context) {
|
|
@@ -336,6 +341,12 @@ export function waitText(result, context) {
|
|
|
336
341
|
return [...blocks, `${done} of ${total} done`].join("\n\n");
|
|
337
342
|
}
|
|
338
343
|
export function historyText(command, result, context) {
|
|
344
|
+
if (result.mode === "exact") {
|
|
345
|
+
const exact = result.historyResult;
|
|
346
|
+
if (exact.kind !== "exact")
|
|
347
|
+
return `${exact.kind === "unknown-history" ? exact.historyId : "unknown"} has no matching retained outcome`;
|
|
348
|
+
return exact.outcome.outcome.kind === "answered" ? exact.outcome.outcome.answer : exact.outcome.outcome.diagnostic;
|
|
349
|
+
}
|
|
339
350
|
if (command.last)
|
|
340
351
|
return result.mode === "last" ? result.answer : "no answer retained";
|
|
341
352
|
if (result.mode !== "page")
|
|
@@ -123,6 +123,11 @@ function tellExitCode(result) {
|
|
|
123
123
|
function forkExitCode(result) {
|
|
124
124
|
return result.receipt.kind === "forked" ? 0 : result.receipt.kind === "upstream-forked" ? 2 : 1;
|
|
125
125
|
}
|
|
126
|
+
function historyExitCode(result) {
|
|
127
|
+
if (result.mode !== "exact")
|
|
128
|
+
return 0;
|
|
129
|
+
return result.historyResult.kind === "exact" ? 0 : 1;
|
|
130
|
+
}
|
|
126
131
|
export function akumaExitCode(result) {
|
|
127
132
|
switch (result.action) {
|
|
128
133
|
case "call":
|
|
@@ -133,6 +138,8 @@ export function akumaExitCode(result) {
|
|
|
133
138
|
return tellExitCode(result);
|
|
134
139
|
case "fork":
|
|
135
140
|
return forkExitCode(result);
|
|
141
|
+
case "history":
|
|
142
|
+
return historyExitCode(result);
|
|
136
143
|
default:
|
|
137
144
|
return 0;
|
|
138
145
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { renderAkumaText } from "./akuma.js";
|
|
2
|
+
import { renderKanshiText } from "./kanshi.js";
|
|
3
|
+
export function renderStatusSetText(result, context) {
|
|
4
|
+
return result.entries
|
|
5
|
+
.map((entry) => entry.kind === "contract"
|
|
6
|
+
? renderKanshiText(entry.report, context, "contract")
|
|
7
|
+
: renderAkumaText({ command: "status", contract: entry.selector, akuma: true, output: "text" }, {
|
|
8
|
+
kind: "akuma",
|
|
9
|
+
action: "status",
|
|
10
|
+
status: entry.status,
|
|
11
|
+
...(entry.alias === undefined ? {} : { alias: entry.alias }),
|
|
12
|
+
}, context))
|
|
13
|
+
.join("\n\n");
|
|
14
|
+
}
|
|
@@ -5,11 +5,14 @@ import { renderConflictMaterialized, renderRefusal } from "./refusal.js";
|
|
|
5
5
|
import { renderCatalogText } from "./catalog.js";
|
|
6
6
|
import { renderNukeText } from "./nuke.js";
|
|
7
7
|
import { renderRegionText } from "./region.js";
|
|
8
|
+
import { renderStatusSetText } from "./status-set.js";
|
|
8
9
|
export function renderText(result, context) {
|
|
9
10
|
if (result.kind === "guidance")
|
|
10
11
|
return result.guidance;
|
|
11
12
|
if (result.kind === "status")
|
|
12
13
|
return renderKanshiText(result.report, context, result.selection);
|
|
14
|
+
if (result.kind === "status-set")
|
|
15
|
+
return renderStatusSetText(result, context);
|
|
13
16
|
if (result.kind === "catalog")
|
|
14
17
|
return renderCatalogText(result.catalog);
|
|
15
18
|
if (result.kind === "contract-history")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AuditReport, ContractHistory, ContractId, ContinuationReport, Delivery, Fact, IntegrationConflictMaterialized, MutationResult, PlacementStop, ReconcileReport, Review, VerificationReuse, VerificationStop } from "../index.js";
|
|
2
2
|
import type { KanshiReport } from "../kanshi/index.js";
|
|
3
|
+
import type { AkumaObservation } from "../index.js";
|
|
3
4
|
import type { RegionRead, Section } from "../kanshi/index.js";
|
|
4
5
|
import type { Catalog } from "../index.js";
|
|
5
6
|
import type { AmendRegionObservation, RegionObservation } from "../library/region.js";
|
|
@@ -211,6 +212,19 @@ export type StatusResult = Readonly<{
|
|
|
211
212
|
report: KanshiReport;
|
|
212
213
|
selection: "world" | "contract";
|
|
213
214
|
}>;
|
|
215
|
+
export type StatusSetResult = Readonly<{
|
|
216
|
+
kind: "status-set";
|
|
217
|
+
entries: readonly (Readonly<{
|
|
218
|
+
selector: string;
|
|
219
|
+
kind: "contract";
|
|
220
|
+
report: KanshiReport;
|
|
221
|
+
}> | Readonly<{
|
|
222
|
+
selector: string;
|
|
223
|
+
kind: "akuma";
|
|
224
|
+
status: AkumaObservation;
|
|
225
|
+
alias?: string;
|
|
226
|
+
}>)[];
|
|
227
|
+
}>;
|
|
214
228
|
export type RegionResult = Readonly<{
|
|
215
229
|
kind: "region";
|
|
216
230
|
region: Section<RegionRead>;
|
|
@@ -227,5 +241,5 @@ export type NukeInvocationResult = Readonly<{
|
|
|
227
241
|
kind: "nuke";
|
|
228
242
|
result: NukeResult;
|
|
229
243
|
}>;
|
|
230
|
-
export type InvocationResult = AcceptedResult | RefusedResult | RetryResult | ObservationResult | GuidanceResult | StatusResult | RegionResult | CatalogResult | ContractHistoryResult | NukeInvocationResult | IntegrationConflictMaterialized;
|
|
244
|
+
export type InvocationResult = AcceptedResult | RefusedResult | RetryResult | ObservationResult | GuidanceResult | StatusResult | StatusSetResult | RegionResult | CatalogResult | ContractHistoryResult | NukeInvocationResult | IntegrationConflictMaterialized;
|
|
231
245
|
export {};
|
package/build/src/cli/runtime.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AllocatedAkuma } from "../akuma/identity.js";
|
|
2
|
+
import { type WorldRoot } from "../world.js";
|
|
3
|
+
export declare function recognizeAndListen(worldRoot: WorldRoot, environment: NodeJS.ProcessEnv, allocated: AllocatedAkuma): Promise<{
|
|
4
|
+
committed: boolean;
|
|
5
|
+
rollback(): Promise<void>;
|
|
6
|
+
} | void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { bindCurrentParticipant, Square, squareAssignedParticipantName, unbindCurrentParticipant, } from "@astrosheep/square";
|
|
2
|
+
import { keiyakuSquarePath } from "../world.js";
|
|
3
|
+
async function openKeiyakuSquare(path) {
|
|
4
|
+
try {
|
|
5
|
+
return await Square.at({ path });
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
if (error.code !== "ENOENT" && error.code !== "unavailable")
|
|
9
|
+
throw error;
|
|
10
|
+
try {
|
|
11
|
+
return await Square.build({ path, markdown: "" });
|
|
12
|
+
}
|
|
13
|
+
catch (buildError) {
|
|
14
|
+
try {
|
|
15
|
+
return await Square.at({ path });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw buildError;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function recognizeAndListen(worldRoot, environment, allocated) {
|
|
24
|
+
const name = squareAssignedParticipantName(environment);
|
|
25
|
+
if (name === undefined)
|
|
26
|
+
return;
|
|
27
|
+
const path = keiyakuSquarePath(worldRoot);
|
|
28
|
+
const square = await openKeiyakuSquare(path);
|
|
29
|
+
let joined = false;
|
|
30
|
+
let bound = false;
|
|
31
|
+
let listening = false;
|
|
32
|
+
const rollback = async () => {
|
|
33
|
+
if (listening || joined) {
|
|
34
|
+
const rollbackSquare = await Square.at({ path });
|
|
35
|
+
try {
|
|
36
|
+
const rollbackParticipant = await rollbackSquare.join(name);
|
|
37
|
+
if (listening)
|
|
38
|
+
await rollbackParticipant.ignore(allocated.id);
|
|
39
|
+
if (joined)
|
|
40
|
+
await rollbackParticipant.done();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await rollbackSquare.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (bound)
|
|
47
|
+
unbindCurrentParticipant(path, name, environment);
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
const joinedResult = await square.joinWithActivity(name);
|
|
51
|
+
joined = joinedResult.activity !== null;
|
|
52
|
+
const participant = joinedResult.participant;
|
|
53
|
+
bound = bindCurrentParticipant(path, name, environment).created;
|
|
54
|
+
const listener = participant;
|
|
55
|
+
const change = await listener.listen(allocated.id);
|
|
56
|
+
listening = change.activity !== null;
|
|
57
|
+
return {
|
|
58
|
+
committed: true,
|
|
59
|
+
rollback,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
try {
|
|
64
|
+
await rollback();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* preserve the original Square edge failure */
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await square.close();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -50,11 +50,16 @@ const FULFILLMENT = [
|
|
|
50
50
|
"",
|
|
51
51
|
"### Deliverer",
|
|
52
52
|
"",
|
|
53
|
+
"Re-read this file on each wake-up; it may have been updated between sessions.",
|
|
53
54
|
"Implement and verify the Objective under the Design, Region, and Criteria in this Contract.",
|
|
55
|
+
"Region is a rough intent, not a hard boundary — natural ripple outside it is fine and not a blocker.",
|
|
54
56
|
"When an Arc is active, stay within that current chapter.",
|
|
55
|
-
"
|
|
57
|
+
"If you discover a contradiction with this Contract or an unclosed point in its terms,",
|
|
58
|
+
"stop and report it before continuing.",
|
|
59
|
+
"",
|
|
60
|
+
"For work requiring three or more steps, prefer `keiyaku task -C <worktree>` to organize and manage Tasks.",
|
|
56
61
|
"Promptly update progress for Tasks already present in the current worktree.",
|
|
57
|
-
"
|
|
62
|
+
"",
|
|
58
63
|
"Deliver from this worktree. A clean worktree delivers HEAD; uncommitted work",
|
|
59
64
|
"needs `deliver --include-dirty`, which captures the final non-ignored tree and",
|
|
60
65
|
"stages or commits nothing. If deliver reports a conflict, run",
|
|
@@ -64,9 +69,17 @@ const FULFILLMENT = [
|
|
|
64
69
|
"",
|
|
65
70
|
"### Reviewer",
|
|
66
71
|
"",
|
|
67
|
-
"
|
|
68
|
-
"Review the complete current worktree snapshot
|
|
69
|
-
"
|
|
72
|
+
"Re-read this file on each wake-up; it may have been updated between sessions.",
|
|
73
|
+
"Review the complete current worktree snapshot against this Contract. Do not modify it.",
|
|
74
|
+
"Understand the original intent behind the Objective, not just its literal text.",
|
|
75
|
+
"Region is a rough intent, not a hard boundary — natural ripple outside it is not a blocker.",
|
|
76
|
+
"Compare each Criterion individually against the delivered work.",
|
|
77
|
+
"Consolidate findings by root cause — group symptoms that share an underlying issue",
|
|
78
|
+
"rather than listing each occurrence separately.",
|
|
79
|
+
"Dig to the architectural or structural root; flag it directly rather than routing around it.",
|
|
80
|
+
"If you discover a contradiction within the Contract or an unclosed point in its terms,",
|
|
81
|
+
"report it as a finding.",
|
|
82
|
+
"Report covered Criteria, findings, and missing evidence.",
|
|
70
83
|
].join("\n");
|
|
71
84
|
export function renderContractGuidance(state) {
|
|
72
85
|
return [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AliasBinding } from "../alias/index.js";
|
|
2
|
-
import { type AkumaStatus, type ForkReceipt, type ReadonlyRestraint
|
|
2
|
+
import { type AkumaStatus, type ForkReceipt, type ReadonlyRestraint } from "../akuma/akuma.js";
|
|
3
|
+
import { type AkumaBornCall } from "../akuma/akuma-product.js";
|
|
3
4
|
import type { AkuId } from "../akuma/identity.js";
|
|
4
5
|
import { type Dispatch, type DispatchFailure } from "../dispatch/index.js";
|
|
5
6
|
import { type AkumaAlias } from "../identity/selector.js";
|
|
@@ -48,11 +49,9 @@ export type CallInput = Readonly<{
|
|
|
48
49
|
contract?: Keiyaku;
|
|
49
50
|
alias?: AkumaAlias;
|
|
50
51
|
allowed?: readonly AllowedAction[];
|
|
51
|
-
resultRoute?: ResultRoute;
|
|
52
52
|
}>;
|
|
53
53
|
export type CallObservation = Readonly<{
|
|
54
54
|
kind: "detached";
|
|
55
|
-
resultRoute: "captured" | "not-captured";
|
|
56
55
|
}> | Readonly<{
|
|
57
56
|
kind: "observed";
|
|
58
57
|
status: AkumaStatus;
|
|
@@ -72,6 +71,16 @@ export type CallResult = Readonly<{
|
|
|
72
71
|
alias: AliasStage;
|
|
73
72
|
observation: CallObservation;
|
|
74
73
|
}>;
|
|
74
|
+
type CallExecution = CallResult["execution"];
|
|
75
|
+
export type BornCall = Readonly<{
|
|
76
|
+
path: WorldRoot;
|
|
77
|
+
born: AkumaBornCall;
|
|
78
|
+
execution: CallExecution;
|
|
79
|
+
mode: "wait" | "detach";
|
|
80
|
+
timeoutMs: number;
|
|
81
|
+
dispatch: DispatchStage;
|
|
82
|
+
alias: AliasStage;
|
|
83
|
+
}>;
|
|
75
84
|
export type ForkInput = Readonly<{
|
|
76
85
|
path: WorldRoot;
|
|
77
86
|
akuma: string;
|
|
@@ -89,5 +98,7 @@ export type ForkResult = Readonly<{
|
|
|
89
98
|
}>> & Readonly<{
|
|
90
99
|
parent: AkuId;
|
|
91
100
|
}>);
|
|
101
|
+
export declare function beginCall(input: CallInput): Promise<BornCall>;
|
|
102
|
+
export declare function finishCall(born: BornCall): Promise<CallResult>;
|
|
92
103
|
export declare function callKeiyaku(input: CallInput): Promise<CallResult>;
|
|
93
104
|
export declare function forkKeiyaku(input: ForkInput): Promise<ForkResult>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { moveAlias } from "../alias/index.js";
|
|
3
|
-
import { Akuma
|
|
3
|
+
import { Akuma } from "../akuma/akuma.js";
|
|
4
|
+
import { beginAkumaCall, finishAkumaCall } from "../akuma/akuma-product.js";
|
|
4
5
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
5
6
|
import { publishDispatch, readDispatch } from "../dispatch/index.js";
|
|
6
7
|
import { parseAkumaAlias } from "../identity/selector.js";
|
|
@@ -77,9 +78,9 @@ function callTimeout(value, mode) {
|
|
|
77
78
|
}
|
|
78
79
|
return value;
|
|
79
80
|
}
|
|
80
|
-
async function observeCall(handle, mode, timeoutMs
|
|
81
|
+
async function observeCall(handle, mode, timeoutMs) {
|
|
81
82
|
if (mode === "detach") {
|
|
82
|
-
return { kind: "detached"
|
|
83
|
+
return { kind: "detached" };
|
|
83
84
|
}
|
|
84
85
|
try {
|
|
85
86
|
return { kind: "observed", status: await handle.wait(undefined, { timeoutMs }) };
|
|
@@ -165,7 +166,7 @@ async function resolveCallExecution(input) {
|
|
|
165
166
|
}
|
|
166
167
|
return undefined;
|
|
167
168
|
}
|
|
168
|
-
export async function
|
|
169
|
+
export async function beginCall(input) {
|
|
169
170
|
const values = requireInput(input, "Keiyaku.call input");
|
|
170
171
|
onlyKeys(values, [
|
|
171
172
|
"path",
|
|
@@ -180,7 +181,6 @@ export async function callKeiyaku(input) {
|
|
|
180
181
|
"contract",
|
|
181
182
|
"alias",
|
|
182
183
|
"allowed",
|
|
183
|
-
"resultRoute",
|
|
184
184
|
], "Keiyaku.call input");
|
|
185
185
|
const path = nonblank(values.path, "path");
|
|
186
186
|
const archetype = nonblank(values.archetype, "archetype");
|
|
@@ -193,7 +193,6 @@ export async function callKeiyaku(input) {
|
|
|
193
193
|
const settings = settingsOption(values.settings);
|
|
194
194
|
const alias = values.alias === undefined ? undefined : parseAkumaAlias(nonblank(values.alias, "alias"));
|
|
195
195
|
const seat = values.contract === undefined ? undefined : seatForKeiyaku(values.contract);
|
|
196
|
-
const resultRoute = values.resultRoute;
|
|
197
196
|
const execution = await resolveCallExecution({
|
|
198
197
|
path,
|
|
199
198
|
...(cwd === undefined ? {} : { cwd }),
|
|
@@ -206,23 +205,20 @@ export async function callKeiyaku(input) {
|
|
|
206
205
|
...(readonlyRequested === undefined ? {} : { readonly: readonlyRequested }),
|
|
207
206
|
...(values.allowed === undefined ? {} : { allowed: values.allowed }),
|
|
208
207
|
...(execution === undefined ? {} : { cwd: execution.cwd }),
|
|
209
|
-
...(resultRoute === undefined ? {} : { resultRoute }),
|
|
210
208
|
};
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
throw new Error("Akuma call is missing its birth execution");
|
|
215
|
-
const readonly = (await handle.status()).readonly;
|
|
209
|
+
const born = await beginAkumaCall(world, call, execution === undefined ? {} : { cwdCanonical: true });
|
|
210
|
+
const akuma = born.kind === "requested" ? born.id : born.allocated.id;
|
|
211
|
+
const completedExecution = execution ?? born.execution;
|
|
216
212
|
const dispatch = seat === undefined
|
|
217
213
|
? { kind: "none" }
|
|
218
|
-
: await dispatchStage({ repository: seat.scope, akuId:
|
|
214
|
+
: await dispatchStage({ repository: seat.scope, akuId: akuma, contractId: seat.id });
|
|
219
215
|
let aliasStage = { kind: "none" };
|
|
220
216
|
if (alias !== undefined) {
|
|
221
217
|
if (dispatch.kind === "failed")
|
|
222
218
|
aliasStage = { kind: "skipped", reason: "dispatch-failed" };
|
|
223
219
|
else {
|
|
224
220
|
try {
|
|
225
|
-
const moved = await moveAlias({ world: path, alias, akuId:
|
|
221
|
+
const moved = await moveAlias({ world: path, alias, akuId: akuma });
|
|
226
222
|
aliasStage = { kind: "aliased", alias: moved.alias, previous: moved.previous };
|
|
227
223
|
}
|
|
228
224
|
catch (error) {
|
|
@@ -230,17 +226,34 @@ export async function callKeiyaku(input) {
|
|
|
230
226
|
}
|
|
231
227
|
}
|
|
232
228
|
}
|
|
233
|
-
const observation = await observeCall(handle, mode, timeoutMs, resultRoute);
|
|
234
229
|
return {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
...(readonly === undefined ? {} : { readonly }),
|
|
230
|
+
path,
|
|
231
|
+
born,
|
|
238
232
|
execution: completedExecution,
|
|
233
|
+
mode,
|
|
234
|
+
timeoutMs,
|
|
239
235
|
dispatch,
|
|
240
236
|
alias: aliasStage,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export async function finishCall(born) {
|
|
240
|
+
const world = akumaWorld(born.path);
|
|
241
|
+
const handle = await finishAkumaCall(world, born.born);
|
|
242
|
+
const readonly = (await handle.status()).readonly;
|
|
243
|
+
const observation = await observeCall(handle, born.mode, born.timeoutMs);
|
|
244
|
+
return {
|
|
245
|
+
kind: "called",
|
|
246
|
+
akuma: handle.id,
|
|
247
|
+
...(readonly === undefined ? {} : { readonly }),
|
|
248
|
+
execution: born.execution,
|
|
249
|
+
dispatch: born.dispatch,
|
|
250
|
+
alias: born.alias,
|
|
241
251
|
observation,
|
|
242
252
|
};
|
|
243
253
|
}
|
|
254
|
+
export async function callKeiyaku(input) {
|
|
255
|
+
return await finishCall(await beginCall(input));
|
|
256
|
+
}
|
|
244
257
|
export async function forkKeiyaku(input) {
|
|
245
258
|
const values = requireInput(input, "Keiyaku.fork input");
|
|
246
259
|
onlyKeys(values, ["path", "akuma", "at", "repo"], "Keiyaku.fork input");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActivityHistory, type AkumaStatus, type InterruptReceipt, type KillEvidence, type TellResult } from "../akuma/index.js";
|
|
1
|
+
import { type ActivityHistory, type OutcomeRow, type AkumaStatus, type InterruptReceipt, type KillEvidence, type TellResult } from "../akuma/index.js";
|
|
2
2
|
import type { ContractId } from "../core/facts/types.js";
|
|
3
3
|
import type { TaskRow } from "../task/index.js";
|
|
4
4
|
import type { WorldRoot } from "../world.js";
|
|
@@ -69,6 +69,7 @@ export type AkumaInterruptResult = Readonly<{
|
|
|
69
69
|
observation: AkumaObservationStage;
|
|
70
70
|
}>;
|
|
71
71
|
export type AkumaHistoryInput = AkumaAddressInput & Readonly<{
|
|
72
|
+
id?: string;
|
|
72
73
|
before?: number;
|
|
73
74
|
since?: number;
|
|
74
75
|
limit?: number;
|
|
@@ -79,6 +80,16 @@ export type AkumaHistoryResult = Readonly<{
|
|
|
79
80
|
id: AkumaStatus["id"];
|
|
80
81
|
history: ActivityHistory;
|
|
81
82
|
contract: DispatchAssociation;
|
|
83
|
+
}> | Readonly<{
|
|
84
|
+
kind: "exact";
|
|
85
|
+
id: AkumaStatus["id"];
|
|
86
|
+
outcome: OutcomeRow;
|
|
87
|
+
contract: DispatchAssociation;
|
|
88
|
+
}> | Readonly<{
|
|
89
|
+
kind: "unknown-history";
|
|
90
|
+
id: AkumaStatus["id"];
|
|
91
|
+
historyId: string;
|
|
92
|
+
contract: DispatchAssociation;
|
|
82
93
|
}> | Readonly<{
|
|
83
94
|
kind: "last";
|
|
84
95
|
id: AkumaStatus["id"];
|
|
@@ -6,6 +6,7 @@ import { observeTaskBoard } from "../task/operations.js";
|
|
|
6
6
|
import { addressAkuma, addressAkumaSet } from "./address.js";
|
|
7
7
|
import { requireInput } from "./input.js";
|
|
8
8
|
import { scopeForRepo } from "./repo.js";
|
|
9
|
+
import { parsePublicHistoryId } from "../akuma/identity.js";
|
|
9
10
|
function source(path) {
|
|
10
11
|
return Akuma.of(path);
|
|
11
12
|
}
|
|
@@ -315,15 +316,24 @@ export async function interruptAkuma(input) {
|
|
|
315
316
|
const observation = await observeAkumaStage(addressed.path, addressed.id, values.repo);
|
|
316
317
|
return { id: addressed.id, receipt, observation };
|
|
317
318
|
}
|
|
318
|
-
|
|
319
|
-
const values = requireInput(input, "Keiyaku.history input");
|
|
319
|
+
function validateHistoryInput(values) {
|
|
320
320
|
for (const key of Object.keys(values)) {
|
|
321
|
-
if (!["path", "akuma", "before", "since", "limit", "last", "repo"].includes(key))
|
|
321
|
+
if (!["path", "akuma", "id", "before", "since", "limit", "last", "repo"].includes(key))
|
|
322
322
|
throw new TypeError(`Keiyaku.history input has unknown field: ${key}`);
|
|
323
|
-
}
|
|
324
323
|
}
|
|
325
324
|
if (values.last !== undefined && typeof values.last !== "boolean")
|
|
326
325
|
throw new TypeError("last must be a boolean");
|
|
326
|
+
if (values.id !== undefined && (typeof values.id !== "string" || values.id.trim() === ""))
|
|
327
|
+
throw new TypeError("id must be a nonblank string");
|
|
328
|
+
if (values.id !== undefined && parsePublicHistoryId(values.id) === null)
|
|
329
|
+
throw new TypeError("id must match turn/<positive safe integer>");
|
|
330
|
+
if (values.id !== undefined &&
|
|
331
|
+
(values.last === true || values.before !== undefined || values.since !== undefined || values.limit !== undefined))
|
|
332
|
+
throw new TypeError("id cannot be combined with last, before, since, or limit");
|
|
333
|
+
}
|
|
334
|
+
export async function historyAkuma(input) {
|
|
335
|
+
const values = requireInput(input, "Keiyaku.history input");
|
|
336
|
+
validateHistoryInput(values);
|
|
327
337
|
const addressed = await addressAkuma(directAddress(values));
|
|
328
338
|
const handle = source(addressed.path).of({ id: addressed.id });
|
|
329
339
|
const contract = await dispatchAssociation(values.repo, addressed.id);
|
|
@@ -334,9 +344,15 @@ export async function historyAkuma(input) {
|
|
|
334
344
|
: { kind: "no-answer", id: addressed.id, contract };
|
|
335
345
|
}
|
|
336
346
|
const history = await handle.history({
|
|
347
|
+
...(values.id === undefined ? {} : { id: values.id }),
|
|
337
348
|
...(values.before === undefined ? {} : { before: values.before }),
|
|
338
349
|
...(values.since === undefined ? {} : { since: values.since }),
|
|
339
350
|
...(values.limit === undefined ? {} : { limit: values.limit }),
|
|
340
351
|
});
|
|
341
|
-
|
|
352
|
+
if (values.id !== undefined) {
|
|
353
|
+
if ("kind" in history && history.kind === "exact")
|
|
354
|
+
return { kind: "exact", id: addressed.id, outcome: history.outcome, contract };
|
|
355
|
+
return { kind: "unknown-history", id: addressed.id, historyId: values.id, contract };
|
|
356
|
+
}
|
|
357
|
+
return { kind: "history", id: addressed.id, history: history, contract };
|
|
342
358
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
1
3
|
import { currentBranchOperation, scopeOperation } from "../protocol/operations.js";
|
|
2
4
|
import { NoGitWorldError } from "../git/repository.js";
|
|
3
5
|
import { optionalNonblank, requireInput } from "./input.js";
|
|
@@ -8,7 +10,7 @@ export { NoGitWorldError };
|
|
|
8
10
|
const REPO_SCOPES = new WeakMap();
|
|
9
11
|
async function resolvePinnedScope(path, gitPath) {
|
|
10
12
|
return await scopeOperation({
|
|
11
|
-
coordinate: path === undefined ? process.cwd() : path,
|
|
13
|
+
coordinate: await realpath(resolve(path === undefined ? process.cwd() : path)),
|
|
12
14
|
...(gitPath === undefined ? {} : { gitPath }),
|
|
13
15
|
});
|
|
14
16
|
}
|
|
@@ -5,12 +5,17 @@ export function detachedExitStatus(code, signal) {
|
|
|
5
5
|
export async function retainDetachedExitEvidence(log, path, from, code, signal) {
|
|
6
6
|
const status = detachedExitStatus(code, signal);
|
|
7
7
|
const marker = Buffer.from(`[child ${status}]\n`);
|
|
8
|
+
const before = await log.stat();
|
|
9
|
+
if (before.size < from)
|
|
10
|
+
throw new Error("run log shrank before exit evidence was retained");
|
|
8
11
|
let written = 0;
|
|
12
|
+
let position = before.size;
|
|
9
13
|
while (written < marker.byteLength) {
|
|
10
|
-
const write = await log.write(marker, written, marker.byteLength - written);
|
|
14
|
+
const write = await log.write(marker, written, marker.byteLength - written, position);
|
|
11
15
|
if (write.bytesWritten === 0)
|
|
12
16
|
throw new Error("run log exit marker write made no progress");
|
|
13
17
|
written += write.bytesWritten;
|
|
18
|
+
position += write.bytesWritten;
|
|
14
19
|
}
|
|
15
20
|
const evidence = await log.stat();
|
|
16
21
|
if (evidence.size < from)
|