@astrosheep/keiyaku 2.9.7 → 2.9.8
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/.tsbuildinfo +1 -1
- package/build/agents/harness/event-persistence.js +7 -5
- package/build/agents/harness/events.js +3 -2
- package/build/agents/providers/codex-app-server/adapter.js +6 -1
- package/build/agents/providers/codex-app-server/session.js +8 -7
- package/build/agents/selector.js +12 -1
- package/build/cli/commands/akuma/view/handler.js +3 -11
- package/build/cli/commands/contract/amend/handler.js +1 -1
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/projection/status/handler.js +5 -4
- package/build/cli/commands/projection/status/meta.js +2 -2
- package/build/cli/commands/task/add/meta.js +9 -1
- package/build/cli/commands/task/shared.js +2 -1
- package/build/cli/completion.js +8 -0
- package/build/cli/render/kanshi.js +2 -2
- package/build/cli/render/path-prefix-compaction.js +119 -76
- package/build/cli/render/projection-activity.js +10 -1
- package/build/cli/render/shared.js +8 -7
- package/build/cli/render/status.js +8 -5
- package/build/cli/render/wait.js +1 -1
- package/build/config/settings/disease.js +4 -4
- package/build/config/settings/loader.js +44 -21
- package/build/core/addressing.js +40 -9
- package/build/core/amend.js +21 -5
- package/build/core/call/context.js +19 -3
- package/build/core/call/execution.js +43 -20
- package/build/core/ledger-batch.js +194 -0
- package/build/core/projection/generation/database.js +22 -0
- package/build/core/projection/generation/projection-generation-execution.js +45 -37
- package/build/core/projection/generation/projection-generation-launcher.js +148 -20
- package/build/core/projection/generation/projection-generation-process.js +3 -1
- package/build/core/projection/generation/projection-generation-runner.js +76 -40
- package/build/core/projection/generation/projection-generation-runtime.js +82 -19
- package/build/core/projection/generation/store.js +17 -1
- package/build/core/projection/generation/transitions.js +89 -12
- package/build/core/projection/index.js +3 -3
- package/build/core/projection/projection-kill.js +22 -10
- package/build/core/projection/projection-runner-lock.js +177 -37
- package/build/core/projection/projection-status.js +143 -55
- package/build/core/projection/projection-wake.js +171 -72
- package/build/core/status/board.js +42 -4
- package/build/core/status/drift.js +21 -5
- package/build/core/status/ledger-batch.js +1 -158
- package/build/core/task/task-git-runtime.js +8 -10
- package/build/core/task/task-git-store.js +5 -3
- package/build/core/worktree-path.js +39 -25
- package/build/flow-error.js +1 -1
- package/build/generated/version.js +2 -2
- package/build/git/refs.js +47 -1
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +18 -0
- package/skills/keiyaku-workflow/SKILL.md +68 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGENT_ACTIVITY_MAX_CHANGES, AGENT_EVENT_TEXT_MAX_CHARS, AGENT_UNKNOWN_PAYLOAD_MAX_CHARS, } from "./events.js";
|
|
1
|
+
import { AGENT_ACTIVITY_MAX_CHANGES, AGENT_DONE_FINAL_MESSAGE_PERSISTENCE_PREVIEW_MAX_CHARS, AGENT_EVENT_TEXT_MAX_CHARS, AGENT_UNKNOWN_PAYLOAD_MAX_CHARS, } from "./events.js";
|
|
2
2
|
import { EXEC_EVENT_BUFFER_HARD_CAP } from "./event-channel.js";
|
|
3
3
|
export function createTruncationMarker(envelope = {
|
|
4
4
|
seq: EXEC_EVENT_BUFFER_HARD_CAP,
|
|
@@ -23,10 +23,10 @@ export function overflowMarkerFromDropped(dropped) {
|
|
|
23
23
|
turn: dropped.turn,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
function truncateText(value) {
|
|
26
|
+
function truncateText(value, limit = AGENT_EVENT_TEXT_MAX_CHARS) {
|
|
27
27
|
return {
|
|
28
|
-
value: value.slice(0,
|
|
29
|
-
truncated: value.length >
|
|
28
|
+
value: value.slice(0, limit),
|
|
29
|
+
truncated: value.length > limit,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
/** Bound unknown native payloads before they enter the projection event stream. */
|
|
@@ -186,7 +186,9 @@ function normalizeBodyForPersistence(body) {
|
|
|
186
186
|
return { body: { ...body, text: text.value }, truncated: text.truncated };
|
|
187
187
|
}
|
|
188
188
|
case "done": {
|
|
189
|
-
const finalMessage = body.finalMessage === undefined
|
|
189
|
+
const finalMessage = body.finalMessage === undefined
|
|
190
|
+
? undefined
|
|
191
|
+
: truncateText(body.finalMessage, AGENT_DONE_FINAL_MESSAGE_PERSISTENCE_PREVIEW_MAX_CHARS);
|
|
190
192
|
return {
|
|
191
193
|
body: { ...body, ...(finalMessage ? { finalMessage: finalMessage.value } : {}) },
|
|
192
194
|
truncated: finalMessage?.truncated ?? false,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const AGENT_EVENT_TEXT_MAX_CHARS =
|
|
2
|
+
export const AGENT_EVENT_TEXT_MAX_CHARS = 16_384;
|
|
3
|
+
export const AGENT_DONE_FINAL_MESSAGE_PERSISTENCE_PREVIEW_MAX_CHARS = 65_536;
|
|
3
4
|
export const AGENT_THOUGHT_TEXT_MAX_CHARS = 4_000;
|
|
4
5
|
export const AGENT_PLAN_MAX_ITEMS = 64;
|
|
5
6
|
export const AGENT_PLAN_ITEM_TEXT_MAX_CHARS = 200;
|
|
6
7
|
export const AGENT_ACTIVITY_MAX_CHANGES = 32;
|
|
7
|
-
export const AGENT_UNKNOWN_PAYLOAD_MAX_CHARS =
|
|
8
|
+
export const AGENT_UNKNOWN_PAYLOAD_MAX_CHARS = AGENT_EVENT_TEXT_MAX_CHARS;
|
|
8
9
|
export const activityCallSchema = z.discriminatedUnion("kind", [
|
|
9
10
|
z.object({ kind: z.literal("run"), command: z.string(), description: z.string().optional() }).strict(),
|
|
10
11
|
z.object({
|
|
@@ -243,7 +243,12 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
|
|
|
243
243
|
builderDiagnostic = (text) => builder.emit({ type: "diagnostic", text });
|
|
244
244
|
emitTurnStarted = () => builder.emit({ type: "turn", phase: "started" });
|
|
245
245
|
const env = buildSubagentEnv(options.env);
|
|
246
|
-
server = new CodexAppServerProcess(executable, env,
|
|
246
|
+
server = new CodexAppServerProcess(executable, env, {
|
|
247
|
+
cwd,
|
|
248
|
+
codexHome: options.codexHome,
|
|
249
|
+
onEvidence: options.onEvidence,
|
|
250
|
+
onEvidenceDiagnostic: (text) => builder.emit({ type: "diagnostic", text }),
|
|
251
|
+
});
|
|
247
252
|
const { turnCompleted } = createCodexAppServerTurnMonitor({
|
|
248
253
|
server,
|
|
249
254
|
builder,
|
|
@@ -3,8 +3,6 @@ import { emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
|
|
|
3
3
|
import { coerceString } from "../../harness/runtime.js";
|
|
4
4
|
import { CODEX_APP_SERVER_PROVIDER } from "./identity.js";
|
|
5
5
|
export class CodexAppServerProcess {
|
|
6
|
-
onEvidence;
|
|
7
|
-
onEvidenceDiagnostic;
|
|
8
6
|
child;
|
|
9
7
|
pending = new Map();
|
|
10
8
|
notificationListeners = new Set();
|
|
@@ -17,15 +15,18 @@ export class CodexAppServerProcess {
|
|
|
17
15
|
gracefulClose;
|
|
18
16
|
forceClose;
|
|
19
17
|
codexHome;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.codexHome = codexHome;
|
|
18
|
+
onEvidence;
|
|
19
|
+
onEvidenceDiagnostic;
|
|
20
|
+
constructor(executable, env, options) {
|
|
21
|
+
this.codexHome = options.codexHome;
|
|
22
|
+
this.onEvidence = options.onEvidence;
|
|
23
|
+
this.onEvidenceDiagnostic = options.onEvidenceDiagnostic;
|
|
24
24
|
const spawnEnv = {
|
|
25
25
|
...env,
|
|
26
|
-
...(codexHome ? { CODEX_HOME: codexHome } : {}),
|
|
26
|
+
...(options.codexHome ? { CODEX_HOME: options.codexHome } : {}),
|
|
27
27
|
};
|
|
28
28
|
this.child = spawn(executable, ["app-server", "--listen", "stdio://"], {
|
|
29
|
+
cwd: options.cwd,
|
|
29
30
|
env: spawnEnv,
|
|
30
31
|
stdio: ["pipe", "pipe", "pipe"],
|
|
31
32
|
});
|
package/build/agents/selector.js
CHANGED
|
@@ -77,7 +77,18 @@ export async function selectSubagent(agentName, cwd) {
|
|
|
77
77
|
throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references missing provider instance '${instanceName}'.`);
|
|
78
78
|
}
|
|
79
79
|
if (!("instance" in providerCandidate) || providerCandidate.instance === undefined) {
|
|
80
|
-
|
|
80
|
+
const source = providerCandidate.source === "global" ? "global" : "local";
|
|
81
|
+
throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references invalid provider instance '${instanceName}': ${providerCandidate.reason}`, {
|
|
82
|
+
facts: {
|
|
83
|
+
kind: "invalid_settings",
|
|
84
|
+
diseases: [{
|
|
85
|
+
source,
|
|
86
|
+
coordinate: providerCandidate.coordinate,
|
|
87
|
+
knob: "providers",
|
|
88
|
+
reason: providerCandidate.reason,
|
|
89
|
+
}],
|
|
90
|
+
},
|
|
91
|
+
});
|
|
81
92
|
}
|
|
82
93
|
const providerInstance = providerCandidate.instance;
|
|
83
94
|
const adapter = providerInstance.kind;
|
|
@@ -2,19 +2,12 @@ import { FlowError } from "../../../../flow-error.js";
|
|
|
2
2
|
import { assertSettingsKnobUsable } from "../../../../config/settings/disease.js";
|
|
3
3
|
import { loadKeiyakuSettings } from "../../../../config/settings/loader.js";
|
|
4
4
|
import { loadAkumaCatalog } from "../../../../config/akuma-loader.js";
|
|
5
|
-
import {
|
|
5
|
+
import { readAkumaNonterminalProjectionStatus } from "../../../../core/projection/index.js";
|
|
6
6
|
import { isResponseArtifactId } from "../../../../core/response-artifact-id.js";
|
|
7
7
|
import { textResponse } from "../../../render/shared.js";
|
|
8
8
|
import { renderProjectionStatusSection } from "../../../render/status.js";
|
|
9
9
|
import { projectDirectory } from "../../shared.js";
|
|
10
10
|
import { renderAkumaShow, renderAkumaView } from "../../akuma.js";
|
|
11
|
-
const NONTERMINAL_PROJECTION_STATES = new Set([
|
|
12
|
-
"minting",
|
|
13
|
-
"startup-timeout",
|
|
14
|
-
"unknown",
|
|
15
|
-
"out",
|
|
16
|
-
"lost",
|
|
17
|
-
]);
|
|
18
11
|
export const handler = async (_stdin, flags, _signal, positional) => {
|
|
19
12
|
const [name] = positional;
|
|
20
13
|
if (!name)
|
|
@@ -28,8 +21,7 @@ export const handler = async (_stdin, flags, _signal, positional) => {
|
|
|
28
21
|
assertSettingsKnobUsable(loaded, "default");
|
|
29
22
|
assertSettingsKnobUsable(loaded, "reviewer");
|
|
30
23
|
const profile = renderAkumaShow(await loadAkumaCatalog(cwd), loaded.providerInstances, loaded.knobs, name).trimEnd();
|
|
31
|
-
const projectionStatus = await
|
|
32
|
-
const
|
|
33
|
-
const projections = renderProjectionStatusSection({ rows }).trimEnd();
|
|
24
|
+
const projectionStatus = await readAkumaNonterminalProjectionStatus(cwd, Date.now(), name);
|
|
25
|
+
const projections = renderProjectionStatusSection(projectionStatus).trimEnd();
|
|
34
26
|
return textResponse(renderAkumaView(profile, projections));
|
|
35
27
|
};
|
|
@@ -5,7 +5,7 @@ import { buildAmendResponse } from "../../../render/arc.js";
|
|
|
5
5
|
import { renderCoreResult } from "../../../render/shared.js";
|
|
6
6
|
import { effectiveDirectory, repositoryDirectory } from "../../shared.js";
|
|
7
7
|
export const handler = async (stdin, flags) => {
|
|
8
|
-
const parsed = buildAmendInput(stdin, effectiveDirectory(flags), flags.contractId, flags.contractAddressSource);
|
|
8
|
+
const parsed = buildAmendInput(stdin, effectiveDirectory(flags), flags.contractId, flags.contractAddressSource, flags.appendScope);
|
|
9
9
|
const cwd = await repositoryDirectory(flags, "amend");
|
|
10
10
|
const address = await resolveExistingContractAddress(cwd, flags.contractId, flags.contractAddressSource, effectiveDirectory(flags));
|
|
11
11
|
return renderCoreResult(amendKeiyaku({
|
|
@@ -2,11 +2,11 @@ export const meta = {
|
|
|
2
2
|
command: "amend",
|
|
3
3
|
summary: "Record an amendment",
|
|
4
4
|
usage: [
|
|
5
|
-
"keiyaku amend [-C|--cwd DIR] [--repo DIR] [--contract ADDR] - < amendment.md",
|
|
6
|
-
"keiyaku @addr amend [-C|--cwd DIR] [--repo DIR] - < amendment.md",
|
|
5
|
+
"keiyaku amend [-C|--cwd DIR] [--repo DIR] [--contract ADDR] [--append-scope PATTERN] - < amendment.md",
|
|
6
|
+
"keiyaku @addr amend [-C|--cwd DIR] [--repo DIR] [--append-scope PATTERN] - < amendment.md",
|
|
7
7
|
],
|
|
8
8
|
stdin: { mode: "document", selector: { kind: "positional", index: 0, required: true, rejectBlank: true } },
|
|
9
|
-
flags: ["cwd", "repo", "contract"],
|
|
10
|
-
purpose: "Append a prose amendment and optional
|
|
9
|
+
flags: ["cwd", "repo", "contract", "append-scope"],
|
|
10
|
+
purpose: "Append a prose amendment and one optional ordered scope-pattern delta from --append-scope or Scope Append.",
|
|
11
11
|
completionRank: 8,
|
|
12
12
|
};
|
|
@@ -6,6 +6,10 @@ import { renderProjectionFilteredStatus, renderStatusBoard } from "../../../rend
|
|
|
6
6
|
import { textResponse } from "../../../render/shared.js";
|
|
7
7
|
import { projectDirectory, repositoryDirectory } from "../../shared.js";
|
|
8
8
|
export const handler = async (_stdin, flags, _signal, positional) => {
|
|
9
|
+
const [projectionId] = positional;
|
|
10
|
+
if (projectionId !== undefined && !isProjectionId(projectionId)) {
|
|
11
|
+
throw new FlowError("EMPTY_PARAM", `invalid projection id: ${projectionId}`);
|
|
12
|
+
}
|
|
9
13
|
// Status is read-only and must open outside Git. projectDirectory still
|
|
10
14
|
// honors --repo as a repository coordinate when the operator names one.
|
|
11
15
|
const cwd = flags.target === undefined
|
|
@@ -14,12 +18,9 @@ export const handler = async (_stdin, flags, _signal, positional) => {
|
|
|
14
18
|
const target = flags.target === undefined ? undefined : normalizeTargetRef(flags.target);
|
|
15
19
|
const board = await readKanshiBoard(cwd, Date.now(), {
|
|
16
20
|
...(target === undefined ? {} : { target }),
|
|
17
|
-
|
|
21
|
+
...(projectionId === undefined ? {} : { projectionId }),
|
|
18
22
|
});
|
|
19
|
-
const [projectionId] = positional;
|
|
20
23
|
if (projectionId === undefined)
|
|
21
24
|
return textResponse(renderStatusBoard(board));
|
|
22
|
-
if (!isProjectionId(projectionId))
|
|
23
|
-
throw new FlowError("EMPTY_PARAM", `invalid projection id: ${projectionId}`);
|
|
24
25
|
return textResponse(renderProjectionFilteredStatus(board, projectionId));
|
|
25
26
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
command: "status",
|
|
3
3
|
summary: "Show the Kanshi board",
|
|
4
|
-
usage: ["keiyaku status [PROJECTION] [-C|--cwd DIR] [--repo DIR] [--target REF]
|
|
4
|
+
usage: ["keiyaku status [PROJECTION] [-C|--cwd DIR] [--repo DIR] [--target REF]"],
|
|
5
5
|
stdin: { mode: "argument" },
|
|
6
|
-
flags: ["cwd", "repo", "target"
|
|
6
|
+
flags: ["cwd", "repo", "target"],
|
|
7
7
|
purpose: "Read the Kanshi board or filter it to one projection without mutation.",
|
|
8
8
|
completionRank: 14,
|
|
9
9
|
};
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
const TASK_ADD_USAGE_PREFIX = "keiyaku task add [-C|--cwd DIR] [--needs ID]... [--parent ID] [--from ID]... [--pri 0-3]";
|
|
1
2
|
export const meta = {
|
|
2
3
|
command: "task add",
|
|
3
4
|
summary: "Add a task",
|
|
4
|
-
usage: [
|
|
5
|
+
usage: [
|
|
6
|
+
`${TASK_ADD_USAGE_PREFIX} <BODY>`,
|
|
7
|
+
`${TASK_ADD_USAGE_PREFIX} - < task.md`,
|
|
8
|
+
],
|
|
5
9
|
stdin: {
|
|
6
10
|
mode: "argument",
|
|
7
11
|
selector: { kind: "positional", index: 0, required: true, rejectBlank: true },
|
|
8
12
|
},
|
|
9
13
|
flags: ["cwd", "needs", "parent", "from", "pri"],
|
|
10
14
|
purpose: "Create a lightweight task from a title and optional body.",
|
|
15
|
+
notes: [
|
|
16
|
+
"<BODY> and literal - are mutually exclusive body selectors. Use literal - with stdin for multiline prose.",
|
|
17
|
+
"Only literal - reads stdin; unselected piped bytes are ignored and never change a literal <BODY>.",
|
|
18
|
+
],
|
|
11
19
|
};
|
|
@@ -17,7 +17,8 @@ export function parseTaskAddInput(input) {
|
|
|
17
17
|
if (!input.trim())
|
|
18
18
|
throw new FlowError("EMPTY_PARAM", "task add input cannot be blank");
|
|
19
19
|
const lines = input.split(/\r?\n/);
|
|
20
|
-
const
|
|
20
|
+
const firstLine = lines[0]?.startsWith("# ") ? lines[0].slice(2) : lines[0];
|
|
21
|
+
const title = firstLine?.trim() ?? "";
|
|
21
22
|
if (!title)
|
|
22
23
|
throw new FlowError("EMPTY_PARAM", "task title cannot be blank");
|
|
23
24
|
if (lines.length > 1 && lines[1].trim() !== "") {
|
package/build/cli/completion.js
CHANGED
|
@@ -19,6 +19,10 @@ function topLevelCommands() {
|
|
|
19
19
|
function subcommandsFor(parent) {
|
|
20
20
|
return [...(getCommandMetadata(parent).subcommands ?? [])];
|
|
21
21
|
}
|
|
22
|
+
function topLevelOptionsFor(command) {
|
|
23
|
+
const metadata = allCommandMetadata().find((meta) => meta.command === command && !command.includes(" "));
|
|
24
|
+
return metadata ? metadata.flags.map((flag) => `--${flag}`) : [];
|
|
25
|
+
}
|
|
22
26
|
function filterPrefix(items, word) {
|
|
23
27
|
return items.filter((item) => item.startsWith(word));
|
|
24
28
|
}
|
|
@@ -50,6 +54,10 @@ export async function renderCompletionCandidates(input) {
|
|
|
50
54
|
if (input.previous === "--filter") {
|
|
51
55
|
return filterPrefix([TASK_FILTER_COMPLETION], word).join("\n");
|
|
52
56
|
}
|
|
57
|
+
const options = input.previous ? topLevelOptionsFor(input.previous) : [];
|
|
58
|
+
if (options.length > 0) {
|
|
59
|
+
return filterPrefix(options, word).join("\n");
|
|
60
|
+
}
|
|
53
61
|
return filterPrefix(topLevelCommands(), word).join("\n");
|
|
54
62
|
}
|
|
55
63
|
export function renderCompletionScript(shell = "bash") {
|
|
@@ -45,8 +45,8 @@ export function renderKanshiBoard(status) {
|
|
|
45
45
|
const name = contract.place ? `${contract.slug} @${contract.place}` : contract.slug;
|
|
46
46
|
const spark = arcSparkline(contract.arcs);
|
|
47
47
|
const facts = [];
|
|
48
|
-
const lifecycle = contract.state.state;
|
|
49
|
-
if (lifecycle === "bound" || lifecycle === "
|
|
48
|
+
const lifecycle = contract.state.state === "active" ? "open" : contract.state.state;
|
|
49
|
+
if (lifecycle === "bound" || lifecycle === "open" || lifecycle === "petitioned") {
|
|
50
50
|
facts.push(lifecycle);
|
|
51
51
|
}
|
|
52
52
|
const arcMark = contract.currentArc.state === "open"
|
|
@@ -1,88 +1,131 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
/**
|
|
5
|
-
export function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!leftParts || !rightParts)
|
|
10
|
-
return literal;
|
|
11
|
-
let sharedCount = 0;
|
|
12
|
-
const maximumShared = Math.min(leftParts.length, rightParts.length) - 1;
|
|
13
|
-
while (sharedCount < maximumShared && leftParts[sharedCount] === rightParts[sharedCount]) {
|
|
14
|
-
sharedCount += 1;
|
|
15
|
-
}
|
|
16
|
-
if (sharedCount === 0)
|
|
17
|
-
return literal;
|
|
18
|
-
const prefix = leftParts.slice(0, sharedCount).join("/");
|
|
19
|
-
const compacted = `${prefix}/{${leftParts.slice(sharedCount).join("/")} × ${rightParts.slice(sharedCount).join("/")}}`;
|
|
20
|
-
return savesEnough(literal, compacted) ? compacted : literal;
|
|
1
|
+
const PREFIX_MARKER = "⋯";
|
|
2
|
+
const MIN_FOLDED_PREFIX_LENGTH = 8;
|
|
3
|
+
export const PATH_PREVIEW_BODY_ROWS = 7;
|
|
4
|
+
/** Render one exact pattern/path without giving display punctuation matcher meaning. */
|
|
5
|
+
export function renderExactPath(value) {
|
|
6
|
+
if (!requiresQuoting(value))
|
|
7
|
+
return value;
|
|
8
|
+
return `"${[...value].map(escapeQuotedCharacter).join("")}"`;
|
|
21
9
|
}
|
|
22
|
-
/**
|
|
23
|
-
export function
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return literal;
|
|
37
|
-
const compacted = renderTrie(trie);
|
|
38
|
-
return savesEnough(literal, compacted) ? compacted : literal;
|
|
10
|
+
/** Fold one qualifying directory prefix while retaining row order and exact suffixes. */
|
|
11
|
+
export function foldPathRows(values) {
|
|
12
|
+
const rendered = values.map(renderExactPath);
|
|
13
|
+
const plainIndexes = values
|
|
14
|
+
.map((value, index) => renderExactPath(value) === value ? index : -1)
|
|
15
|
+
.filter((index) => index >= 0);
|
|
16
|
+
const prefix = bestSharedDirectoryPrefix(values, plainIndexes);
|
|
17
|
+
if (!prefix)
|
|
18
|
+
return { rows: rendered };
|
|
19
|
+
const foldedIndexes = new Set(plainIndexes.filter((index) => values[index].startsWith(prefix)));
|
|
20
|
+
return {
|
|
21
|
+
prefix,
|
|
22
|
+
rows: values.map((value, index) => foldedIndexes.has(index) ? `${PREFIX_MARKER}${value.slice(prefix.length)}` : rendered[index]),
|
|
23
|
+
};
|
|
39
24
|
}
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
/** Render one overlap fact as two labeled exact pattern rows. */
|
|
26
|
+
export function renderPatternPair(leftLabel, leftPattern, rightLabel, rightPattern) {
|
|
27
|
+
const folded = foldPathRows([leftPattern, rightPattern]);
|
|
28
|
+
const labelWidth = Math.max(leftLabel.length, rightLabel.length) + 2;
|
|
29
|
+
return [
|
|
30
|
+
...(folded.prefix ? [`${PREFIX_MARKER} = ${folded.prefix}`] : []),
|
|
31
|
+
`${leftLabel.padEnd(labelWidth)}${folded.rows[0]}`,
|
|
32
|
+
`${rightLabel.padEnd(labelWidth)}${folded.rows[1]}`,
|
|
33
|
+
];
|
|
42
34
|
}
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
/** Render a bounded path snapshot; declarations consume the same row budget as paths. */
|
|
36
|
+
export function renderPathPreview(snapshot, bodyRows = PATH_PREVIEW_BODY_ROWS) {
|
|
37
|
+
const plan = planPathPreview(snapshot.preview, Math.max(0, bodyRows));
|
|
38
|
+
const omitted = Math.max(0, snapshot.count - plan.visibleCount);
|
|
39
|
+
return [
|
|
40
|
+
`scope · ${snapshot.count} pattern${snapshot.count === 1 ? "" : "s"}`,
|
|
41
|
+
...(plan.prefix ? [`${PREFIX_MARKER} = ${plan.prefix}`] : []),
|
|
42
|
+
...plan.rows,
|
|
43
|
+
...(omitted > 0 ? [`+${omitted} more patterns`] : []),
|
|
44
|
+
];
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
46
|
+
function planPathPreview(preview, bodyRows) {
|
|
47
|
+
const fullValues = preview.slice(0, bodyRows);
|
|
48
|
+
if (fullValues.length < bodyRows) {
|
|
49
|
+
return { ...foldPathRows(fullValues), visibleCount: fullValues.length };
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
function insertPath(node, parts) {
|
|
57
|
-
let current = node;
|
|
58
|
-
for (const part of parts) {
|
|
59
|
-
let child = current.children.get(part);
|
|
60
|
-
if (!child) {
|
|
61
|
-
child = createTrie();
|
|
62
|
-
current.children.set(part, child);
|
|
63
|
-
}
|
|
64
|
-
current = child;
|
|
51
|
+
const chargedValues = preview.slice(0, Math.max(0, bodyRows - 1));
|
|
52
|
+
const chargedFold = foldPathRows(chargedValues);
|
|
53
|
+
if (chargedFold.prefix) {
|
|
54
|
+
return { ...chargedFold, visibleCount: chargedValues.length };
|
|
65
55
|
}
|
|
66
|
-
|
|
56
|
+
return { rows: fullValues.map(renderExactPath), visibleCount: fullValues.length };
|
|
67
57
|
}
|
|
68
|
-
function
|
|
69
|
-
if (
|
|
58
|
+
function requiresQuoting(value) {
|
|
59
|
+
if (value.length === 0 || /^\s|\s$/u.test(value))
|
|
70
60
|
return true;
|
|
71
|
-
return
|
|
61
|
+
return value.includes("\\")
|
|
62
|
+
|| value.includes('"')
|
|
63
|
+
|| value.includes(PREFIX_MARKER)
|
|
64
|
+
|| [...value].some(isControlCharacter);
|
|
72
65
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
function isControlCharacter(character) {
|
|
67
|
+
const codePoint = character.codePointAt(0);
|
|
68
|
+
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
69
|
+
}
|
|
70
|
+
function escapeQuotedCharacter(character) {
|
|
71
|
+
if (character === "\\")
|
|
72
|
+
return "\\\\";
|
|
73
|
+
if (character === '"')
|
|
74
|
+
return '\\"';
|
|
75
|
+
const codePoint = character.codePointAt(0);
|
|
76
|
+
const named = new Map([
|
|
77
|
+
[0x07, "\\a"],
|
|
78
|
+
[0x08, "\\b"],
|
|
79
|
+
[0x09, "\\t"],
|
|
80
|
+
[0x0a, "\\n"],
|
|
81
|
+
[0x0b, "\\v"],
|
|
82
|
+
[0x0c, "\\f"],
|
|
83
|
+
[0x0d, "\\r"],
|
|
84
|
+
]).get(codePoint);
|
|
85
|
+
if (named)
|
|
86
|
+
return named;
|
|
87
|
+
if (!isControlCharacter(character))
|
|
88
|
+
return character;
|
|
89
|
+
return [...Buffer.from(character, "utf8")]
|
|
90
|
+
.map((byte) => `\\${byte.toString(8).padStart(3, "0")}`)
|
|
91
|
+
.join("");
|
|
77
92
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
93
|
+
function bestSharedDirectoryPrefix(values, plainIndexes) {
|
|
94
|
+
let best;
|
|
95
|
+
for (let left = 0; left < plainIndexes.length; left += 1) {
|
|
96
|
+
for (let right = left + 1; right < plainIndexes.length; right += 1) {
|
|
97
|
+
const prefix = sharedDirectoryPrefix(values[plainIndexes[left]], values[plainIndexes[right]]);
|
|
98
|
+
if (!prefix || prefix.length < MIN_FOLDED_PREFIX_LENGTH)
|
|
99
|
+
continue;
|
|
100
|
+
const members = plainIndexes.filter((index) => values[index].startsWith(prefix));
|
|
101
|
+
const common = members
|
|
102
|
+
.slice(1)
|
|
103
|
+
.reduce((current, index) => sharedDirectoryPrefix(current, values[index]) ?? "", values[members[0]]);
|
|
104
|
+
if (common.length < MIN_FOLDED_PREFIX_LENGTH)
|
|
105
|
+
continue;
|
|
106
|
+
const candidate = {
|
|
107
|
+
prefix: common,
|
|
108
|
+
count: members.length,
|
|
109
|
+
first: members[0],
|
|
110
|
+
score: common.length * (members.length - 1),
|
|
111
|
+
};
|
|
112
|
+
if (!best
|
|
113
|
+
|| candidate.score > best.score
|
|
114
|
+
|| (candidate.score === best.score && candidate.count > best.count)
|
|
115
|
+
|| (candidate.score === best.score && candidate.count === best.count && candidate.first < best.first)
|
|
116
|
+
|| (candidate.score === best.score && candidate.count === best.count && candidate.first === best.first
|
|
117
|
+
&& candidate.prefix < best.prefix)) {
|
|
118
|
+
best = candidate;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return best?.prefix;
|
|
85
123
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
124
|
+
function sharedDirectoryPrefix(left, right) {
|
|
125
|
+
let sharedLength = 0;
|
|
126
|
+
const maximum = Math.min(left.length, right.length);
|
|
127
|
+
while (sharedLength < maximum && left[sharedLength] === right[sharedLength])
|
|
128
|
+
sharedLength += 1;
|
|
129
|
+
const slash = left.lastIndexOf("/", sharedLength - 1);
|
|
130
|
+
return slash >= 0 ? left.slice(0, slash + 1) : undefined;
|
|
88
131
|
}
|
|
@@ -2,9 +2,10 @@ import { compactText } from "./compact-text.js";
|
|
|
2
2
|
import { displayColumns, fitVariableLine, resolveLineColumns, truncateMiddleColumns } from "./line-width.js";
|
|
3
3
|
import { presentFoldedToolActivity, } from "./tool-presentation.js";
|
|
4
4
|
import { presentLedgerPath, summarizeToolLedger } from "./tool-ledger-rollup.js";
|
|
5
|
-
const TIME_GUTTER_WIDTH =
|
|
5
|
+
const TIME_GUTTER_WIDTH = 5;
|
|
6
6
|
const VERB_WIDTH = 7;
|
|
7
7
|
const SILENCE_THRESHOLD_MS = 60_000;
|
|
8
|
+
const TIMELINE_GAP_MARKER = "⋮";
|
|
8
9
|
export function formatTimelineDuration(ms) {
|
|
9
10
|
const totalSeconds = Math.max(0, Math.floor(ms / 1_000));
|
|
10
11
|
if (totalSeconds < 60)
|
|
@@ -72,6 +73,9 @@ function compressedPrefix(prefix) {
|
|
|
72
73
|
function timelineGutter(label, spine) {
|
|
73
74
|
return label || `${"".padStart(TIME_GUTTER_WIDTH)}${spine}`;
|
|
74
75
|
}
|
|
76
|
+
function renderTimelineGap(omittedEventCount, maxColumns) {
|
|
77
|
+
return fitVariableLine(`${timelineGutter("", TIMELINE_GAP_MARKER)} `, `${omittedEventCount} more`, maxColumns);
|
|
78
|
+
}
|
|
75
79
|
function compactTokens(value) {
|
|
76
80
|
if (value < 1_000)
|
|
77
81
|
return String(value);
|
|
@@ -171,6 +175,7 @@ export function renderProjectionActivity(activity, input) {
|
|
|
171
175
|
const lines = [];
|
|
172
176
|
const rowKinds = [];
|
|
173
177
|
let priorAtMs;
|
|
178
|
+
let priorOrder;
|
|
174
179
|
let priorMinute;
|
|
175
180
|
let priorCompletedRan;
|
|
176
181
|
let hasEndAnchor = false;
|
|
@@ -185,6 +190,10 @@ export function renderProjectionActivity(activity, input) {
|
|
|
185
190
|
for (let index = 0; index < entries.length; index += 1) {
|
|
186
191
|
const entry = entries[index];
|
|
187
192
|
const atMs = Date.parse(entry.at);
|
|
193
|
+
if (priorOrder !== undefined && entry.order > priorOrder + 1) {
|
|
194
|
+
pushRow(renderTimelineGap(entry.order - priorOrder - 1, maxColumns), "activity");
|
|
195
|
+
}
|
|
196
|
+
priorOrder = entry.order;
|
|
188
197
|
if (entry.kind === "clock") {
|
|
189
198
|
if (Number.isFinite(atMs))
|
|
190
199
|
priorAtMs = atMs;
|
|
@@ -3,7 +3,7 @@ import { commissionSlug, formatCommissionCoordinate } from "../../core/ids.js";
|
|
|
3
3
|
import { assembleResponse, buildSection, DISPLAY_TEXT_MAX_CHARS, FORMAT_LIST_MAX_ITEM_CHARS, FORMAT_LIST_MAX_ITEMS, formatMaybe, formatWarnings, shellQuote, truncateForDisplay, } from "./format.js";
|
|
4
4
|
import { WARNING_SECTION_TITLE } from "./response-style.js";
|
|
5
5
|
import { prependAddressReceipt } from "./address.js";
|
|
6
|
-
import {
|
|
6
|
+
import { renderPathPreview, renderPatternPair } from "./path-prefix-compaction.js";
|
|
7
7
|
export function textResponse(text) {
|
|
8
8
|
const payload = {};
|
|
9
9
|
return {
|
|
@@ -76,16 +76,17 @@ function renderScopeOverlapWarnings(overlaps) {
|
|
|
76
76
|
return [...overlapsByCounterparty.entries()].map(([contractId, overlaps]) => renderScopeOverlapGroup(commissionSlug(contractId), overlaps));
|
|
77
77
|
}
|
|
78
78
|
function renderScopeOverlapGroup(contractAddr, overlaps) {
|
|
79
|
-
const
|
|
79
|
+
const nextAddr = commissionSlug(overlaps[0].contracts[0]);
|
|
80
|
+
const pairs = overlaps.flatMap((overlap) => {
|
|
80
81
|
const [nextPattern, existingPattern] = overlap.patterns;
|
|
81
|
-
const patternPair =
|
|
82
|
+
const patternPair = renderPatternPair(nextAddr, nextPattern, contractAddr, existingPattern);
|
|
82
83
|
const evidence = overlap.kind === "potential"
|
|
83
|
-
? "potential
|
|
84
|
-
:
|
|
85
|
-
return
|
|
84
|
+
? ["potential · no current tracked path proves it"]
|
|
85
|
+
: renderPathPreview(overlap.paths);
|
|
86
|
+
return [...patternPair, ...evidence].map((line) => ` ${line}`);
|
|
86
87
|
});
|
|
87
88
|
return [
|
|
88
|
-
`▲
|
|
89
|
+
`▲ overlap ${nextAddr} ↔ ${contractAddr}`,
|
|
89
90
|
...pairs,
|
|
90
91
|
` consider --after ${contractAddr}`,
|
|
91
92
|
].join("\n");
|
|
@@ -74,7 +74,7 @@ function renderProjectionOmission(board) {
|
|
|
74
74
|
const count = board.omitted?.byState[state] ?? 0;
|
|
75
75
|
return count > 0 ? [`${count} ${state}`] : [];
|
|
76
76
|
});
|
|
77
|
-
return ` · +${board.omitted.total} historical projections not shown (${counts.join(" · ")})
|
|
77
|
+
return ` · +${board.omitted.total} historical projections not shown (${counts.join(" · ")})`;
|
|
78
78
|
}
|
|
79
79
|
function renderProjectionAge(row) {
|
|
80
80
|
const duration = formatAgeMs(row.durationMs) ?? "?";
|
|
@@ -170,15 +170,18 @@ export function renderProjectionFilteredStatus(board, projectionId) {
|
|
|
170
170
|
if (!row)
|
|
171
171
|
return `${heading}\nno projections on projection ${projectionId}\n`;
|
|
172
172
|
if (row.state === "failed" || row.terminalFailure) {
|
|
173
|
-
|
|
174
|
-
return
|
|
173
|
+
const lines = [...renderTerminalFailureFace(projectionId, row.terminalFailure), "", `» keiyaku wait ${projectionId}`];
|
|
174
|
+
return lines.join("\n");
|
|
175
175
|
}
|
|
176
176
|
const lines = [heading, "", ...renderProjectionRow(row), ""];
|
|
177
177
|
if (row.observation.compatibility === "incompatible") {
|
|
178
178
|
return `${lines.join("\n")}\n`;
|
|
179
179
|
}
|
|
180
|
-
if (
|
|
181
|
-
lines.push(`» ${
|
|
180
|
+
if (isTerminalProjectionStatus(row)) {
|
|
181
|
+
lines.push(`» keiyaku wait ${projectionId}`);
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
}
|
|
184
|
+
lines.push(`» ${formatTellCommand(projectionId, '"..."')}`);
|
|
182
185
|
lines.push(`» keiyaku wait ${projectionId}`);
|
|
183
186
|
return `${lines.join("\n")}\n`;
|
|
184
187
|
}
|
package/build/cli/render/wait.js
CHANGED
|
@@ -73,7 +73,7 @@ function waitHeader(projectionId, akuma, snapshot, outputColumns) {
|
|
|
73
73
|
function pendingTellLines(snapshot, maxColumns) {
|
|
74
74
|
return snapshot.tells.window.map((tell) => {
|
|
75
75
|
const age = Math.max(0, snapshot.observedAtMs - Date.parse(tell.createdAt));
|
|
76
|
-
const fixed = `${"".padStart(
|
|
76
|
+
const fixed = `${"".padStart(5)}│ ${"tell".padEnd(7)} `;
|
|
77
77
|
const suffix = ` · undelivered ${formatTimelineDuration(Number.isFinite(age) ? age : 0)}`;
|
|
78
78
|
return `${fixed}${truncateColumns(`“${tell.text}”`, Math.max(0, maxColumns - displayColumns(fixed) - displayColumns(suffix)))}${suffix}`;
|
|
79
79
|
});
|