@astrosheep/keiyaku 4.1.0 → 4.1.2
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/README.md +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +3 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -6
- package/build/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-handle.d.ts +39 -0
- package/build/src/akuma/akuma-handle.js +263 -0
- package/build/src/akuma/akuma-observe.d.ts +19 -0
- package/build/src/akuma/akuma-observe.js +74 -0
- package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
- package/build/src/akuma/akuma-product-symbols.js +1 -0
- package/build/src/akuma/akuma-product.d.ts +18 -0
- package/build/src/akuma/akuma-product.js +154 -0
- package/build/src/akuma/akuma.d.ts +37 -81
- package/build/src/akuma/akuma.js +39 -447
- package/build/src/akuma/allowed.d.ts +1 -0
- package/build/src/akuma/allowed.js +6 -0
- package/build/src/akuma/archetype.js +8 -5
- package/build/src/akuma/body-supervisor.d.ts +26 -0
- package/build/src/akuma/body-supervisor.js +89 -0
- package/build/src/akuma/body.d.ts +45 -4
- package/build/src/akuma/body.js +145 -35
- package/build/src/akuma/coordinate.js +17 -7
- package/build/src/akuma/heart/facts.js +1 -1
- package/build/src/akuma/heart/index.d.ts +1 -1
- package/build/src/akuma/heart/index.js +2 -4
- package/build/src/akuma/heart/request-authority.js +4 -4
- package/build/src/akuma/heart/request-rows.js +27 -16
- package/build/src/akuma/heart/rows.js +79 -41
- package/build/src/akuma/heart/soul.js +1 -1
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +76 -11
- package/build/src/akuma/heart/tells.js +35 -18
- package/build/src/akuma/heart/timeline.js +19 -11
- package/build/src/akuma/identity.d.ts +1 -0
- package/build/src/akuma/identity.js +1 -0
- package/build/src/akuma/index.d.ts +3 -3
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/nuke.js +106 -14
- package/build/src/akuma/projection-read.d.ts +17 -0
- package/build/src/akuma/projection-read.js +135 -0
- package/build/src/akuma/projection.d.ts +1 -16
- package/build/src/akuma/projection.js +77 -154
- package/build/src/akuma/provider-recipe.js +11 -5
- package/build/src/akuma/provider.js +105 -62
- package/build/src/akuma/providers/acp/config.js +5 -6
- package/build/src/akuma/providers/acp/core.js +17 -10
- package/build/src/akuma/providers/acp/events.js +38 -29
- package/build/src/akuma/providers/acp/index.js +13 -9
- package/build/src/akuma/providers/claude/events.js +8 -12
- package/build/src/akuma/providers/claude/index.d.ts +1 -0
- package/build/src/akuma/providers/claude/index.js +55 -26
- package/build/src/akuma/providers/claude/input.js +6 -2
- package/build/src/akuma/providers/codex-app-server/events.js +61 -36
- package/build/src/akuma/providers/codex-app-server/index.js +25 -13
- package/build/src/akuma/providers/grok-build/index.js +22 -25
- package/build/src/akuma/providers/index.js +2 -18
- package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
- package/build/src/akuma/providers/opencode-sdk/index.js +26 -17
- package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
- package/build/src/akuma/providers/pi/events.js +26 -19
- package/build/src/akuma/providers/pi/index.js +58 -40
- package/build/src/akuma/publication.js +9 -9
- package/build/src/akuma/request-execution.d.ts +6 -0
- package/build/src/akuma/request-execution.js +86 -0
- package/build/src/akuma/request-serve.d.ts +12 -1
- package/build/src/akuma/request-serve.js +19 -122
- package/build/src/akuma/request-wire.d.ts +2 -2
- package/build/src/akuma/request-wire.js +75 -102
- package/build/src/akuma/requests.js +2 -2
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
- package/build/src/akuma/{body-turn.js → turn-drive.js} +20 -103
- package/build/src/akuma-body.js +6 -5
- package/build/src/body/amend.d.ts +4 -1
- package/build/src/body/amend.js +14 -6
- package/build/src/body/decode.js +2 -4
- package/build/src/body/envelope.js +2 -4
- package/build/src/body/region.d.ts +1 -2
- package/build/src/body/region.js +2 -70
- package/build/src/body/render.js +17 -12
- package/build/src/body/shape.js +1 -7
- package/build/src/cli/accepted.d.ts +1 -1
- package/build/src/cli/accepted.js +8 -1
- package/build/src/cli/commands/akuma-invoke.js +18 -6
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +11 -24
- package/build/src/cli/commands/amend.d.ts +2 -2
- package/build/src/cli/commands/amend.js +2 -4
- package/build/src/cli/commands/bind.d.ts +4 -4
- package/build/src/cli/commands/bind.js +16 -6
- package/build/src/cli/commands/contract-invoke.d.ts +25 -0
- package/build/src/cli/commands/contract-invoke.js +193 -0
- package/build/src/cli/commands/contract.d.ts +109 -10
- package/build/src/cli/commands/contract.js +339 -16
- package/build/src/cli/commands/install.d.ts +1 -1
- package/build/src/cli/commands/install.js +23 -15
- package/build/src/cli/commands/task-invoke.js +187 -67
- package/build/src/cli/commands/task-query.js +48 -24
- package/build/src/cli/commands/task.js +150 -38
- package/build/src/cli/coordinates.js +2 -4
- package/build/src/cli/invoke.js +92 -208
- package/build/src/cli/main.js +4 -2
- package/build/src/cli/parse.d.ts +3 -90
- package/build/src/cli/parse.js +27 -233
- package/build/src/cli/render/akuma-activity.d.ts +38 -0
- package/build/src/cli/render/akuma-activity.js +362 -0
- package/build/src/cli/render/akuma-tool-command.js +6 -7
- package/build/src/cli/render/akuma-tool.js +10 -8
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +61 -353
- package/build/src/cli/render/audit.js +5 -15
- package/build/src/cli/render/board.d.ts +1 -0
- package/build/src/cli/render/board.js +17 -0
- package/build/src/cli/render/catalog.js +81 -8
- package/build/src/cli/render/contract-history.d.ts +2 -0
- package/build/src/cli/render/contract-history.js +83 -0
- package/build/src/cli/render/contract-observation.d.ts +10 -0
- package/build/src/cli/render/contract-observation.js +58 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +41 -142
- package/build/src/cli/render/kanshi.js +239 -215
- package/build/src/cli/render/receipt.js +2 -9
- package/build/src/cli/render/refusal.js +10 -15
- package/build/src/cli/render/region.js +22 -6
- package/build/src/cli/render/settings.js +4 -4
- package/build/src/cli/render/task.js +34 -14
- package/build/src/cli/render/terminal.js +16 -5
- package/build/src/cli/result.d.ts +3 -3
- package/build/src/cli/runtime.d.ts +6 -1
- package/build/src/cli/runtime.js +84 -63
- package/build/src/cli/selectors.js +2 -4
- package/build/src/cli/usage.js +6 -5
- package/build/src/contract-worktree.d.ts +1 -23
- package/build/src/contract-worktree.js +79 -155
- package/build/src/coordination/durable-file.js +7 -3
- package/build/src/coordination/sqlite-transaction-lock.js +29 -12
- package/build/src/core/facts/codec.js +7 -3
- package/build/src/core/facts/types.d.ts +1 -1
- package/build/src/core/subject.js +16 -9
- package/build/src/core/verbs/abandon.d.ts +1 -1
- package/build/src/core/verbs/abandon.js +1 -1
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +7 -7
- package/build/src/core/verbs/attestation.js +4 -1
- package/build/src/core/verbs/bind.d.ts +1 -1
- package/build/src/core/verbs/bind.js +1 -1
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +10 -8
- package/build/src/dispatch/index.js +8 -6
- package/build/src/duration.js +5 -1
- package/build/src/git/admission.js +5 -3
- package/build/src/git/hooks.d.ts +8 -0
- package/build/src/git/hooks.js +63 -14
- package/build/src/git/identity.js +1 -4
- package/build/src/git/integration.d.ts +2 -3
- package/build/src/git/integration.js +32 -39
- package/build/src/git/nuke.d.ts +1 -1
- package/build/src/git/nuke.js +110 -57
- package/build/src/git/observe.d.ts +13 -1
- package/build/src/git/observe.js +51 -44
- package/build/src/git/process.js +4 -2
- package/build/src/git/read-observation.js +10 -8
- package/build/src/git/reconcile.d.ts +22 -2
- package/build/src/git/reconcile.js +38 -274
- package/build/src/git/repository.d.ts +2 -0
- package/build/src/git/repository.js +13 -22
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/target-placement.d.ts +9 -10
- package/build/src/git/target-placement.js +59 -62
- package/build/src/git/tender.js +13 -46
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/git/terminal-seal.js +15 -12
- package/build/src/git/tree.js +5 -2
- package/build/src/git/workspace.d.ts +24 -2
- package/build/src/git/workspace.js +120 -16
- package/build/src/identity/coordinates.js +3 -1
- package/build/src/identity/selector.js +6 -3
- package/build/src/kanshi/fleet.d.ts +6 -0
- package/build/src/kanshi/fleet.js +27 -0
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +60 -48
- package/build/src/kanshi/report.d.ts +11 -23
- package/build/src/kanshi/select.js +25 -24
- package/build/src/library/address.d.ts +2 -1
- package/build/src/library/address.js +7 -8
- package/build/src/library/akuma-creation.js +20 -12
- package/build/src/library/audit.d.ts +0 -1
- package/build/src/library/audit.js +1 -2
- package/build/src/library/bind.d.ts +53 -5
- package/build/src/library/bind.js +98 -22
- package/build/src/library/catalog.d.ts +1 -0
- package/build/src/library/catalog.js +6 -3
- package/build/src/library/configuration.js +3 -1
- package/build/src/library/continuation.d.ts +15 -4
- package/build/src/library/continuation.js +24 -16
- package/build/src/library/contract-bind.d.ts +26 -0
- package/build/src/library/contract-bind.js +106 -0
- package/build/src/library/contract-operations.d.ts +77 -0
- package/build/src/library/contract-operations.js +130 -0
- package/build/src/library/contract-types.d.ts +95 -0
- package/build/src/library/contract-types.js +1 -0
- package/build/src/library/contract.d.ts +19 -154
- package/build/src/library/contract.js +46 -261
- package/build/src/library/fleet.d.ts +1 -1
- package/build/src/library/fleet.js +19 -11
- package/build/src/library/input.d.ts +2 -0
- package/build/src/library/input.js +14 -0
- package/build/src/library/keiyaku.d.ts +8 -7
- package/build/src/library/keiyaku.js +3 -3
- package/build/src/library/nuke.js +14 -5
- package/build/src/library/reconcile.d.ts +9 -4
- package/build/src/library/reconcile.js +30 -24
- package/build/src/library/refusal.d.ts +13 -5
- package/build/src/library/refusal.js +7 -0
- package/build/src/library/region.d.ts +8 -7
- package/build/src/library/region.js +31 -11
- package/build/src/library/repo.js +2 -2
- package/build/src/markdown/lex.js +9 -2
- package/build/src/markdown/parse.js +11 -7
- package/build/src/markdown/query.js +4 -2
- package/build/src/protocol/amend.js +3 -1
- package/build/src/protocol/attempt.d.ts +7 -3
- package/build/src/protocol/attempt.js +9 -5
- package/build/src/protocol/audit.d.ts +1 -1
- package/build/src/protocol/audit.js +4 -14
- package/build/src/protocol/bind.d.ts +19 -6
- package/build/src/protocol/bind.js +28 -13
- package/build/src/protocol/completion.d.ts +0 -1
- package/build/src/protocol/completion.js +1 -3
- package/build/src/protocol/deliver.d.ts +1 -3
- package/build/src/protocol/deliver.js +15 -39
- package/build/src/protocol/intent.d.ts +1 -0
- package/build/src/protocol/intent.js +9 -7
- package/build/src/protocol/operations.d.ts +3 -7
- package/build/src/protocol/operations.js +5 -3
- package/build/src/protocol/placement.d.ts +0 -1
- package/build/src/protocol/placement.js +6 -6
- package/build/src/protocol/read/observation.d.ts +11 -0
- package/build/src/protocol/read/observation.js +37 -0
- package/build/src/protocol/read/status.d.ts +23 -12
- package/build/src/protocol/read/status.js +90 -72
- package/build/src/protocol/reconcile.d.ts +1 -0
- package/build/src/protocol/reconcile.js +2 -3
- package/build/src/protocol/reintegrate.js +1 -1
- package/build/src/protocol/review.d.ts +0 -1
- package/build/src/protocol/review.js +15 -22
- package/build/src/protocol/run.d.ts +1 -0
- package/build/src/protocol/run.js +4 -5
- package/build/src/runtime/proc/launch.d.ts +33 -0
- package/build/src/runtime/proc/launch.js +98 -0
- package/build/src/runtime/proc/line-rpc.js +9 -3
- package/build/src/runtime/proc/run.d.ts +12 -6
- package/build/src/runtime/proc/run.js +78 -21
- package/build/src/runtime/proc/stdio.js +7 -10
- package/build/src/settings.js +6 -2
- package/build/src/settlement/holder.js +13 -10
- package/build/src/settlement/settle.js +39 -6
- package/build/src/task/board.js +12 -6
- package/build/src/task/compose.js +85 -22
- package/build/src/task/context.js +21 -8
- package/build/src/task/document.js +46 -10
- package/build/src/task/identity.js +5 -2
- package/build/src/task/index.d.ts +0 -1
- package/build/src/task/index.js +119 -34
- package/build/src/task/input.js +29 -7
- package/build/src/task/mutation.js +61 -19
- package/build/src/task/operations.d.ts +6 -1
- package/build/src/task/operations.js +83 -29
- package/build/src/task/query.js +55 -26
- package/build/src/task/store.d.ts +3 -1
- package/build/src/task/store.js +81 -17
- package/build/src/verification/execution.js +14 -11
- package/build/src/workspace-place.js +19 -7
- package/build/src/world.js +16 -6
- package/package.json +8 -3
|
@@ -3,6 +3,8 @@ import { open } from "node:fs/promises";
|
|
|
3
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import crossSpawn from "cross-spawn";
|
|
6
|
+
import { spawnOptionsFor } from "./launch.js";
|
|
7
|
+
export { handoffProcess } from "./launch.js";
|
|
6
8
|
const TERMINATION_GRACE_MS = 250;
|
|
7
9
|
const STREAM_TAIL_BYTES = 16 * 1024;
|
|
8
10
|
function tailCapture(limit) {
|
|
@@ -27,9 +29,7 @@ function tailCapture(limit) {
|
|
|
27
29
|
result() {
|
|
28
30
|
const size = Math.min(total, limit);
|
|
29
31
|
const start = total > limit ? cursor : 0;
|
|
30
|
-
const value = start === 0
|
|
31
|
-
? bytes.subarray(0, size)
|
|
32
|
-
: Buffer.concat([bytes.subarray(start), bytes.subarray(0, start)]);
|
|
32
|
+
const value = start === 0 ? bytes.subarray(0, size) : Buffer.concat([bytes.subarray(start), bytes.subarray(0, start)]);
|
|
33
33
|
return { text: value.toString("utf8"), truncated: total > limit };
|
|
34
34
|
},
|
|
35
35
|
};
|
|
@@ -38,6 +38,9 @@ function ignoreMissingProcess(error) {
|
|
|
38
38
|
if (error.code !== "ESRCH")
|
|
39
39
|
throw error;
|
|
40
40
|
}
|
|
41
|
+
function detachedExitStatus(code, signal) {
|
|
42
|
+
return code === null ? `signal ${signal ?? "unknown"}` : `exit ${code}`;
|
|
43
|
+
}
|
|
41
44
|
const WINDOWS_TERMINATION_TIMEOUT_MS = 1_000;
|
|
42
45
|
const execFileAsync = promisify(execFile);
|
|
43
46
|
async function terminateWindowsTree(pid) {
|
|
@@ -52,8 +55,14 @@ export async function terminateOwnedProcess(child, force = false) {
|
|
|
52
55
|
return;
|
|
53
56
|
let exited = false;
|
|
54
57
|
const exit = new Promise((resolve) => {
|
|
55
|
-
child.once("exit", () => {
|
|
56
|
-
|
|
58
|
+
child.once("exit", () => {
|
|
59
|
+
exited = true;
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
child.once("close", () => {
|
|
63
|
+
exited = true;
|
|
64
|
+
resolve();
|
|
65
|
+
});
|
|
57
66
|
});
|
|
58
67
|
if (process.platform === "win32") {
|
|
59
68
|
await terminateWindowsTree(pid);
|
|
@@ -89,16 +98,39 @@ export async function terminateOwnedProcess(child, force = false) {
|
|
|
89
98
|
}
|
|
90
99
|
await exit;
|
|
91
100
|
}
|
|
101
|
+
async function retainDetachedExitEvidence(log, path, from, code, signal) {
|
|
102
|
+
const status = detachedExitStatus(code, signal);
|
|
103
|
+
const marker = Buffer.from(`[child ${status}]\n`);
|
|
104
|
+
let written = 0;
|
|
105
|
+
while (written < marker.byteLength) {
|
|
106
|
+
const write = await log.write(marker, written, marker.byteLength - written);
|
|
107
|
+
if (write.bytesWritten === 0)
|
|
108
|
+
throw new Error("run log exit marker write made no progress");
|
|
109
|
+
written += write.bytesWritten;
|
|
110
|
+
}
|
|
111
|
+
const evidence = await log.stat();
|
|
112
|
+
if (evidence.size < from)
|
|
113
|
+
throw new Error("run log shrank before exit evidence was retained");
|
|
114
|
+
const referenced = await open(path, "r");
|
|
115
|
+
try {
|
|
116
|
+
const current = await referenced.stat();
|
|
117
|
+
if (current.dev !== evidence.dev || current.ino !== evidence.ino) {
|
|
118
|
+
throw new Error("run log path changed before exit evidence was retained");
|
|
119
|
+
}
|
|
120
|
+
if (current.size < evidence.size)
|
|
121
|
+
throw new Error("run log path size changed before exit evidence was retained");
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
await referenced.close();
|
|
125
|
+
}
|
|
126
|
+
return { code, signal, log: { path, from, to: evidence.size } };
|
|
127
|
+
}
|
|
92
128
|
export async function spawnDetachedProcess(input) {
|
|
93
129
|
const log = await open(input.log, "a");
|
|
130
|
+
let launched = false;
|
|
94
131
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
env: input.env,
|
|
98
|
-
detached: true,
|
|
99
|
-
stdio: ["ignore", log.fd, log.fd],
|
|
100
|
-
windowsHide: true,
|
|
101
|
-
});
|
|
132
|
+
const from = (await log.stat()).size;
|
|
133
|
+
const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor("retained", input, ["ignore", log.fd, log.fd]));
|
|
102
134
|
await new Promise((resolve, reject) => {
|
|
103
135
|
child.once("spawn", resolve);
|
|
104
136
|
child.once("error", reject);
|
|
@@ -106,13 +138,43 @@ export async function spawnDetachedProcess(input) {
|
|
|
106
138
|
if (child.pid === undefined)
|
|
107
139
|
throw new Error("detached process spawned without a pid");
|
|
108
140
|
const pid = child.pid;
|
|
141
|
+
const exited = new Promise((resolve, reject) => {
|
|
142
|
+
child.once("close", (code, signal) => {
|
|
143
|
+
void (async () => {
|
|
144
|
+
const status = detachedExitStatus(code, signal);
|
|
145
|
+
let failure;
|
|
146
|
+
let result;
|
|
147
|
+
try {
|
|
148
|
+
result = await retainDetachedExitEvidence(log, input.log, from, code, signal);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
failure = error;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await log.close();
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
failure ??= error;
|
|
158
|
+
}
|
|
159
|
+
if (failure !== undefined) {
|
|
160
|
+
throw new Error(`pre-admission ${status}: run-log evidence unavailable: ${failure instanceof Error ? failure.message : String(failure)}`);
|
|
161
|
+
}
|
|
162
|
+
resolve(result);
|
|
163
|
+
})().catch(reject);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
void exited.catch(() => undefined);
|
|
109
167
|
let state = "active";
|
|
110
168
|
let termination;
|
|
111
|
-
const invalidate = () => {
|
|
169
|
+
const invalidate = () => {
|
|
170
|
+
state = "inert";
|
|
171
|
+
};
|
|
112
172
|
child.once("exit", invalidate);
|
|
113
173
|
child.once("close", invalidate);
|
|
174
|
+
launched = true;
|
|
114
175
|
return {
|
|
115
176
|
pid,
|
|
177
|
+
exited,
|
|
116
178
|
terminate(force = false) {
|
|
117
179
|
if (state === "inert")
|
|
118
180
|
return Promise.resolve();
|
|
@@ -131,7 +193,8 @@ export async function spawnDetachedProcess(input) {
|
|
|
131
193
|
};
|
|
132
194
|
}
|
|
133
195
|
finally {
|
|
134
|
-
|
|
196
|
+
if (!launched)
|
|
197
|
+
await log.close();
|
|
135
198
|
}
|
|
136
199
|
}
|
|
137
200
|
function terminalOutcome(code, stdout, stderr, consuming) {
|
|
@@ -146,13 +209,7 @@ function terminalOutcome(code, stdout, stderr, consuming) {
|
|
|
146
209
|
async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = spawn) {
|
|
147
210
|
if (input.signal?.aborted === true)
|
|
148
211
|
return { outcome: { kind: "cancelled" }, pid: null };
|
|
149
|
-
const child = spawnProcess(input.argv[0], input.argv.slice(1),
|
|
150
|
-
cwd: input.cwd,
|
|
151
|
-
env: input.env,
|
|
152
|
-
detached: true,
|
|
153
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
154
|
-
windowsHide: true,
|
|
155
|
-
});
|
|
212
|
+
const child = spawnProcess(input.argv[0], input.argv.slice(1), spawnOptionsFor("retained", input, ["ignore", "pipe", "pipe"]));
|
|
156
213
|
const stdout = tailCapture(STREAM_TAIL_BYTES);
|
|
157
214
|
const stderr = tailCapture(STREAM_TAIL_BYTES);
|
|
158
215
|
let stop;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { spawnOptionsFor } from "./launch.js";
|
|
3
4
|
import { terminateOwnedProcess } from "./run.js";
|
|
4
5
|
const DEFAULT_DRAIN_TIMEOUT_MS = 1_000;
|
|
5
6
|
export function spawnStdioProcess(input) {
|
|
6
|
-
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
7
|
+
const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor("retained", {
|
|
7
8
|
cwd: input.cwd,
|
|
8
9
|
env: input.env ?? process.env,
|
|
9
|
-
|
|
10
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
-
windowsHide: true,
|
|
12
|
-
});
|
|
10
|
+
}, ["pipe", "pipe", "pipe"]));
|
|
13
11
|
let stderr = "";
|
|
14
12
|
let inputEnded = false;
|
|
15
13
|
let forced;
|
|
16
14
|
child.stderr.setEncoding("utf8");
|
|
17
|
-
child.stderr.on("data", (chunk) => {
|
|
15
|
+
child.stderr.on("data", (chunk) => {
|
|
16
|
+
stderr = `${stderr}${chunk}`.slice(-4_000);
|
|
17
|
+
});
|
|
18
18
|
const exited = new Promise((resolve) => {
|
|
19
19
|
child.once("close", (code, signal) => resolve({ code, signal, stderr: stderr.trim() }));
|
|
20
20
|
child.once("error", (error) => resolve({ code: null, signal: null, stderr: error.message }));
|
|
@@ -38,10 +38,7 @@ export function spawnStdioProcess(input) {
|
|
|
38
38
|
await forced;
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
const timedOut = await Promise.race([
|
|
42
|
-
exited.then(() => false),
|
|
43
|
-
delay(timeoutMs, true, { ref: false }),
|
|
44
|
-
]);
|
|
41
|
+
const timedOut = await Promise.race([exited.then(() => false), delay(timeoutMs, true, { ref: false })]);
|
|
45
42
|
if (timedOut)
|
|
46
43
|
await forceClose();
|
|
47
44
|
};
|
package/build/src/settings.js
CHANGED
|
@@ -59,7 +59,9 @@ function namespaceEntries(name, scope, loaded) {
|
|
|
59
59
|
return { entries: [], failure: { scope, diagnostic: `${name} must be an object of named values` } };
|
|
60
60
|
}
|
|
61
61
|
return {
|
|
62
|
-
entries: Object.keys(value)
|
|
62
|
+
entries: Object.keys(value)
|
|
63
|
+
.sort()
|
|
64
|
+
.map((entryName) => ({
|
|
63
65
|
name: entryName,
|
|
64
66
|
value: value[entryName],
|
|
65
67
|
source: scope,
|
|
@@ -110,5 +112,7 @@ export async function settings(input = {}) {
|
|
|
110
112
|
export async function projectSettings(root) {
|
|
111
113
|
if (typeof root !== "string" || root.trim().length === 0)
|
|
112
114
|
throw new TypeError("settings root must be a nonblank string");
|
|
113
|
-
return settingsFromScopes(await readScope(join(resolve(root), ".keiyaku", "settings.json")), {
|
|
115
|
+
return settingsFromScopes(await readScope(join(resolve(root), ".keiyaku", "settings.json")), {
|
|
116
|
+
state: { kind: "absent" },
|
|
117
|
+
});
|
|
114
118
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
3
|
import { contractId } from "../core/facts/types.js";
|
|
4
|
-
import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit
|
|
4
|
+
import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit } from "../git/repository.js";
|
|
5
5
|
import { withGitTargetedReadObservation, } from "../git/read-observation.js";
|
|
6
6
|
import { parseTaskId } from "../task/identity.js";
|
|
7
7
|
import { acquireTaskSettlementFence } from "./fence.js";
|
|
@@ -107,7 +107,9 @@ async function admitWithTaskHolderFence(repository, taskId, action) {
|
|
|
107
107
|
try {
|
|
108
108
|
held.close();
|
|
109
109
|
}
|
|
110
|
-
catch {
|
|
110
|
+
catch {
|
|
111
|
+
/* The operation failure remains decisive before admission. */
|
|
112
|
+
}
|
|
111
113
|
throw error;
|
|
112
114
|
}
|
|
113
115
|
return finishTaskHolderAdmission(taskId, result, () => held.close());
|
|
@@ -117,9 +119,7 @@ export function claimTaskHolderWithFence(repository, taskId, action) {
|
|
|
117
119
|
}
|
|
118
120
|
export async function releaseTaskHolder(channel, observation, owner) {
|
|
119
121
|
const current = (await readTaskHolderProjectionFromDecision(channel, observation)).get(owner) ?? null;
|
|
120
|
-
return current === null || current.disposition !== "held"
|
|
121
|
-
? null
|
|
122
|
-
: update({ ...current, disposition: "released" });
|
|
122
|
+
return current === null || current.disposition !== "held" ? null : update({ ...current, disposition: "released" });
|
|
123
123
|
}
|
|
124
124
|
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
125
125
|
export async function publishTaskHolderRelease(repository, channel, observation, owner) {
|
|
@@ -133,16 +133,20 @@ export async function publishTaskHolderRelease(repository, channel, observation,
|
|
|
133
133
|
parent: observation.admission.snapshot.commit,
|
|
134
134
|
message: `release held Task completion: ${owner}`,
|
|
135
135
|
});
|
|
136
|
-
const publication = await updateRefsAtomically(repository, [
|
|
136
|
+
const publication = await updateRefsAtomically(repository, [
|
|
137
|
+
{
|
|
137
138
|
ref: GIT_REF,
|
|
138
139
|
newOid: commit,
|
|
139
140
|
expectedOid: observation.admission.snapshot.commit,
|
|
140
|
-
}
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
141
143
|
if (publication.kind === "published")
|
|
142
144
|
return { kind: "released" };
|
|
143
145
|
return {
|
|
144
146
|
kind: "non-published",
|
|
145
|
-
diagnostic: publication.kind === "non-published"
|
|
147
|
+
diagnostic: publication.kind === "non-published"
|
|
148
|
+
? detail(publication.error)
|
|
149
|
+
: "Task holder release publication state is unknown",
|
|
146
150
|
};
|
|
147
151
|
}
|
|
148
152
|
export async function readTaskHolderProjectionFromDecision(channel, observation) {
|
|
@@ -179,8 +183,7 @@ export async function releaseTaskHolderWithFence(repository, channel, owner, act
|
|
|
179
183
|
: admitWithTaskHolderFence(repository, taskId, action);
|
|
180
184
|
}
|
|
181
185
|
export async function readTaskHoldersAt(observation) {
|
|
182
|
-
const entries = [...observation.snapshot.paths]
|
|
183
|
-
.filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
|
|
186
|
+
const entries = [...observation.snapshot.paths].filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
|
|
184
187
|
const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
|
|
185
188
|
const holders = [];
|
|
186
189
|
const seen = new Set();
|
|
@@ -25,7 +25,12 @@ async function settleTasks(input) {
|
|
|
25
25
|
observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
|
-
lags.push({
|
|
28
|
+
lags.push({
|
|
29
|
+
kind: "settlement-failed",
|
|
30
|
+
surface: "task-holder",
|
|
31
|
+
contractId: candidate.id,
|
|
32
|
+
diagnostic: diagnostic(error),
|
|
33
|
+
});
|
|
29
34
|
return;
|
|
30
35
|
}
|
|
31
36
|
const state = observation.journals.get(candidate.id)?.state ?? null;
|
|
@@ -36,7 +41,12 @@ async function settleTasks(input) {
|
|
|
36
41
|
holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
|
|
37
42
|
}
|
|
38
43
|
catch (error) {
|
|
39
|
-
lags.push({
|
|
44
|
+
lags.push({
|
|
45
|
+
kind: "settlement-failed",
|
|
46
|
+
surface: "task-holder",
|
|
47
|
+
contractId: candidate.id,
|
|
48
|
+
diagnostic: diagnostic(error),
|
|
49
|
+
});
|
|
40
50
|
return;
|
|
41
51
|
}
|
|
42
52
|
if (holder === null || holder.disposition !== "held")
|
|
@@ -47,13 +57,25 @@ async function settleTasks(input) {
|
|
|
47
57
|
result = await settleTask(world, taskId);
|
|
48
58
|
}
|
|
49
59
|
catch (error) {
|
|
50
|
-
lags.push({
|
|
60
|
+
lags.push({
|
|
61
|
+
kind: "settlement-failed",
|
|
62
|
+
surface: "task",
|
|
63
|
+
contractId: candidate.id,
|
|
64
|
+
taskId,
|
|
65
|
+
diagnostic: diagnostic(error),
|
|
66
|
+
});
|
|
51
67
|
return;
|
|
52
68
|
}
|
|
53
69
|
if (result.kind === "changed")
|
|
54
70
|
actions.push({ kind: "task", taskId, action: "done" });
|
|
55
71
|
else if (result.kind !== "unchanged") {
|
|
56
|
-
lags.push({
|
|
72
|
+
lags.push({
|
|
73
|
+
kind: "settlement-failed",
|
|
74
|
+
surface: "task",
|
|
75
|
+
contractId: candidate.id,
|
|
76
|
+
taskId,
|
|
77
|
+
diagnostic: taskFailure(result),
|
|
78
|
+
});
|
|
57
79
|
return;
|
|
58
80
|
}
|
|
59
81
|
try {
|
|
@@ -69,7 +91,13 @@ async function settleTasks(input) {
|
|
|
69
91
|
}
|
|
70
92
|
}
|
|
71
93
|
catch (error) {
|
|
72
|
-
lags.push({
|
|
94
|
+
lags.push({
|
|
95
|
+
kind: "settlement-failed",
|
|
96
|
+
surface: "task-holder",
|
|
97
|
+
contractId: candidate.id,
|
|
98
|
+
taskId,
|
|
99
|
+
diagnostic: diagnostic(error),
|
|
100
|
+
});
|
|
73
101
|
}
|
|
74
102
|
}
|
|
75
103
|
async function settleNamespace(state, effects, actions, lags) {
|
|
@@ -111,7 +139,12 @@ async function settleObserved(input) {
|
|
|
111
139
|
});
|
|
112
140
|
}
|
|
113
141
|
catch (error) {
|
|
114
|
-
lags.push({
|
|
142
|
+
lags.push({
|
|
143
|
+
kind: "settlement-failed",
|
|
144
|
+
surface: "task",
|
|
145
|
+
contractId: input.state.id,
|
|
146
|
+
diagnostic: diagnostic(error),
|
|
147
|
+
});
|
|
115
148
|
}
|
|
116
149
|
}
|
|
117
150
|
await settleNamespace(input.state, input.effects, actions, lags);
|
package/build/src/task/board.js
CHANGED
|
@@ -73,12 +73,16 @@ export function projectDetailFacts(board, id, relations) {
|
|
|
73
73
|
needs,
|
|
74
74
|
blockers: needs.filter((need) => !need.released).map(({ released: _released, ...ref }) => ref),
|
|
75
75
|
blocks: relations.blocks(id),
|
|
76
|
-
parent: task.parent === null ? null : taskRef(board, task.parent),
|
|
77
|
-
|
|
76
|
+
parent: task.parent === null ? null : taskRef(board, task.parent),
|
|
77
|
+
children: relations.children(id),
|
|
78
|
+
supersedes: task.supersedes.map((target) => taskRef(board, target)),
|
|
79
|
+
supersededBy: relations.supersededBy(id),
|
|
78
80
|
related: relations.related(id),
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
|
-
function inScope(task, scope) {
|
|
83
|
+
function inScope(task, scope) {
|
|
84
|
+
return scope === null || sameNamespace(parseTaskId(task.id).namespace, scope);
|
|
85
|
+
}
|
|
82
86
|
export function projectTaskRow(board, relations, task) {
|
|
83
87
|
const children = relations.children(task.id);
|
|
84
88
|
const total = children.length;
|
|
@@ -95,8 +99,10 @@ export function projectTaskRow(board, relations, task) {
|
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
101
|
export function projectRows(board, relations, scope, selection) {
|
|
98
|
-
return [...board.tasks.values()]
|
|
99
|
-
|
|
102
|
+
return [...board.tasks.values()]
|
|
103
|
+
.filter((task) => inScope(task, scope))
|
|
104
|
+
.filter((task) => selection === "all" || (selection === "closed") === terminal(task.state))
|
|
105
|
+
.map((task) => projectTaskRow(board, relations, task))
|
|
100
106
|
.sort((a, b) => a.priority - b.priority || Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
|
|
101
107
|
}
|
|
102
108
|
export function projectBlocked(board, scope, relations) {
|
|
@@ -123,7 +129,7 @@ export function projectTaskBoardObservation(board) {
|
|
|
123
129
|
};
|
|
124
130
|
}
|
|
125
131
|
function relationIds(task, relation) {
|
|
126
|
-
return relation === "parent" ? task.parent === null ? [] : [task.parent] : task[relation];
|
|
132
|
+
return relation === "parent" ? (task.parent === null ? [] : [task.parent]) : task[relation];
|
|
127
133
|
}
|
|
128
134
|
export function relationProblem(board, before, after) {
|
|
129
135
|
for (const relation of ["needs", "parent", "supersedes", "relates"]) {
|
|
@@ -4,7 +4,9 @@ import { serializeTaskDocument } from "./document.js";
|
|
|
4
4
|
import { allocateLocalId, deriveLocalStem, formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
|
|
5
5
|
import { authorityPath, readBoard, replaceAuthority, withTaskLocks } from "./store.js";
|
|
6
6
|
import { advanceTaskTimestamp } from "./operations.js";
|
|
7
|
-
function failure(diagnostic) {
|
|
7
|
+
function failure(diagnostic) {
|
|
8
|
+
return { kind: "invalid-composition", diagnostic };
|
|
9
|
+
}
|
|
8
10
|
function parseNamespace(value) {
|
|
9
11
|
if (value === "")
|
|
10
12
|
return [];
|
|
@@ -30,14 +32,32 @@ function parseNode(text, depth, index) {
|
|
|
30
32
|
const title = values.slice(0, at < 0 ? values.length : at).join(" ");
|
|
31
33
|
if (title.trim().length === 0)
|
|
32
34
|
throw new TypeError("compose + node requires a title");
|
|
33
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
index,
|
|
37
|
+
depth,
|
|
38
|
+
kind: "new",
|
|
39
|
+
title,
|
|
40
|
+
assignments: (at < 0 ? [] : values.slice(at)).map((token) => assignment(token) ??
|
|
41
|
+
(() => {
|
|
42
|
+
throw new TypeError(`invalid assignment token: ${token}`);
|
|
43
|
+
})()),
|
|
44
|
+
};
|
|
34
45
|
}
|
|
35
46
|
const [rawId, ...rest] = tokens;
|
|
36
47
|
if (rawId === undefined || !rawId.startsWith("@task/"))
|
|
37
48
|
throw new TypeError("compose node must begin with + or @task/");
|
|
38
49
|
const id = `task/${rawId.slice(1).slice(5)}`;
|
|
39
50
|
parseTaskId(id);
|
|
40
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
index,
|
|
53
|
+
depth,
|
|
54
|
+
kind: "existing",
|
|
55
|
+
id,
|
|
56
|
+
assignments: rest.map((token) => assignment(token) ??
|
|
57
|
+
(() => {
|
|
58
|
+
throw new TypeError(`invalid assignment token: ${token}`);
|
|
59
|
+
})()),
|
|
60
|
+
};
|
|
41
61
|
}
|
|
42
62
|
function parseSketch(markdown) {
|
|
43
63
|
try {
|
|
@@ -73,7 +93,10 @@ function parseSketch(markdown) {
|
|
|
73
93
|
}
|
|
74
94
|
current.body.push(text.startsWith("\\") ? text.slice(1) : text);
|
|
75
95
|
}
|
|
76
|
-
const nodes = mutable.map(({ node, body }) => ({
|
|
96
|
+
const nodes = mutable.map(({ node, body }) => ({
|
|
97
|
+
...node,
|
|
98
|
+
...(body.length === 0 ? {} : { body: body.join("\n") }),
|
|
99
|
+
}));
|
|
77
100
|
return { ...(namespace === undefined ? {} : { namespace }), nodes };
|
|
78
101
|
}
|
|
79
102
|
catch (error) {
|
|
@@ -83,14 +106,23 @@ function parseSketch(markdown) {
|
|
|
83
106
|
function ids(value) {
|
|
84
107
|
if (value === "")
|
|
85
108
|
return [];
|
|
86
|
-
const parsed = value.split(",").map((raw) => {
|
|
87
|
-
|
|
109
|
+
const parsed = value.split(",").map((raw) => {
|
|
110
|
+
if (!raw.startsWith("@task/"))
|
|
111
|
+
throw new TypeError("relation values must be @TaskId");
|
|
112
|
+
const id = raw.slice(1);
|
|
113
|
+
parseTaskId(id);
|
|
114
|
+
return id;
|
|
115
|
+
});
|
|
88
116
|
if (new Set(parsed).size !== parsed.length)
|
|
89
117
|
throw new TypeError("relation values must not contain duplicates");
|
|
90
118
|
return parsed;
|
|
91
119
|
}
|
|
92
|
-
function scalarId(value) {
|
|
93
|
-
|
|
120
|
+
function scalarId(value) {
|
|
121
|
+
const values = ids(value);
|
|
122
|
+
if (values.length > 1)
|
|
123
|
+
throw new TypeError("parent accepts at most one TaskId");
|
|
124
|
+
return values[0] ?? null;
|
|
125
|
+
}
|
|
94
126
|
function changed(current, value, append) {
|
|
95
127
|
return append ? [...current, ...value.filter((id) => !current.includes(id))] : value;
|
|
96
128
|
}
|
|
@@ -122,9 +154,19 @@ function currentTimestamp() {
|
|
|
122
154
|
}
|
|
123
155
|
function createdTask(id, title, at, actor) {
|
|
124
156
|
return {
|
|
125
|
-
id,
|
|
157
|
+
id,
|
|
158
|
+
title,
|
|
159
|
+
state: "open",
|
|
160
|
+
priority: 2,
|
|
161
|
+
needs: [],
|
|
162
|
+
parent: null,
|
|
163
|
+
supersedes: [],
|
|
164
|
+
relates: [],
|
|
165
|
+
note: "",
|
|
126
166
|
...(actor === undefined ? {} : { createdBy: actor }),
|
|
127
|
-
createdAt: at,
|
|
167
|
+
createdAt: at,
|
|
168
|
+
updatedAt: at,
|
|
169
|
+
body: "",
|
|
128
170
|
};
|
|
129
171
|
}
|
|
130
172
|
function plan(sketch, board, defaultNamespace, at, actor) {
|
|
@@ -148,7 +190,7 @@ function plan(sketch, board, defaultNamespace, at, actor) {
|
|
|
148
190
|
const byDepth = [], addressed = new Set();
|
|
149
191
|
const planned = [];
|
|
150
192
|
for (const node of sketch.nodes) {
|
|
151
|
-
const before = node.kind === "new" ? null : board.tasks.get(node.id) ?? null;
|
|
193
|
+
const before = node.kind === "new" ? null : (board.tasks.get(node.id) ?? null);
|
|
152
194
|
let current = node.kind === "new" ? allocations.get(node.index) : all.get(node.id);
|
|
153
195
|
if (current === undefined)
|
|
154
196
|
throw new TypeError(`compose task does not exist: ${node.id}`);
|
|
@@ -164,8 +206,8 @@ function plan(sketch, board, defaultNamespace, at, actor) {
|
|
|
164
206
|
current = { ...current, parent };
|
|
165
207
|
}
|
|
166
208
|
current = applyAssignments(current, node.assignments, node.body);
|
|
167
|
-
if (before !== null
|
|
168
|
-
|
|
209
|
+
if (before !== null &&
|
|
210
|
+
!Buffer.from(serializeTaskDocument(current)).equals(Buffer.from(serializeTaskDocument(before)))) {
|
|
169
211
|
current = {
|
|
170
212
|
...current,
|
|
171
213
|
updatedAt: advanceTaskTimestamp(before.updatedAt, at),
|
|
@@ -187,15 +229,22 @@ function plan(sketch, board, defaultNamespace, at, actor) {
|
|
|
187
229
|
return failure(error instanceof Error ? error.message : String(error));
|
|
188
230
|
}
|
|
189
231
|
}
|
|
190
|
-
function ref(id) {
|
|
191
|
-
|
|
232
|
+
function ref(id) {
|
|
233
|
+
return `@${id}`;
|
|
234
|
+
}
|
|
235
|
+
function bodyLines(body) {
|
|
236
|
+
return body.split("\n").map((line) => (/^[\\]|^\+ |^@task\//u.test(line) ? `\\${line}` : line));
|
|
237
|
+
}
|
|
192
238
|
function draft(namespace, remaining) {
|
|
193
239
|
const lines = [`ns=${namespace.join("/")}`];
|
|
194
240
|
for (const item of remaining) {
|
|
195
241
|
const task = item.after;
|
|
196
242
|
const assignments = [
|
|
197
|
-
`pri=${task.priority}`,
|
|
198
|
-
`
|
|
243
|
+
`pri=${task.priority}`,
|
|
244
|
+
`needs=${task.needs.map(ref).join(",")}`,
|
|
245
|
+
`parent=${task.parent === null ? "" : ref(task.parent)}`,
|
|
246
|
+
`supersedes=${task.supersedes.map(ref).join(",")}`,
|
|
247
|
+
`relates=${task.relates.map(ref).join(",")}`,
|
|
199
248
|
];
|
|
200
249
|
lines.push(`${item.node.kind === "new" ? `+ ${task.title}` : ref(task.id)} ${assignments.join(" ")}${task.body === "" ? " body=" : ""}`);
|
|
201
250
|
if (task.body !== "")
|
|
@@ -224,19 +273,33 @@ export async function composeTasks(world, markdown, signal, actor, defaultNamesp
|
|
|
224
273
|
for (let index = 0; index < queue.length; index += 1) {
|
|
225
274
|
signal?.throwIfAborted();
|
|
226
275
|
const item = queue[index], path = authorityPath(world, item.after.id);
|
|
227
|
-
const beforeBytes = item.before === null ? null : fresh.bytes.get(item.after.id) ?? null;
|
|
276
|
+
const beforeBytes = item.before === null ? null : (fresh.bytes.get(item.after.id) ?? null);
|
|
228
277
|
const afterBytes = serializeTaskDocument(item.after);
|
|
229
278
|
const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8"), after = Buffer.from(afterBytes).toString("utf8");
|
|
230
279
|
if (before === after)
|
|
231
280
|
continue;
|
|
232
|
-
if (await replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
|
|
281
|
+
if ((await replaceAuthority({ path, expected: beforeBytes, next: afterBytes })) !== "replaced")
|
|
233
282
|
return {
|
|
234
|
-
kind: "incomplete",
|
|
283
|
+
kind: "incomplete",
|
|
284
|
+
documentChanges: changes,
|
|
285
|
+
stopped: { kind: "retry", reason: "concurrent-modification" },
|
|
286
|
+
draft: draft(namespace, queue.slice(index)),
|
|
235
287
|
};
|
|
236
288
|
const label = `${item.after.id}.md`;
|
|
237
|
-
changes.push({
|
|
289
|
+
changes.push({
|
|
290
|
+
taskId: item.after.id,
|
|
291
|
+
kind: item.before === null ? "created" : "updated",
|
|
292
|
+
documentDiff: documentDiff(label, label, before, after),
|
|
293
|
+
});
|
|
238
294
|
}
|
|
239
295
|
return { kind: "accepted", documentChanges: changes };
|
|
240
296
|
});
|
|
241
|
-
return result === "busy"
|
|
297
|
+
return result === "busy"
|
|
298
|
+
? {
|
|
299
|
+
kind: "incomplete",
|
|
300
|
+
documentChanges: [],
|
|
301
|
+
stopped: { kind: "retry", reason: "busy" },
|
|
302
|
+
draft: draft(namespace, ordered),
|
|
303
|
+
}
|
|
304
|
+
: result;
|
|
242
305
|
}
|
|
@@ -2,13 +2,23 @@ import { lstat, mkdir, readFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { repairDerivedFile, replaceFileDurably } from "../coordination/durable-file.js";
|
|
4
4
|
import { normalizeIdentityStem } from "../identity/normalize.js";
|
|
5
|
-
function directory(root) {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function directory(root) {
|
|
6
|
+
return join(root, ".keiyaku", "namespace");
|
|
7
|
+
}
|
|
8
|
+
function currentPath(root) {
|
|
9
|
+
return join(directory(root), "current");
|
|
10
|
+
}
|
|
11
|
+
function ignorePath(root) {
|
|
12
|
+
return join(directory(root), ".gitignore");
|
|
13
|
+
}
|
|
8
14
|
function validNamespaceSegments(value) {
|
|
9
|
-
return Array.isArray(value) &&
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
return (Array.isArray(value) &&
|
|
16
|
+
value.every((segment) => typeof segment === "string" &&
|
|
17
|
+
segment.length > 0 &&
|
|
18
|
+
normalizeIdentityStem({ source: segment }) === segment &&
|
|
19
|
+
!segment.includes("/") &&
|
|
20
|
+
segment !== "." &&
|
|
21
|
+
segment !== ".."));
|
|
12
22
|
}
|
|
13
23
|
async function readAt(root) {
|
|
14
24
|
let bytes;
|
|
@@ -66,8 +76,11 @@ export async function repairNamespaceContext(root, segments) {
|
|
|
66
76
|
}
|
|
67
77
|
export async function resolveTaskNamespaceContext(input) {
|
|
68
78
|
const selected = await readNamespaceContext(input);
|
|
69
|
-
return selected === "absent"
|
|
70
|
-
?
|
|
79
|
+
return selected === "absent"
|
|
80
|
+
? []
|
|
81
|
+
: typeof selected === "object" && "kind" in selected
|
|
82
|
+
? { kind: "invalid-namespace-context", path: selected.path }
|
|
83
|
+
: selected;
|
|
71
84
|
}
|
|
72
85
|
export async function writeTaskNamespaceContext(directory, value) {
|
|
73
86
|
await installNamespaceContext(directory, value);
|