@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
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(5)}│ ${"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"];
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
-
import { SETTINGS_FILE } from "../../keiyaku.js";
|
|
3
2
|
export function findSettingsDiseasesForKnob(diseases, knob) {
|
|
4
|
-
return diseases.filter((disease) => disease.knob === knob);
|
|
3
|
+
return diseases.filter((disease) => disease.knob === knob || disease.knob === undefined);
|
|
5
4
|
}
|
|
6
5
|
export function formatSettingsDiseases(diseases) {
|
|
7
6
|
return diseases.map((disease) => {
|
|
8
|
-
const where = disease.knob ? `${disease.
|
|
7
|
+
const where = disease.knob ? `${disease.coordinate}:${disease.knob}` : `${disease.coordinate}:(root)`;
|
|
9
8
|
return `${where}: ${disease.reason}`;
|
|
10
9
|
});
|
|
11
10
|
}
|
|
@@ -20,11 +19,12 @@ export function assertSettingsKnobUsable(loaded, knob) {
|
|
|
20
19
|
const hits = findSettingsDiseasesForKnob(loaded.diseases, knob);
|
|
21
20
|
if (hits.length === 0)
|
|
22
21
|
return;
|
|
23
|
-
throw new FlowError("INVALID_SETTINGS", `Invalid settings
|
|
22
|
+
throw new FlowError("INVALID_SETTINGS", `Invalid settings for diseased knob '${knob}'.`, {
|
|
24
23
|
facts: {
|
|
25
24
|
kind: "invalid_settings",
|
|
26
25
|
diseases: hits.map((disease) => ({
|
|
27
26
|
source: disease.source,
|
|
27
|
+
coordinate: disease.coordinate,
|
|
28
28
|
...(disease.knob !== undefined ? { knob: disease.knob } : {}),
|
|
29
29
|
reason: disease.reason,
|
|
30
30
|
})),
|
|
@@ -58,11 +58,11 @@ function formatProviderInstanceIssues(name, error) {
|
|
|
58
58
|
})
|
|
59
59
|
.join("; ");
|
|
60
60
|
}
|
|
61
|
-
function collectProviderCandidates(
|
|
61
|
+
function collectProviderCandidates(settingsSource, providerSource, rawProviders) {
|
|
62
62
|
if (!isPlainObject(rawProviders)) {
|
|
63
63
|
return {
|
|
64
64
|
candidates: [],
|
|
65
|
-
disease: {
|
|
65
|
+
disease: { ...settingsSource, knob: "providers", reason: "providers: must be a JSON object" },
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
const candidates = [];
|
|
@@ -71,7 +71,8 @@ function collectProviderCandidates(source, rawProviders) {
|
|
|
71
71
|
if (!nameResult.success) {
|
|
72
72
|
candidates.push({
|
|
73
73
|
name,
|
|
74
|
-
source,
|
|
74
|
+
source: providerSource,
|
|
75
|
+
coordinate: settingsSource.coordinate,
|
|
75
76
|
reason: formatProviderInstanceIssues(name, nameResult.error),
|
|
76
77
|
});
|
|
77
78
|
continue;
|
|
@@ -80,7 +81,8 @@ function collectProviderCandidates(source, rawProviders) {
|
|
|
80
81
|
if (!instanceResult.success) {
|
|
81
82
|
candidates.push({
|
|
82
83
|
name,
|
|
83
|
-
source,
|
|
84
|
+
source: providerSource,
|
|
85
|
+
coordinate: settingsSource.coordinate,
|
|
84
86
|
reason: formatProviderInstanceIssues(name, instanceResult.error),
|
|
85
87
|
});
|
|
86
88
|
continue;
|
|
@@ -93,7 +95,7 @@ function collectProviderCandidates(source, rawProviders) {
|
|
|
93
95
|
"writableRoots" in instance
|
|
94
96
|
? instance.writableRoots
|
|
95
97
|
: undefined;
|
|
96
|
-
if (writableRoots !== undefined &&
|
|
98
|
+
if (writableRoots !== undefined && providerSource === "global") {
|
|
97
99
|
let relativeIndex;
|
|
98
100
|
for (let i = 0; i < writableRoots.length; i++) {
|
|
99
101
|
if (!path.posix.isAbsolute(writableRoots[i])) {
|
|
@@ -104,13 +106,14 @@ function collectProviderCandidates(source, rawProviders) {
|
|
|
104
106
|
if (relativeIndex !== undefined) {
|
|
105
107
|
candidates.push({
|
|
106
108
|
name,
|
|
107
|
-
source,
|
|
109
|
+
source: providerSource,
|
|
110
|
+
coordinate: settingsSource.coordinate,
|
|
108
111
|
reason: `providers.${name}.writableRoots.${relativeIndex}: writableRoots item must be an absolute path for global provider instances`,
|
|
109
112
|
});
|
|
110
113
|
continue;
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
|
-
candidates.push({ name, source, instance: instanceResult.data });
|
|
116
|
+
candidates.push({ name, source: providerSource, coordinate: settingsSource.coordinate, instance: instanceResult.data });
|
|
114
117
|
}
|
|
115
118
|
return { candidates };
|
|
116
119
|
}
|
|
@@ -126,21 +129,23 @@ function mergeProviderCandidates(catalog, candidates) {
|
|
|
126
129
|
}
|
|
127
130
|
return { selected, shadows };
|
|
128
131
|
}
|
|
129
|
-
function collectSourceDiseases(
|
|
132
|
+
function collectSourceDiseases(settingsSource, providerSource, read) {
|
|
130
133
|
if (!read.found) {
|
|
131
|
-
return { partial: {}, diseases: [], providerCandidates: [] };
|
|
134
|
+
return { settingsSource, partial: {}, diseases: [], providerCandidates: [] };
|
|
132
135
|
}
|
|
133
136
|
if (read.parseError !== undefined) {
|
|
134
137
|
return {
|
|
138
|
+
settingsSource,
|
|
135
139
|
partial: {},
|
|
136
|
-
diseases: [{
|
|
140
|
+
diseases: [{ ...settingsSource, reason: `settings root is unreadable (${read.parseError})` }],
|
|
137
141
|
providerCandidates: [],
|
|
138
142
|
};
|
|
139
143
|
}
|
|
140
144
|
if (!isPlainObject(read.raw)) {
|
|
141
145
|
return {
|
|
146
|
+
settingsSource,
|
|
142
147
|
partial: {},
|
|
143
|
-
diseases: [{
|
|
148
|
+
diseases: [{ ...settingsSource, reason: "settings root must be a JSON object" }],
|
|
144
149
|
providerCandidates: [],
|
|
145
150
|
};
|
|
146
151
|
}
|
|
@@ -151,7 +156,7 @@ function collectSourceDiseases(source, providerSource, read) {
|
|
|
151
156
|
for (const key of Object.keys(raw)) {
|
|
152
157
|
if (!KNOWN_SETTINGS_ROOT_KEYS.includes(key)) {
|
|
153
158
|
diseases.push({
|
|
154
|
-
|
|
159
|
+
...settingsSource,
|
|
155
160
|
knob: key,
|
|
156
161
|
reason: key === "agents"
|
|
157
162
|
? 'settings key "agents" has been removed; define profiles in .keiyaku/akuma/<name>.md'
|
|
@@ -160,7 +165,7 @@ function collectSourceDiseases(source, providerSource, read) {
|
|
|
160
165
|
}
|
|
161
166
|
}
|
|
162
167
|
if ("providers" in raw) {
|
|
163
|
-
const collectedProviders = collectProviderCandidates(providerSource, raw.providers);
|
|
168
|
+
const collectedProviders = collectProviderCandidates(settingsSource, providerSource, raw.providers);
|
|
164
169
|
providerCandidates = collectedProviders.candidates;
|
|
165
170
|
if (collectedProviders.disease !== undefined)
|
|
166
171
|
diseases.push(collectedProviders.disease);
|
|
@@ -170,9 +175,9 @@ function collectSourceDiseases(source, providerSource, read) {
|
|
|
170
175
|
continue;
|
|
171
176
|
// worktreeBootstrap is project-layer only. Global/user presence is a named
|
|
172
177
|
// disease and never contributes execution authority.
|
|
173
|
-
if (knob === "worktreeBootstrap" && source === "global") {
|
|
178
|
+
if (knob === "worktreeBootstrap" && settingsSource.source === "global") {
|
|
174
179
|
diseases.push({
|
|
175
|
-
|
|
180
|
+
...settingsSource,
|
|
176
181
|
knob,
|
|
177
182
|
reason: "worktreeBootstrap is project-only and never supplies a value from global settings",
|
|
178
183
|
});
|
|
@@ -182,7 +187,7 @@ function collectSourceDiseases(source, providerSource, read) {
|
|
|
182
187
|
const result = schema.safeParse(raw[knob]);
|
|
183
188
|
if (!result.success) {
|
|
184
189
|
diseases.push({
|
|
185
|
-
|
|
190
|
+
...settingsSource,
|
|
186
191
|
knob,
|
|
187
192
|
reason: formatZodIssues(result.error),
|
|
188
193
|
});
|
|
@@ -192,7 +197,7 @@ function collectSourceDiseases(source, providerSource, read) {
|
|
|
192
197
|
continue;
|
|
193
198
|
partial[knob] = result.data;
|
|
194
199
|
}
|
|
195
|
-
return { partial, diseases, providerCandidates };
|
|
200
|
+
return { settingsSource, partial, diseases, providerCandidates };
|
|
196
201
|
}
|
|
197
202
|
function mergeSettingsKnobs(base, override) {
|
|
198
203
|
// worktreeBootstrap is whole-value project authority; never field-merge it.
|
|
@@ -217,9 +222,21 @@ function mergeSettingsKnobs(base, override) {
|
|
|
217
222
|
merged.worktreeBootstrap = bootstrap;
|
|
218
223
|
return merged;
|
|
219
224
|
}
|
|
225
|
+
function collectKnobSources(global, local) {
|
|
226
|
+
const sources = {};
|
|
227
|
+
for (const knob of KNOWN_SETTINGS_KNOBS) {
|
|
228
|
+
if (global.partial[knob] !== undefined)
|
|
229
|
+
sources[knob] = global.settingsSource;
|
|
230
|
+
if (local.partial[knob] !== undefined)
|
|
231
|
+
sources[knob] = local.settingsSource;
|
|
232
|
+
}
|
|
233
|
+
return sources;
|
|
234
|
+
}
|
|
220
235
|
export async function loadKeiyakuSettings(cwd) {
|
|
221
236
|
const localSettingsPath = path.join(cwd, SETTINGS_FILE);
|
|
222
237
|
const globalSettingsPath = path.join(getKeiyakuHome(), path.basename(SETTINGS_FILE));
|
|
238
|
+
const globalSource = { source: "global", coordinate: globalSettingsPath };
|
|
239
|
+
const localSource = { source: "local", coordinate: localSettingsPath };
|
|
223
240
|
let globalRead;
|
|
224
241
|
let localRead;
|
|
225
242
|
try {
|
|
@@ -233,12 +250,13 @@ export async function loadKeiyakuSettings(cwd) {
|
|
|
233
250
|
const reason = error instanceof Error ? error.message : String(error);
|
|
234
251
|
return {
|
|
235
252
|
knobs: null,
|
|
236
|
-
diseases: [{
|
|
253
|
+
diseases: [{ ...localSource, reason: `settings root is unreadable (${reason})` }],
|
|
254
|
+
knobSources: {},
|
|
237
255
|
providerInstances: builtinProviderInstanceCatalog(),
|
|
238
256
|
};
|
|
239
257
|
}
|
|
240
|
-
const globalCollected = collectSourceDiseases(
|
|
241
|
-
const localCollected = collectSourceDiseases(
|
|
258
|
+
const globalCollected = collectSourceDiseases(globalSource, "global", globalRead);
|
|
259
|
+
const localCollected = collectSourceDiseases(localSource, "project", localRead);
|
|
242
260
|
const diseases = [...globalCollected.diseases, ...localCollected.diseases];
|
|
243
261
|
let providerInstances = builtinProviderInstanceCatalog();
|
|
244
262
|
providerInstances = mergeProviderCandidates(providerInstances, globalCollected.providerCandidates);
|
|
@@ -248,5 +266,10 @@ export async function loadKeiyakuSettings(cwd) {
|
|
|
248
266
|
knobs = mergeSettingsKnobs(globalCollected.partial, localCollected.partial);
|
|
249
267
|
}
|
|
250
268
|
// Empty object still means "settings files were present"; callers treat null as absent files.
|
|
251
|
-
return {
|
|
269
|
+
return {
|
|
270
|
+
knobs,
|
|
271
|
+
diseases,
|
|
272
|
+
knobSources: collectKnobSources(globalCollected, localCollected),
|
|
273
|
+
providerInstances,
|
|
274
|
+
};
|
|
252
275
|
}
|
package/build/core/addressing.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { FlowError } from "../flow-error.js";
|
|
3
3
|
import { errorContainsAnyPattern, NOT_GIT_REPOSITORY_PATTERNS } from "../git/core.js";
|
|
4
|
-
import { commissionSlug, formatCommissionCoordinate } from "./ids.js";
|
|
5
|
-
import {
|
|
4
|
+
import { commissionSlug, formatCommissionCoordinate, isCommissionId } from "./ids.js";
|
|
5
|
+
import { readAllLedgerSnapshots, readLedgerSnapshot } from "./ledger-batch.js";
|
|
6
6
|
import { isResponseArtifactId } from "./response-artifact-id.js";
|
|
7
7
|
import { activeContractBoundByEngineWorktree, stableRepoRoot } from "./worktree-path.js";
|
|
8
8
|
import { assertNoRenewRecoverySession } from "./renew-session.js";
|
|
@@ -115,12 +115,8 @@ export class AddressResolutionError extends FlowError {
|
|
|
115
115
|
function candidatePlace(candidate) {
|
|
116
116
|
return candidate.bind.data.workspace === "worktree" ? candidate.bind.data.place ?? null : null;
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
const ids = await listContractIds(cwd);
|
|
120
|
-
const ledgers = await Promise.all(ids.map(async (contractId) => readLedger(cwd, contractId)));
|
|
118
|
+
function commissionAddressCandidatesFromLedgers(ledgers) {
|
|
121
119
|
return ledgers.flatMap((ledger) => {
|
|
122
|
-
if (!ledger)
|
|
123
|
-
return [];
|
|
124
120
|
const bind = ledger.entries.find((entry) => entry.kind === "bind");
|
|
125
121
|
if (!bind || bind.contract !== ledger.contractId)
|
|
126
122
|
return [];
|
|
@@ -133,6 +129,17 @@ async function commissionAddressCandidates(cwd) {
|
|
|
133
129
|
}];
|
|
134
130
|
});
|
|
135
131
|
}
|
|
132
|
+
async function commissionAddressCandidates(cwd, exactContractId) {
|
|
133
|
+
if (exactContractId) {
|
|
134
|
+
const ledger = await readLedgerSnapshot(cwd, exactContractId);
|
|
135
|
+
return ledger ? commissionAddressCandidatesFromLedgers([ledger]) : [];
|
|
136
|
+
}
|
|
137
|
+
const reads = await readAllLedgerSnapshots(cwd);
|
|
138
|
+
const failed = reads.find((read) => "error" in read);
|
|
139
|
+
if (failed && "error" in failed)
|
|
140
|
+
throw failed.error;
|
|
141
|
+
return commissionAddressCandidatesFromLedgers(reads.filter((read) => !("error" in read)));
|
|
142
|
+
}
|
|
136
143
|
function matchCommissionAddress(query, candidates) {
|
|
137
144
|
return candidates.flatMap((candidate) => {
|
|
138
145
|
const matches = [];
|
|
@@ -221,7 +228,7 @@ export async function resolveContractAddress(cwd, raw, source = raw?.startsWith(
|
|
|
221
228
|
actual: "artifact",
|
|
222
229
|
});
|
|
223
230
|
}
|
|
224
|
-
const candidates = await commissionAddressCandidates(repo.stableRoot);
|
|
231
|
+
const candidates = await commissionAddressCandidates(repo.stableRoot, isCommissionId(query) ? query : undefined);
|
|
225
232
|
const matches = matchCommissionAddress(query, candidates);
|
|
226
233
|
const attempt = {
|
|
227
234
|
repo,
|
|
@@ -260,7 +267,7 @@ export async function resolveContractAddress(cwd, raw, source = raw?.startsWith(
|
|
|
260
267
|
try {
|
|
261
268
|
const effectiveRepo = await stableRepoRoot(workdir);
|
|
262
269
|
if (effectiveRepo === repo.stableRoot) {
|
|
263
|
-
boundContractId = await activeContractBoundByEngineWorktree(workdir);
|
|
270
|
+
boundContractId = await activeContractBoundByEngineWorktree(workdir, effectiveRepo);
|
|
264
271
|
}
|
|
265
272
|
}
|
|
266
273
|
catch (error) {
|
|
@@ -305,6 +312,30 @@ export async function resolveContractAddress(cwd, raw, source = raw?.startsWith(
|
|
|
305
312
|
? "no active commission found; pass @place, @slug, full @commission-id, or --contract <addr>"
|
|
306
313
|
: "multiple active commissions found; pass @place, @slug, full @commission-id, or --contract <addr>", attempt);
|
|
307
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* `call` may inherit only the active commission proven by the selected
|
|
317
|
+
* engine-owned worktree. Unlike existing-contract resolution, this never
|
|
318
|
+
* considers sole-active cardinality.
|
|
319
|
+
*/
|
|
320
|
+
export async function resolveCallWorktreeAddress(cwd, effectiveDirectory = cwd) {
|
|
321
|
+
const repo = await resolvedRepository(cwd);
|
|
322
|
+
const workdir = path.resolve(effectiveDirectory);
|
|
323
|
+
let contractId;
|
|
324
|
+
try {
|
|
325
|
+
const effectiveRepo = await stableRepoRoot(workdir);
|
|
326
|
+
if (effectiveRepo !== repo.stableRoot)
|
|
327
|
+
return undefined;
|
|
328
|
+
contractId = await activeContractBoundByEngineWorktree(workdir, effectiveRepo);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
if (!errorContainsAnyPattern(error, NOT_GIT_REPOSITORY_PATTERNS))
|
|
332
|
+
throw error;
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
if (!contractId)
|
|
336
|
+
return undefined;
|
|
337
|
+
return await resolveContractAddress(repo.stableRoot, contractId, "worktree-binding", workdir);
|
|
338
|
+
}
|
|
308
339
|
export async function resolveExistingContractAddress(cwd, raw, source = raw?.startsWith("@") ? "at-address" : "explicit-flag", effectiveDirectory = cwd) {
|
|
309
340
|
const address = await resolveContractAddress(cwd, raw, source, effectiveDirectory);
|
|
310
341
|
try {
|
package/build/core/amend.js
CHANGED
|
@@ -5,7 +5,7 @@ import { refreshContractView } from "./contract-view.js";
|
|
|
5
5
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
6
6
|
import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
|
|
7
7
|
import { resolveDefaultBranch } from "./status/drift.js";
|
|
8
|
-
import { markdownSourceSections, normalizeScopePattern, validateScopeFragmentLines } from "./scope.js";
|
|
8
|
+
import { markdownSourceSections, normalizeScopePattern, ScopePatternError, validateScopeFragmentLines } from "./scope.js";
|
|
9
9
|
import { normalizeSectionTitle } from "./markdown/titles.js";
|
|
10
10
|
import { resolveContractDeliveryCwd } from "./worktree-path.js";
|
|
11
11
|
function amendScopeHeading(name) {
|
|
@@ -44,9 +44,25 @@ function scopePatterns(markdown, section) {
|
|
|
44
44
|
emptyMessage: `amend input has an empty ${amendScopeHeading(section.name)} section`,
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
function normalizeAmendScopePatterns(patterns) {
|
|
48
|
+
try {
|
|
49
|
+
return patterns.map((pattern) => normalizeScopePattern(pattern));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof ScopePatternError) {
|
|
53
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", error.message);
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function buildAmendInput(markdown, cwd, contractId, contractAddressSource, appendScope) {
|
|
48
59
|
const { sections, prose } = parseAmendScopeSections(markdown);
|
|
49
|
-
|
|
60
|
+
if (sections.append && appendScope && appendScope.length > 0) {
|
|
61
|
+
throw new FlowError("INVALID_KEIYAKU_DRAFT", "amend input may use either --append-scope or ## Scope Append, not both");
|
|
62
|
+
}
|
|
63
|
+
const append = sections.append
|
|
64
|
+
? scopePatterns(markdown, sections.append)
|
|
65
|
+
: normalizeAmendScopePatterns(appendScope ?? []);
|
|
50
66
|
if (!prose.trim()) {
|
|
51
67
|
throw new FlowError("EMPTY_PARAM", "amendment cannot be empty");
|
|
52
68
|
}
|
|
@@ -55,7 +71,7 @@ export function buildAmendInput(markdown, cwd, contractId, contractAddressSource
|
|
|
55
71
|
...(contractId ? { contractId } : {}),
|
|
56
72
|
...(contractAddressSource ? { contractAddressSource } : {}),
|
|
57
73
|
amendment: prose,
|
|
58
|
-
...(
|
|
74
|
+
...(append.length > 0 ? { scopeDelta: { add: append } } : {}),
|
|
59
75
|
};
|
|
60
76
|
}
|
|
61
77
|
function requireAmendment(value) {
|
|
@@ -76,7 +92,7 @@ function normalizeScopeDelta(scopeDelta) {
|
|
|
76
92
|
if (!scopeDelta)
|
|
77
93
|
return undefined;
|
|
78
94
|
// Scope patterns are gitignore text; do not requireText/trim, or escaped trailing spaces die.
|
|
79
|
-
const add = scopeDelta.add
|
|
95
|
+
const add = normalizeAmendScopePatterns(scopeDelta.add);
|
|
80
96
|
if (add.length === 0)
|
|
81
97
|
return undefined;
|
|
82
98
|
return { add };
|