@astrosheep/keiyaku 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -56
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms_v2.js +103 -0
- package/build/agents/harness/activity-values.js +54 -0
- package/build/agents/harness/event-channel.js +55 -20
- package/build/agents/harness/events.js +47 -1
- package/build/agents/harness/execution-handle.js +292 -61
- package/build/agents/harness/index.js +103 -121
- package/build/agents/harness/pump.js +36 -47
- package/build/agents/harness/runtime.js +3 -0
- package/build/agents/launch-snapshot_v2.js +348 -0
- package/build/agents/opencode-sdk.js +42 -169
- package/build/agents/providers/claude-agent-sdk.js +240 -79
- package/build/agents/providers/codex-app-server.js +280 -72
- package/build/agents/providers/codex-sdk.js +159 -41
- package/build/agents/providers/event-type-tail.js +16 -0
- package/build/agents/providers/opencode-sdk.js +165 -59
- package/build/agents/providers/pi.js +254 -42
- package/build/agents/selector_v2.js +122 -0
- package/build/cli/commands/akuma.js +120 -42
- package/build/cli/completion.js +6 -7
- package/build/cli/flags.js +36 -10
- package/build/cli/help.js +42 -26
- package/build/cli/index.js +225 -93
- package/build/cli/parse.js +194 -75
- package/build/cli/projection-address.js +11 -0
- package/build/cli/render/address.js +88 -0
- package/build/cli/render/arc.js +10 -10
- package/build/cli/render/call.js +59 -0
- package/build/cli/render/compact-text.js +3 -0
- package/build/cli/render/errors.js +4 -2
- package/build/cli/render/format.js +6 -0
- package/build/cli/render/line-width.js +70 -0
- package/build/cli/render/misc.js +7 -4
- package/build/cli/render/petition.js +4 -3
- package/build/cli/render/projection-activity.js +201 -0
- package/build/cli/render/shared.js +46 -12
- package/build/cli/render/status.js +59 -10
- package/build/cli/render/tool-ledger-rollup.js +74 -0
- package/build/cli/render/tool-presentation.js +97 -0
- package/build/cli/render/wait.js +229 -0
- package/build/cli/skills-install.js +42 -24
- package/build/cli/subagent-guard.js +1 -1
- package/build/config/akuma-loader_v2.js +222 -0
- package/build/config/env-keys.js +0 -4
- package/build/config/env.js +0 -6
- package/build/config/settings.js +325 -12
- package/build/core/addressing.js +224 -0
- package/build/core/amend.js +30 -47
- package/build/core/arc.js +68 -60
- package/build/core/bind.js +105 -25
- package/build/core/call-persist.js +69 -0
- package/build/core/call.js +658 -0
- package/build/core/claim.js +72 -44
- package/build/core/command-io.js +19 -19
- package/build/core/context.js +11 -36
- package/build/core/draft.js +2 -3
- package/build/core/entry.js +101 -6
- package/build/core/execution-coordinate.js +62 -0
- package/build/core/execution-pact.js +141 -0
- package/build/core/forfeit.js +2 -3
- package/build/core/hints.js +10 -10
- package/build/core/ids.js +6 -0
- package/build/core/ledger.js +16 -1
- package/build/core/petition-claim-gates.js +14 -12
- package/build/core/petition-claim.js +1 -0
- package/build/core/petition-run.js +6 -2
- package/build/core/petition.js +63 -21
- package/build/core/places.js +106 -13
- package/build/core/projection/generation.js +412 -0
- package/build/core/projection/heart.js +316 -0
- package/build/core/projection/identity.js +81 -0
- package/build/core/projection/leash.js +87 -0
- package/build/core/projection/mint.js +154 -0
- package/build/core/projection-activity.js +219 -0
- package/build/core/projection-coordinate.js +35 -0
- package/build/core/projection-core.js +69 -463
- package/build/core/projection-generation-continuation.js +31 -0
- package/build/core/projection-generation-execution.js +148 -0
- package/build/core/projection-generation-identity.js +17 -0
- package/build/core/projection-generation-launcher.js +90 -0
- package/build/core/projection-generation-process.js +43 -0
- package/build/core/projection-generation-runner.js +144 -0
- package/build/core/projection-generation-runtime.js +15 -0
- package/build/core/projection-generation-store.js +707 -0
- package/build/core/projection-identity.js +11 -0
- package/build/core/projection-life-observer.js +87 -0
- package/build/core/projection-life-protocol.js +95 -0
- package/build/core/projection-mint.js +96 -24
- package/build/core/projection-runner-lock.js +230 -0
- package/build/core/projection-status.js +207 -107
- package/build/core/projection-tells.js +401 -0
- package/build/core/projection-wait.js +155 -0
- package/build/core/projection-wake.js +109 -231
- package/build/core/queue_v2.js +342 -0
- package/build/core/registry.js +6 -3
- package/build/core/render.js +63 -1
- package/build/core/renew.js +80 -53
- package/build/core/scope.js +186 -137
- package/build/core/seal.js +16 -19
- package/build/core/status.js +88 -25
- package/build/core/stored-agent-event.js +101 -0
- package/build/core/transcripts.js +214 -228
- package/build/core/verdict.js +27 -15
- package/build/core/worktree-bootstrap.js +187 -0
- package/build/core/worktree-path.js +72 -18
- package/build/flow-error.js +4 -6
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +6 -1
- package/build/index.js +8 -5
- package/build/keiyaku.js +1 -3
- package/package.json +6 -5
- package/skills/keiyaku/SKILL.md +6 -47
- package/skills/keiyaku-akuma/SKILL.md +75 -0
- package/build/agents/effective-policy.js +0 -32
- package/build/agents/index.js +0 -114
- package/build/agents/selector.js +0 -28
- package/build/cli/render/summon.js +0 -17
- package/build/cli/render/tell.js +0 -11
- package/build/config/akuma-loader.js +0 -268
- package/build/core/queue.js +0 -73
- package/build/core/summon-persist.js +0 -73
- package/build/core/summon.js +0 -377
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { getConfig } from "../../config/env.js";
|
|
2
2
|
import { SECTION_ICONS } from "./response-style.js";
|
|
3
|
+
/** Quote a shell argument only when the token is not already a single safe word. */
|
|
4
|
+
export function shellQuote(value) {
|
|
5
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value))
|
|
6
|
+
return value;
|
|
7
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
8
|
+
}
|
|
3
9
|
export const DISPLAY_TEXT_MAX_CHARS = 5000;
|
|
4
10
|
export const FORMAT_MAYBE_MAX_CHARS = 1200;
|
|
5
11
|
export const FORMAT_LIST_MAX_ITEMS = 20;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const NON_TTY_LINE_COLUMNS = 120;
|
|
2
|
+
function isCombining(codePoint) {
|
|
3
|
+
return (codePoint >= 0x0300 && codePoint <= 0x036f)
|
|
4
|
+
|| (codePoint >= 0x1ab0 && codePoint <= 0x1aff)
|
|
5
|
+
|| (codePoint >= 0x1dc0 && codePoint <= 0x1dff)
|
|
6
|
+
|| (codePoint >= 0x20d0 && codePoint <= 0x20ff)
|
|
7
|
+
|| (codePoint >= 0xfe20 && codePoint <= 0xfe2f)
|
|
8
|
+
|| codePoint === 0xfe0f;
|
|
9
|
+
}
|
|
10
|
+
function isWide(codePoint) {
|
|
11
|
+
return codePoint >= 0x1100 && (codePoint <= 0x115f
|
|
12
|
+
|| codePoint === 0x2329
|
|
13
|
+
|| codePoint === 0x232a
|
|
14
|
+
|| (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f)
|
|
15
|
+
|| (codePoint >= 0xac00 && codePoint <= 0xd7a3)
|
|
16
|
+
|| (codePoint >= 0xf900 && codePoint <= 0xfaff)
|
|
17
|
+
|| (codePoint >= 0xfe10 && codePoint <= 0xfe19)
|
|
18
|
+
|| (codePoint >= 0xfe30 && codePoint <= 0xfe6f)
|
|
19
|
+
|| (codePoint >= 0xff00 && codePoint <= 0xff60)
|
|
20
|
+
|| (codePoint >= 0xffe0 && codePoint <= 0xffe6)
|
|
21
|
+
|| (codePoint >= 0x1f300 && codePoint <= 0x1faff)
|
|
22
|
+
|| (codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
23
|
+
}
|
|
24
|
+
function characterColumns(character) {
|
|
25
|
+
const codePoint = character.codePointAt(0);
|
|
26
|
+
if (codePoint === undefined || codePoint === 0 || isCombining(codePoint))
|
|
27
|
+
return 0;
|
|
28
|
+
if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0))
|
|
29
|
+
return 0;
|
|
30
|
+
return isWide(codePoint) ? 2 : 1;
|
|
31
|
+
}
|
|
32
|
+
export function displayColumns(value) {
|
|
33
|
+
let width = 0;
|
|
34
|
+
for (const character of value)
|
|
35
|
+
width += characterColumns(character);
|
|
36
|
+
return width;
|
|
37
|
+
}
|
|
38
|
+
export function truncateColumns(value, maxColumns) {
|
|
39
|
+
if (maxColumns <= 0)
|
|
40
|
+
return "";
|
|
41
|
+
if (displayColumns(value) <= maxColumns)
|
|
42
|
+
return value;
|
|
43
|
+
if (maxColumns === 1)
|
|
44
|
+
return "…";
|
|
45
|
+
const budget = maxColumns - 1;
|
|
46
|
+
let width = 0;
|
|
47
|
+
let output = "";
|
|
48
|
+
for (const character of value) {
|
|
49
|
+
const next = characterColumns(character);
|
|
50
|
+
if (width + next > budget)
|
|
51
|
+
break;
|
|
52
|
+
output += character;
|
|
53
|
+
width += next;
|
|
54
|
+
}
|
|
55
|
+
return `${output}…`;
|
|
56
|
+
}
|
|
57
|
+
export function resolveLineColumns(stream = process.stdout) {
|
|
58
|
+
return stream.isTTY === true && Number.isInteger(stream.columns) && stream.columns > 0
|
|
59
|
+
? stream.columns
|
|
60
|
+
: NON_TTY_LINE_COLUMNS;
|
|
61
|
+
}
|
|
62
|
+
/** Preserve the fixed prefix and spend the remaining terminal columns on the variable tail. */
|
|
63
|
+
export function fitVariableLine(fixedPrefix, variableTail, maxColumns) {
|
|
64
|
+
return `${fixedPrefix}${truncateColumns(variableTail, Math.max(0, maxColumns - displayColumns(fixedPrefix)))}`;
|
|
65
|
+
}
|
|
66
|
+
/** Preserve both decision-bearing affixes and truncate only the descriptive middle. */
|
|
67
|
+
export function fitAffixedLine(fixedPrefix, variableMiddle, fixedSuffix, maxColumns) {
|
|
68
|
+
const middleBudget = Math.max(0, maxColumns - displayColumns(fixedPrefix) - displayColumns(fixedSuffix));
|
|
69
|
+
return `${fixedPrefix}${truncateColumns(variableMiddle, middleBudget)}${fixedSuffix}`;
|
|
70
|
+
}
|
package/build/cli/render/misc.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assembleResponse, buildPayload, buildSection, colorizeErrorStatus, FORMAT_LIST_MAX_ITEM_CHARS, formatHints, } from "./format.js";
|
|
2
2
|
import { RESPONSE_MARKERS } from "./response-style.js";
|
|
3
3
|
import { formatReview, HINTS_LIST_MAX_ITEMS, REVIEW_SECTION_TITLE, toReviewPayload, } from "./shared.js";
|
|
4
|
+
import { prependAddressReceipt, renderAddressCarrier } from "./address.js";
|
|
4
5
|
export function buildErrorResponse(input) {
|
|
5
6
|
const shouldRaiseProtocolError = input.errorType === "runtime_error";
|
|
6
7
|
const text = buildErrorText(input);
|
|
@@ -24,7 +25,8 @@ function buildErrorText(input) {
|
|
|
24
25
|
const errorSection = buildSection("Error", input.message);
|
|
25
26
|
const reviewSection = input.review ? buildSection(REVIEW_SECTION_TITLE, formatReview(input.review)) : null;
|
|
26
27
|
const hintsSection = buildSection("Hints", formatHints(input.hints, { maxItems: HINTS_LIST_MAX_ITEMS, maxItemChars: FORMAT_LIST_MAX_ITEM_CHARS }));
|
|
27
|
-
|
|
28
|
+
const body = assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, reviewSection, hintsSection].filter((section) => section !== null));
|
|
29
|
+
return input.addressCarrier ? `${renderAddressCarrier(input.addressCarrier)}\n${body}` : body;
|
|
28
30
|
}
|
|
29
31
|
export function buildHelpResponse(input) {
|
|
30
32
|
return {
|
|
@@ -33,8 +35,9 @@ export function buildHelpResponse(input) {
|
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
export function buildLogResponse(result) {
|
|
36
|
-
|
|
38
|
+
const { address: _address, ...resultData } = result;
|
|
39
|
+
return prependAddressReceipt({
|
|
37
40
|
text: result.markdown,
|
|
38
|
-
payload: buildPayload(
|
|
39
|
-
};
|
|
41
|
+
payload: buildPayload(resultData),
|
|
42
|
+
}, result.address);
|
|
40
43
|
}
|
|
@@ -2,8 +2,9 @@ import { shortCommitHash } from "../../core/render.js";
|
|
|
2
2
|
import { assembleResponse, buildPayload, buildSection, formatMaybe, formatWarnings, } from "./format.js";
|
|
3
3
|
import { DIFF_OVERVIEW_SECTION_TITLE, RESPONSE_MARKERS, WARNING_SECTION_TITLE } from "./response-style.js";
|
|
4
4
|
import { BRANCH_LABEL, INFO_BRANCH_MAX_CHARS, INFO_COMMIT_MAX_CHARS, INFO_PLEA_MAX_CHARS, } from "./shared.js";
|
|
5
|
+
import { prependAddressReceipt } from "./address.js";
|
|
5
6
|
export function buildPetitionClaimResponse(result, input) {
|
|
6
|
-
const { status: _status, ...resultData } = result;
|
|
7
|
+
const { status: _status, address: _address, ...resultData } = result;
|
|
7
8
|
const renderedCommit = result.commit ? shortCommitHash(result.commit) : undefined;
|
|
8
9
|
const commitSuffix = renderedCommit ? ` [${renderedCommit}]` : "";
|
|
9
10
|
const summary = result.deletedBranch
|
|
@@ -25,8 +26,8 @@ export function buildPetitionClaimResponse(result, input) {
|
|
|
25
26
|
...formatMaybe("Oath", input.oath, INFO_PLEA_MAX_CHARS),
|
|
26
27
|
];
|
|
27
28
|
const text = assembleResponse(`${RESPONSE_MARKERS.claim} Keiyaku Fulfilled (Claim)`, summary, [ledgerSection, diffSection, warningSection].filter((section) => section !== null), infoLines);
|
|
28
|
-
return {
|
|
29
|
+
return prependAddressReceipt({
|
|
29
30
|
text,
|
|
30
31
|
payload: buildPayload(resultData),
|
|
31
|
-
};
|
|
32
|
+
}, result.address);
|
|
32
33
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { compactText } from "./compact-text.js";
|
|
2
|
+
import { fitAffixedLine, fitVariableLine, resolveLineColumns } from "./line-width.js";
|
|
3
|
+
import { presentFoldedToolActivity, } from "./tool-presentation.js";
|
|
4
|
+
import { presentLedgerPath, summarizeToolLedger } from "./tool-ledger-rollup.js";
|
|
5
|
+
const TIME_GUTTER_WIDTH = 6;
|
|
6
|
+
const VERB_WIDTH = 7;
|
|
7
|
+
const SILENCE_THRESHOLD_MS = 60_000;
|
|
8
|
+
export function formatTimelineDuration(ms) {
|
|
9
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1_000));
|
|
10
|
+
if (totalSeconds < 60)
|
|
11
|
+
return `${totalSeconds}s`;
|
|
12
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
13
|
+
const seconds = totalSeconds % 60;
|
|
14
|
+
if (totalMinutes < 60)
|
|
15
|
+
return `${totalMinutes}m${String(seconds).padStart(2, "0")}s`;
|
|
16
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
17
|
+
const minutes = totalMinutes % 60;
|
|
18
|
+
if (hours < 24)
|
|
19
|
+
return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
20
|
+
const days = Math.floor(hours / 24);
|
|
21
|
+
return `${days}d${String(hours % 24).padStart(2, "0")}h`;
|
|
22
|
+
}
|
|
23
|
+
function formatActionDuration(ms) {
|
|
24
|
+
if (ms === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (ms < 10_000 && ms % 1_000 !== 0)
|
|
27
|
+
return `${(ms / 1_000).toFixed(1)}s`;
|
|
28
|
+
return formatTimelineDuration(ms);
|
|
29
|
+
}
|
|
30
|
+
function elapsedLabel(anchorMs, at) {
|
|
31
|
+
const eventMs = Date.parse(at);
|
|
32
|
+
if (!Number.isFinite(eventMs))
|
|
33
|
+
return "now";
|
|
34
|
+
return formatTimelineDuration(Math.max(0, anchorMs - eventMs));
|
|
35
|
+
}
|
|
36
|
+
function compactTokens(value) {
|
|
37
|
+
if (value < 1_000)
|
|
38
|
+
return String(value);
|
|
39
|
+
const compact = (value / 1_000).toFixed(value < 10_000 ? 1 : 0).replace(/\.0$/, "");
|
|
40
|
+
return `${compact}k`;
|
|
41
|
+
}
|
|
42
|
+
function warningText(row) {
|
|
43
|
+
const warning = row.warning;
|
|
44
|
+
const modelRoute = warning.originalModel && warning.fallbackModel
|
|
45
|
+
? ` ${warning.originalModel}→${warning.fallbackModel}`
|
|
46
|
+
: warning.direction
|
|
47
|
+
? ` ${warning.direction}`
|
|
48
|
+
: "";
|
|
49
|
+
return `provider ${warning.reason}${modelRoute}${row.count > 1 ? ` (${row.count})` : ""}`;
|
|
50
|
+
}
|
|
51
|
+
function renderPlan(plan, maxColumns) {
|
|
52
|
+
const width = 5;
|
|
53
|
+
const filled = plan.total === 0 ? 0 : Math.min(width, Math.round((plan.done / plan.total) * width));
|
|
54
|
+
const bar = `${"▰".repeat(filled)}${"▱".repeat(width - filled)}`;
|
|
55
|
+
const fixed = `${"now".padStart(TIME_GUTTER_WIDTH)}│ ${"plan".padEnd(VERB_WIDTH)} ${bar} ${plan.done}/${plan.total}`;
|
|
56
|
+
return fitVariableLine(fixed, plan.activeItem ? ` → ${compactText(plan.activeItem)}` : "", maxColumns);
|
|
57
|
+
}
|
|
58
|
+
function rollupCountLabel(semanticClass) {
|
|
59
|
+
return semanticClass === "run" ? "ran" : semanticClass;
|
|
60
|
+
}
|
|
61
|
+
function summarizeProjectionToolLedger(activity, workspaceRoot) {
|
|
62
|
+
return summarizeToolLedger(activity.ledgerActivities, activity.ledgerTurns, (value) => presentLedgerPath(value, workspaceRoot));
|
|
63
|
+
}
|
|
64
|
+
function toolCountSegments(summary) {
|
|
65
|
+
return summary.counts.map(({ semanticClass, count }) => `${count} ${rollupCountLabel(semanticClass)}`);
|
|
66
|
+
}
|
|
67
|
+
export function renderProjectionActivitySummary(activity, input) {
|
|
68
|
+
const summary = summarizeProjectionToolLedger(activity, input.workspaceRoot);
|
|
69
|
+
const segments = toolCountSegments(summary);
|
|
70
|
+
if (segments.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
const spine = input.state === "dead" || input.state === "lost" ? "┆" : "│";
|
|
73
|
+
return fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}${spine} `, segments.join(" · "), input.maxColumns ?? resolveLineColumns());
|
|
74
|
+
}
|
|
75
|
+
function renderTerminalRollup(activity, input) {
|
|
76
|
+
const summary = summarizeProjectionToolLedger(activity, input.workspaceRoot);
|
|
77
|
+
const segments = [
|
|
78
|
+
...toolCountSegments(summary),
|
|
79
|
+
...(summary.tokens === undefined ? [] : [`${compactTokens(summary.tokens)} tok`]),
|
|
80
|
+
...(summary.costUsd === undefined ? [] : [`$${summary.costUsd.toFixed(2)}`]),
|
|
81
|
+
];
|
|
82
|
+
const lines = input.diagnostic
|
|
83
|
+
? [fitVariableLine(`${elapsedLabel(input.anchorMs, input.diagnostic.at).padStart(TIME_GUTTER_WIDTH)}┆ ${"warning".padEnd(VERB_WIDTH)} `, compactText(input.diagnostic.text), input.maxColumns)]
|
|
84
|
+
: [];
|
|
85
|
+
const endPrefix = `${"end".padStart(TIME_GUTTER_WIDTH)}└`;
|
|
86
|
+
lines.push(segments.length > 0
|
|
87
|
+
? fitVariableLine(`${endPrefix} `, segments.join(" · "), input.maxColumns)
|
|
88
|
+
: endPrefix);
|
|
89
|
+
for (const file of summary.files) {
|
|
90
|
+
const repeat = file.count > 1 ? ` ×${file.count}` : "";
|
|
91
|
+
const diffstat = file.diffstat ? ` +${file.diffstat.additions} −${file.diffstat.deletions}` : "";
|
|
92
|
+
lines.push(fitVariableLine("", `${file.path}${repeat}${diffstat}`, input.maxColumns));
|
|
93
|
+
}
|
|
94
|
+
if (summary.hiddenFileCount > 0) {
|
|
95
|
+
lines.push(fitVariableLine("", `… ${summary.hiddenFileCount} more files`, input.maxColumns));
|
|
96
|
+
}
|
|
97
|
+
return { lines, hasMutationFiles: summary.files.length > 0 };
|
|
98
|
+
}
|
|
99
|
+
function timelineEntries(activity) {
|
|
100
|
+
const entries = [];
|
|
101
|
+
for (const row of activity.timeline) {
|
|
102
|
+
if (row.kind === "ran") {
|
|
103
|
+
for (const observation of row.observations) {
|
|
104
|
+
if (observation.order !== row.order)
|
|
105
|
+
entries.push({ kind: "clock", order: observation.order, at: observation.at });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
entries.push({ kind: "row", order: row.order, at: row.at, row });
|
|
109
|
+
}
|
|
110
|
+
return entries.sort((left, right) => left.order - right.order);
|
|
111
|
+
}
|
|
112
|
+
export function renderProjectionActivity(activity, input) {
|
|
113
|
+
const lines = [];
|
|
114
|
+
let priorAtMs;
|
|
115
|
+
let hasEndAnchor = false;
|
|
116
|
+
let lastRunningTool;
|
|
117
|
+
const maxColumns = input.maxColumns ?? resolveLineColumns();
|
|
118
|
+
const entries = timelineEntries(activity);
|
|
119
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
120
|
+
const entry = entries[index];
|
|
121
|
+
const atMs = Date.parse(entry.at);
|
|
122
|
+
if (entry.kind === "clock") {
|
|
123
|
+
if (Number.isFinite(atMs))
|
|
124
|
+
priorAtMs = atMs;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (priorAtMs !== undefined && Number.isFinite(atMs) && atMs - priorAtMs >= SILENCE_THRESHOLD_MS) {
|
|
128
|
+
lines.push(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}┆ `, `${formatTimelineDuration(atMs - priorAtMs)} pass in silence`, maxColumns));
|
|
129
|
+
}
|
|
130
|
+
if (Number.isFinite(atMs))
|
|
131
|
+
priorAtMs = atMs;
|
|
132
|
+
const row = entry.row;
|
|
133
|
+
const label = row.kind === "turn" ? "" : elapsedLabel(input.anchorMs, row.at);
|
|
134
|
+
const spine = row.kind === "turn"
|
|
135
|
+
? "├"
|
|
136
|
+
: input.state === "dead" || input.state === "lost"
|
|
137
|
+
? "┆"
|
|
138
|
+
: "│";
|
|
139
|
+
if (row.kind === "ran") {
|
|
140
|
+
const duration = formatActionDuration(row.durationMs);
|
|
141
|
+
const activityState = row.state === "completed"
|
|
142
|
+
? "completed"
|
|
143
|
+
: input.state === "dead" || input.state === "lost"
|
|
144
|
+
? "interrupted"
|
|
145
|
+
: input.state === "stalled" || input.state === "unknown"
|
|
146
|
+
? "unresolved"
|
|
147
|
+
: "running";
|
|
148
|
+
const presentation = presentFoldedToolActivity(row, { state: activityState, ...(duration ? { duration } : {}) }, (value) => presentLedgerPath(value, input.workspaceRoot) ?? value);
|
|
149
|
+
const fixed = `${label.padStart(TIME_GUTTER_WIDTH)}${spine} ${(presentation.semanticClass === "run" ? "ran" : presentation.semanticClass).padEnd(VERB_WIDTH)} `;
|
|
150
|
+
lines.push(fitAffixedLine(fixed, presentation.variableText, presentation.fixedSuffix, maxColumns));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (row.kind === "said") {
|
|
154
|
+
lines.push(fitVariableLine(`${label.padStart(TIME_GUTTER_WIDTH)}${spine} ${"said".padEnd(VERB_WIDTH)} `, `“${compactText(row.text)}”`, maxColumns));
|
|
155
|
+
}
|
|
156
|
+
else if (row.kind === "thought") {
|
|
157
|
+
const text = `${compactText(row.text)}${row.truncated && !row.text.endsWith("…") ? "…" : ""}`;
|
|
158
|
+
const duration = formatActionDuration(row.durationMs);
|
|
159
|
+
lines.push(fitVariableLine(`${label.padStart(TIME_GUTTER_WIDTH)}${spine} ${"thought".padEnd(VERB_WIDTH)} `, `${duration ? `${duration} — ` : ""}“${text}”`, maxColumns));
|
|
160
|
+
}
|
|
161
|
+
else if (row.kind === "warning") {
|
|
162
|
+
lines.push(fitVariableLine(`${label.padStart(TIME_GUTTER_WIDTH)}${spine} ${"warning".padEnd(VERB_WIDTH)} `, warningText(row), maxColumns));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const usage = row.inputTokens !== undefined && row.outputTokens !== undefined
|
|
166
|
+
? ` · ${compactTokens(row.inputTokens)}▸${compactTokens(row.outputTokens)} tok`
|
|
167
|
+
: "";
|
|
168
|
+
const cost = row.costUsd !== undefined ? ` · $${row.costUsd.toFixed(2)}` : "";
|
|
169
|
+
lines.push(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}├ `, `turn ${row.turn} sealed${usage}${cost}`, maxColumns));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const row of activity.pinned) {
|
|
173
|
+
if (row.kind === "ran" && row.state === "running")
|
|
174
|
+
lastRunningTool = compactText(row.tool);
|
|
175
|
+
}
|
|
176
|
+
if (input.showPlan && activity.plan) {
|
|
177
|
+
const planAtMs = Date.parse(activity.plan.at);
|
|
178
|
+
if (priorAtMs !== undefined && Number.isFinite(planAtMs) && planAtMs - priorAtMs >= SILENCE_THRESHOLD_MS) {
|
|
179
|
+
lines.push(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}┆ `, `${formatTimelineDuration(planAtMs - priorAtMs)} pass in silence`, maxColumns));
|
|
180
|
+
}
|
|
181
|
+
lines.push(renderPlan(activity.plan, maxColumns));
|
|
182
|
+
}
|
|
183
|
+
let hasMutationFiles = false;
|
|
184
|
+
if (["returned", "dead", "lost", "dismissed"].includes(input.state)) {
|
|
185
|
+
const rollup = renderTerminalRollup(activity, {
|
|
186
|
+
anchorMs: input.anchorMs,
|
|
187
|
+
...(input.workspaceRoot ? { workspaceRoot: input.workspaceRoot } : {}),
|
|
188
|
+
...(input.terminalDiagnostic ? { diagnostic: input.terminalDiagnostic } : {}),
|
|
189
|
+
maxColumns,
|
|
190
|
+
});
|
|
191
|
+
lines.push(...rollup.lines);
|
|
192
|
+
hasEndAnchor = true;
|
|
193
|
+
hasMutationFiles = rollup.hasMutationFiles;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
lines,
|
|
197
|
+
...(lastRunningTool ? { lastRunningTool } : {}),
|
|
198
|
+
hasEndAnchor,
|
|
199
|
+
hasMutationFiles,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import { assembleResponse, buildSection, DISPLAY_TEXT_MAX_CHARS, FORMAT_LIST_MAX_ITEM_CHARS, FORMAT_LIST_MAX_ITEMS, formatMaybe, formatWarnings, truncateForDisplay, } from "./format.js";
|
|
5
|
-
import {
|
|
3
|
+
import { KEIYAKU_FILE } from "../../keiyaku.js";
|
|
4
|
+
import { assembleResponse, buildSection, DISPLAY_TEXT_MAX_CHARS, FORMAT_LIST_MAX_ITEM_CHARS, FORMAT_LIST_MAX_ITEMS, formatMaybe, formatWarnings, shellQuote, truncateForDisplay, } from "./format.js";
|
|
5
|
+
import { WARNING_SECTION_TITLE } from "./response-style.js";
|
|
6
|
+
import { prependAddressReceipt } from "./address.js";
|
|
6
7
|
export function textResponse(text) {
|
|
7
8
|
return {
|
|
8
9
|
text,
|
|
@@ -41,6 +42,7 @@ export function buildBindResponse(result, cwd) {
|
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
export function buildForfeitResponse(result) {
|
|
45
|
+
const { address: _address, ...resultData } = result;
|
|
44
46
|
const base = `Forfeited ${result.contractId}.`;
|
|
45
47
|
const warnings = [
|
|
46
48
|
...(result.cleanup.worktreeRemoveFailure
|
|
@@ -55,10 +57,10 @@ export function buildForfeitResponse(result) {
|
|
|
55
57
|
: []),
|
|
56
58
|
];
|
|
57
59
|
if (warnings.length === 0) {
|
|
58
|
-
return coreTextResponse(base, result);
|
|
60
|
+
return prependAddressReceipt(coreTextResponse(base, resultData), result.address);
|
|
59
61
|
}
|
|
60
62
|
const warningSection = buildSection(WARNING_SECTION_TITLE, formatWarnings(warnings));
|
|
61
|
-
return coreTextResponse(assembleResponse(base, "", [warningSection].filter((section) => section !== null)), result);
|
|
63
|
+
return prependAddressReceipt(coreTextResponse(assembleResponse(base, "", [warningSection].filter((section) => section !== null)), resultData), result.address);
|
|
62
64
|
}
|
|
63
65
|
export const RULES_SECTION_MAX_CHARS = 5000;
|
|
64
66
|
export const BRANCH_LABEL = "Workspace";
|
|
@@ -71,10 +73,8 @@ export const STATUS_COMMANDS_SECTION_TITLE = "Commands";
|
|
|
71
73
|
export const CONTENT_SECTION_TITLE = "Content";
|
|
72
74
|
export const STATUS_ACTIVE_SUMMARY_PREFIX = "● Active";
|
|
73
75
|
export const STATUS_INACTIVE_SUMMARY = "○ Inactive";
|
|
74
|
-
export const SUMMON_RESPONSE_HEADING = `${RESPONSE_MARKERS.arc} Summoned`;
|
|
75
76
|
export const SAVED_TO_PREFIX = "Saved to";
|
|
76
|
-
export const
|
|
77
|
-
export const INFO_AGENT_MAX_CHARS = 120;
|
|
77
|
+
export const INFO_AKUMA_MAX_CHARS = 120;
|
|
78
78
|
export const INFO_FROM_DRAFT_MAX_CHARS = 300;
|
|
79
79
|
export const EXISTING_BRANCHES_MAX_ITEMS = FORMAT_LIST_MAX_ITEMS;
|
|
80
80
|
export const INFO_BRANCH_MAX_CHARS = 200;
|
|
@@ -108,10 +108,44 @@ export function formatPersistenceSummary(responsePath) {
|
|
|
108
108
|
return "";
|
|
109
109
|
return `${SAVED_TO_PREFIX} ${responsePath}.`;
|
|
110
110
|
}
|
|
111
|
-
export function
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
export function formatCallCompletionHeading(projection) {
|
|
112
|
+
switch (projection?.phase) {
|
|
113
|
+
case "active":
|
|
114
|
+
case "quiescent": return "still out there";
|
|
115
|
+
case "done": return "returned";
|
|
116
|
+
case "failed": return "dead";
|
|
117
|
+
case "dismissed": return "dismissed";
|
|
118
|
+
case "lost": return undefined;
|
|
119
|
+
default: return "no record of this akuma";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Machine-generated durable revive command: artifactId + explicit repository coordinate. */
|
|
123
|
+
export function formatDurableReviveCommand(input) {
|
|
124
|
+
return input.repositoryCoordinate
|
|
125
|
+
? `keiyaku revive ${input.artifactId} --repo ${shellQuote(input.repositoryCoordinate)}`
|
|
126
|
+
: `keiyaku revive ${input.artifactId}`;
|
|
127
|
+
}
|
|
128
|
+
export function projectionRejoinHintLines(projectionId) {
|
|
129
|
+
return [
|
|
130
|
+
"Come back for the result:",
|
|
131
|
+
` » keiyaku wait ${projectionId}`,
|
|
132
|
+
"Steer it while it works:",
|
|
133
|
+
` » keiyaku tell ${projectionId} "your words"`,
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
export function formatCallNextAction(input) {
|
|
137
|
+
if (input.projection?.phase === "active") {
|
|
138
|
+
return projectionRejoinHintLines(input.projection.id).join("\n");
|
|
139
|
+
}
|
|
140
|
+
if (input.projection?.phase === "done") {
|
|
141
|
+
return [
|
|
142
|
+
"The akuma is back.",
|
|
143
|
+
...(input.artifactId ? [`record: ${input.artifactId}`] : []),
|
|
144
|
+
"talk to it:",
|
|
145
|
+
` » keiyaku tell ${input.projection.id} "your words"`,
|
|
146
|
+
].join("\n");
|
|
147
|
+
}
|
|
148
|
+
return "⟶ keiyaku status";
|
|
115
149
|
}
|
|
116
150
|
export function appendPersistenceSummary(base, responsePath) {
|
|
117
151
|
const persistence = formatPersistenceSummary(responsePath);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { formatSettingsDiseases } from "../../config/settings.js";
|
|
2
2
|
import { formatAgeMs, projectionGlyph, renderKanshiBoard, shortCommitHash } from "../../core/render.js";
|
|
3
|
+
import { renderProjectionActivitySummary, } from "./projection-activity.js";
|
|
3
4
|
export const STATUS_DISEASES_SECTION_TITLE = "Diseases";
|
|
4
|
-
export const STATUS_PROJECTION_SECTION_TITLE = "
|
|
5
|
+
export const STATUS_PROJECTION_SECTION_TITLE = "akuma";
|
|
5
6
|
export const STATUS_CLAIM_RECEIPTS_SECTION_TITLE = "claim receipts";
|
|
6
7
|
export function renderSettingsDiseasesSection(diseases) {
|
|
7
8
|
if (diseases.length === 0)
|
|
@@ -21,22 +22,67 @@ function renderProjectionAge(row) {
|
|
|
21
22
|
return "dismissed";
|
|
22
23
|
if (row.state === "failed")
|
|
23
24
|
return "failed";
|
|
24
|
-
|
|
25
|
+
if (row.state === "startup-timeout")
|
|
26
|
+
return "startup timed out";
|
|
27
|
+
if (row.state === "unknown")
|
|
28
|
+
return "unknown";
|
|
29
|
+
return formatAgeMs(row.activityAgeMs) ?? "";
|
|
30
|
+
}
|
|
31
|
+
function renderProjectionTellCounts(row) {
|
|
32
|
+
const { inbox, inflight, submitted, consumed } = row.tellCounts;
|
|
33
|
+
if (inbox === 0 && inflight === 0 && submitted === 0 && consumed === 0)
|
|
34
|
+
return "";
|
|
35
|
+
return ` tell ${inbox}▸${inflight}▸${submitted}▸${consumed}`;
|
|
36
|
+
}
|
|
37
|
+
function projectionActivityState(row) {
|
|
38
|
+
switch (row.state) {
|
|
39
|
+
case "done": return "returned";
|
|
40
|
+
case "failed":
|
|
41
|
+
case "dead": return "dead";
|
|
42
|
+
case "lost": return "lost";
|
|
43
|
+
case "dismissed": return "dismissed";
|
|
44
|
+
case "minting":
|
|
45
|
+
case "out": return "running";
|
|
46
|
+
case "startup-timeout": return "stalled";
|
|
47
|
+
case "unknown": return "unknown";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function renderProjectionActivityLine(row) {
|
|
51
|
+
if (row.state === "startup-timeout" || row.state === "unknown")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!row.activity)
|
|
54
|
+
return undefined;
|
|
55
|
+
return renderProjectionActivitySummary(row.activity, {
|
|
56
|
+
state: projectionActivityState(row),
|
|
57
|
+
...(row.workspaceRoot ? { workspaceRoot: row.workspaceRoot } : {}),
|
|
58
|
+
});
|
|
25
59
|
}
|
|
26
60
|
function renderProjectionRow(row) {
|
|
27
|
-
const glyph = projectionGlyph(row.state === "failed"
|
|
28
|
-
|
|
29
|
-
|
|
61
|
+
const glyph = projectionGlyph(row.state === "failed"
|
|
62
|
+
? "lost"
|
|
63
|
+
: row.state === "startup-timeout"
|
|
64
|
+
? "minting"
|
|
65
|
+
: row.state);
|
|
66
|
+
let heading;
|
|
67
|
+
if (row.state === "startup-timeout" || row.state === "unknown") {
|
|
68
|
+
const akuma = (row.akuma ?? "(unknown)").padEnd(18);
|
|
69
|
+
heading = ` ${glyph} ${row.id} ${akuma} ${renderProjectionAge(row)}`.trimEnd();
|
|
70
|
+
}
|
|
71
|
+
else if (row.state === "minting") {
|
|
72
|
+
heading = ` ${glyph} ${row.id.padEnd(18)} (minting)${renderProjectionDiagnostics(row)}`.trimEnd();
|
|
30
73
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
74
|
+
else {
|
|
75
|
+
const age = renderProjectionAge(row).padEnd(9);
|
|
76
|
+
const akuma = (row.akuma ?? "(unknown)").padEnd(18);
|
|
77
|
+
heading = ` ${glyph} ${row.id} ${akuma} ${age}${renderProjectionTellCounts(row)}${renderProjectionDiagnostics(row)}`.trimEnd();
|
|
78
|
+
}
|
|
79
|
+
const activity = renderProjectionActivityLine(row);
|
|
80
|
+
return activity ? [heading, activity] : [heading];
|
|
35
81
|
}
|
|
36
82
|
export function renderProjectionStatusSection(board) {
|
|
37
83
|
if (!board || board.rows.length === 0)
|
|
38
84
|
return "";
|
|
39
|
-
return `${STATUS_PROJECTION_SECTION_TITLE}\n${board.rows.
|
|
85
|
+
return `${STATUS_PROJECTION_SECTION_TITLE}\n${board.rows.flatMap(renderProjectionRow).join("\n")}\n`;
|
|
40
86
|
}
|
|
41
87
|
function renderClaimReconciliationSection(recon) {
|
|
42
88
|
if (!recon)
|
|
@@ -59,6 +105,9 @@ export function renderStatusBoard(board) {
|
|
|
59
105
|
if (!projectionText && !receiptsText && board.diseases.length === 0)
|
|
60
106
|
return boardText;
|
|
61
107
|
const sections = [boardText.trimEnd()];
|
|
108
|
+
if (board.targetFilter !== null) {
|
|
109
|
+
sections.push("repository-wide — not filtered by target");
|
|
110
|
+
}
|
|
62
111
|
if (projectionText)
|
|
63
112
|
sections.push(projectionText.trimEnd());
|
|
64
113
|
if (receiptsText)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { activitySemanticClass } from "./tool-presentation.js";
|
|
3
|
+
const CLASS_ORDER = ["run", "read", "write", "edit", "search", "tool"];
|
|
4
|
+
export function presentLedgerPath(value, workspaceRoot) {
|
|
5
|
+
const candidate = value.trim();
|
|
6
|
+
if (!candidate)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!path.isAbsolute(candidate))
|
|
9
|
+
return candidate;
|
|
10
|
+
if (!workspaceRoot || !path.isAbsolute(workspaceRoot))
|
|
11
|
+
return candidate;
|
|
12
|
+
const relative = path.relative(workspaceRoot, candidate);
|
|
13
|
+
if (relative === "")
|
|
14
|
+
return ".";
|
|
15
|
+
if (path.isAbsolute(relative) || relative === ".." || relative.startsWith(`..${path.sep}`))
|
|
16
|
+
return candidate;
|
|
17
|
+
return relative.split(path.sep).join("/");
|
|
18
|
+
}
|
|
19
|
+
export function summarizeToolLedger(activities, turns, presentPath) {
|
|
20
|
+
const counts = new Map();
|
|
21
|
+
const files = new Map();
|
|
22
|
+
for (const row of activities) {
|
|
23
|
+
const semanticClass = activitySemanticClass(row.call);
|
|
24
|
+
counts.set(semanticClass, (counts.get(semanticClass) ?? 0) + 1);
|
|
25
|
+
if (row.call.kind !== "fileChange")
|
|
26
|
+
continue;
|
|
27
|
+
const pathDiffstat = row.call.changes.length === 1 && (row.call.omittedChanges ?? 0) === 0
|
|
28
|
+
? row.result?.diffstat
|
|
29
|
+
: undefined;
|
|
30
|
+
for (const change of row.call.changes) {
|
|
31
|
+
if (change.op === "delete")
|
|
32
|
+
continue;
|
|
33
|
+
const visible = presentPath(change.path);
|
|
34
|
+
if (!visible)
|
|
35
|
+
continue;
|
|
36
|
+
const fileClass = change.op === "add" ? "write" : "edit";
|
|
37
|
+
const prior = files.get(visible);
|
|
38
|
+
if (prior) {
|
|
39
|
+
prior.semanticClass = fileClass;
|
|
40
|
+
prior.count += 1;
|
|
41
|
+
prior.allHaveDiffstat &&= pathDiffstat !== undefined;
|
|
42
|
+
prior.additions += pathDiffstat?.additions ?? 0;
|
|
43
|
+
prior.deletions += pathDiffstat?.deletions ?? 0;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
files.set(visible, {
|
|
47
|
+
semanticClass: fileClass,
|
|
48
|
+
count: 1,
|
|
49
|
+
additions: pathDiffstat?.additions ?? 0,
|
|
50
|
+
deletions: pathDiffstat?.deletions ?? 0,
|
|
51
|
+
allHaveDiffstat: pathDiffstat !== undefined,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const visibleFiles = [...files].map(([visiblePath, file]) => ({
|
|
57
|
+
semanticClass: file.semanticClass,
|
|
58
|
+
path: visiblePath,
|
|
59
|
+
count: file.count,
|
|
60
|
+
...(file.allHaveDiffstat ? { diffstat: { additions: file.additions, deletions: file.deletions } } : {}),
|
|
61
|
+
}));
|
|
62
|
+
const tokenFacts = turns.filter((turn) => turn.inputTokens !== undefined && turn.outputTokens !== undefined);
|
|
63
|
+
const costFacts = turns.filter((turn) => turn.costUsd !== undefined);
|
|
64
|
+
return {
|
|
65
|
+
counts: CLASS_ORDER.flatMap((semanticClass) => {
|
|
66
|
+
const count = counts.get(semanticClass);
|
|
67
|
+
return count === undefined ? [] : [{ semanticClass, count }];
|
|
68
|
+
}),
|
|
69
|
+
files: visibleFiles.slice(0, 10),
|
|
70
|
+
hiddenFileCount: Math.max(0, visibleFiles.length - 10),
|
|
71
|
+
...(tokenFacts.length ? { tokens: tokenFacts.reduce((sum, turn) => sum + turn.inputTokens + turn.outputTokens, 0) } : {}),
|
|
72
|
+
...(costFacts.length ? { costUsd: costFacts.reduce((sum, turn) => sum + turn.costUsd, 0) } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|