@astrosheep/keiyaku 4.5.26 → 4.5.28
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 +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +82 -162
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +153 -76
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +68 -64
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +115 -146
- package/build/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +11 -0
- package/build/src/akuma/akuma-handle.d.ts +54 -1
- package/build/src/akuma/akuma-handle.js +79 -8
- package/build/src/akuma/akuma-instance.js +6 -22
- package/build/src/akuma/akuma-observe.d.ts +6 -0
- package/build/src/akuma/akuma-observe.js +3 -1
- package/build/src/akuma/akuma-probe.d.ts +20 -1
- package/build/src/akuma/akuma-probe.js +20 -3
- package/build/src/akuma/akuma.d.ts +13 -0
- package/build/src/akuma/akuma.js +1 -0
- package/build/src/akuma/allowed.d.ts +2 -1
- package/build/src/akuma/allowed.js +9 -4
- package/build/src/akuma/archetype.js +3 -3
- package/build/src/akuma/body.js +8 -3
- package/build/src/akuma/fleet-execution.d.ts +13 -2
- package/build/src/akuma/fleet-execution.js +45 -0
- package/build/src/akuma/fleet-observation.d.ts +241 -0
- package/build/src/akuma/fleet-observation.js +13 -0
- package/build/src/akuma/fleet-owner-port.d.ts +12 -0
- package/build/src/akuma/fleet-owner-port.js +46 -0
- package/build/src/akuma/fleet-request.d.ts +55 -4
- package/build/src/akuma/fleet-request.js +101 -6
- package/build/src/akuma/heart/index.d.ts +1 -0
- package/build/src/akuma/heart/index.js +4 -1
- package/build/src/akuma/heart/rows.d.ts +1 -0
- package/build/src/akuma/heart/rows.js +8 -0
- package/build/src/akuma/heart/soul.js +2 -2
- package/build/src/akuma/identity.js +11 -9
- package/build/src/akuma/projection.d.ts +36 -0
- package/build/src/akuma/projection.js +2 -1
- package/build/src/akuma/provider.d.ts +14 -0
- package/build/src/akuma/provider.js +66 -4
- package/build/src/akuma/providers/acp/events.js +16 -8
- package/build/src/akuma/providers/claude/events.js +5 -7
- package/build/src/akuma/providers/opencode-sdk/events.js +9 -5
- package/build/src/akuma/providers/pi/events.js +4 -3
- package/build/src/akuma/request-lifecycle.js +10 -1
- package/build/src/akuma-body.js +1 -33
- package/build/src/alias/index.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +8 -0
- package/build/src/cli/commands/akuma-invoke.js +67 -6
- package/build/src/cli/commands/akuma.d.ts +1 -0
- package/build/src/cli/commands/akuma.js +19 -30
- package/build/src/cli/commands/contract-help.d.ts +1 -0
- package/build/src/cli/commands/contract-help.js +9 -5
- package/build/src/cli/invoke.js +1 -1
- package/build/src/cli/render/akuma-activity.d.ts +7 -1
- package/build/src/cli/render/akuma-activity.js +164 -50
- package/build/src/cli/render/akuma-tool.d.ts +5 -0
- package/build/src/cli/render/akuma-tool.js +118 -14
- package/build/src/cli/render/akuma.d.ts +12 -1
- package/build/src/cli/render/akuma.js +74 -2
- package/build/src/cli/render/catalog.js +2 -3
- package/build/src/cli/render/contract-observation.d.ts +1 -0
- package/build/src/cli/render/contract-observation.js +20 -1
- package/build/src/cli/render/contract.js +4 -4
- package/build/src/cli/render/execution-progress.d.ts +1 -0
- package/build/src/cli/render/execution-progress.js +77 -3
- package/build/src/cli/render/kanshi.js +5 -19
- package/build/src/cli/render/terminal.js +3 -1
- package/build/src/cli/runtime.d.ts +6 -0
- package/build/src/cli/runtime.js +35 -17
- package/build/src/core/facts/offer.d.ts +1 -2
- package/build/src/core/verbs/abandon.js +1 -1
- package/build/src/core/verbs/amend.js +1 -1
- package/build/src/core/verbs/arc.js +1 -1
- package/build/src/core/verbs/attestation.js +1 -1
- package/build/src/core/verbs/bind.js +1 -1
- package/build/src/core/verbs/deliver.js +1 -1
- package/build/src/core/verbs/placement.js +1 -1
- package/build/src/core/verbs/reintegrate.js +1 -1
- package/build/src/git/admission.js +18 -7
- package/build/src/identity/coordinates.js +1 -1
- package/build/src/identity/normalize.d.ts +3 -0
- package/build/src/identity/normalize.js +10 -0
- package/build/src/identity/selector.d.ts +1 -0
- package/build/src/identity/selector.js +15 -4
- package/build/src/library/address.d.ts +33 -3
- package/build/src/library/address.js +90 -29
- package/build/src/library/akuma-creation.d.ts +1 -1
- package/build/src/library/akuma-creation.js +10 -52
- package/build/src/library/composition.d.ts +14 -1
- package/build/src/library/fleet.d.ts +13 -2
- package/build/src/library/fleet.js +142 -48
- package/build/src/library/keiyaku.d.ts +14 -1
- package/build/src/plugin/public.d.ts +2 -0
- package/build/src/protocol/completion.js +9 -2
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/package.json +1 -1
|
@@ -1,7 +1,75 @@
|
|
|
1
1
|
import { parseAkumaStatus } from "../../akuma/akuma.js";
|
|
2
|
-
import { DEFAULT_CONTEXT, akumaRawAnswer, associatedIdentity, historyText, killResultText, mutationObservationStageText, snapshotHeading, snapshotText, tellText, waitText, } from "./akuma-activity.js";
|
|
2
|
+
import { activityStream, DEFAULT_CONTEXT, akumaRawAnswer as akumaActivityRawAnswer, associatedIdentity, historyText, killResultText, mutationObservationStageText, snapshotHeading, snapshotText, tellText, waitText, } from "./akuma-activity.js";
|
|
3
3
|
import { safeText } from "./terminal.js";
|
|
4
|
-
|
|
4
|
+
function tellWaitConclusion(result) {
|
|
5
|
+
return result.observation.reason === "answered"
|
|
6
|
+
? "✓ answered"
|
|
7
|
+
: result.observation.reason === "failed"
|
|
8
|
+
? `! failed · ${safeText(result.observation.diagnostic)}`
|
|
9
|
+
: result.observation.reason === "unanswered"
|
|
10
|
+
? "○ unanswered"
|
|
11
|
+
: "⧖ deadline";
|
|
12
|
+
}
|
|
13
|
+
export function tellWaitProgressStream(akuma, alias, context) {
|
|
14
|
+
let target = akuma;
|
|
15
|
+
const stream = activityStream(context);
|
|
16
|
+
let opened = false;
|
|
17
|
+
return {
|
|
18
|
+
admitted: (tell, id) => {
|
|
19
|
+
target = id;
|
|
20
|
+
if (target === undefined)
|
|
21
|
+
throw new Error("Tell progress admitted without identity");
|
|
22
|
+
return [
|
|
23
|
+
tellText({
|
|
24
|
+
kind: "akuma",
|
|
25
|
+
action: "tell",
|
|
26
|
+
mode: "ordinary",
|
|
27
|
+
result: { akuma: target, tell },
|
|
28
|
+
body: "",
|
|
29
|
+
...(alias === undefined ? {} : { alias }),
|
|
30
|
+
}, context),
|
|
31
|
+
];
|
|
32
|
+
},
|
|
33
|
+
observe: (observation) => {
|
|
34
|
+
const lines = [];
|
|
35
|
+
if (target === undefined)
|
|
36
|
+
throw new Error("Tell progress observed before admission");
|
|
37
|
+
if (!opened) {
|
|
38
|
+
opened = true;
|
|
39
|
+
lines.push(...snapshotHeading(target, alias, undefined));
|
|
40
|
+
lines.push(...stream.seed({ snapshot: observation.status.timeline, rows: observation.rows }));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
lines.push(...stream({ snapshot: observation.status.timeline, rows: observation.rows }));
|
|
44
|
+
}
|
|
45
|
+
return lines;
|
|
46
|
+
},
|
|
47
|
+
conclude: (result) => [...stream.flush(), tellWaitConclusion(result)],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function waitedTellProgress(result, alias, context) {
|
|
51
|
+
const receipt = tellText({
|
|
52
|
+
kind: "akuma",
|
|
53
|
+
action: "tell",
|
|
54
|
+
mode: "ordinary",
|
|
55
|
+
result: { akuma: result.akuma, tell: result.tell },
|
|
56
|
+
body: "",
|
|
57
|
+
...(alias === undefined ? {} : { alias }),
|
|
58
|
+
}, context);
|
|
59
|
+
const conclusion = tellWaitConclusion(result);
|
|
60
|
+
return `${receipt}\n${conclusion}`;
|
|
61
|
+
}
|
|
62
|
+
function waitedTellRawAnswer(result) {
|
|
63
|
+
if (result.result.observation.reason !== "answered")
|
|
64
|
+
return "";
|
|
65
|
+
const answer = result.result.observation.answer;
|
|
66
|
+
return typeof answer === "string" ? answer : JSON.stringify(answer);
|
|
67
|
+
}
|
|
68
|
+
export function akumaRawAnswer(result) {
|
|
69
|
+
if (result.action === "tell" && result.mode === "wait")
|
|
70
|
+
return waitedTellRawAnswer(result);
|
|
71
|
+
return akumaActivityRawAnswer(result);
|
|
72
|
+
}
|
|
5
73
|
function dispatchLines(stage) {
|
|
6
74
|
if (stage.kind === "none")
|
|
7
75
|
return [];
|
|
@@ -83,6 +151,8 @@ export function renderAkumaText(command, result, context = DEFAULT_CONTEXT) {
|
|
|
83
151
|
case "wait":
|
|
84
152
|
return waitText(result, context);
|
|
85
153
|
case "tell":
|
|
154
|
+
if (result.mode === "wait")
|
|
155
|
+
return "";
|
|
86
156
|
return result.mode === "ordinary"
|
|
87
157
|
? tellText(result, context)
|
|
88
158
|
: mutationObservationStageText(result.result.id, result.result.observation, context, {
|
|
@@ -125,6 +195,8 @@ function killExitCode(result) {
|
|
|
125
195
|
function tellExitCode(result) {
|
|
126
196
|
if (result.mode === "schema")
|
|
127
197
|
return 0;
|
|
198
|
+
if (result.mode === "wait")
|
|
199
|
+
return result.result.observation.reason === "failed" ? 2 : 0;
|
|
128
200
|
return result.mode === "ordinary"
|
|
129
201
|
? result.result.tell.wake.kind === "failed"
|
|
130
202
|
? 2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { abbreviateGitIds, afterWording,
|
|
1
|
+
import { abbreviateGitIds, afterWording, candidateIntegrationFacts, dependentWording, gateFact, gitIdsInRow, targetFacts, } from "./contract-observation.js";
|
|
2
2
|
import { safeText } from "./terminal.js";
|
|
3
3
|
import { dispositionText, taskMark } from "./task.js";
|
|
4
4
|
function akumaMark(life) {
|
|
@@ -87,10 +87,9 @@ function renderContractCatalog(catalog) {
|
|
|
87
87
|
const blocks = rows.map((row) => {
|
|
88
88
|
const lines = [
|
|
89
89
|
`${catalogMark(row)} ${safeText(row.id)} · ${row.phase} · ${formatAge(row.phaseAt, catalog.observedAt)} · ${safeText(row.title ?? "title unavailable")}`,
|
|
90
|
-
` ${
|
|
90
|
+
...candidateIntegrationFacts(row.delivery, row.verification, abbreviations).map((fact) => ` ${safeText(fact)}`),
|
|
91
91
|
...targetFacts(row, abbreviations).map((fact) => ` ${safeText(fact)}`),
|
|
92
92
|
...[],
|
|
93
|
-
...(verificationFact(row.verification) === undefined ? [] : [` ${verificationFact(row.verification)}`]),
|
|
94
93
|
...row.after.map((edge) => ` ${afterWording(edge)}`),
|
|
95
94
|
...(row.dependents.length === 0 ? [] : [` dependents ${row.dependents.map(dependentWording).join(" · ")}`]),
|
|
96
95
|
...(row.gates.reports.length === 0 ? [] : [` ${row.gates.reports.map(gateFact).join(" ")}`]),
|
|
@@ -3,6 +3,7 @@ export declare function gateGlyph(report: ContractGateReport): string;
|
|
|
3
3
|
export declare function gateFact(report: ContractGateReport): string;
|
|
4
4
|
export declare function candidateFact(delivery: ContractRow["delivery"]): string;
|
|
5
5
|
export declare function verificationFact(status: ContractRow["verification"]): string | undefined;
|
|
6
|
+
export declare function candidateIntegrationFacts(delivery: ContractRow["delivery"], verification: ContractRow["verification"], abbreviations: ReadonlyMap<string, string>): readonly string[];
|
|
6
7
|
export declare function afterWording(edge: ContractAfterEdge): string;
|
|
7
8
|
export declare function dependentWording(dependent: ContractDependent): string;
|
|
8
9
|
export declare function mergeSummary(observation: ContractWorkspaceObservation): string | undefined;
|
|
@@ -19,9 +19,28 @@ export function verificationFact(status) {
|
|
|
19
19
|
if (status === undefined)
|
|
20
20
|
return undefined;
|
|
21
21
|
if (status.kind === "recorded")
|
|
22
|
-
return `verification ${status.verdict}${status.snapshot === undefined ? "" : ` ·
|
|
22
|
+
return `verification ${status.verdict}${status.snapshot === undefined ? "" : ` · snapshot ${shortGitId(status.snapshot)}`}`;
|
|
23
23
|
return `verification ${status.kind}`;
|
|
24
24
|
}
|
|
25
|
+
export function candidateIntegrationFacts(delivery, verification, abbreviations) {
|
|
26
|
+
const verificationLine = verificationFact(verification);
|
|
27
|
+
if (delivery === null) {
|
|
28
|
+
return [candidateFact(delivery), ...(verificationLine === undefined ? [] : [verificationLine])];
|
|
29
|
+
}
|
|
30
|
+
const integration = displayGitId(delivery.integration.snapshot, abbreviations);
|
|
31
|
+
const foldedVerification = verification?.kind === "recorded" && verification.snapshot === delivery.integration.snapshot
|
|
32
|
+
? ` · verification ${verification.verdict}`
|
|
33
|
+
: "";
|
|
34
|
+
return [
|
|
35
|
+
candidateFact(delivery),
|
|
36
|
+
`tender commit ${displayGitId(delivery.tenderSnapshot, abbreviations)}`,
|
|
37
|
+
`integration result ${integration}${foldedVerification}`,
|
|
38
|
+
`predecessor ${displayGitId(delivery.integration.predecessor, abbreviations)}`,
|
|
39
|
+
`method ${delivery.method}`,
|
|
40
|
+
`content identity (not commit) ${delivery.integration.changeId}`,
|
|
41
|
+
...(foldedVerification.length === 0 && verificationLine !== undefined ? [verificationLine] : []),
|
|
42
|
+
];
|
|
43
|
+
}
|
|
25
44
|
export function afterWording(edge) {
|
|
26
45
|
if (edge.endpoint.kind === "claimed")
|
|
27
46
|
return `after ${edge.contractId} · claimed`;
|
|
@@ -152,9 +152,9 @@ function shortGitId(value) {
|
|
|
152
152
|
/**
|
|
153
153
|
* Shared presentation of a completed placement, so a deliver and a review that placed the same candidate read
|
|
154
154
|
* identically: one git-shaped target movement row, naming the reference it advanced, then the final lifecycle
|
|
155
|
-
* state. Satisfied Verification adds one fact row naming the
|
|
156
|
-
*
|
|
157
|
-
* `history`, never in ordinary receipt text. A completion without an advanced reference states no movement.
|
|
155
|
+
* state. Satisfied Verification adds one fact row naming the integrated result; placement made that commit the
|
|
156
|
+
* reference's new head, so the movement and integrated-result rows share one sha. Verification mode words stay in
|
|
157
|
+
* JSON and `history`, never in ordinary receipt text. A completion without an advanced reference states no movement.
|
|
158
158
|
* Journal ULIDs stay in JSON and `history`, never in ordinary receipt text.
|
|
159
159
|
*/
|
|
160
160
|
function completedPlacementLines(result, columns) {
|
|
@@ -169,7 +169,7 @@ function completedPlacementLines(result, columns) {
|
|
|
169
169
|
], columns);
|
|
170
170
|
}
|
|
171
171
|
if (completion.verification?.verdict === "satisfied") {
|
|
172
|
-
receiptRow(lines, " ", "
|
|
172
|
+
receiptRow(lines, " ", "integration result", [{ text: `${shortGitId(completion.integration)} · verification satisfied` }], columns);
|
|
173
173
|
}
|
|
174
174
|
lines.push(...nonGatingVerificationLines(result, columns));
|
|
175
175
|
// Placement admission always admits the claim entry beside the movement, so a completed placement is claimed.
|
|
@@ -16,6 +16,7 @@ export declare class ExecutionProgressRenderer {
|
|
|
16
16
|
private declarationFailed;
|
|
17
17
|
private phaseFailed;
|
|
18
18
|
private phaseUnknown;
|
|
19
|
+
private readonly liveOutput;
|
|
19
20
|
constructor(input: ExecutionProgressOptions);
|
|
20
21
|
consume(event: ExecutionEvent): Promise<void>;
|
|
21
22
|
finish(): void;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { renderOpaqueBlock, safeText } from "./terminal.js";
|
|
2
2
|
import { StatusLine } from "./status-line.js";
|
|
3
3
|
const LIVE_OUTPUT_BYTES = 4 * 1024;
|
|
4
|
-
function boundedUtf8Prefix(value) {
|
|
4
|
+
function boundedUtf8Prefix(value, maximumBytes = LIVE_OUTPUT_BYTES) {
|
|
5
5
|
let bytes = 0;
|
|
6
6
|
let text = "";
|
|
7
7
|
for (const character of value) {
|
|
8
8
|
const size = Buffer.byteLength(character);
|
|
9
|
-
if (bytes + size >
|
|
9
|
+
if (bytes + size > maximumBytes)
|
|
10
10
|
return { text, truncated: true };
|
|
11
11
|
bytes += size;
|
|
12
12
|
text += character;
|
|
@@ -47,6 +47,74 @@ function outputLines(observation, context) {
|
|
|
47
47
|
...(output.truncated ? [" [live output truncated]"] : []),
|
|
48
48
|
];
|
|
49
49
|
}
|
|
50
|
+
class VerificationLiveOutput {
|
|
51
|
+
streams = new Map();
|
|
52
|
+
activeStream;
|
|
53
|
+
reset() {
|
|
54
|
+
this.streams.clear();
|
|
55
|
+
this.activeStream = undefined;
|
|
56
|
+
}
|
|
57
|
+
lines(observation, context) {
|
|
58
|
+
const stream = observation.stream;
|
|
59
|
+
const lines = this.flushOtherStreams(stream, context);
|
|
60
|
+
const previous = this.streams.get(stream);
|
|
61
|
+
const state = previous ?? { bytes: 0, truncated: false, pending: "", prefix: " " };
|
|
62
|
+
if (this.activeStream !== stream) {
|
|
63
|
+
state.prefix = " ";
|
|
64
|
+
lines.push(stream);
|
|
65
|
+
this.activeStream = stream;
|
|
66
|
+
}
|
|
67
|
+
this.streams.set(stream, state);
|
|
68
|
+
if (state.truncated)
|
|
69
|
+
return lines;
|
|
70
|
+
const output = boundedUtf8Prefix(observation.text, LIVE_OUTPUT_BYTES - state.bytes);
|
|
71
|
+
state.bytes += Buffer.byteLength(output.text);
|
|
72
|
+
state.pending += output.text;
|
|
73
|
+
lines.push(...this.drainCompleteLines(state, context));
|
|
74
|
+
this.streams.set(stream, state);
|
|
75
|
+
if (output.truncated) {
|
|
76
|
+
lines.push(...this.drainPartialLine(state, context));
|
|
77
|
+
state.truncated = true;
|
|
78
|
+
lines.push(" [live output truncated]");
|
|
79
|
+
}
|
|
80
|
+
return lines;
|
|
81
|
+
}
|
|
82
|
+
finish(context) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
for (const state of this.streams.values())
|
|
85
|
+
lines.push(...this.drainPartialLine(state, context));
|
|
86
|
+
this.streams.clear();
|
|
87
|
+
this.activeStream = undefined;
|
|
88
|
+
return lines;
|
|
89
|
+
}
|
|
90
|
+
flushOtherStreams(stream, context) {
|
|
91
|
+
const lines = [];
|
|
92
|
+
for (const [otherStream, state] of this.streams)
|
|
93
|
+
if (otherStream !== stream)
|
|
94
|
+
lines.push(...this.drainPartialLine(state, context));
|
|
95
|
+
return lines;
|
|
96
|
+
}
|
|
97
|
+
drainCompleteLines(state, context) {
|
|
98
|
+
const lines = [];
|
|
99
|
+
for (;;) {
|
|
100
|
+
const newline = state.pending.indexOf("\n");
|
|
101
|
+
if (newline < 0)
|
|
102
|
+
break;
|
|
103
|
+
const line = state.pending.slice(0, newline);
|
|
104
|
+
state.pending = state.pending.slice(newline + 1);
|
|
105
|
+
lines.push(...renderOpaqueBlock(line, state.prefix, context.columns));
|
|
106
|
+
state.prefix = " ";
|
|
107
|
+
}
|
|
108
|
+
return lines;
|
|
109
|
+
}
|
|
110
|
+
drainPartialLine(state, context) {
|
|
111
|
+
if (state.pending.length === 0)
|
|
112
|
+
return [];
|
|
113
|
+
const lines = renderOpaqueBlock(state.pending, state.prefix, context.columns);
|
|
114
|
+
state.pending = "";
|
|
115
|
+
return lines;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
50
118
|
/** Sparse rendering for consumers that do not own a live stream. */
|
|
51
119
|
export function executionProgressLines(event, context) {
|
|
52
120
|
switch (event.kind) {
|
|
@@ -75,6 +143,7 @@ export class ExecutionProgressRenderer {
|
|
|
75
143
|
declarationFailed = false;
|
|
76
144
|
phaseFailed = false;
|
|
77
145
|
phaseUnknown = false;
|
|
146
|
+
liveOutput = new VerificationLiveOutput();
|
|
78
147
|
constructor(input) {
|
|
79
148
|
this.input = input;
|
|
80
149
|
this.status = new StatusLine(input.stream, input);
|
|
@@ -101,10 +170,12 @@ export class ExecutionProgressRenderer {
|
|
|
101
170
|
async consumeVerification(event) {
|
|
102
171
|
const observation = event.observation;
|
|
103
172
|
if (observation.kind === "output") {
|
|
104
|
-
await this.write(
|
|
173
|
+
await this.write(this.liveOutput.lines(observation, this.input.context));
|
|
105
174
|
return;
|
|
106
175
|
}
|
|
107
176
|
if (observation.state === "started") {
|
|
177
|
+
await this.write(this.liveOutput.finish(this.input.context));
|
|
178
|
+
this.liveOutput.reset();
|
|
108
179
|
this.verificationStartedAt ??= (this.input.now ?? (() => performance.now()))();
|
|
109
180
|
if (this.status.isTTY)
|
|
110
181
|
this.status.show((duration) => `verify ● ${phaseDetail(observation)} · ${elapsed(duration)}`);
|
|
@@ -112,6 +183,7 @@ export class ExecutionProgressRenderer {
|
|
|
112
183
|
await this.write([phaseStartLine(observation)]);
|
|
113
184
|
return;
|
|
114
185
|
}
|
|
186
|
+
await this.write(this.liveOutput.finish(this.input.context));
|
|
115
187
|
const mark = phaseMark(observation.outcome);
|
|
116
188
|
this.phaseFailed ||= mark === "×";
|
|
117
189
|
this.phaseUnknown ||= mark === "?";
|
|
@@ -127,6 +199,8 @@ export class ExecutionProgressRenderer {
|
|
|
127
199
|
await this.write([phaseFinishLine(observation)]);
|
|
128
200
|
}
|
|
129
201
|
write(lines) {
|
|
202
|
+
if (lines.length === 0)
|
|
203
|
+
return Promise.resolve();
|
|
130
204
|
if (this.status.isTTY) {
|
|
131
205
|
this.status.writeBlock(lines);
|
|
132
206
|
return Promise.resolve();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { abbreviateGitIds, afterWording,
|
|
1
|
+
import { abbreviateGitIds, afterWording, candidateIntegrationFacts, dependentWording, displayGitId, gateFact, gitIdsInRow, mergeSummary, targetFacts, } from "./contract-observation.js";
|
|
2
2
|
import { entityLines, elapsedMilliseconds, identityLine, plumbFacts, RECENT_TONE_MS, renderSectionBlock, linkedEntityLines, safeText, tone, } from "./terminal.js";
|
|
3
3
|
import { akumaMark, endpointFact, formatAge, NARROW_COLUMNS, renderAkuma } from "./kanshi-akuma.js";
|
|
4
4
|
import { dispositionText } from "./task.js";
|
|
@@ -179,7 +179,7 @@ function renderSelectedContractRow(row, report, context, abbreviations) {
|
|
|
179
179
|
: []),
|
|
180
180
|
]);
|
|
181
181
|
lines.push(...semanticBlock("gates", gateFacts, context));
|
|
182
|
-
lines.push(...semanticBlock("candidate/integration",
|
|
182
|
+
lines.push(...semanticBlock("candidate/integration", candidateIntegrationFacts(row.delivery, row.verification, abbreviations), context));
|
|
183
183
|
lines.push(...semanticBlock("target", targetFacts(row, abbreviations), context));
|
|
184
184
|
const workspaceFacts = [workspaceState(row)];
|
|
185
185
|
if (row.workspaceObservation.kind !== "unappointed" &&
|
|
@@ -199,31 +199,17 @@ function renderSelectedContractRow(row, report, context, abbreviations) {
|
|
|
199
199
|
}
|
|
200
200
|
return lines;
|
|
201
201
|
}
|
|
202
|
-
function candidateFacts(row, abbreviations) {
|
|
203
|
-
const verification = verificationFact(row.verification);
|
|
204
|
-
if (row.delivery === null)
|
|
205
|
-
return [candidateFact(row.delivery), ...(verification === undefined ? [] : [verification])];
|
|
206
|
-
const delivery = row.delivery;
|
|
207
|
-
return [
|
|
208
|
-
candidateFact(delivery),
|
|
209
|
-
`tender commit ${displayGitId(delivery.tenderSnapshot, abbreviations)}`,
|
|
210
|
-
`integration commit ${displayGitId(delivery.integration.snapshot, abbreviations)} · predecessor ${displayGitId(delivery.integration.predecessor, abbreviations)}`,
|
|
211
|
-
`method ${delivery.method}`,
|
|
212
|
-
`content identity (not commit) ${delivery.integration.changeId}`,
|
|
213
|
-
...(verification === undefined ? [] : [verification]),
|
|
214
|
-
];
|
|
215
|
-
}
|
|
216
202
|
function renderWorldContractRow(row, report, context) {
|
|
217
203
|
const title = row.title ?? "title unavailable";
|
|
204
|
+
const abbreviations = gitAbbreviations(report);
|
|
218
205
|
const contractFacts = [
|
|
219
|
-
|
|
220
|
-
...targetFacts(row,
|
|
206
|
+
...candidateIntegrationFacts(row.delivery, row.verification, abbreviations),
|
|
207
|
+
...targetFacts(row, abbreviations),
|
|
221
208
|
...[],
|
|
222
209
|
...(linkedAkumaSummary(row, report) === undefined ? [] : [linkedAkumaSummary(row, report)]),
|
|
223
210
|
...row.after.map(afterWording),
|
|
224
211
|
...(row.dependents.length === 0 ? [] : [`dependents ${row.dependents.map(dependentWording).join(" · ")}`]),
|
|
225
212
|
...row.gates.reports.map(gateFact),
|
|
226
|
-
...(verificationFact(row.verification) === undefined ? [] : [verificationFact(row.verification)]),
|
|
227
213
|
];
|
|
228
214
|
const linkedFacts = [
|
|
229
215
|
...(row.holder.kind === "held" ? [linkedTask(report, row.holder.taskId)] : []),
|
|
@@ -158,7 +158,9 @@ export function quotedText(value) {
|
|
|
158
158
|
.join(""));
|
|
159
159
|
}
|
|
160
160
|
export function safeText(value) {
|
|
161
|
-
return value.replaceAll(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, (character) =>
|
|
161
|
+
return value.replaceAll(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, (character) =>
|
|
162
|
+
// ZWJ and ZWNJ are legitimate joiners that carry a grapheme cluster's shape; they move no cursor.
|
|
163
|
+
character === "\u200c" || character === "\u200d" ? character : /\s/u.test(character) ? " " : "�");
|
|
162
164
|
}
|
|
163
165
|
export function checkoutNotFollowableLines(refusal) {
|
|
164
166
|
const lines = [
|
|
@@ -5,4 +5,10 @@ export declare function writeExecutionProgress(events: AsyncIterable<ExecutionEv
|
|
|
5
5
|
export declare function writeTask(command: Extract<ParsedCommand, {
|
|
6
6
|
command: "task";
|
|
7
7
|
}>, result: TaskInvocationResult): Promise<number>;
|
|
8
|
+
type AkumaFailureProjection = Readonly<{
|
|
9
|
+
body: string;
|
|
10
|
+
exitCode: 1 | 3;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function akumaFailureProjection(error: unknown, command: ParsedCommand): Promise<AkumaFailureProjection | undefined>;
|
|
8
13
|
export declare function runCliCommand(invocation: ParsedExecution): Promise<number>;
|
|
14
|
+
export {};
|
package/build/src/cli/runtime.js
CHANGED
|
@@ -133,19 +133,37 @@ async function writeResult(command, result) {
|
|
|
133
133
|
writeCliStream(process.stdout, body);
|
|
134
134
|
return invocationExitCode(contractResult);
|
|
135
135
|
}
|
|
136
|
-
function
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
export async function akumaFailureProjection(error, command) {
|
|
137
|
+
const [{ AkumaNotBornError, AkumaObservationError }, { AkumaAddressError, AkumaWorldScopeError }] = await Promise.all([import("../akuma/akuma-errors.js"), import("../library/address.js")]);
|
|
138
|
+
if (error instanceof AkumaNotBornError) {
|
|
139
|
+
return {
|
|
140
|
+
body: command.output === "json" ? error.message : `× Akuma not found ${safeText(error.id)}`,
|
|
141
|
+
exitCode: 1,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (error instanceof AkumaAddressError) {
|
|
145
|
+
const body = error.refusal.kind === "akuma-alias-not-found"
|
|
146
|
+
? `× Akuma alias not found ${safeText(error.refusal.alias)}`
|
|
147
|
+
: `× invalid Akuma ${safeText(error.refusal.selector)}`;
|
|
148
|
+
return { body: command.output === "json" ? error.message : body, exitCode: 1 };
|
|
149
|
+
}
|
|
150
|
+
if (error instanceof AkumaWorldScopeError) {
|
|
151
|
+
return {
|
|
152
|
+
body: command.output === "json"
|
|
153
|
+
? JSON.stringify(error.refusal)
|
|
154
|
+
: error.refusal.ids.map((id) => `× Akuma not in this World ${safeText(id)}`).join("\n"),
|
|
155
|
+
exitCode: 1,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (error instanceof AkumaObservationError) {
|
|
159
|
+
return {
|
|
160
|
+
body: command.output === "json"
|
|
161
|
+
? error.message
|
|
162
|
+
: `× Akuma observation failed ${safeText(error.id)} — ${safeText(error.diagnostic)}`,
|
|
163
|
+
exitCode: 3,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
149
167
|
}
|
|
150
168
|
async function commandFailureText(error, command) {
|
|
151
169
|
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
@@ -189,10 +207,10 @@ export async function runCliCommand(invocation) {
|
|
|
189
207
|
: executionFailureLines(receipt, category, diagnostic, displayContext().columns).join("\n"));
|
|
190
208
|
return 3;
|
|
191
209
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
210
|
+
const akumaFailure = await akumaFailureProjection(error, command);
|
|
211
|
+
if (akumaFailure !== undefined) {
|
|
212
|
+
writeCliStream(process.stderr, akumaFailure.body);
|
|
213
|
+
return akumaFailure.exitCode;
|
|
196
214
|
}
|
|
197
215
|
if (command.command === "bind") {
|
|
198
216
|
const { BindDraftError } = await import("./draft.js");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContractId, JournalEntry, SnapshotId } from "./types.js";
|
|
2
2
|
export type ContractJournalAppend = Readonly<{
|
|
3
3
|
readonly contractId: ContractId;
|
|
4
|
-
readonly expectedHead: ContractHead | null;
|
|
5
4
|
readonly entries: readonly JournalEntry[];
|
|
6
5
|
}>;
|
|
7
6
|
/** A target operation is optional because claim can be journal-only. */
|
|
@@ -29,5 +29,5 @@ export function decideAbandon({ input, attempt, observation, }) {
|
|
|
29
29
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
30
30
|
data: input.note === undefined ? {} : { note: input.note },
|
|
31
31
|
};
|
|
32
|
-
return { kind: "offer", offer: { facts: [{ contractId: id,
|
|
32
|
+
return { kind: "offer", offer: { facts: [{ contractId: id, entries: [abandoned] }] } };
|
|
33
33
|
}
|
|
@@ -57,5 +57,5 @@ export function decideAmend({ input, attempt, observation, }) {
|
|
|
57
57
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
58
58
|
data,
|
|
59
59
|
};
|
|
60
|
-
return { kind: "offer", offer: { facts: [{ contractId: id,
|
|
60
|
+
return { kind: "offer", offer: { facts: [{ contractId: id, entries: [entry] }] } };
|
|
61
61
|
}
|
|
@@ -35,6 +35,6 @@ export function decideAttestation({ input, attempt, observation, }) {
|
|
|
35
35
|
};
|
|
36
36
|
return {
|
|
37
37
|
kind: "offer",
|
|
38
|
-
offer: { facts: [{ contractId: input.contractId,
|
|
38
|
+
offer: { facts: [{ contractId: input.contractId, entries: [attestation] }] },
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -38,5 +38,5 @@ export function decideBind({ input, attempt, observation, }) {
|
|
|
38
38
|
...(input.actor === undefined ? {} : { actor: input.actor }),
|
|
39
39
|
data,
|
|
40
40
|
};
|
|
41
|
-
return { kind: "offer", offer: { facts: [{ contractId: id,
|
|
41
|
+
return { kind: "offer", offer: { facts: [{ contractId: id, entries: [bind] }] } };
|
|
42
42
|
}
|
|
@@ -48,5 +48,5 @@ export function decideDeliver({ input, attempt, observation, }) {
|
|
|
48
48
|
data: input.preparation.data,
|
|
49
49
|
};
|
|
50
50
|
entries.push(deliver);
|
|
51
|
-
return { kind: "offer", offer: { facts: [{ contractId: input.contractId,
|
|
51
|
+
return { kind: "offer", offer: { facts: [{ contractId: input.contractId, entries }] } };
|
|
52
52
|
}
|
|
@@ -104,7 +104,7 @@ export function decidePlacement({ input, attempt, observation, }) {
|
|
|
104
104
|
return {
|
|
105
105
|
kind: "offer",
|
|
106
106
|
offer: {
|
|
107
|
-
facts: [{ contractId: id,
|
|
107
|
+
facts: [{ contractId: id, entries: [claimed] }],
|
|
108
108
|
...(current.coordinates.target === undefined
|
|
109
109
|
? {}
|
|
110
110
|
: {
|
|
@@ -19,6 +19,6 @@ export function decideReintegrate({ input, attempt, observation, }) {
|
|
|
19
19
|
};
|
|
20
20
|
return {
|
|
21
21
|
kind: "offer",
|
|
22
|
-
offer: { facts: [{ contractId: input.contractId,
|
|
22
|
+
offer: { facts: [{ contractId: input.contractId, entries: [reintegrated] }] },
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { decodeJournal, encodeEntry } from "../core/facts/codec.js";
|
|
2
|
+
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
|
+
import { foldJournal } from "../core/facts/fold.js";
|
|
2
4
|
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, updateGitTreeFromFrozenDirectories, updateRefsAtomically, writeBlob, writeStateCommit, } from "./repository.js";
|
|
3
5
|
import { contractJournalPath, contractJournalPaths, gitObjectIdForSnapshot, mintContractHead, mintSnapshotId, } from "./identity.js";
|
|
4
6
|
import { validPath } from "./tree.js";
|
|
@@ -57,6 +59,19 @@ async function buildOffer(repository, admission, appends, companions) {
|
|
|
57
59
|
const snapshot = admission.snapshot;
|
|
58
60
|
const changes = new Map();
|
|
59
61
|
const heads = {};
|
|
62
|
+
const candidates = appends.map((append) => {
|
|
63
|
+
const current = readCanonicalJournal(admission, append.contractId);
|
|
64
|
+
let journal = current.bytes;
|
|
65
|
+
for (const entry of append.entries)
|
|
66
|
+
journal = Buffer.concat([journal, Buffer.from(encodeEntry(entry))]);
|
|
67
|
+
const entries = decodeJournal(journal.toString("utf8"));
|
|
68
|
+
const foreign = entries.find((entry) => entry.contract !== append.contractId);
|
|
69
|
+
if (foreign !== undefined) {
|
|
70
|
+
throw new AuthorityCorruptionError(`candidate journal entry belongs to ${foreign.contract}, not ${append.contractId}`);
|
|
71
|
+
}
|
|
72
|
+
const state = foldJournal(append.contractId, entries);
|
|
73
|
+
return { append, current, journal, state };
|
|
74
|
+
});
|
|
60
75
|
if (snapshot.commit === null) {
|
|
61
76
|
changes.set(GIT_FORMAT_PATH, { oid: await writeBlob(repository, GIT_FORMAT_BYTES), mode: "100644", type: "blob" });
|
|
62
77
|
}
|
|
@@ -67,14 +82,10 @@ async function buildOffer(repository, admission, appends, companions) {
|
|
|
67
82
|
type: "blob",
|
|
68
83
|
});
|
|
69
84
|
}
|
|
70
|
-
for (const
|
|
71
|
-
const current =
|
|
72
|
-
let journal = current.bytes;
|
|
73
|
-
for (const entry of append.entries)
|
|
74
|
-
journal = Buffer.concat([journal, Buffer.from(encodeEntry(entry))]);
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const { append, current, journal, state } = candidate;
|
|
75
87
|
const blob = await writeBlob(repository, journal);
|
|
76
|
-
const
|
|
77
|
-
const terminal = entries.some((entry) => entry.kind === "claimed" || entry.kind === "abandoned");
|
|
88
|
+
const terminal = state.terminal !== null;
|
|
78
89
|
const destination = contractJournalPath(append.contractId, terminal ? "terminal" : "active");
|
|
79
90
|
if (current.path !== null && current.path !== destination)
|
|
80
91
|
changes.set(current.path, null);
|
|
@@ -2,7 +2,7 @@ function prefix(family) {
|
|
|
2
2
|
return `${family}/`;
|
|
3
3
|
}
|
|
4
4
|
function validSegment(segment) {
|
|
5
|
-
return segment.length > 0 && !/[\/\s\p{Cc}]/u.test(segment);
|
|
5
|
+
return segment.length > 0 && segment !== "." && segment !== ".." && !/[\/\s\p{Cc}]/u.test(segment);
|
|
6
6
|
}
|
|
7
7
|
export function identityCoordinate(input) {
|
|
8
8
|
if (input.segments.length === 0 || !input.segments.every(validSegment)) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare const MAX_AKUMA_NAME_BYTES = 64;
|
|
2
|
+
export declare function isCanonicalAkumaName(value: string): boolean;
|
|
3
|
+
export declare function validateAkumaName(value: string): string;
|
|
1
4
|
export declare function normalizeIdentityStem(input: Readonly<{
|
|
2
5
|
source: string;
|
|
3
6
|
}>): string;
|
|
@@ -5,6 +5,16 @@ const SUFFIX = /^[\p{Letter}\p{Number}]+$/u;
|
|
|
5
5
|
function graphemes(value) {
|
|
6
6
|
return [...GRAPHEMES.segment(value)].map(({ segment }) => segment);
|
|
7
7
|
}
|
|
8
|
+
export const MAX_AKUMA_NAME_BYTES = 64;
|
|
9
|
+
export function isCanonicalAkumaName(value) {
|
|
10
|
+
return value.length > 0 && normalizeIdentityStem({ source: value }) === value;
|
|
11
|
+
}
|
|
12
|
+
export function validateAkumaName(value) {
|
|
13
|
+
if (!isCanonicalAkumaName(value) || Buffer.byteLength(value, "utf8") > MAX_AKUMA_NAME_BYTES) {
|
|
14
|
+
throw new TypeError(`Akuma name must be one normalized human identity segment of at most ${MAX_AKUMA_NAME_BYTES} UTF-8 bytes`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
8
18
|
export function normalizeIdentityStem(input) {
|
|
9
19
|
const source = input.source.normalize("NFKC").toLowerCase().normalize("NFKC");
|
|
10
20
|
let result = "";
|
|
@@ -6,6 +6,7 @@ export type AkumaAlias = string & {
|
|
|
6
6
|
export type AkumaGlob = string & {
|
|
7
7
|
readonly [AKUMA_GLOB]: true;
|
|
8
8
|
};
|
|
9
|
+
export declare function parseReadableAkumaAlias(value: string): AkumaAlias;
|
|
9
10
|
export declare function parseAkumaAlias(value: string): AkumaAlias;
|
|
10
11
|
export declare function parseAkumaGlob(value: string): AkumaGlob;
|
|
11
12
|
export declare function matchesAkumaGlob(glob: AkumaGlob, value: string): boolean;
|