@astrosheep/keiyaku 4.5.27 → 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 -163
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +153 -78
- 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-handle.d.ts +1 -0
- package/build/src/akuma/akuma-handle.js +4 -1
- package/build/src/akuma/akuma-observe.d.ts +6 -0
- package/build/src/akuma/akuma-observe.js +3 -1
- package/build/src/akuma/akuma.d.ts +1 -0
- package/build/src/akuma/akuma.js +1 -0
- package/build/src/akuma/fleet-execution.d.ts +7 -1
- package/build/src/akuma/fleet-execution.js +12 -11
- package/build/src/akuma/fleet-observation.d.ts +3 -0
- package/build/src/akuma/identity.js +11 -9
- package/build/src/alias/index.js +3 -3
- package/build/src/cli/commands/akuma-invoke.js +13 -4
- package/build/src/cli/commands/akuma.js +9 -15
- package/build/src/cli/commands/contract-help.d.ts +1 -0
- package/build/src/cli/commands/contract-help.js +1 -0
- package/build/src/cli/invoke.js +1 -1
- package/build/src/cli/render/akuma-activity.js +6 -1
- package/build/src/cli/render/akuma.d.ts +9 -0
- package/build/src/cli/render/akuma.js +48 -8
- 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.js +2 -2
- 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 +2 -1
- package/build/src/library/fleet.d.ts +2 -1
- package/build/src/library/fleet.js +7 -3
- package/build/src/library/keiyaku.d.ts +2 -1
- package/build/src/protocol/completion.js +9 -2
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { identityCoordinate, identitySegments } from "../identity/coordinates.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isCanonicalAkumaName, validateAkumaName } from "../identity/normalize.js";
|
|
6
6
|
const HEX8 = /^[0-9a-f]{8}$/u;
|
|
7
7
|
export function parsePublicHistoryId(value) {
|
|
8
8
|
const match = /^turn\/([1-9][0-9]*)$/u.exec(value);
|
|
@@ -12,10 +12,7 @@ export function parsePublicHistoryId(value) {
|
|
|
12
12
|
return Number.isSafeInteger(sequence) ? sequence : null;
|
|
13
13
|
}
|
|
14
14
|
export function archetypeName(value) {
|
|
15
|
-
|
|
16
|
-
throw new TypeError("Akuma name must be one normalized human identity segment");
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
15
|
+
return validateAkumaName(value);
|
|
19
16
|
}
|
|
20
17
|
function suffixSegment(value) {
|
|
21
18
|
if (!HEX8.test(value))
|
|
@@ -32,15 +29,20 @@ export function parseAkuId(value) {
|
|
|
32
29
|
const segments = identitySegments({ family: "aku", value });
|
|
33
30
|
if (segments.length !== 2)
|
|
34
31
|
throw new TypeError("Akuma identity must be aku/<akuma>/<hex8>");
|
|
35
|
-
const archetype =
|
|
32
|
+
const archetype = segments[0];
|
|
33
|
+
// Existing identities remain addressable after new births gain a name limit.
|
|
34
|
+
if (!isCanonicalAkumaName(archetype))
|
|
35
|
+
throw new TypeError("Akuma name must be one normalized human identity segment");
|
|
36
36
|
const suffix = suffixSegment(segments[1]);
|
|
37
|
-
return { id:
|
|
37
|
+
return { id: value, archetype, suffix };
|
|
38
38
|
}
|
|
39
39
|
export function akumaRunRoot(worldRoot) {
|
|
40
40
|
return join(worldRoot, ".keiyaku", "akuma", "run");
|
|
41
41
|
}
|
|
42
42
|
export function akumaPaths(input) {
|
|
43
|
-
|
|
43
|
+
if (!isCanonicalAkumaName(input.archetype))
|
|
44
|
+
throw new TypeError("Akuma name must be one normalized human identity segment");
|
|
45
|
+
const directory = join(input.runRoot, `${input.archetype}-${suffixSegment(input.suffix)}`);
|
|
44
46
|
return {
|
|
45
47
|
directory,
|
|
46
48
|
heart: join(directory, "heart.db"),
|
|
@@ -54,7 +56,7 @@ export function akuIdFromDirectoryName(name) {
|
|
|
54
56
|
throw new Error(`invalid Akuma run directory ${name}`);
|
|
55
57
|
const archetype = name.slice(0, -9);
|
|
56
58
|
const suffix = name.slice(-8);
|
|
57
|
-
return
|
|
59
|
+
return parseAkuId(`aku/${archetype}/${suffix}`);
|
|
58
60
|
}
|
|
59
61
|
export async function ensureAkumaRunRoot(worldRoot) {
|
|
60
62
|
const runRoot = akumaRunRoot(worldRoot);
|
package/build/src/alias/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseAkuId } from "../akuma/identity.js";
|
|
|
4
4
|
import { replaceFileDurably } from "../coordination/durable-file.js";
|
|
5
5
|
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
6
6
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
7
|
-
import { parseAkumaAlias } from "../identity/selector.js";
|
|
7
|
+
import { parseAkumaAlias, parseReadableAkumaAlias } from "../identity/selector.js";
|
|
8
8
|
const VERSION = 1;
|
|
9
9
|
function paths(world) {
|
|
10
10
|
if (typeof world !== "string" || world.trim().length === 0) {
|
|
@@ -56,7 +56,7 @@ function decode(path, bytes) {
|
|
|
56
56
|
if (typeof rawAkuId !== "string")
|
|
57
57
|
corruption(`Alias target must be an AkuId: ${path}`);
|
|
58
58
|
try {
|
|
59
|
-
bindings.push({ alias:
|
|
59
|
+
bindings.push({ alias: parseReadableAkumaAlias(rawAlias), akuId: parseAkuId(rawAkuId).id });
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
62
|
return corruption(`Alias binding is invalid: ${path}`, error);
|
|
@@ -114,7 +114,7 @@ export async function readAliases(world) {
|
|
|
114
114
|
return await read(paths(world).authority);
|
|
115
115
|
}
|
|
116
116
|
export async function resolveAlias(world, value) {
|
|
117
|
-
const alias =
|
|
117
|
+
const alias = parseReadableAkumaAlias(value);
|
|
118
118
|
return (await readAliases(world)).find((binding) => binding.alias === alias)?.akuId ?? null;
|
|
119
119
|
}
|
|
120
120
|
export async function moveAlias(input) {
|
|
@@ -4,7 +4,7 @@ import { emitInitiatingPluginSignal } from "../../plugin/akuma-signals.js";
|
|
|
4
4
|
import { observeAkumaStatus } from "../../akuma/akuma-observe.js";
|
|
5
5
|
import { AuthorityCorruptionError, Keiyaku, } from "../../index.js";
|
|
6
6
|
import { callObservationStream, waitObservationStream } from "../render/akuma-activity.js";
|
|
7
|
-
import { callObservationHead,
|
|
7
|
+
import { callObservationHead, tellWaitProgressStream } from "../render/akuma.js";
|
|
8
8
|
import { killAkuma, tellAkuma, tellWaitAkuma, waitAkuma } from "../../library/fleet.js";
|
|
9
9
|
import { localExecutionContext } from "../../akuma/requests.js";
|
|
10
10
|
import { Akuma, Schema } from "../../akuma/index.js";
|
|
@@ -188,6 +188,8 @@ function decodeWaitedTellSchema(result, schema) {
|
|
|
188
188
|
}
|
|
189
189
|
async function invokeWaitedTell(command, input, body) {
|
|
190
190
|
const schema = command.schema === undefined ? undefined : await schemaFromFile(command.schema);
|
|
191
|
+
const alias = inputAlias(command.akuma);
|
|
192
|
+
const progress = command.output === "text" ? tellWaitProgressStream(undefined, alias, resultContext()) : undefined;
|
|
191
193
|
const result = await tellWaitAkuma({
|
|
192
194
|
...(await inputInitiator(input)),
|
|
193
195
|
path: input.path,
|
|
@@ -198,11 +200,18 @@ async function invokeWaitedTell(command, input, body) {
|
|
|
198
200
|
...(command.interrupt ? { interrupt: true } : {}),
|
|
199
201
|
...(input.repo === undefined ? {} : { repo: input.repo }),
|
|
200
202
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
203
|
+
...(progress === undefined
|
|
204
|
+
? {}
|
|
205
|
+
: {
|
|
206
|
+
observe: {
|
|
207
|
+
admitted: (tell, id) => writeProgress(progress.admitted(tell, id).join("\n")),
|
|
208
|
+
observe: (observation) => writeProgress(progress.observe(observation).join("\n")),
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
201
211
|
}, input.execution ?? localExecutionContext());
|
|
202
212
|
const rendered = schema === undefined ? result : decodeWaitedTellSchema(result, schema);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
writeProgress(waitedTellProgress(rendered, alias, resultContext()));
|
|
213
|
+
if (progress !== undefined)
|
|
214
|
+
writeProgress(progress.conclude(rendered).join("\n"));
|
|
206
215
|
return {
|
|
207
216
|
kind: "akuma",
|
|
208
217
|
action: "tell",
|
|
@@ -11,20 +11,19 @@ const AKUMA_COMMAND_SPECS = {
|
|
|
11
11
|
contract: "value",
|
|
12
12
|
alias: "value",
|
|
13
13
|
wait: "value",
|
|
14
|
-
detach: "boolean",
|
|
15
14
|
allowed: "repeatable",
|
|
16
15
|
json: "boolean",
|
|
17
16
|
schema: "value",
|
|
18
17
|
},
|
|
19
|
-
usage: "call <akuma-name> [--contract <kei/...>] [--workdir <path>] [--alias
|
|
18
|
+
usage: "call <akuma-name> [--contract <kei/...>] [--workdir <path>] [--alias <name>] [--allowed <product.action>]... [--schema <file>] [--wait <duration>] (<prompt> | -)",
|
|
20
19
|
purpose: "Birth an Akuma from <akuma-name> with one prompt.",
|
|
21
20
|
details: [
|
|
22
21
|
"Give <prompt> as one argument, or use - to read stdin.",
|
|
23
|
-
"Default:
|
|
24
|
-
"--contract
|
|
22
|
+
"Default: return after birth without waiting for the first answer. Explicit --wait observes the first work for that duration.",
|
|
23
|
+
"--contract associates the born Akuma with that Contract for Dispatch; it never selects a workdir.",
|
|
25
24
|
"--workdir selects the execution directory; a relative path is relative to the invocation cwd.",
|
|
26
|
-
"Without --workdir,
|
|
27
|
-
"--alias assigns the world-local @name selector to the born Akuma.",
|
|
25
|
+
"Without --workdir, the call uses the invocation cwd whether or not --contract is present.",
|
|
26
|
+
"--alias <name> assigns the world-local @name selector to the born Akuma; @name is also accepted.",
|
|
28
27
|
`Legal actions: ${ALLOWED_ACTIONS.join(", ")}.`,
|
|
29
28
|
`Default actions: ${DEFAULT_ALLOWED_ACTIONS.join(", ")}.`,
|
|
30
29
|
"Repeated --allowed adds actions to the selected Akuma's defaults; it never narrows them.",
|
|
@@ -156,11 +155,7 @@ function scanAkuma(action, argv, fail) {
|
|
|
156
155
|
stdin = true;
|
|
157
156
|
}
|
|
158
157
|
else if (token === "-d") {
|
|
159
|
-
|
|
160
|
-
fail(`option ${token} is not valid for ${action}`);
|
|
161
|
-
if (flags.detach !== undefined)
|
|
162
|
-
fail("duplicate option: --detach");
|
|
163
|
-
flags.detach = true;
|
|
158
|
+
fail(`option ${token} is not valid for ${action}`);
|
|
164
159
|
}
|
|
165
160
|
else if (!token.startsWith("--")) {
|
|
166
161
|
if (isBlankInput(token))
|
|
@@ -303,15 +298,14 @@ function parseCall(flags, archetype, prompt, output, fail) {
|
|
|
303
298
|
let alias;
|
|
304
299
|
if (flags.alias !== undefined) {
|
|
305
300
|
try {
|
|
306
|
-
|
|
301
|
+
const name = stringFlag(flags.alias, "--alias requires a name", fail);
|
|
302
|
+
alias = parseAkumaAlias(name.startsWith("@") ? name : `@${name}`);
|
|
307
303
|
}
|
|
308
304
|
catch (error) {
|
|
309
305
|
fail(error instanceof Error ? error.message : "invalid Akuma alias");
|
|
310
306
|
}
|
|
311
307
|
}
|
|
312
|
-
|
|
313
|
-
fail("call --wait and --detach are mutually exclusive");
|
|
314
|
-
const mode = flags.detach === true ? "detach" : "wait";
|
|
308
|
+
const mode = flags.wait === undefined ? "detach" : "wait";
|
|
315
309
|
const timeoutMs = flags.wait === undefined ? undefined : parseDuration(flags.wait, "--wait", fail);
|
|
316
310
|
const allowed = parseAllowedFlag(flags.allowed, fail);
|
|
317
311
|
const schema = flags.schema === undefined ? undefined : stringFlag(flags.schema, "call --schema requires a file", fail);
|
|
@@ -94,6 +94,7 @@ export declare const CONTRACT_COMMAND_SPECS: {
|
|
|
94
94
|
};
|
|
95
95
|
readonly usage: "status [<contract>|@name|<aku/...>]...";
|
|
96
96
|
readonly purpose: "Read the world status board or one or more Contract and Akuma projections.";
|
|
97
|
+
readonly details: "An Akuma projection reports that Akuma's frozen execution workdir; --contract associates a Contract without selecting one.";
|
|
97
98
|
};
|
|
98
99
|
readonly show: {
|
|
99
100
|
readonly positional: "optional";
|
|
@@ -166,6 +166,7 @@ export const CONTRACT_COMMAND_SPECS = {
|
|
|
166
166
|
flags: { json: "boolean" },
|
|
167
167
|
usage: "status [<contract>|@name|<aku/...>]...",
|
|
168
168
|
purpose: "Read the world status board or one or more Contract and Akuma projections.",
|
|
169
|
+
details: "An Akuma projection reports that Akuma's frozen execution workdir; --contract associates a Contract without selecting one.",
|
|
169
170
|
},
|
|
170
171
|
show: {
|
|
171
172
|
positional: "optional",
|
package/build/src/cli/invoke.js
CHANGED
|
@@ -348,7 +348,7 @@ async function callExecutionCwd(input) {
|
|
|
348
348
|
if (input.workdir !== undefined) {
|
|
349
349
|
return await canonicalBirthCwd(resolve(input.cwd, input.workdir), `workdir is not an existing directory: ${input.workdir}`);
|
|
350
350
|
}
|
|
351
|
-
return input.
|
|
351
|
+
return input.cwd;
|
|
352
352
|
}
|
|
353
353
|
async function invokeParsedAkuma(input) {
|
|
354
354
|
const executionCwd = await callExecutionCwd({
|
|
@@ -872,6 +872,7 @@ function answeredBlock(observation, answer, alias, columns) {
|
|
|
872
872
|
function snapshotCore(view, context, options) {
|
|
873
873
|
const activity = snapshotActivityLines(view.status.timeline, context);
|
|
874
874
|
const facts = [
|
|
875
|
+
...(view.status.cwd === undefined ? [] : [`cwd ${safeText(view.status.cwd)}`]),
|
|
875
876
|
...(options.showAllowed === true ? [`allowed ${view.status.allowed.join(", ") || "none"}`] : []),
|
|
876
877
|
...(view.status.readonly?.enforcement === "none" ? [`! ${safeText(view.status.readonly.diagnostic)}`] : []),
|
|
877
878
|
...contractFacts(view.contract),
|
|
@@ -1050,7 +1051,11 @@ export function historyText(command, result, context) {
|
|
|
1050
1051
|
export function tellText(result, context) {
|
|
1051
1052
|
const wake = result.result.tell.wake;
|
|
1052
1053
|
const target = identity(result.result.akuma, result.alias);
|
|
1053
|
-
const row = groupedRows([result.result.tell.row], context
|
|
1054
|
+
const row = groupedRows([result.result.tell.row], context, false, {
|
|
1055
|
+
...plainLayout(),
|
|
1056
|
+
head: (time, _glyph, _verb, columns) => eventPrefix(wake.kind === "held" ? "⧗" : "⧖", "tell", time, columns),
|
|
1057
|
+
singleLine: true,
|
|
1058
|
+
}).join("\n");
|
|
1054
1059
|
if (wake.kind === "failed") {
|
|
1055
1060
|
const child = "child" in wake ? wake.child : undefined;
|
|
1056
1061
|
const failure = `! tell delivery failed · ${safeText(wake.diagnostic)}${child === undefined ? "" : ` · log ${child.log.path} ${child.log.from}..${child.log.to}`}`;
|
|
@@ -2,7 +2,16 @@ import type { AkumaInvocationResult } from "../commands/akuma-invoke.js";
|
|
|
2
2
|
import type { ParsedCommand } from "../parse.js";
|
|
3
3
|
import { type ObservedCallHead } from "./akuma-activity.js";
|
|
4
4
|
import type { AkumaTellWaitResult } from "../../akuma/fleet-observation.js";
|
|
5
|
+
import type { AkuId } from "../../akuma/identity.js";
|
|
6
|
+
import type { LiveStatusObservation } from "../../akuma/akuma-observe.js";
|
|
7
|
+
import type { TellResult } from "../../akuma/akuma.js";
|
|
5
8
|
import { type TextRenderContext } from "./terminal.js";
|
|
9
|
+
export type TellWaitProgress = Readonly<{
|
|
10
|
+
admitted: (tell: TellResult, id: AkuId) => readonly string[];
|
|
11
|
+
observe: (observation: LiveStatusObservation) => readonly string[];
|
|
12
|
+
conclude: (result: AkumaTellWaitResult) => readonly string[];
|
|
13
|
+
}>;
|
|
14
|
+
export declare function tellWaitProgressStream(akuma: AkuId | undefined, alias: string | undefined, context: TextRenderContext): TellWaitProgress;
|
|
6
15
|
export declare function waitedTellProgress(result: AkumaTellWaitResult, alias: string | undefined, context: TextRenderContext): string;
|
|
7
16
|
export declare function akumaRawAnswer(result: AkumaInvocationResult): string | undefined;
|
|
8
17
|
/**
|
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
import { parseAkumaStatus } from "../../akuma/akuma.js";
|
|
2
|
-
import { DEFAULT_CONTEXT, akumaRawAnswer as akumaActivityRawAnswer, 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
|
+
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
|
+
}
|
|
4
50
|
export function waitedTellProgress(result, alias, context) {
|
|
5
51
|
const receipt = tellText({
|
|
6
52
|
kind: "akuma",
|
|
@@ -10,13 +56,7 @@ export function waitedTellProgress(result, alias, context) {
|
|
|
10
56
|
body: "",
|
|
11
57
|
...(alias === undefined ? {} : { alias }),
|
|
12
58
|
}, context);
|
|
13
|
-
const conclusion = result
|
|
14
|
-
? "✓ answered"
|
|
15
|
-
: result.observation.reason === "failed"
|
|
16
|
-
? `! failed · ${safeText(result.observation.diagnostic)}`
|
|
17
|
-
: result.observation.reason === "unanswered"
|
|
18
|
-
? "○ unanswered"
|
|
19
|
-
: "⧖ deadline";
|
|
59
|
+
const conclusion = tellWaitConclusion(result);
|
|
20
60
|
return `${receipt}\n${conclusion}`;
|
|
21
61
|
}
|
|
22
62
|
function waitedTellRawAnswer(result) {
|
|
@@ -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;
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import { MAX_AKUMA_NAME_BYTES, normalizeIdentityStem, validateAkumaName } from "./normalize.js";
|
|
2
2
|
const AKUMA_GLOB_SUFFIX = /^[0-9a-f*]+$/u;
|
|
3
|
+
const LEGACY_AKUMA_ALIAS_PATTERN = /^@[a-z][a-z0-9-]{0,63}$/u;
|
|
4
|
+
// Old alias spellings remain readable, but new bindings use canonical Akuma names.
|
|
5
|
+
export function parseReadableAkumaAlias(value) {
|
|
6
|
+
if (LEGACY_AKUMA_ALIAS_PATTERN.test(value))
|
|
7
|
+
return value;
|
|
8
|
+
return parseAkumaAlias(value);
|
|
9
|
+
}
|
|
3
10
|
export function parseAkumaAlias(value) {
|
|
4
|
-
if (!
|
|
5
|
-
throw new TypeError("Akuma alias must
|
|
11
|
+
if (!value.startsWith("@"))
|
|
12
|
+
throw new TypeError("Akuma alias selector must start with @");
|
|
13
|
+
try {
|
|
14
|
+
validateAkumaName(value.slice(1));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new TypeError(`Akuma alias name must be a normalized Akuma name of at most ${MAX_AKUMA_NAME_BYTES} UTF-8 bytes`);
|
|
6
18
|
}
|
|
7
19
|
return value;
|
|
8
20
|
}
|
|
@@ -26,4 +38,3 @@ export function matchesAkumaGlob(glob, value) {
|
|
|
26
38
|
.replaceAll("*", "[^/]*");
|
|
27
39
|
return new RegExp(`^${pattern}$`, "u").test(value);
|
|
28
40
|
}
|
|
29
|
-
import { normalizeIdentityStem } from "./normalize.js";
|
|
@@ -5,7 +5,7 @@ import { akumaAddressability, requireBornAkuma } from "../akuma/akuma-probe.js";
|
|
|
5
5
|
import { parseAkuId } from "../akuma/identity.js";
|
|
6
6
|
import { contractId } from "../core/facts/types.js";
|
|
7
7
|
import { readDispatches } from "../dispatch/index.js";
|
|
8
|
-
import { matchesAkumaGlob,
|
|
8
|
+
import { matchesAkumaGlob, parseReadableAkumaAlias, parseAkumaGlob, } from "../identity/selector.js";
|
|
9
9
|
import { World } from "../world.js";
|
|
10
10
|
import { requireInput } from "./input.js";
|
|
11
11
|
import { scopeForRepo } from "./repo.js";
|
|
@@ -34,7 +34,7 @@ function nonblank(value, label) {
|
|
|
34
34
|
}
|
|
35
35
|
function parseAddressAlias(selector) {
|
|
36
36
|
try {
|
|
37
|
-
return
|
|
37
|
+
return parseReadableAkumaAlias(selector);
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
40
|
if (!(error instanceof TypeError))
|
|
@@ -73,7 +73,7 @@ export type CallResult = Readonly<{
|
|
|
73
73
|
readonly?: ReadonlyRestraint;
|
|
74
74
|
execution: Readonly<{
|
|
75
75
|
cwd: string;
|
|
76
|
-
source: "input" | "
|
|
76
|
+
source: "input" | "caller" | "process" | "world";
|
|
77
77
|
}>;
|
|
78
78
|
dispatch: DispatchStage;
|
|
79
79
|
alias: AliasStage;
|
|
@@ -9,7 +9,6 @@ import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
|
9
9
|
import { publishDispatch, readDispatch } from "../dispatch/index.js";
|
|
10
10
|
import { parseAkumaAlias } from "../identity/selector.js";
|
|
11
11
|
import { emitCalledPluginSignal } from "../plugin/akuma-signals.js";
|
|
12
|
-
import { readManagedWorktreeAppointment } from "../workspace-place.js";
|
|
13
12
|
import { World } from "../world.js";
|
|
14
13
|
import { Akuma as PublicAkuma } from "../akuma/akuma-instance.js";
|
|
15
14
|
import { requestForwardedFleetTellAnswer } from "../akuma/fleet-request.js";
|
|
@@ -17,7 +16,6 @@ import { localExecutionContext } from "../akuma/requests.js";
|
|
|
17
16
|
import { callReadonly, canonicalBirthCwd } from "../akuma/call-input.js";
|
|
18
17
|
import { requireInput } from "./input.js";
|
|
19
18
|
import { addressAkuma } from "./address.js";
|
|
20
|
-
import { KeiyakuRefused } from "./contract.js";
|
|
21
19
|
import { seatForKeiyaku } from "./contract-handle.js";
|
|
22
20
|
import { scopeForRepo } from "./repo.js";
|
|
23
21
|
function nonblank(value, label) {
|
|
@@ -67,10 +65,10 @@ function integrationFailure(error) {
|
|
|
67
65
|
};
|
|
68
66
|
}
|
|
69
67
|
function callMode(value) {
|
|
70
|
-
if (value === undefined || value === "
|
|
71
|
-
return "wait";
|
|
72
|
-
if (value === "detach")
|
|
68
|
+
if (value === undefined || value === "detach")
|
|
73
69
|
return "detach";
|
|
70
|
+
if (value === "wait")
|
|
71
|
+
return "wait";
|
|
74
72
|
throw new TypeError("mode must be wait or detach");
|
|
75
73
|
}
|
|
76
74
|
function callTimeout(value, mode) {
|
|
@@ -196,49 +194,13 @@ async function forkDispatchStage(input) {
|
|
|
196
194
|
return { kind: "failed", failure: integrationFailure(error) };
|
|
197
195
|
}
|
|
198
196
|
}
|
|
199
|
-
function unavailableWorkspace(contractId, detail) {
|
|
200
|
-
return new Error(`Contract workspace is unavailable: ${contractId} ${detail}`);
|
|
201
|
-
}
|
|
202
|
-
async function currentManagedContract(contract, contractId) {
|
|
203
|
-
let state;
|
|
204
|
-
try {
|
|
205
|
-
state = await contract.state();
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
if (error instanceof Error && error.message === `contract does not exist: ${contractId}`) {
|
|
209
|
-
throw new KeiyakuRefused({ kind: "contract-missing", contractId });
|
|
210
|
-
}
|
|
211
|
-
throw error;
|
|
212
|
-
}
|
|
213
|
-
if (state.terminal !== null)
|
|
214
|
-
throw new KeiyakuRefused({ kind: "terminal", contractId: state.id });
|
|
215
|
-
return state;
|
|
216
|
-
}
|
|
217
197
|
async function resolveCallExecution(input) {
|
|
218
|
-
if (input.cwd
|
|
219
|
-
return
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
if (input.contract !== undefined) {
|
|
225
|
-
const seat = seatForKeiyaku(input.contract);
|
|
226
|
-
if (seat === null)
|
|
227
|
-
throw new TypeError("contract must be a Keiyaku");
|
|
228
|
-
const state = await currentManagedContract(input.contract, seat.id);
|
|
229
|
-
const appointment = await readManagedWorktreeAppointment(seat.scope, state.id);
|
|
230
|
-
if (appointment.kind === "unappointed") {
|
|
231
|
-
throw unavailableWorkspace(state.id, "is unappointed; use reconcile");
|
|
232
|
-
}
|
|
233
|
-
if (appointment.kind === "failed") {
|
|
234
|
-
throw new Error(`${appointment.diagnostic}; use reconcile`);
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
cwd: await canonicalBirthCwd(appointment.path, `Contract workspace is unavailable: ${appointment.path}`),
|
|
238
|
-
source: "contract-worktree",
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
return undefined;
|
|
198
|
+
if (input.cwd === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
return {
|
|
201
|
+
cwd: await canonicalBirthCwd(input.cwd),
|
|
202
|
+
source: "input",
|
|
203
|
+
};
|
|
242
204
|
}
|
|
243
205
|
async function resolveAliasStage(path, alias, dispatch, akuma) {
|
|
244
206
|
if (alias === undefined)
|
|
@@ -326,11 +288,7 @@ function schemaTell(input) {
|
|
|
326
288
|
}
|
|
327
289
|
async function prepareCall(input, context) {
|
|
328
290
|
const parsed = await parseCallInput(input);
|
|
329
|
-
const execution = await resolveCallExecution({
|
|
330
|
-
path: parsed.path,
|
|
331
|
-
...(parsed.cwd === undefined ? {} : { cwd: parsed.cwd }),
|
|
332
|
-
...(parsed.seat === undefined ? {} : { contract: parsed.values.contract }),
|
|
333
|
-
});
|
|
291
|
+
const execution = await resolveCallExecution(parsed.cwd === undefined ? {} : { cwd: parsed.cwd });
|
|
334
292
|
const world = akumaWorld(parsed.path, parsed.home, parsed.settings, context);
|
|
335
293
|
const { born, akuma } = await admitCall({
|
|
336
294
|
path: parsed.path,
|
|
@@ -16,7 +16,7 @@ export declare function composeLibrary(context?: ExecutionContext, composition?:
|
|
|
16
16
|
readonly?: import("../akuma/provider-recipe.js").ReadonlyRestraint;
|
|
17
17
|
execution: Readonly<{
|
|
18
18
|
cwd: string;
|
|
19
|
-
source: "input" | "
|
|
19
|
+
source: "input" | "caller" | "process" | "world";
|
|
20
20
|
}>;
|
|
21
21
|
dispatch: import("./akuma-creation.js").DispatchStage;
|
|
22
22
|
alias: import("./akuma-creation.js").AliasStage;
|
|
@@ -357,6 +357,7 @@ export declare function composeLibrary(context?: ExecutionContext, composition?:
|
|
|
357
357
|
};
|
|
358
358
|
} | undefined;
|
|
359
359
|
};
|
|
360
|
+
cwd?: string | undefined;
|
|
360
361
|
readonly?: {
|
|
361
362
|
enforcement: "native";
|
|
362
363
|
} | {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @architectureCompositionRoot */
|
|
2
2
|
import { type ActivityHistory, type OutcomeRow, type AkumaStatus, type InterruptReceipt } from "../akuma/akuma.js";
|
|
3
3
|
import { type ExecutionContext } from "../akuma/requests.js";
|
|
4
|
-
import type { WaitObserver } from "../akuma/fleet-execution.js";
|
|
4
|
+
import type { TellWaitObserver, WaitObserver } from "../akuma/fleet-execution.js";
|
|
5
5
|
import { type DispatchAssociation } from "../dispatch/index.js";
|
|
6
6
|
import { type Schema } from "../akuma/schema.js";
|
|
7
7
|
import { type AkumaAddressInput, type AkumaSetAddressInput } from "./address.js";
|
|
@@ -23,6 +23,7 @@ export type AkumaTellWaitInput = AkumaAddressInput & Readonly<{
|
|
|
23
23
|
interrupt?: boolean;
|
|
24
24
|
initiator?: string;
|
|
25
25
|
signal?: AbortSignal;
|
|
26
|
+
observe?: TellWaitObserver;
|
|
26
27
|
}>;
|
|
27
28
|
export type { TellResult, TellWake } from "../akuma/akuma.js";
|
|
28
29
|
export type { CreatedTaskObservation } from "../task/created-observation.js";
|
|
@@ -285,10 +285,9 @@ export async function tellAkuma(input, execution = localExecutionContext()) {
|
|
|
285
285
|
...(callerSignal === undefined ? {} : { signal: callerSignal }),
|
|
286
286
|
});
|
|
287
287
|
}
|
|
288
|
-
|
|
289
|
-
const values = requireInput(input, "Keiyaku tell wait input");
|
|
288
|
+
function validateTellWaitInput(values) {
|
|
290
289
|
for (const key of Object.keys(values)) {
|
|
291
|
-
if (!["path", "akuma", "body", "repo", "timeoutMs", "schema", "interrupt", "initiator", "signal"].includes(key)) {
|
|
290
|
+
if (!["path", "akuma", "body", "repo", "timeoutMs", "schema", "interrupt", "initiator", "signal", "observe"].includes(key)) {
|
|
292
291
|
throw new TypeError(`Keiyaku tell wait input has unknown field: ${key}`);
|
|
293
292
|
}
|
|
294
293
|
}
|
|
@@ -302,6 +301,10 @@ export async function tellWaitAkuma(input, execution = localExecutionContext())
|
|
|
302
301
|
}
|
|
303
302
|
if (values.interrupt !== undefined && typeof values.interrupt !== "boolean")
|
|
304
303
|
throw new TypeError("interrupt must be a boolean");
|
|
304
|
+
}
|
|
305
|
+
export async function tellWaitAkuma(input, execution = localExecutionContext()) {
|
|
306
|
+
const values = requireInput(input, "Keiyaku tell wait input");
|
|
307
|
+
validateTellWaitInput(values);
|
|
305
308
|
const callerSignal = signal(values.signal);
|
|
306
309
|
const channel = executionChannel(execution);
|
|
307
310
|
if (channel.kind === "body-request") {
|
|
@@ -328,6 +331,7 @@ export async function tellWaitAkuma(input, execution = localExecutionContext())
|
|
|
328
331
|
...(input.schema === undefined ? {} : { schemaJson: schemaJsonText(input.schema) }),
|
|
329
332
|
...(input.interrupt === true ? { interrupt: true } : {}),
|
|
330
333
|
...(input.initiator === undefined ? {} : { initiator: input.initiator }),
|
|
334
|
+
...(input.observe === undefined ? {} : { onObserve: input.observe }),
|
|
331
335
|
...(callerSignal === undefined ? {} : { signal: callerSignal }),
|
|
332
336
|
});
|
|
333
337
|
}
|
|
@@ -48,7 +48,7 @@ export declare const Keiyaku: Readonly<{
|
|
|
48
48
|
readonly?: import("../akuma/provider-recipe.js").ReadonlyRestraint;
|
|
49
49
|
execution: Readonly<{
|
|
50
50
|
cwd: string;
|
|
51
|
-
source: "input" | "
|
|
51
|
+
source: "input" | "caller" | "process" | "world";
|
|
52
52
|
}>;
|
|
53
53
|
dispatch: import("./akuma-creation.js").DispatchStage;
|
|
54
54
|
alias: import("./akuma-creation.js").AliasStage;
|
|
@@ -389,6 +389,7 @@ export declare const Keiyaku: Readonly<{
|
|
|
389
389
|
};
|
|
390
390
|
} | undefined;
|
|
391
391
|
};
|
|
392
|
+
cwd?: string | undefined;
|
|
392
393
|
readonly?: {
|
|
393
394
|
enforcement: "native";
|
|
394
395
|
} | {
|
|
@@ -3,6 +3,7 @@ import { admitPlacement, observeTargetPlacement } from "./placement.js";
|
|
|
3
3
|
import { reintegrateOperation } from "./reintegrate.js";
|
|
4
4
|
import { contractCheckpoint, executionStop, } from "./progress.js";
|
|
5
5
|
import { placementStop, timestamp, unpackVerificationOutcome } from "./operations.js";
|
|
6
|
+
import { VERIFIED } from "../verification/declaration.js";
|
|
6
7
|
const MAX_REINTEGRATION_CYCLES = 3;
|
|
7
8
|
function reintegrationStop(result) {
|
|
8
9
|
if (result.kind === "placement-failed")
|
|
@@ -154,15 +155,21 @@ async function placeCurrentCandidate(input, cursor) {
|
|
|
154
155
|
}
|
|
155
156
|
return result;
|
|
156
157
|
}
|
|
158
|
+
/** Verification blocks completion only when the Contract selected the verified gate; placement owns gate eligibility. */
|
|
159
|
+
function verificationBlocks(state) {
|
|
160
|
+
return state.terms.gates.includes(VERIFIED);
|
|
161
|
+
}
|
|
157
162
|
async function verifyCandidateReadiness(input, cursor) {
|
|
158
163
|
const observedPlacement = await observeCandidateTarget(input, cursor);
|
|
159
164
|
await verifyCurrentCandidate(input, cursor);
|
|
160
|
-
|
|
165
|
+
const verification = cursor.evidence.verification;
|
|
166
|
+
const blocking = verification !== undefined && verificationBlocks(cursor.checkpoint.state);
|
|
167
|
+
if (blocking || observedPlacement !== undefined) {
|
|
161
168
|
return {
|
|
162
169
|
kind: "stopped",
|
|
163
170
|
checkpoint: cursor.checkpoint,
|
|
164
171
|
evidence: { ...cursor.evidence, ...(observedPlacement === undefined ? {} : { placement: observedPlacement }) },
|
|
165
|
-
stop:
|
|
172
|
+
stop: blocking ? verification : observedPlacement,
|
|
166
173
|
};
|
|
167
174
|
}
|
|
168
175
|
return undefined;
|