@astrosheep/keiyaku 2.9.5 → 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 +20 -4
- package/build/agents/harness/projection.js +8 -7
- package/build/agents/harness/router.js +2 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
- package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
- package/build/agents/providers/codex-app-server/adapter.js +11 -10
- package/build/agents/providers/codex-app-server/registration.js +2 -1
- package/build/agents/providers/opencode-sdk/adapter.js +7 -0
- package/build/agents/providers/opencode-sdk/registration.js +2 -1
- package/build/agents/providers/pi/adapter.js +10 -0
- package/build/agents/providers/pi/registration.js +3 -1
- 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/completion.js +9 -2
- package/build/cli/flags.js +11 -2
- 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/errors.js +18 -1
- package/build/cli/render/format.js +5 -2
- 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/audit/candidate.js +48 -0
- package/build/core/audit/coordinates.js +180 -0
- package/build/core/audit/evidence.js +158 -0
- package/build/core/audit/facade.js +42 -0
- package/build/core/audit/readiness.js +4 -0
- package/build/core/audit/report.js +38 -0
- package/build/core/audit.js +1 -650
- 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/query.js +65 -23
- 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 +82 -7
package/build/cli/index.js
CHANGED
|
@@ -57,9 +57,6 @@ function prepareBindInput(stdin, flags) {
|
|
|
57
57
|
checks: flags.bindChecks,
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
-
if (flags.dryRun) {
|
|
61
|
-
throw new FlowError("EMPTY_PARAM", "--dry-run requires --place, --objective, --scope, and --checks");
|
|
62
|
-
}
|
|
63
60
|
if (!nonblankStdin) {
|
|
64
61
|
// --task may supply name/objective defaults; core merges and reports missing sections.
|
|
65
62
|
if (flags.bindTaskIds !== undefined && flags.bindTaskIds.length > 0) {
|
|
@@ -188,11 +185,17 @@ function selectedArgument(parsed) {
|
|
|
188
185
|
if (stdin.selector === undefined)
|
|
189
186
|
return undefined;
|
|
190
187
|
if (stdin.selector.kind === "positional") {
|
|
191
|
-
|
|
188
|
+
const token = parsed.positional[stdin.selector.index];
|
|
189
|
+
if (token === undefined && !stdin.selector.required)
|
|
190
|
+
return undefined;
|
|
191
|
+
return { token, replace: () => parsed.flags };
|
|
192
192
|
}
|
|
193
193
|
const flagName = stdin.selector.name;
|
|
194
|
+
const token = parsed.flags[flagName];
|
|
195
|
+
if (token === undefined && !stdin.selector.required)
|
|
196
|
+
return undefined;
|
|
194
197
|
return {
|
|
195
|
-
token
|
|
198
|
+
token,
|
|
196
199
|
replace: (value) => ({ ...parsed.flags, [flagName]: value }),
|
|
197
200
|
};
|
|
198
201
|
}
|
|
@@ -248,7 +251,8 @@ export async function runCliCommand(tokens) {
|
|
|
248
251
|
await printFailure(error, cwd);
|
|
249
252
|
return 1;
|
|
250
253
|
}
|
|
251
|
-
// Bind
|
|
254
|
+
// Bind input compilation is side-effect free. Dry-run then performs the same
|
|
255
|
+
// repository admission as bind but does not persist an artifact on failure.
|
|
252
256
|
if (parsed.command === "bind") {
|
|
253
257
|
try {
|
|
254
258
|
stdin = prepareBindInput(stdin, parsed.flags);
|
package/build/cli/parse-flags.js
CHANGED
|
@@ -54,6 +54,9 @@ export function assignFlag(flags, name, value) {
|
|
|
54
54
|
case "detach":
|
|
55
55
|
flags.detach = Boolean(value);
|
|
56
56
|
return;
|
|
57
|
+
case "interrupt":
|
|
58
|
+
flags.interrupt = Boolean(value);
|
|
59
|
+
return;
|
|
57
60
|
case "draft-only":
|
|
58
61
|
flags.draftOnly = Boolean(value);
|
|
59
62
|
return;
|
|
@@ -102,6 +105,9 @@ export function assignFlag(flags, name, value) {
|
|
|
102
105
|
case "append-scope":
|
|
103
106
|
flags.appendScope = [...(flags.appendScope ?? []), String(value)];
|
|
104
107
|
return;
|
|
108
|
+
case "renew":
|
|
109
|
+
flags.renewBeforePetition = Boolean(value);
|
|
110
|
+
return;
|
|
105
111
|
case "resolve":
|
|
106
112
|
flags.renewResolve = Boolean(value);
|
|
107
113
|
return;
|
|
@@ -83,7 +83,7 @@ export function commandPositionalRange(command) {
|
|
|
83
83
|
if (command === "task add") {
|
|
84
84
|
return { min: 1, max: 1, label: "<BODY|->" };
|
|
85
85
|
}
|
|
86
|
-
if (command === "task show" || command === "task start" || command === "task drop") {
|
|
86
|
+
if (command === "task show" || command === "task log" || command === "task start" || command === "task drop") {
|
|
87
87
|
return { min: 1, max: 1, label: "<ID>" };
|
|
88
88
|
}
|
|
89
89
|
if (command === "task update") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asMessage, isFlowError, pickHintsFromError } from "../../flow-error.js";
|
|
1
|
+
import { asMessage, isFlowError, pickHintsFromError, } from "../../flow-error.js";
|
|
2
2
|
import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
|
|
3
3
|
import { loadKeiyakuSettings } from "../../config/settings/loader.js";
|
|
4
4
|
import { loadAkumaCatalog } from "../../config/akuma-loader.js";
|
|
@@ -6,6 +6,20 @@ import { appendDebugLog } from "../../telemetry/debug-log.js";
|
|
|
6
6
|
import { ACTOR_IDENTITY, TOOL_NAMES, BUILTIN_AGENT_NAMES } from "../../keiyaku.js";
|
|
7
7
|
import { buildErrorResponse } from "./misc.js";
|
|
8
8
|
import { addressCarrierFromError } from "../../core/addressing.js";
|
|
9
|
+
import { shellQuote } from "./format.js";
|
|
10
|
+
const TASK_FILTER_GRAMMAR_EXAMPLE = "title~audit,scope";
|
|
11
|
+
function formatTaskFilterExpression(terms) {
|
|
12
|
+
return terms.map((term) => `${term.field}${term.operator}${term.values.join(",")}`).join("|");
|
|
13
|
+
}
|
|
14
|
+
function renderInvalidTaskFilterMessage(facts) {
|
|
15
|
+
const guidance = facts.recoveredTerms.length === 0
|
|
16
|
+
? `Grammar example: keiyaku task ls --filter ${shellQuote(TASK_FILTER_GRAMMAR_EXAMPLE)}`
|
|
17
|
+
: `Paste-ready equivalent for parsed terms: keiyaku task ls --filter ${shellQuote(formatTaskFilterExpression(facts.recoveredTerms))}`;
|
|
18
|
+
return `invalid task filter: ${facts.detail}. Valid fields: ${facts.fields.join(", ")}. `
|
|
19
|
+
+ `Valid operators: ${facts.operators.join(", ")}. `
|
|
20
|
+
+ `Repeat --filter for AND; use commas for same-field OR or | between complete filters. `
|
|
21
|
+
+ guidance;
|
|
22
|
+
}
|
|
9
23
|
function renderScopeViolation(evidence) {
|
|
10
24
|
if (evidence.outcome === "no-positive-match") {
|
|
11
25
|
return ` ${evidence.path}\n no positive pattern matched`;
|
|
@@ -35,6 +49,9 @@ export function renderScopeAuditMessage(scope, evidence) {
|
|
|
35
49
|
].join("\n");
|
|
36
50
|
}
|
|
37
51
|
function renderErrorMessage(error) {
|
|
52
|
+
if (isFlowError(error) && error.facts?.kind === "invalid_task_filter") {
|
|
53
|
+
return renderInvalidTaskFilterMessage(error.facts);
|
|
54
|
+
}
|
|
38
55
|
if (isFlowError(error) && error.facts?.kind === "scope_violation") {
|
|
39
56
|
return renderScopeAuditMessage(error.facts.scope, error.facts.violations);
|
|
40
57
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { getConfig } from "../../config/env.js";
|
|
2
2
|
import { SECTION_ICONS } from "./response-style.js";
|
|
3
|
-
|
|
3
|
+
// Command-copy output is POSIX shell syntax: close the quote, emit a literal
|
|
4
|
+
// apostrophe, then reopen the quote. Other shell dialects are not supported.
|
|
5
|
+
const POSIX_SINGLE_QUOTE_ESCAPE = "'\\''";
|
|
6
|
+
/** Quote a POSIX shell argument only when the token is not already a single safe word. */
|
|
4
7
|
export function shellQuote(value) {
|
|
5
8
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value))
|
|
6
9
|
return value;
|
|
7
|
-
return `'${value.replace(/'/g,
|
|
10
|
+
return `'${value.replace(/'/g, POSIX_SINGLE_QUOTE_ESCAPE)}'`;
|
|
8
11
|
}
|
|
9
12
|
export function formatAgeMs(ms) {
|
|
10
13
|
if (ms === undefined)
|
|
@@ -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,88 @@
|
|
|
1
|
+
const MIN_COMPACTION_SAVINGS = 4;
|
|
2
|
+
const SAFE_PATTERN_COMPONENT = /^[A-Za-z0-9._*?\[\]-]+$/;
|
|
3
|
+
const SAFE_CONCRETE_PATH_COMPONENT = /^[A-Za-z0-9._-]+$/;
|
|
4
|
+
/** Compact one displayed pattern-pair without merging distinct overlap facts. */
|
|
5
|
+
export function compactPatternPair(left, right) {
|
|
6
|
+
const literal = `${left} × ${right}`;
|
|
7
|
+
const leftParts = safePatternComponents(left);
|
|
8
|
+
const rightParts = safePatternComponents(right);
|
|
9
|
+
if (!leftParts || !rightParts)
|
|
10
|
+
return literal;
|
|
11
|
+
let sharedCount = 0;
|
|
12
|
+
const maximumShared = Math.min(leftParts.length, rightParts.length) - 1;
|
|
13
|
+
while (sharedCount < maximumShared && leftParts[sharedCount] === rightParts[sharedCount]) {
|
|
14
|
+
sharedCount += 1;
|
|
15
|
+
}
|
|
16
|
+
if (sharedCount === 0)
|
|
17
|
+
return literal;
|
|
18
|
+
const prefix = leftParts.slice(0, sharedCount).join("/");
|
|
19
|
+
const compacted = `${prefix}/{${leftParts.slice(sharedCount).join("/")} × ${rightParts.slice(sharedCount).join("/")}}`;
|
|
20
|
+
return savesEnough(literal, compacted) ? compacted : literal;
|
|
21
|
+
}
|
|
22
|
+
/** Compact a concrete preview through a stable slash-component trie when unambiguous. */
|
|
23
|
+
export function compactPathPreview(paths) {
|
|
24
|
+
const literal = paths.join(", ");
|
|
25
|
+
if (paths.length < 2)
|
|
26
|
+
return literal;
|
|
27
|
+
const components = paths.map(safeConcretePathComponents);
|
|
28
|
+
if (components.some((parts) => !parts))
|
|
29
|
+
return literal;
|
|
30
|
+
const trie = createTrie();
|
|
31
|
+
for (const parts of components)
|
|
32
|
+
insertPath(trie, parts);
|
|
33
|
+
if (hasTerminalBranch(trie))
|
|
34
|
+
return literal;
|
|
35
|
+
if (!hasSharedDirectoryPrefix(trie))
|
|
36
|
+
return literal;
|
|
37
|
+
const compacted = renderTrie(trie);
|
|
38
|
+
return savesEnough(literal, compacted) ? compacted : literal;
|
|
39
|
+
}
|
|
40
|
+
function safePatternComponents(value) {
|
|
41
|
+
return safeSlashComponents(value, SAFE_PATTERN_COMPONENT);
|
|
42
|
+
}
|
|
43
|
+
function safeConcretePathComponents(value) {
|
|
44
|
+
return safeSlashComponents(value, SAFE_CONCRETE_PATH_COMPONENT);
|
|
45
|
+
}
|
|
46
|
+
function safeSlashComponents(value, safeComponent) {
|
|
47
|
+
const parts = value.split("/");
|
|
48
|
+
if (parts.length === 0 || parts.some((part) => part === "." || part === ".." || !safeComponent.test(part))) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return parts;
|
|
52
|
+
}
|
|
53
|
+
function createTrie() {
|
|
54
|
+
return { terminal: false, children: new Map() };
|
|
55
|
+
}
|
|
56
|
+
function insertPath(node, parts) {
|
|
57
|
+
let current = node;
|
|
58
|
+
for (const part of parts) {
|
|
59
|
+
let child = current.children.get(part);
|
|
60
|
+
if (!child) {
|
|
61
|
+
child = createTrie();
|
|
62
|
+
current.children.set(part, child);
|
|
63
|
+
}
|
|
64
|
+
current = child;
|
|
65
|
+
}
|
|
66
|
+
current.terminal = true;
|
|
67
|
+
}
|
|
68
|
+
function hasTerminalBranch(node) {
|
|
69
|
+
if (node.terminal && node.children.size > 0)
|
|
70
|
+
return true;
|
|
71
|
+
return [...node.children.values()].some(hasTerminalBranch);
|
|
72
|
+
}
|
|
73
|
+
function hasSharedDirectoryPrefix(node, isRoot = true) {
|
|
74
|
+
if (!isRoot && node.children.size > 1)
|
|
75
|
+
return true;
|
|
76
|
+
return [...node.children.values()].some((child) => hasSharedDirectoryPrefix(child, false));
|
|
77
|
+
}
|
|
78
|
+
function renderTrie(node) {
|
|
79
|
+
const entries = [...node.children.entries()].map(([part, child]) => {
|
|
80
|
+
if (child.children.size === 0)
|
|
81
|
+
return part;
|
|
82
|
+
return `${part}/${renderTrie(child)}`;
|
|
83
|
+
});
|
|
84
|
+
return entries.length === 1 ? entries[0] : `{${entries.join(",")}}`;
|
|
85
|
+
}
|
|
86
|
+
function savesEnough(literal, compacted) {
|
|
87
|
+
return literal.length - compacted.length >= MIN_COMPACTION_SAVINGS;
|
|
88
|
+
}
|
|
@@ -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,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";
|