@astrosheep/keiyaku 2.9.6 → 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/harness/outcome.js +10 -0
- 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/contract/bind/handler.js +10 -4
- package/build/cli/commands/contract/bind/meta.js +6 -6
- package/build/cli/commands/contract/petition/handler.js +16 -3
- package/build/cli/commands/contract/petition/meta.js +4 -4
- package/build/cli/commands/metadata.js +3 -2
- package/build/cli/commands/projection/status/handler.js +5 -4
- package/build/cli/commands/projection/status/meta.js +2 -2
- package/build/cli/commands/projection/tell/handler.js +10 -2
- package/build/cli/commands/projection/tell/meta.js +3 -3
- package/build/cli/commands/task/add/meta.js +9 -1
- package/build/cli/commands/task/catalog.js +2 -0
- package/build/cli/commands/task/log/handler.js +12 -0
- package/build/cli/commands/task/log/meta.js +8 -0
- package/build/cli/commands/task/shared.js +2 -1
- package/build/cli/completion.js +8 -0
- package/build/cli/flags.js +8 -0
- package/build/cli/index.js +10 -6
- package/build/cli/parse-flags.js +6 -0
- package/build/cli/parse-metadata.js +1 -1
- package/build/cli/render/kanshi.js +2 -2
- package/build/cli/render/line-width.js +33 -0
- package/build/cli/render/path-prefix-compaction.js +131 -0
- package/build/cli/render/petition.js +4 -0
- package/build/cli/render/projection-activity.js +103 -21
- package/build/cli/render/shared.js +38 -15
- package/build/cli/render/status.js +8 -5
- package/build/cli/render/success-response.js +2 -0
- package/build/cli/render/tool-presentation.js +3 -3
- package/build/cli/render/wait.js +68 -48
- package/build/cli/subagent-guard.js +3 -0
- package/build/cli/types.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/bind.js +111 -6
- package/build/core/call/context.js +19 -3
- package/build/core/call/execution.js +43 -20
- package/build/core/draft.js +1 -1
- package/build/core/ledger-batch.js +194 -0
- package/build/core/projection/generation/database.js +43 -2
- package/build/core/projection/generation/model.js +24 -3
- package/build/core/projection/generation/projection-generation-continuation.js +75 -10
- package/build/core/projection/generation/projection-generation-execution.js +46 -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 +145 -66
- package/build/core/projection/generation/projection-generation-runtime.js +93 -11
- package/build/core/projection/generation/store.js +26 -1
- package/build/core/projection/generation/transitions.js +193 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +1 -1
- package/build/core/projection/projection-kill.js +53 -10
- package/build/core/projection/projection-life-protocol.js +10 -0
- package/build/core/projection/projection-runner-lock.js +177 -37
- package/build/core/projection/projection-status.js +143 -55
- package/build/core/projection/projection-wait.js +53 -15
- package/build/core/projection/projection-wake.js +177 -46
- package/build/core/projection/tell/database.js +18 -0
- package/build/core/projection/tell/model.js +1 -0
- package/build/core/projection/tell/store.js +102 -55
- package/build/core/registry.js +82 -69
- package/build/core/scope.js +9 -9
- 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/index.js +2 -2
- package/build/core/task/task-contract.js +18 -0
- package/build/core/task/task-git-runtime.js +8 -10
- package/build/core/task/task-git-store.js +55 -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 +28 -0
- package/skills/keiyaku-workflow/SKILL.md +142 -18
|
@@ -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"
|
|
@@ -54,6 +54,39 @@ export function truncateColumns(value, maxColumns) {
|
|
|
54
54
|
}
|
|
55
55
|
return `${output}…`;
|
|
56
56
|
}
|
|
57
|
+
/** Truncate descriptive command text in the middle while retaining both ends. */
|
|
58
|
+
export function truncateMiddleColumns(value, maxColumns) {
|
|
59
|
+
if (maxColumns <= 0)
|
|
60
|
+
return "";
|
|
61
|
+
if (displayColumns(value) <= maxColumns)
|
|
62
|
+
return value;
|
|
63
|
+
if (maxColumns === 1)
|
|
64
|
+
return "…";
|
|
65
|
+
const budget = maxColumns - 1;
|
|
66
|
+
const leftBudget = Math.ceil(budget / 2);
|
|
67
|
+
const rightBudget = Math.floor(budget / 2);
|
|
68
|
+
let left = "";
|
|
69
|
+
let width = 0;
|
|
70
|
+
for (const character of value) {
|
|
71
|
+
const next = characterColumns(character);
|
|
72
|
+
if (width + next > leftBudget)
|
|
73
|
+
break;
|
|
74
|
+
left += character;
|
|
75
|
+
width += next;
|
|
76
|
+
}
|
|
77
|
+
const chars = [...value];
|
|
78
|
+
let right = "";
|
|
79
|
+
width = 0;
|
|
80
|
+
for (let index = chars.length - 1; index >= 0; index -= 1) {
|
|
81
|
+
const character = chars[index];
|
|
82
|
+
const next = characterColumns(character);
|
|
83
|
+
if (width + next > rightBudget)
|
|
84
|
+
break;
|
|
85
|
+
right = character + right;
|
|
86
|
+
width += next;
|
|
87
|
+
}
|
|
88
|
+
return `${left}…${right}`;
|
|
89
|
+
}
|
|
57
90
|
export function resolveLineColumns(stream = process.stdout) {
|
|
58
91
|
return stream.isTTY === true && Number.isInteger(stream.columns) && stream.columns > 0
|
|
59
92
|
? stream.columns
|
|
@@ -0,0 +1,131 @@
|
|
|
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("")}"`;
|
|
9
|
+
}
|
|
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
|
+
};
|
|
24
|
+
}
|
|
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
|
+
];
|
|
34
|
+
}
|
|
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
|
+
}
|
|
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
|
+
}
|
|
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 };
|
|
55
|
+
}
|
|
56
|
+
return { rows: fullValues.map(renderExactPath), visibleCount: fullValues.length };
|
|
57
|
+
}
|
|
58
|
+
function requiresQuoting(value) {
|
|
59
|
+
if (value.length === 0 || /^\s|\s$/u.test(value))
|
|
60
|
+
return true;
|
|
61
|
+
return value.includes("\\")
|
|
62
|
+
|| value.includes('"')
|
|
63
|
+
|| value.includes(PREFIX_MARKER)
|
|
64
|
+
|| [...value].some(isControlCharacter);
|
|
65
|
+
}
|
|
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("");
|
|
92
|
+
}
|
|
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;
|
|
123
|
+
}
|
|
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;
|
|
131
|
+
}
|
|
@@ -4,6 +4,10 @@ import { assembleResponse, buildPayload, buildSection, formatMaybe, formatWarnin
|
|
|
4
4
|
import { DIFF_OVERVIEW_SECTION_TITLE, RESPONSE_MARKERS, WARNING_SECTION_TITLE } from "./response-style.js";
|
|
5
5
|
import { INFO_COMMIT_MAX_CHARS, INFO_PLEA_MAX_CHARS, } from "./shared.js";
|
|
6
6
|
import { prependAddressReceipt } from "./address.js";
|
|
7
|
+
/** Public consequence for a completed renew phase of petition --renew. */
|
|
8
|
+
export function renderPetitionRenewedConsequence(input) {
|
|
9
|
+
return `renewed onto ${input.newBase.slice(0, 8)} · ${input.sealed?.receipts.length ?? 0} receipts replayed`;
|
|
10
|
+
}
|
|
7
11
|
function formatPetitionSealSource(source) {
|
|
8
12
|
return source.arc === null
|
|
9
13
|
? `no open arc · fence ${source.fence}`
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { compactText } from "./compact-text.js";
|
|
2
|
-
import {
|
|
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)
|
|
@@ -33,6 +34,48 @@ function elapsedLabel(anchorMs, at) {
|
|
|
33
34
|
return "now";
|
|
34
35
|
return formatTimelineDuration(Math.max(0, anchorMs - eventMs));
|
|
35
36
|
}
|
|
37
|
+
export function formatWallClock(at) {
|
|
38
|
+
const value = new Date(at);
|
|
39
|
+
if (!Number.isFinite(value.getTime()))
|
|
40
|
+
return " : │";
|
|
41
|
+
return `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}│`;
|
|
42
|
+
}
|
|
43
|
+
function commonPrefix(left, right) {
|
|
44
|
+
const a = [...left];
|
|
45
|
+
const b = [...right];
|
|
46
|
+
let index = 0;
|
|
47
|
+
while (index < a.length && index < b.length && a[index] === b[index])
|
|
48
|
+
index += 1;
|
|
49
|
+
return a.slice(0, index).join("");
|
|
50
|
+
}
|
|
51
|
+
function compressedPrefix(prefix) {
|
|
52
|
+
const points = [...prefix];
|
|
53
|
+
if (points.length < 24)
|
|
54
|
+
return undefined;
|
|
55
|
+
let cut = -1;
|
|
56
|
+
for (let index = points.length - 1; index >= 0; index -= 1) {
|
|
57
|
+
if (points[index] === " ") {
|
|
58
|
+
cut = index;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (cut < 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
const stable = points.slice(0, cut).join("");
|
|
65
|
+
const stablePoints = [...stable];
|
|
66
|
+
if (stablePoints.length < 24)
|
|
67
|
+
return undefined;
|
|
68
|
+
return {
|
|
69
|
+
text: `${stablePoints.slice(0, 10).join("")}…${stablePoints.slice(-14).join("")}`,
|
|
70
|
+
resumeAt: cut + 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function timelineGutter(label, spine) {
|
|
74
|
+
return label || `${"".padStart(TIME_GUTTER_WIDTH)}${spine}`;
|
|
75
|
+
}
|
|
76
|
+
function renderTimelineGap(omittedEventCount, maxColumns) {
|
|
77
|
+
return fitVariableLine(`${timelineGutter("", TIMELINE_GAP_MARKER)} `, `${omittedEventCount} more`, maxColumns);
|
|
78
|
+
}
|
|
36
79
|
function compactTokens(value) {
|
|
37
80
|
if (value < 1_000)
|
|
38
81
|
return String(value);
|
|
@@ -92,22 +135,28 @@ export function renderProjectionActivitySummary(activity, input) {
|
|
|
92
135
|
function renderTerminalRollup(activity, input) {
|
|
93
136
|
const summary = summarizeProjectionToolLedger(activity, input.workspaceRoot);
|
|
94
137
|
const segments = projectionSummarySegments(summary);
|
|
95
|
-
const
|
|
96
|
-
? [
|
|
138
|
+
const rows = input.diagnostic
|
|
139
|
+
? [{
|
|
140
|
+
kind: "diagnostic",
|
|
141
|
+
line: fitVariableLine(`${elapsedLabel(input.anchorMs, input.diagnostic.at).padStart(TIME_GUTTER_WIDTH)}┆ ${"warning".padEnd(VERB_WIDTH)} `, compactText(input.diagnostic.text), input.maxColumns),
|
|
142
|
+
}]
|
|
97
143
|
: [];
|
|
98
144
|
const endPrefix = `${"end".padStart(TIME_GUTTER_WIDTH)}└`;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
:
|
|
145
|
+
rows.push({
|
|
146
|
+
kind: "end-anchor",
|
|
147
|
+
line: segments.length > 0
|
|
148
|
+
? fitVariableLine(`${endPrefix} `, segments.join(" · "), input.maxColumns)
|
|
149
|
+
: endPrefix,
|
|
150
|
+
});
|
|
102
151
|
for (const file of summary.files) {
|
|
103
152
|
const repeat = file.count > 1 ? ` ×${file.count}` : "";
|
|
104
153
|
const diffstat = file.diffstat ? ` +${file.diffstat.additions} −${file.diffstat.deletions}` : "";
|
|
105
|
-
|
|
154
|
+
rows.push({ kind: "file", line: fitVariableLine("", `${file.path}${repeat}${diffstat}`, input.maxColumns) });
|
|
106
155
|
}
|
|
107
156
|
if (summary.hiddenFileCount > 0) {
|
|
108
|
-
|
|
157
|
+
rows.push({ kind: "file", line: fitVariableLine("", `… ${summary.hiddenFileCount} more files`, input.maxColumns) });
|
|
109
158
|
}
|
|
110
|
-
return {
|
|
159
|
+
return { rows, hasMutationFiles: summary.files.length > 0 };
|
|
111
160
|
}
|
|
112
161
|
function timelineEntries(activity) {
|
|
113
162
|
const entries = [];
|
|
@@ -124,26 +173,42 @@ function timelineEntries(activity) {
|
|
|
124
173
|
}
|
|
125
174
|
export function renderProjectionActivity(activity, input) {
|
|
126
175
|
const lines = [];
|
|
176
|
+
const rowKinds = [];
|
|
127
177
|
let priorAtMs;
|
|
178
|
+
let priorOrder;
|
|
179
|
+
let priorMinute;
|
|
180
|
+
let priorCompletedRan;
|
|
128
181
|
let hasEndAnchor = false;
|
|
129
182
|
let lastRunningTool;
|
|
130
183
|
const maxColumns = input.maxColumns ?? resolveLineColumns();
|
|
131
184
|
const entries = timelineEntries(activity);
|
|
185
|
+
const pushRow = (line, kind) => {
|
|
186
|
+
lines.push(line);
|
|
187
|
+
rowKinds.push(kind);
|
|
188
|
+
return true;
|
|
189
|
+
};
|
|
132
190
|
for (let index = 0; index < entries.length; index += 1) {
|
|
133
191
|
const entry = entries[index];
|
|
134
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;
|
|
135
197
|
if (entry.kind === "clock") {
|
|
136
198
|
if (Number.isFinite(atMs))
|
|
137
199
|
priorAtMs = atMs;
|
|
138
200
|
continue;
|
|
139
201
|
}
|
|
140
202
|
if (priorAtMs !== undefined && Number.isFinite(atMs) && atMs - priorAtMs >= SILENCE_THRESHOLD_MS) {
|
|
141
|
-
|
|
203
|
+
pushRow(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}┆ `, `${formatTimelineDuration(atMs - priorAtMs)} pass in silence`, maxColumns), "silence");
|
|
142
204
|
}
|
|
143
205
|
if (Number.isFinite(atMs))
|
|
144
206
|
priorAtMs = atMs;
|
|
145
207
|
const row = entry.row;
|
|
146
|
-
const
|
|
208
|
+
const minute = row.kind === "turn" ? "" : formatWallClock(row.at);
|
|
209
|
+
const label = row.kind === "turn" || minute === priorMinute ? "" : minute;
|
|
210
|
+
if (minute)
|
|
211
|
+
priorMinute = minute;
|
|
147
212
|
const spine = row.kind === "turn"
|
|
148
213
|
? "├"
|
|
149
214
|
: input.state === "dead" || input.state === "lost"
|
|
@@ -159,27 +224,41 @@ export function renderProjectionActivity(activity, input) {
|
|
|
159
224
|
? "unresolved"
|
|
160
225
|
: "running";
|
|
161
226
|
const presentation = presentFoldedToolActivity(row, { state: activityState, ...(duration ? { duration } : {}) }, (value) => presentLedgerPath(value, input.workspaceRoot) ?? value);
|
|
162
|
-
const fixed = `${label
|
|
163
|
-
|
|
227
|
+
const fixed = `${timelineGutter(label, spine)} ${(presentation.semanticClass === "run" ? "ran" : presentation.semanticClass).padEnd(VERB_WIDTH)} `;
|
|
228
|
+
let variable = presentation.variableText;
|
|
229
|
+
if (row.state === "completed" && priorCompletedRan !== undefined) {
|
|
230
|
+
const common = commonPrefix(priorCompletedRan, variable);
|
|
231
|
+
const prefix = compressedPrefix(common);
|
|
232
|
+
if (prefix) {
|
|
233
|
+
variable = `${prefix.text} ${[...variable].slice(prefix.resumeAt).join("")}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
priorCompletedRan = row.state === "completed" ? presentation.variableText : undefined;
|
|
237
|
+
const budget = Math.max(0, maxColumns - displayColumns(fixed) - displayColumns(presentation.fixedSuffix));
|
|
238
|
+
pushRow(`${fixed}${truncateMiddleColumns(variable, budget)}${presentation.fixedSuffix}`, "activity");
|
|
164
239
|
continue;
|
|
165
240
|
}
|
|
166
241
|
if (row.kind === "said") {
|
|
167
|
-
|
|
242
|
+
priorCompletedRan = undefined;
|
|
243
|
+
pushRow(fitVariableLine(`${timelineGutter(label, spine)} ${"said".padEnd(VERB_WIDTH)} `, `“${compactText(row.text)}”`, maxColumns), "activity");
|
|
168
244
|
}
|
|
169
245
|
else if (row.kind === "thought") {
|
|
246
|
+
priorCompletedRan = undefined;
|
|
170
247
|
const text = `${compactText(row.text)}${row.truncated && !row.text.endsWith("…") ? "…" : ""}`;
|
|
171
248
|
const duration = formatActionDuration(row.durationMs);
|
|
172
|
-
|
|
249
|
+
pushRow(fitVariableLine(`${timelineGutter(label, spine)} ${"thought".padEnd(VERB_WIDTH)} `, `${duration ? `${duration} — ` : ""}“${text}”`, maxColumns), "activity");
|
|
173
250
|
}
|
|
174
251
|
else if (row.kind === "warning") {
|
|
175
|
-
|
|
252
|
+
priorCompletedRan = undefined;
|
|
253
|
+
pushRow(fitVariableLine(`${timelineGutter(label, spine)} ${"warning".padEnd(VERB_WIDTH)} `, warningText(row), maxColumns), "activity");
|
|
176
254
|
}
|
|
177
255
|
else {
|
|
256
|
+
priorCompletedRan = undefined;
|
|
178
257
|
const usage = row.inputTokens !== undefined && row.outputTokens !== undefined
|
|
179
258
|
? ` · ${compactTokens(row.inputTokens)}▸${compactTokens(row.outputTokens)} tok`
|
|
180
259
|
: "";
|
|
181
260
|
const cost = row.costUsd !== undefined ? ` · $${row.costUsd.toFixed(2)}` : "";
|
|
182
|
-
|
|
261
|
+
pushRow(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}├ `, `turn ${row.turn}${usage}${cost}`, maxColumns), "activity");
|
|
183
262
|
}
|
|
184
263
|
}
|
|
185
264
|
for (const row of activity.pinned) {
|
|
@@ -189,9 +268,9 @@ export function renderProjectionActivity(activity, input) {
|
|
|
189
268
|
if (input.showPlan && activity.plan) {
|
|
190
269
|
const planAtMs = Date.parse(activity.plan.at);
|
|
191
270
|
if (priorAtMs !== undefined && Number.isFinite(planAtMs) && planAtMs - priorAtMs >= SILENCE_THRESHOLD_MS) {
|
|
192
|
-
|
|
271
|
+
pushRow(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}┆ `, `${formatTimelineDuration(planAtMs - priorAtMs)} pass in silence`, maxColumns), "silence");
|
|
193
272
|
}
|
|
194
|
-
|
|
273
|
+
pushRow(renderPlan(activity.plan, maxColumns), "plan");
|
|
195
274
|
}
|
|
196
275
|
let hasMutationFiles = false;
|
|
197
276
|
if (["returned", "dead", "lost", "dismissed"].includes(input.state)) {
|
|
@@ -201,12 +280,15 @@ export function renderProjectionActivity(activity, input) {
|
|
|
201
280
|
...(input.terminalDiagnostic ? { diagnostic: input.terminalDiagnostic } : {}),
|
|
202
281
|
maxColumns,
|
|
203
282
|
});
|
|
204
|
-
|
|
283
|
+
for (const row of rollup.rows) {
|
|
284
|
+
pushRow(row.line, row.kind);
|
|
285
|
+
}
|
|
205
286
|
hasEndAnchor = true;
|
|
206
287
|
hasMutationFiles = rollup.hasMutationFiles;
|
|
207
288
|
}
|
|
208
289
|
return {
|
|
209
290
|
lines,
|
|
291
|
+
rowKinds,
|
|
210
292
|
...(lastRunningTool ? { lastRunningTool } : {}),
|
|
211
293
|
hasEndAnchor,
|
|
212
294
|
hasMutationFiles,
|
|
@@ -3,6 +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 { renderPathPreview, renderPatternPair } from "./path-prefix-compaction.js";
|
|
6
7
|
export function textResponse(text) {
|
|
7
8
|
const payload = {};
|
|
8
9
|
return {
|
|
@@ -24,18 +25,26 @@ export function buildBindDraftResponse(draftPath) {
|
|
|
24
25
|
const payload = { draftPath };
|
|
25
26
|
return coreTextResponse(`Draft saved to ${draftPath}.`, payload);
|
|
26
27
|
}
|
|
28
|
+
export function buildBindPreviewResponse(result) {
|
|
29
|
+
const overlapWarnings = renderScopeOverlapWarnings(result.scopeOverlaps);
|
|
30
|
+
const payload = {
|
|
31
|
+
id: result.id,
|
|
32
|
+
canonicalDraft: result.canonicalDraft,
|
|
33
|
+
target: result.target,
|
|
34
|
+
place: result.place,
|
|
35
|
+
workspace: result.workspace,
|
|
36
|
+
after: result.after,
|
|
37
|
+
warnings: overlapWarnings,
|
|
38
|
+
scopeOverlaps: result.scopeOverlaps,
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
...coreTextResponse(result.canonicalDraft, payload),
|
|
42
|
+
...(overlapWarnings.length > 0 ? { stderr: ["Warnings:", ...overlapWarnings].join("\n") } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
27
45
|
export function buildBindResponse(result, cwd) {
|
|
28
46
|
void cwd;
|
|
29
|
-
const
|
|
30
|
-
for (const overlap of result.scopeOverlaps) {
|
|
31
|
-
const counterparty = overlap.contracts[1];
|
|
32
|
-
const group = overlapsByCounterparty.get(counterparty);
|
|
33
|
-
if (group)
|
|
34
|
-
group.push(overlap);
|
|
35
|
-
else
|
|
36
|
-
overlapsByCounterparty.set(counterparty, [overlap]);
|
|
37
|
-
}
|
|
38
|
-
const overlapWarnings = [...overlapsByCounterparty.entries()].map(([contractId, overlaps]) => renderScopeOverlapGroup(commissionSlug(contractId), overlaps));
|
|
47
|
+
const overlapWarnings = renderScopeOverlapWarnings(result.scopeOverlaps);
|
|
39
48
|
const place = result.place;
|
|
40
49
|
const payload = {
|
|
41
50
|
id: result.id,
|
|
@@ -54,16 +63,30 @@ export function buildBindResponse(result, cwd) {
|
|
|
54
63
|
...(overlapWarnings.length > 0 ? ["Warnings:", ...overlapWarnings] : []),
|
|
55
64
|
].join("\n"), payload);
|
|
56
65
|
}
|
|
66
|
+
function renderScopeOverlapWarnings(overlaps) {
|
|
67
|
+
const overlapsByCounterparty = new Map();
|
|
68
|
+
for (const overlap of overlaps) {
|
|
69
|
+
const counterparty = overlap.contracts[1];
|
|
70
|
+
const group = overlapsByCounterparty.get(counterparty);
|
|
71
|
+
if (group)
|
|
72
|
+
group.push(overlap);
|
|
73
|
+
else
|
|
74
|
+
overlapsByCounterparty.set(counterparty, [overlap]);
|
|
75
|
+
}
|
|
76
|
+
return [...overlapsByCounterparty.entries()].map(([contractId, overlaps]) => renderScopeOverlapGroup(commissionSlug(contractId), overlaps));
|
|
77
|
+
}
|
|
57
78
|
function renderScopeOverlapGroup(contractAddr, overlaps) {
|
|
58
|
-
const
|
|
79
|
+
const nextAddr = commissionSlug(overlaps[0].contracts[0]);
|
|
80
|
+
const pairs = overlaps.flatMap((overlap) => {
|
|
59
81
|
const [nextPattern, existingPattern] = overlap.patterns;
|
|
82
|
+
const patternPair = renderPatternPair(nextAddr, nextPattern, contractAddr, existingPattern);
|
|
60
83
|
const evidence = overlap.kind === "potential"
|
|
61
|
-
? "potential
|
|
62
|
-
:
|
|
63
|
-
return
|
|
84
|
+
? ["potential · no current tracked path proves it"]
|
|
85
|
+
: renderPathPreview(overlap.paths);
|
|
86
|
+
return [...patternPair, ...evidence].map((line) => ` ${line}`);
|
|
64
87
|
});
|
|
65
88
|
return [
|
|
66
|
-
`▲
|
|
89
|
+
`▲ overlap ${nextAddr} ↔ ${contractAddr}`,
|
|
67
90
|
...pairs,
|
|
68
91
|
` consider --after ${contractAddr}`,
|
|
69
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
|
}
|
|
@@ -62,6 +62,8 @@ export async function writeSuccessResponse(command, flags, response) {
|
|
|
62
62
|
if (command === "bind" && flags.dryRun) {
|
|
63
63
|
// Canonical Markdown already ends with exactly one newline.
|
|
64
64
|
await writeCliOutput(process.stdout, responseText(response));
|
|
65
|
+
if (response.stderr)
|
|
66
|
+
await writeCliOutput(process.stderr, `${response.stderr}\n`);
|
|
65
67
|
return;
|
|
66
68
|
}
|
|
67
69
|
if (command !== "revive") {
|
|
@@ -53,9 +53,9 @@ export function presentToolActivity(input) {
|
|
|
53
53
|
}
|
|
54
54
|
else if (input.state === "running") {
|
|
55
55
|
if (message)
|
|
56
|
-
variableText = `${primary} —
|
|
56
|
+
variableText = `${primary} — ${message}`;
|
|
57
57
|
else
|
|
58
|
-
fixedSuffix =
|
|
58
|
+
fixedSuffix = ` — ${input.duration ?? "0s"}…`;
|
|
59
59
|
}
|
|
60
60
|
else if (input.result?.status === "error") {
|
|
61
61
|
if (input.result.exitCode !== undefined) {
|
|
@@ -70,7 +70,7 @@ export function presentToolActivity(input) {
|
|
|
70
70
|
fixedSuffix = ` — +${input.result.diffstat.additions} −${input.result.diffstat.deletions}`;
|
|
71
71
|
}
|
|
72
72
|
else if (input.call.kind === "run") {
|
|
73
|
-
fixedSuffix =
|
|
73
|
+
fixedSuffix = input.duration ? ` — ${input.duration}` : "";
|
|
74
74
|
}
|
|
75
75
|
else if (input.call.kind === "other") {
|
|
76
76
|
fixedSuffix = " — done";
|