@astrosheep/keiyaku 2.9.6 → 2.9.7
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/outcome.js +10 -0
- 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/tell/handler.js +10 -2
- package/build/cli/commands/projection/tell/meta.js +3 -3
- 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/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/line-width.js +33 -0
- package/build/cli/render/path-prefix-compaction.js +88 -0
- package/build/cli/render/petition.js +4 -0
- package/build/cli/render/projection-activity.js +93 -20
- package/build/cli/render/shared.js +34 -12
- 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/core/bind.js +111 -6
- package/build/core/draft.js +1 -1
- package/build/core/projection/generation/database.js +21 -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 +4 -3
- package/build/core/projection/generation/projection-generation-runner.js +96 -53
- package/build/core/projection/generation/projection-generation-runtime.js +19 -0
- package/build/core/projection/generation/store.js +9 -0
- package/build/core/projection/generation/transitions.js +105 -3
- package/build/core/projection/index.js +2 -2
- package/build/core/projection/projection-core.js +1 -1
- package/build/core/projection/projection-kill.js +31 -0
- package/build/core/projection/projection-life-protocol.js +10 -0
- package/build/core/projection/projection-wait.js +53 -15
- package/build/core/projection/projection-wake.js +35 -3
- 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/task/index.js +2 -2
- package/build/core/task/task-contract.js +18 -0
- package/build/core/task/task-git-store.js +50 -0
- package/build/generated/version.js +2 -2
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +10 -0
- package/skills/keiyaku-workflow/SKILL.md +76 -7
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
const TIME_GUTTER_WIDTH = 6;
|
|
@@ -33,6 +33,45 @@ function elapsedLabel(anchorMs, at) {
|
|
|
33
33
|
return "now";
|
|
34
34
|
return formatTimelineDuration(Math.max(0, anchorMs - eventMs));
|
|
35
35
|
}
|
|
36
|
+
export function formatWallClock(at) {
|
|
37
|
+
const value = new Date(at);
|
|
38
|
+
if (!Number.isFinite(value.getTime()))
|
|
39
|
+
return " : │";
|
|
40
|
+
return `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}│`;
|
|
41
|
+
}
|
|
42
|
+
function commonPrefix(left, right) {
|
|
43
|
+
const a = [...left];
|
|
44
|
+
const b = [...right];
|
|
45
|
+
let index = 0;
|
|
46
|
+
while (index < a.length && index < b.length && a[index] === b[index])
|
|
47
|
+
index += 1;
|
|
48
|
+
return a.slice(0, index).join("");
|
|
49
|
+
}
|
|
50
|
+
function compressedPrefix(prefix) {
|
|
51
|
+
const points = [...prefix];
|
|
52
|
+
if (points.length < 24)
|
|
53
|
+
return undefined;
|
|
54
|
+
let cut = -1;
|
|
55
|
+
for (let index = points.length - 1; index >= 0; index -= 1) {
|
|
56
|
+
if (points[index] === " ") {
|
|
57
|
+
cut = index;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (cut < 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
const stable = points.slice(0, cut).join("");
|
|
64
|
+
const stablePoints = [...stable];
|
|
65
|
+
if (stablePoints.length < 24)
|
|
66
|
+
return undefined;
|
|
67
|
+
return {
|
|
68
|
+
text: `${stablePoints.slice(0, 10).join("")}…${stablePoints.slice(-14).join("")}`,
|
|
69
|
+
resumeAt: cut + 1,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function timelineGutter(label, spine) {
|
|
73
|
+
return label || `${"".padStart(TIME_GUTTER_WIDTH)}${spine}`;
|
|
74
|
+
}
|
|
36
75
|
function compactTokens(value) {
|
|
37
76
|
if (value < 1_000)
|
|
38
77
|
return String(value);
|
|
@@ -92,22 +131,28 @@ export function renderProjectionActivitySummary(activity, input) {
|
|
|
92
131
|
function renderTerminalRollup(activity, input) {
|
|
93
132
|
const summary = summarizeProjectionToolLedger(activity, input.workspaceRoot);
|
|
94
133
|
const segments = projectionSummarySegments(summary);
|
|
95
|
-
const
|
|
96
|
-
? [
|
|
134
|
+
const rows = input.diagnostic
|
|
135
|
+
? [{
|
|
136
|
+
kind: "diagnostic",
|
|
137
|
+
line: fitVariableLine(`${elapsedLabel(input.anchorMs, input.diagnostic.at).padStart(TIME_GUTTER_WIDTH)}┆ ${"warning".padEnd(VERB_WIDTH)} `, compactText(input.diagnostic.text), input.maxColumns),
|
|
138
|
+
}]
|
|
97
139
|
: [];
|
|
98
140
|
const endPrefix = `${"end".padStart(TIME_GUTTER_WIDTH)}└`;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
:
|
|
141
|
+
rows.push({
|
|
142
|
+
kind: "end-anchor",
|
|
143
|
+
line: segments.length > 0
|
|
144
|
+
? fitVariableLine(`${endPrefix} `, segments.join(" · "), input.maxColumns)
|
|
145
|
+
: endPrefix,
|
|
146
|
+
});
|
|
102
147
|
for (const file of summary.files) {
|
|
103
148
|
const repeat = file.count > 1 ? ` ×${file.count}` : "";
|
|
104
149
|
const diffstat = file.diffstat ? ` +${file.diffstat.additions} −${file.diffstat.deletions}` : "";
|
|
105
|
-
|
|
150
|
+
rows.push({ kind: "file", line: fitVariableLine("", `${file.path}${repeat}${diffstat}`, input.maxColumns) });
|
|
106
151
|
}
|
|
107
152
|
if (summary.hiddenFileCount > 0) {
|
|
108
|
-
|
|
153
|
+
rows.push({ kind: "file", line: fitVariableLine("", `… ${summary.hiddenFileCount} more files`, input.maxColumns) });
|
|
109
154
|
}
|
|
110
|
-
return {
|
|
155
|
+
return { rows, hasMutationFiles: summary.files.length > 0 };
|
|
111
156
|
}
|
|
112
157
|
function timelineEntries(activity) {
|
|
113
158
|
const entries = [];
|
|
@@ -124,11 +169,19 @@ function timelineEntries(activity) {
|
|
|
124
169
|
}
|
|
125
170
|
export function renderProjectionActivity(activity, input) {
|
|
126
171
|
const lines = [];
|
|
172
|
+
const rowKinds = [];
|
|
127
173
|
let priorAtMs;
|
|
174
|
+
let priorMinute;
|
|
175
|
+
let priorCompletedRan;
|
|
128
176
|
let hasEndAnchor = false;
|
|
129
177
|
let lastRunningTool;
|
|
130
178
|
const maxColumns = input.maxColumns ?? resolveLineColumns();
|
|
131
179
|
const entries = timelineEntries(activity);
|
|
180
|
+
const pushRow = (line, kind) => {
|
|
181
|
+
lines.push(line);
|
|
182
|
+
rowKinds.push(kind);
|
|
183
|
+
return true;
|
|
184
|
+
};
|
|
132
185
|
for (let index = 0; index < entries.length; index += 1) {
|
|
133
186
|
const entry = entries[index];
|
|
134
187
|
const atMs = Date.parse(entry.at);
|
|
@@ -138,12 +191,15 @@ export function renderProjectionActivity(activity, input) {
|
|
|
138
191
|
continue;
|
|
139
192
|
}
|
|
140
193
|
if (priorAtMs !== undefined && Number.isFinite(atMs) && atMs - priorAtMs >= SILENCE_THRESHOLD_MS) {
|
|
141
|
-
|
|
194
|
+
pushRow(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}┆ `, `${formatTimelineDuration(atMs - priorAtMs)} pass in silence`, maxColumns), "silence");
|
|
142
195
|
}
|
|
143
196
|
if (Number.isFinite(atMs))
|
|
144
197
|
priorAtMs = atMs;
|
|
145
198
|
const row = entry.row;
|
|
146
|
-
const
|
|
199
|
+
const minute = row.kind === "turn" ? "" : formatWallClock(row.at);
|
|
200
|
+
const label = row.kind === "turn" || minute === priorMinute ? "" : minute;
|
|
201
|
+
if (minute)
|
|
202
|
+
priorMinute = minute;
|
|
147
203
|
const spine = row.kind === "turn"
|
|
148
204
|
? "├"
|
|
149
205
|
: input.state === "dead" || input.state === "lost"
|
|
@@ -159,27 +215,41 @@ export function renderProjectionActivity(activity, input) {
|
|
|
159
215
|
? "unresolved"
|
|
160
216
|
: "running";
|
|
161
217
|
const presentation = presentFoldedToolActivity(row, { state: activityState, ...(duration ? { duration } : {}) }, (value) => presentLedgerPath(value, input.workspaceRoot) ?? value);
|
|
162
|
-
const fixed = `${label
|
|
163
|
-
|
|
218
|
+
const fixed = `${timelineGutter(label, spine)} ${(presentation.semanticClass === "run" ? "ran" : presentation.semanticClass).padEnd(VERB_WIDTH)} `;
|
|
219
|
+
let variable = presentation.variableText;
|
|
220
|
+
if (row.state === "completed" && priorCompletedRan !== undefined) {
|
|
221
|
+
const common = commonPrefix(priorCompletedRan, variable);
|
|
222
|
+
const prefix = compressedPrefix(common);
|
|
223
|
+
if (prefix) {
|
|
224
|
+
variable = `${prefix.text} ${[...variable].slice(prefix.resumeAt).join("")}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
priorCompletedRan = row.state === "completed" ? presentation.variableText : undefined;
|
|
228
|
+
const budget = Math.max(0, maxColumns - displayColumns(fixed) - displayColumns(presentation.fixedSuffix));
|
|
229
|
+
pushRow(`${fixed}${truncateMiddleColumns(variable, budget)}${presentation.fixedSuffix}`, "activity");
|
|
164
230
|
continue;
|
|
165
231
|
}
|
|
166
232
|
if (row.kind === "said") {
|
|
167
|
-
|
|
233
|
+
priorCompletedRan = undefined;
|
|
234
|
+
pushRow(fitVariableLine(`${timelineGutter(label, spine)} ${"said".padEnd(VERB_WIDTH)} `, `“${compactText(row.text)}”`, maxColumns), "activity");
|
|
168
235
|
}
|
|
169
236
|
else if (row.kind === "thought") {
|
|
237
|
+
priorCompletedRan = undefined;
|
|
170
238
|
const text = `${compactText(row.text)}${row.truncated && !row.text.endsWith("…") ? "…" : ""}`;
|
|
171
239
|
const duration = formatActionDuration(row.durationMs);
|
|
172
|
-
|
|
240
|
+
pushRow(fitVariableLine(`${timelineGutter(label, spine)} ${"thought".padEnd(VERB_WIDTH)} `, `${duration ? `${duration} — ` : ""}“${text}”`, maxColumns), "activity");
|
|
173
241
|
}
|
|
174
242
|
else if (row.kind === "warning") {
|
|
175
|
-
|
|
243
|
+
priorCompletedRan = undefined;
|
|
244
|
+
pushRow(fitVariableLine(`${timelineGutter(label, spine)} ${"warning".padEnd(VERB_WIDTH)} `, warningText(row), maxColumns), "activity");
|
|
176
245
|
}
|
|
177
246
|
else {
|
|
247
|
+
priorCompletedRan = undefined;
|
|
178
248
|
const usage = row.inputTokens !== undefined && row.outputTokens !== undefined
|
|
179
249
|
? ` · ${compactTokens(row.inputTokens)}▸${compactTokens(row.outputTokens)} tok`
|
|
180
250
|
: "";
|
|
181
251
|
const cost = row.costUsd !== undefined ? ` · $${row.costUsd.toFixed(2)}` : "";
|
|
182
|
-
|
|
252
|
+
pushRow(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}├ `, `turn ${row.turn}${usage}${cost}`, maxColumns), "activity");
|
|
183
253
|
}
|
|
184
254
|
}
|
|
185
255
|
for (const row of activity.pinned) {
|
|
@@ -189,9 +259,9 @@ export function renderProjectionActivity(activity, input) {
|
|
|
189
259
|
if (input.showPlan && activity.plan) {
|
|
190
260
|
const planAtMs = Date.parse(activity.plan.at);
|
|
191
261
|
if (priorAtMs !== undefined && Number.isFinite(planAtMs) && planAtMs - priorAtMs >= SILENCE_THRESHOLD_MS) {
|
|
192
|
-
|
|
262
|
+
pushRow(fitVariableLine(`${"".padStart(TIME_GUTTER_WIDTH)}┆ `, `${formatTimelineDuration(planAtMs - priorAtMs)} pass in silence`, maxColumns), "silence");
|
|
193
263
|
}
|
|
194
|
-
|
|
264
|
+
pushRow(renderPlan(activity.plan, maxColumns), "plan");
|
|
195
265
|
}
|
|
196
266
|
let hasMutationFiles = false;
|
|
197
267
|
if (["returned", "dead", "lost", "dismissed"].includes(input.state)) {
|
|
@@ -201,12 +271,15 @@ export function renderProjectionActivity(activity, input) {
|
|
|
201
271
|
...(input.terminalDiagnostic ? { diagnostic: input.terminalDiagnostic } : {}),
|
|
202
272
|
maxColumns,
|
|
203
273
|
});
|
|
204
|
-
|
|
274
|
+
for (const row of rollup.rows) {
|
|
275
|
+
pushRow(row.line, row.kind);
|
|
276
|
+
}
|
|
205
277
|
hasEndAnchor = true;
|
|
206
278
|
hasMutationFiles = rollup.hasMutationFiles;
|
|
207
279
|
}
|
|
208
280
|
return {
|
|
209
281
|
lines,
|
|
282
|
+
rowKinds,
|
|
210
283
|
...(lastRunningTool ? { lastRunningTool } : {}),
|
|
211
284
|
hasEndAnchor,
|
|
212
285
|
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 { compactPathPreview, compactPatternPair } 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,13 +63,26 @@ 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
79
|
const pairs = overlaps.map((overlap) => {
|
|
59
80
|
const [nextPattern, existingPattern] = overlap.patterns;
|
|
81
|
+
const patternPair = compactPatternPair(nextPattern, existingPattern);
|
|
60
82
|
const evidence = overlap.kind === "potential"
|
|
61
83
|
? "potential; no current tracked path proves it"
|
|
62
|
-
: `concrete; ${overlap.paths.count} current tracked path${overlap.paths.count === 1 ? "" : "s"}: ${overlap.paths.preview
|
|
63
|
-
return ` ${
|
|
84
|
+
: `concrete; ${overlap.paths.count} current tracked path${overlap.paths.count === 1 ? "" : "s"}: ${compactPathPreview(overlap.paths.preview)}${overlap.paths.truncated ? ", …" : ""}`;
|
|
85
|
+
return ` ${patternPair} (${evidence})`;
|
|
64
86
|
});
|
|
65
87
|
return [
|
|
66
88
|
`▲ scope overlap with ${contractAddr}:`,
|
|
@@ -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";
|
package/build/cli/render/wait.js
CHANGED
|
@@ -3,6 +3,27 @@ import { displayColumns, resolveLineColumns, truncateColumns } from "./line-widt
|
|
|
3
3
|
import { formatTellCommand, projectionRejoinHintLines, textResponse } from "./shared.js";
|
|
4
4
|
import { renderTerminalFailureFace } from "./terminal-failure.js";
|
|
5
5
|
const WAIT_HEADER_MAX_COLUMNS = 60;
|
|
6
|
+
const WAIT_BODY_MAX_ROWS = 10;
|
|
7
|
+
const WAIT_ROW_PRIORITY = {
|
|
8
|
+
"end-anchor": 100,
|
|
9
|
+
"storage-diagnostic": 90,
|
|
10
|
+
diagnostic: 90,
|
|
11
|
+
tell: 80,
|
|
12
|
+
summary: 70,
|
|
13
|
+
activity: 60,
|
|
14
|
+
plan: 55,
|
|
15
|
+
liveness: 50,
|
|
16
|
+
silence: 65,
|
|
17
|
+
file: 20,
|
|
18
|
+
};
|
|
19
|
+
/** One owner admits every wait-body row, then restores presentation order. */
|
|
20
|
+
export function selectWaitBodyRows(rows, limit = WAIT_BODY_MAX_ROWS) {
|
|
21
|
+
const selected = rows
|
|
22
|
+
.slice()
|
|
23
|
+
.sort((left, right) => WAIT_ROW_PRIORITY[right.kind] - WAIT_ROW_PRIORITY[left.kind] || left.order - right.order)
|
|
24
|
+
.slice(0, limit);
|
|
25
|
+
return selected.sort((left, right) => left.order - right.order);
|
|
26
|
+
}
|
|
6
27
|
function shortAddress(projectionId) {
|
|
7
28
|
return /\/([0-9a-f]{8})$/.exec(projectionId)?.[1] ?? projectionId;
|
|
8
29
|
}
|
|
@@ -26,29 +47,10 @@ function isTerminalPhase(phase) {
|
|
|
26
47
|
return ["done", "failed", "killed", "dismissed"].includes(phase);
|
|
27
48
|
}
|
|
28
49
|
function waitStateText(snapshot) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return `pulse ${pulse} · breathing ${formatTimelineDuration(snapshot.durationMs)}`;
|
|
34
|
-
}
|
|
35
|
-
case "minting":
|
|
36
|
-
return `starting · lived ${formatTimelineDuration(snapshot.durationMs)}`;
|
|
37
|
-
case "startup-timeout":
|
|
38
|
-
return `startup timed out · lived ${formatTimelineDuration(snapshot.durationMs)}`;
|
|
39
|
-
case "unknown":
|
|
40
|
-
return `state unknown · observed ${formatTimelineDuration(snapshot.durationMs)}`;
|
|
41
|
-
case "done":
|
|
42
|
-
return `returned · lived ${formatTimelineDuration(snapshot.durationMs)}`;
|
|
43
|
-
case "failed":
|
|
44
|
-
return `dead · lived ${formatTimelineDuration(snapshot.durationMs)}`;
|
|
45
|
-
case "killed":
|
|
46
|
-
return "killed";
|
|
47
|
-
case "dismissed":
|
|
48
|
-
return "dismissed";
|
|
49
|
-
case "lost":
|
|
50
|
-
return `no pulse ${snapshot.activityAgeMs === undefined ? "unknown" : formatTimelineDuration(snapshot.activityAgeMs)}`;
|
|
51
|
-
}
|
|
50
|
+
const idle = snapshot.activityAgeMs === undefined
|
|
51
|
+
? formatTimelineDuration(snapshot.durationMs)
|
|
52
|
+
: formatTimelineDuration(snapshot.activityAgeMs);
|
|
53
|
+
return `up ${formatTimelineDuration(snapshot.durationMs)} · idle ${idle}`;
|
|
52
54
|
}
|
|
53
55
|
function projectionIdentityParts(projectionId) {
|
|
54
56
|
const match = /^(.*)\/([0-9a-f]{8})$/.exec(projectionId);
|
|
@@ -56,8 +58,7 @@ function projectionIdentityParts(projectionId) {
|
|
|
56
58
|
}
|
|
57
59
|
function waitHeader(projectionId, akuma, snapshot, outputColumns) {
|
|
58
60
|
const maxColumns = Math.min(WAIT_HEADER_MAX_COLUMNS, outputColumns);
|
|
59
|
-
const
|
|
60
|
-
const state = `${waitStateText(snapshot)}${tellSuffix}`;
|
|
61
|
+
const state = waitStateText(snapshot);
|
|
61
62
|
const identity = projectionIdentityParts(projectionId);
|
|
62
63
|
// Slug often mirrors the Akuma name; print once when they are the same fact.
|
|
63
64
|
const description = identity.slug && akuma && identity.slug === akuma
|
|
@@ -69,6 +70,14 @@ function waitHeader(projectionId, akuma, snapshot, outputColumns) {
|
|
|
69
70
|
const dashCount = Math.max(1, maxColumns - displayColumns(`${prefix} ${state}`));
|
|
70
71
|
return `${prefix} ${"─".repeat(dashCount)} ${state}`;
|
|
71
72
|
}
|
|
73
|
+
function pendingTellLines(snapshot, maxColumns) {
|
|
74
|
+
return snapshot.tells.window.map((tell) => {
|
|
75
|
+
const age = Math.max(0, snapshot.observedAtMs - Date.parse(tell.createdAt));
|
|
76
|
+
const fixed = `${"".padStart(6)}│ ${"tell".padEnd(7)} `;
|
|
77
|
+
const suffix = ` · undelivered ${formatTimelineDuration(Number.isFinite(age) ? age : 0)}`;
|
|
78
|
+
return `${fixed}${truncateColumns(`“${tell.text}”`, Math.max(0, maxColumns - displayColumns(fixed) - displayColumns(suffix)))}${suffix}`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
72
81
|
function ledgerHint(projectionId, snapshot) {
|
|
73
82
|
const file = snapshot.eventLedgerFile ?? "events.jsonl";
|
|
74
83
|
return `· ledger: .keiyaku/projection/${projectionId}/${file}`;
|
|
@@ -136,7 +145,11 @@ function unknownResponse(projectionId, akuma, snapshot, maxColumns) {
|
|
|
136
145
|
...(snapshot.workspaceRoot ? { workspaceRoot: snapshot.workspaceRoot } : {}),
|
|
137
146
|
maxColumns,
|
|
138
147
|
});
|
|
139
|
-
const historicalLines = rendered.lines.map((line) =>
|
|
148
|
+
const historicalLines = selectWaitBodyRows(rendered.lines.map((line, index) => ({
|
|
149
|
+
kind: rendered.rowKinds[index] ?? "activity",
|
|
150
|
+
line,
|
|
151
|
+
order: index,
|
|
152
|
+
}))).map((row) => row.line.replace(/[┆├]/, "│"));
|
|
140
153
|
return {
|
|
141
154
|
...textResponse([
|
|
142
155
|
`? ${akuma} · projection: ${projectionId} · unknown`,
|
|
@@ -186,7 +199,7 @@ export function buildWaitResponse(projectionId, result, options = {}) {
|
|
|
186
199
|
&& (result.terminalSnapshot || isTerminalPhase(result.snapshot.phase))
|
|
187
200
|
? { ...result.snapshot, phase: "active" }
|
|
188
201
|
: result.snapshot;
|
|
189
|
-
const maxColumns = options.maxColumns ?? resolveLineColumns();
|
|
202
|
+
const maxColumns = Math.min(120, options.maxColumns ?? resolveLineColumns());
|
|
190
203
|
if (result.state === "terminal" && result.verdict.state === "killed") {
|
|
191
204
|
return {
|
|
192
205
|
...textResponse(`× killed ${projectionId}`),
|
|
@@ -203,6 +216,14 @@ export function buildWaitResponse(projectionId, result, options = {}) {
|
|
|
203
216
|
if (state === "returned") {
|
|
204
217
|
return returnedResponse(projectionId, result);
|
|
205
218
|
}
|
|
219
|
+
const tells = pendingTellLines(snapshot, maxColumns);
|
|
220
|
+
const summary = ["running", "stalled", "unknown"].includes(state)
|
|
221
|
+
? renderProjectionActivitySummary(snapshot.activity, {
|
|
222
|
+
state,
|
|
223
|
+
...(snapshot.workspaceRoot ? { workspaceRoot: snapshot.workspaceRoot } : {}),
|
|
224
|
+
maxColumns,
|
|
225
|
+
})
|
|
226
|
+
: undefined;
|
|
206
227
|
const rendered = renderProjectionActivity(snapshot.activity, {
|
|
207
228
|
anchorMs: snapshot.observedAtMs,
|
|
208
229
|
state,
|
|
@@ -210,28 +231,27 @@ export function buildWaitResponse(projectionId, result, options = {}) {
|
|
|
210
231
|
...(snapshot.workspaceRoot ? { workspaceRoot: snapshot.workspaceRoot } : {}),
|
|
211
232
|
maxColumns,
|
|
212
233
|
});
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
...rendered.lines,
|
|
218
|
-
]
|
|
219
|
-
.map((line) => truncateColumns(line, maxColumns));
|
|
220
|
-
if (rendered.lines.length === 0 && state === "running") {
|
|
221
|
-
lines.push(" not a word out of it yet — pulse is steady");
|
|
234
|
+
let order = 0;
|
|
235
|
+
const bodyPlan = [];
|
|
236
|
+
for (const diagnostic of snapshot.eventDiagnostics ?? []) {
|
|
237
|
+
bodyPlan.push({ kind: "storage-diagnostic", line: `‼ ${diagnostic.kind} · ${diagnostic.detail}`, order: order++ });
|
|
222
238
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
rendered.lines.forEach((line, index) => {
|
|
240
|
+
bodyPlan.push({ kind: rendered.rowKinds[index] ?? "activity", line, order: order++ });
|
|
241
|
+
});
|
|
242
|
+
for (const line of tells)
|
|
243
|
+
bodyPlan.push({ kind: "tell", line, order: order++ });
|
|
244
|
+
if (rendered.lines.length === 0 && tells.length === 0 && state === "running") {
|
|
245
|
+
bodyPlan.push({ kind: "liveness", line: " not a word out of it yet — idle", order: order++ });
|
|
246
|
+
}
|
|
247
|
+
if (!rendered.hasEndAnchor && summary) {
|
|
248
|
+
bodyPlan.push({ kind: "summary", line: summary, order: order++ });
|
|
249
|
+
}
|
|
250
|
+
const bodyLines = selectWaitBodyRows(bodyPlan).map((row) => truncateColumns(row.line, maxColumns));
|
|
251
|
+
const lines = [waitHeader(projectionId, result.pact.akuma, snapshot, maxColumns), ...bodyLines];
|
|
252
|
+
const pendingTellCount = snapshot.tells.totals.undelivered;
|
|
253
|
+
if (pendingTellCount > 0) {
|
|
254
|
+
lines.push(`${pendingTellCount} tell${pendingTellCount === 1 ? "" : "s"} undelivered`);
|
|
235
255
|
}
|
|
236
256
|
const nextActions = state === "running" && options.runningActions === false
|
|
237
257
|
? []
|
|
@@ -11,6 +11,9 @@ function isSubagentSafeCommand(args) {
|
|
|
11
11
|
const subcommand = args[1];
|
|
12
12
|
return subcommand === "list" || subcommand === "show" || SUBAGENT_SAFE_FLAGS.has(subcommand ?? "");
|
|
13
13
|
}
|
|
14
|
+
if (head === "task") {
|
|
15
|
+
return args[1] === "log" || SUBAGENT_SAFE_FLAGS.has(args[1] ?? "");
|
|
16
|
+
}
|
|
14
17
|
return ((head !== undefined && SUBAGENT_SAFE_COMMANDS.has(head)) ||
|
|
15
18
|
(args.length === 1 && head !== undefined && SUBAGENT_SAFE_FLAGS.has(head)));
|
|
16
19
|
}
|
package/build/cli/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const TASK_SUBCOMMANDS = ["add", "show", "ls", "start", "stop", "done", "drop", "update", "doctor"];
|
|
1
|
+
export const TASK_SUBCOMMANDS = ["add", "show", "ls", "log", "start", "stop", "done", "drop", "update", "doctor"];
|
package/build/core/bind.js
CHANGED
|
@@ -3,12 +3,12 @@ import { assertSettingsKnobUsable } from "../config/settings/disease.js";
|
|
|
3
3
|
import { loadKeiyakuSettings } from "../config/settings/loader.js";
|
|
4
4
|
import { FlowError } from "../flow-error.js";
|
|
5
5
|
import { activeLedgerRef, appendEntry, appendWithRetry, readLedger } from "./ledger.js";
|
|
6
|
-
import { describeMissingBindSections, materializeBindDraft, mergeBindDraft, missingBindDraftSections, parseBindDraft, parsePartialBindDraft, renderMissingBindSectionsTemplate, } from "./draft.js";
|
|
7
|
-
import { assertValidContractId, buildContractId } from "./ids.js";
|
|
8
|
-
import { registerScope, releaseScope } from "./registry.js";
|
|
6
|
+
import { describeMissingBindSections, materializeBindDraft, mergeBindDraft, missingBindDraftSections, parseBindDraft, parsePartialBindDraft, renderBindDraft, renderMissingBindSectionsTemplate, } from "./draft.js";
|
|
7
|
+
import { assertValidContractId, buildContractId, isCommissionId } from "./ids.js";
|
|
8
|
+
import { previewScopeRegistration, registerScope, releaseScope } from "./registry.js";
|
|
9
9
|
import { resolveDefaultBranch } from "./status/drift.js";
|
|
10
10
|
import { normalizeTargetRef } from "./target-ref.js";
|
|
11
|
-
import { reconcileTaskForfeit, settleTaskBoardForBind } from "./task/index.js";
|
|
11
|
+
import { previewTaskBoardForBind, reconcileTaskForfeit, settleTaskBoardForBind } from "./task/index.js";
|
|
12
12
|
import { bootstrapEntryDataFromObservation, isBootstrapSuccess, runWorktreeBootstrap, } from "./worktree-bootstrap.js";
|
|
13
13
|
import { contractWorktreePathFromBind, stableRepoRoot } from "./worktree-path.js";
|
|
14
14
|
import { allocateWorktreePlace, cleanupCreatedBindResources, createContractBranch, defaultCreateWorktree, materializeContractView, resolveTreeRoot, } from "./bind-workspace.js";
|
|
@@ -138,6 +138,99 @@ export function composeTaskBindDraft(input) {
|
|
|
138
138
|
}
|
|
139
139
|
return materializeBindDraft(merged);
|
|
140
140
|
}
|
|
141
|
+
async function validateBindAfter(cwd, after) {
|
|
142
|
+
const unique = new Set();
|
|
143
|
+
for (const contractId of after) {
|
|
144
|
+
if (unique.has(contractId)) {
|
|
145
|
+
throw new FlowError("INVALID_TARGET", `contract ${contractId} selected more than once by --after`);
|
|
146
|
+
}
|
|
147
|
+
unique.add(contractId);
|
|
148
|
+
if (!isCommissionId(contractId)) {
|
|
149
|
+
throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not a valid contract id`);
|
|
150
|
+
}
|
|
151
|
+
if (!await readLedger(cwd, contractId)) {
|
|
152
|
+
throw new FlowError("INVALID_TARGET", `after contract ${contractId} does not exist`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The common read-only bind admission candidate. Mutation uses this candidate
|
|
158
|
+
* before task settlement; preview stops after it has observed all admission
|
|
159
|
+
* facts and rendered the canonical draft.
|
|
160
|
+
*/
|
|
161
|
+
async function admitBindCandidate(input, draft) {
|
|
162
|
+
const id = buildContractId({ name: draft.name, nowMs: input.nowMs, randomBytes: input.randomBytes });
|
|
163
|
+
await assertValidContractId(input.cwd, id);
|
|
164
|
+
await validateBindAfter(input.cwd, input.after);
|
|
165
|
+
const provisionalEntry = makeBindEntry({
|
|
166
|
+
id,
|
|
167
|
+
at: new Date(input.nowMs).toISOString(),
|
|
168
|
+
actor: input.actor,
|
|
169
|
+
draft,
|
|
170
|
+
// Preview never writes the task-board commit. The current target head is
|
|
171
|
+
// sufficient for its read-only provisional entry and worktree coordinate.
|
|
172
|
+
base: input.base,
|
|
173
|
+
target: input.target,
|
|
174
|
+
after: input.after,
|
|
175
|
+
workspace: input.workspace,
|
|
176
|
+
place: input.place,
|
|
177
|
+
bootstrap: input.bootstrapPlan,
|
|
178
|
+
});
|
|
179
|
+
const tree = await resolveTreeRoot(input.cwd, input.workspace, id, provisionalEntry);
|
|
180
|
+
const scopeOverlaps = await previewScopeRegistration(input.cwd, {
|
|
181
|
+
contractId: id,
|
|
182
|
+
scope: draft.scope,
|
|
183
|
+
tree,
|
|
184
|
+
exclusive: input.exclusive,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
id,
|
|
188
|
+
draft,
|
|
189
|
+
canonicalDraft: renderBindDraft(draft),
|
|
190
|
+
target: input.target,
|
|
191
|
+
place: input.workspace === "worktree" ? input.place ?? null : null,
|
|
192
|
+
workspace: input.workspace,
|
|
193
|
+
after: input.after,
|
|
194
|
+
scopeOverlaps,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export async function previewBindContract(input) {
|
|
198
|
+
const isTaskBound = input.taskIds !== undefined && input.taskIds.length > 0;
|
|
199
|
+
const ordinaryDraft = !isTaskBound ? parseBindDraft(input.draft) : undefined;
|
|
200
|
+
const workspace = input.workspace ?? "here";
|
|
201
|
+
const defaultBranch = await resolveDefaultBranch(input.cwd);
|
|
202
|
+
if (!defaultBranch) {
|
|
203
|
+
throw new FlowError("INTERNAL_STATE", "cannot bind: no default branch");
|
|
204
|
+
}
|
|
205
|
+
const target = normalizeTargetRef(defaultBranch.branch);
|
|
206
|
+
const loadedSettings = await loadKeiyakuSettings(input.cwd);
|
|
207
|
+
let bootstrapPlan;
|
|
208
|
+
if (workspace === "worktree") {
|
|
209
|
+
assertSettingsKnobUsable(loadedSettings, "worktreeBootstrap");
|
|
210
|
+
bootstrapPlan = resolvedBootstrapPlan(loadedSettings.knobs?.worktreeBootstrap);
|
|
211
|
+
}
|
|
212
|
+
const randomBytes = input.randomBytes?.(0) ?? randomBytesDefault();
|
|
213
|
+
const place = workspace === "worktree" ? await allocateWorktreePlace(input.cwd, randomBytes) : undefined;
|
|
214
|
+
const taskAdmission = isTaskBound
|
|
215
|
+
? await previewTaskBoardForBind({ cwd: input.cwd, taskIds: input.taskIds })
|
|
216
|
+
: { tasks: [] };
|
|
217
|
+
const draft = isTaskBound
|
|
218
|
+
? composeTaskBindDraft({ task: taskAdmission.tasks[0], draft: input.draft })
|
|
219
|
+
: ordinaryDraft;
|
|
220
|
+
return await admitBindCandidate({
|
|
221
|
+
cwd: input.cwd,
|
|
222
|
+
actor: input.actor,
|
|
223
|
+
nowMs: input.nowMs,
|
|
224
|
+
after: input.after ?? [],
|
|
225
|
+
exclusive: input.exclusive,
|
|
226
|
+
workspace,
|
|
227
|
+
randomBytes,
|
|
228
|
+
base: defaultBranch.head,
|
|
229
|
+
target,
|
|
230
|
+
bootstrapPlan,
|
|
231
|
+
place,
|
|
232
|
+
}, draft);
|
|
233
|
+
}
|
|
141
234
|
export async function bindContract(input) {
|
|
142
235
|
const isTaskBound = input.taskIds !== undefined && input.taskIds.length > 0;
|
|
143
236
|
const ordinaryDraft = !isTaskBound ? parseBindDraft(input.draft) : undefined;
|
|
@@ -161,8 +254,20 @@ export async function bindContract(input) {
|
|
|
161
254
|
let settlement;
|
|
162
255
|
try {
|
|
163
256
|
const prepare = async (draft) => {
|
|
164
|
-
const
|
|
165
|
-
|
|
257
|
+
const candidate = await admitBindCandidate({
|
|
258
|
+
cwd: input.cwd,
|
|
259
|
+
actor: input.actor,
|
|
260
|
+
nowMs: input.nowMs,
|
|
261
|
+
after: input.after ?? [],
|
|
262
|
+
exclusive: input.exclusive,
|
|
263
|
+
workspace,
|
|
264
|
+
randomBytes,
|
|
265
|
+
base: defaultBranch.head,
|
|
266
|
+
target,
|
|
267
|
+
bootstrapPlan,
|
|
268
|
+
place,
|
|
269
|
+
}, draft);
|
|
270
|
+
const id = candidate.id;
|
|
166
271
|
const ref = activeLedgerRef(id);
|
|
167
272
|
return {
|
|
168
273
|
contractId: id,
|
package/build/core/draft.js
CHANGED
|
@@ -204,7 +204,7 @@ export function renderMissingBindSectionsTemplate(missing) {
|
|
|
204
204
|
continue;
|
|
205
205
|
}
|
|
206
206
|
if (section === "scope") {
|
|
207
|
-
lines.push("## Scope", "
|
|
207
|
+
lines.push("## Scope", "~~~", "<pattern>", "<another-pattern>", "~~~");
|
|
208
208
|
continue;
|
|
209
209
|
}
|
|
210
210
|
lines.push("## Checks", "- <verification>");
|