@astrosheep/keiyaku 2.9.7 → 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/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/projection/status/handler.js +5 -4
- package/build/cli/commands/projection/status/meta.js +2 -2
- package/build/cli/commands/task/add/meta.js +9 -1
- package/build/cli/commands/task/shared.js +2 -1
- package/build/cli/completion.js +8 -0
- package/build/cli/render/kanshi.js +2 -2
- package/build/cli/render/path-prefix-compaction.js +119 -76
- package/build/cli/render/projection-activity.js +10 -1
- package/build/cli/render/shared.js +8 -7
- package/build/cli/render/status.js +8 -5
- package/build/cli/render/wait.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/call/context.js +19 -3
- package/build/core/call/execution.js +43 -20
- package/build/core/ledger-batch.js +194 -0
- package/build/core/projection/generation/database.js +22 -0
- package/build/core/projection/generation/projection-generation-execution.js +45 -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 +76 -40
- package/build/core/projection/generation/projection-generation-runtime.js +82 -19
- package/build/core/projection/generation/store.js +17 -1
- package/build/core/projection/generation/transitions.js +89 -12
- package/build/core/projection/index.js +3 -3
- package/build/core/projection/projection-kill.js +22 -10
- package/build/core/projection/projection-runner-lock.js +177 -37
- package/build/core/projection/projection-status.js +143 -55
- package/build/core/projection/projection-wake.js +171 -72
- 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/task-git-runtime.js +8 -10
- package/build/core/task/task-git-store.js +5 -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 +18 -0
- package/skills/keiyaku-workflow/SKILL.md +68 -13
|
@@ -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 };
|
|
@@ -2,7 +2,7 @@ import * as fs from "fs/promises";
|
|
|
2
2
|
import { FlowError } from "../../flow-error.js";
|
|
3
3
|
import { assertGitObservation, observeGitRepository } from "../../git/branches.js";
|
|
4
4
|
import { createGit, wrapGitError } from "../../git/core.js";
|
|
5
|
-
import { AddressResolutionError, buildResolutionAttempt, buildUncommissionedAddress, buildUncommissionedRepositoryAddress, resolveContractAddress, withResolvedAddress, } from "../addressing.js";
|
|
5
|
+
import { AddressResolutionError, buildResolutionAttempt, buildUncommissionedAddress, buildUncommissionedRepositoryAddress, resolveContractAddress, resolveCallWorktreeAddress, withResolvedAddress, } from "../addressing.js";
|
|
6
6
|
import { normalizeFilesystemCoordinate } from "../../fs/path-coordinate.js";
|
|
7
7
|
import { findOpenArcView } from "../arc.js";
|
|
8
8
|
import { readLedger } from "../ledger.js";
|
|
@@ -139,7 +139,24 @@ export async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
139
139
|
},
|
|
140
140
|
}));
|
|
141
141
|
}
|
|
142
|
-
if (input.bare
|
|
142
|
+
if (input.bare) {
|
|
143
|
+
return {
|
|
144
|
+
activeKeiyaku: false,
|
|
145
|
+
effectiveCwd: executionCwd,
|
|
146
|
+
historyCwd: ledgerCwd,
|
|
147
|
+
profileCwd: ledgerCwd,
|
|
148
|
+
projectionCwd: ledgerCwd,
|
|
149
|
+
address: buildUncommissionedRepositoryAddress({
|
|
150
|
+
stableRoot: ledgerCwd,
|
|
151
|
+
workdir: executionCwd,
|
|
152
|
+
binding: "bare",
|
|
153
|
+
}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const address = input.contractId
|
|
157
|
+
? await resolveContractAddress(ledgerCwd, input.contractId, input.contractAddressSource)
|
|
158
|
+
: await resolveCallWorktreeAddress(ledgerCwd, executionCwd);
|
|
159
|
+
if (!address) {
|
|
143
160
|
return {
|
|
144
161
|
activeKeiyaku: false,
|
|
145
162
|
effectiveCwd: executionCwd,
|
|
@@ -153,7 +170,6 @@ export async function readCallKeiyakuContext(executionCwd, input) {
|
|
|
153
170
|
}),
|
|
154
171
|
};
|
|
155
172
|
}
|
|
156
|
-
const address = await resolveContractAddress(ledgerCwd, input.contractId, input.contractAddressSource);
|
|
157
173
|
return await withResolvedAddress(address, async () => {
|
|
158
174
|
const contractId = address.binding.commissionId;
|
|
159
175
|
const ledger = await readLedger(ledgerCwd, contractId);
|
|
@@ -7,7 +7,7 @@ import { assertProjectionAlias, moveProjectionAlias } from "../projection/index.
|
|
|
7
7
|
import { projectionDir } from "../projection/index.js";
|
|
8
8
|
import { createProjectionExecutionId } from "../projection/index.js";
|
|
9
9
|
import { PROJECTION_ADOPTION_TIMEOUT_MS, startProjectionGeneration } from "../projection/index.js";
|
|
10
|
-
import { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, } from "../projection/index.js";
|
|
10
|
+
import { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, acquireProjectionRunnerLock, } from "../projection/index.js";
|
|
11
11
|
import { observeProjectionLife } from "../projection/index.js";
|
|
12
12
|
import { mintProjection, mintRepositoryProjection } from "../projection/index.js";
|
|
13
13
|
import { waitForProjection } from "../projection/index.js";
|
|
@@ -60,13 +60,29 @@ export function outcomeFromGeneration(current) {
|
|
|
60
60
|
return restoreOutcome(record);
|
|
61
61
|
}
|
|
62
62
|
export async function executeSubagent(agentName, prompt, cwd, options) {
|
|
63
|
-
const { projection: projectionCoordinate, launchSnapshot } = await buildCallConfig(agentName, prompt, cwd, options);
|
|
63
|
+
const { projection: projectionCoordinate, launchSnapshot, launchFacts } = await buildCallConfig(agentName, prompt, cwd, options);
|
|
64
64
|
if (!projectionCoordinate) {
|
|
65
65
|
throw new FlowError("INTERNAL_STATE", "durable execution requires a projection coordinate");
|
|
66
66
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const launchOwnership = acquireProjectionRunnerLock(projectionCoordinate.dir);
|
|
68
|
+
const launched = await startProjectionGeneration(projectionCoordinate.dir, projectionCoordinate.executionId, {
|
|
69
|
+
commitLaunch: () => {
|
|
70
|
+
const store = openProjectionGenerationStore(projectionCoordinate.dir);
|
|
71
|
+
try {
|
|
72
|
+
return store.launchIfSettled({ executionId: projectionCoordinate.executionId, facts: launchFacts }).status === "committed";
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
store.close();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
launchOwnership,
|
|
79
|
+
});
|
|
80
|
+
launchOwnership.close();
|
|
81
|
+
if (launched.status !== "adopted") {
|
|
82
|
+
const detail = launched.status === "pending"
|
|
83
|
+
? "runner retained startup ownership without adopting before the bounded launch wait"
|
|
84
|
+
: launched.detail;
|
|
85
|
+
throw new FlowError("SUBAGENT_EXEC_ERROR", detail);
|
|
70
86
|
}
|
|
71
87
|
const joined = await waitForProjection({
|
|
72
88
|
projectionDirectory: projectionCoordinate.dir,
|
|
@@ -111,13 +127,28 @@ export async function executeSubagent(agentName, prompt, cwd, options) {
|
|
|
111
127
|
throw new FlowError("INTERNAL_STATE", "Unhandled subagent outcome state.");
|
|
112
128
|
}
|
|
113
129
|
export async function launchSubagentGeneration(agentName, prompt, cwd, options) {
|
|
114
|
-
const { projection, launchSnapshot } = await buildCallConfig(agentName, prompt, cwd, options);
|
|
130
|
+
const { projection, launchSnapshot, launchFacts } = await buildCallConfig(agentName, prompt, cwd, options);
|
|
115
131
|
if (!projection) {
|
|
116
132
|
throw new FlowError("INTERNAL_STATE", "durable launch requires a projection coordinate");
|
|
117
133
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
const launchOwnership = acquireProjectionRunnerLock(projection.dir);
|
|
135
|
+
const launched = await startProjectionGeneration(projection.dir, projection.executionId, {
|
|
136
|
+
commitLaunch: () => {
|
|
137
|
+
const store = openProjectionGenerationStore(projection.dir);
|
|
138
|
+
try {
|
|
139
|
+
return store.launchIfSettled({ executionId: projection.executionId, facts: launchFacts }).status === "committed";
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
store.close();
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
launchOwnership,
|
|
146
|
+
});
|
|
147
|
+
launchOwnership.close();
|
|
148
|
+
if (launched.status !== "adopted") {
|
|
149
|
+
throw new FlowError("SUBAGENT_EXEC_ERROR", launched.status === "pending"
|
|
150
|
+
? "runner retained startup ownership without adopting before the bounded launch wait"
|
|
151
|
+
: launched.detail);
|
|
121
152
|
}
|
|
122
153
|
return { projection, launchSnapshot };
|
|
123
154
|
}
|
|
@@ -173,16 +204,6 @@ export async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
|
173
204
|
} : {}),
|
|
174
205
|
},
|
|
175
206
|
}));
|
|
176
|
-
const store = openProjectionGenerationStore(minted.dir);
|
|
177
|
-
try {
|
|
178
|
-
const launch = store.launchIfSettled({ executionId, facts: launchFacts });
|
|
179
|
-
if (launch.status !== "committed") {
|
|
180
|
-
throw new FlowError("INTERNAL_STATE", `initial generation launch rejected: ${launch.reason}`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
finally {
|
|
184
|
-
store.close();
|
|
185
|
-
}
|
|
186
207
|
const previousAliasTarget = options.alias === undefined
|
|
187
208
|
? undefined
|
|
188
209
|
: await moveProjectionAlias(options.projectionCwd ?? cwd, assertProjectionAlias(options.alias), minted.id);
|
|
@@ -202,6 +223,7 @@ export async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
|
202
223
|
id: minted.id,
|
|
203
224
|
dir: minted.dir,
|
|
204
225
|
executionId,
|
|
226
|
+
launchFacts,
|
|
205
227
|
...(options.alias ? { alias: options.alias } : {}),
|
|
206
228
|
...(previousAliasTargetStillRunning ? { previousAliasTarget: previousAliasTargetStillRunning } : {}),
|
|
207
229
|
};
|
|
@@ -209,9 +231,10 @@ export async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
|
209
231
|
// Every public launch has one durable projection object. Repository-free
|
|
210
232
|
// authority changes the storage owner, not whether the object exists; mint
|
|
211
233
|
// failure is therefore a launch failure rather than an in-process fallback.
|
|
212
|
-
const projectionCoordinate = await publishProjection();
|
|
234
|
+
const { launchFacts, ...projectionCoordinate } = await publishProjection();
|
|
213
235
|
return {
|
|
214
236
|
projection: projectionCoordinate,
|
|
215
237
|
launchSnapshot,
|
|
238
|
+
launchFacts,
|
|
216
239
|
};
|
|
217
240
|
}
|