@astrosheep/keiyaku 4.0.1 → 4.0.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 +137 -5
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +23 -14
- package/build/src/akuma/akuma.js +120 -104
- package/build/src/akuma/archetype.d.ts +7 -6
- package/build/src/akuma/archetype.js +68 -41
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +408 -213
- package/build/src/akuma/heart/facts.d.ts +25 -34
- package/build/src/akuma/heart/facts.js +18 -7
- package/build/src/akuma/heart/index.d.ts +39 -42
- package/build/src/akuma/heart/index.js +134 -93
- package/build/src/akuma/heart/rows.d.ts +9 -31
- package/build/src/akuma/heart/rows.js +26 -44
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +7 -15
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +28 -14
- package/build/src/akuma/heart/storage.js +86 -26
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +6 -1
- package/build/src/akuma/index.js +6 -0
- package/build/src/akuma/projection.d.ts +117 -38
- package/build/src/akuma/projection.js +208 -97
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +12 -4
- package/build/src/akuma/provider.js +117 -70
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/claude/events.js +66 -13
- package/build/src/akuma/providers/claude/index.d.ts +2 -1
- package/build/src/akuma/providers/claude/index.js +35 -21
- package/build/src/akuma/providers/codex-app-server/events.js +4 -1
- package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
- package/build/src/akuma/providers/codex-app-server/index.js +24 -31
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +24 -59
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
- package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
- package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
- package/build/src/akuma/providers/pi/events.js +49 -11
- package/build/src/akuma/providers/pi/index.d.ts +1 -1
- package/build/src/akuma/providers/pi/index.js +50 -21
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -7
- package/build/src/akuma/requests.js +92 -65
- package/build/src/alias/index.d.ts +2 -2
- package/build/src/alias/index.js +10 -10
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/cli/accepted.d.ts +17 -13
- package/build/src/cli/accepted.js +72 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
- package/build/src/cli/commands/akuma-invoke.js +14 -9
- package/build/src/cli/commands/akuma.d.ts +13 -3
- package/build/src/cli/commands/akuma.js +67 -34
- package/build/src/cli/commands/contract.d.ts +18 -5
- package/build/src/cli/commands/contract.js +9 -3
- package/build/src/cli/commands/task-invoke.d.ts +17 -4
- package/build/src/cli/commands/task-invoke.js +43 -20
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +51 -13
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +4 -3
- package/build/src/cli/invoke.js +160 -179
- package/build/src/cli/main.js +34 -6
- package/build/src/cli/parse.d.ts +9 -2
- package/build/src/cli/parse.js +29 -7
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +2 -2
- package/build/src/cli/render/akuma-tool.js +30 -4
- package/build/src/cli/render/akuma.d.ts +1 -0
- package/build/src/cli/render/akuma.js +211 -116
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +192 -79
- package/build/src/cli/render/kanshi.js +263 -168
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -20
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +7 -0
- package/build/src/cli/render/terminal.js +46 -0
- package/build/src/cli/render/text.js +6 -3
- package/build/src/cli/result.d.ts +115 -20
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +6 -5
- package/build/src/contract-worktree.js +95 -57
- package/build/src/coordination/durable-file.d.ts +4 -3
- package/build/src/coordination/durable-file.js +29 -25
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +0 -11
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +1 -7
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +4 -1
- package/build/src/dispatch/index.d.ts +2 -2
- package/build/src/dispatch/index.js +17 -17
- package/build/src/git/admission.d.ts +1 -1
- package/build/src/git/admission.js +11 -11
- package/build/src/git/hooks.js +13 -13
- package/build/src/git/integration.d.ts +14 -15
- package/build/src/git/integration.js +80 -44
- package/build/src/git/observe.d.ts +5 -3
- package/build/src/git/observe.js +18 -12
- package/build/src/git/read-observation.js +9 -9
- package/build/src/git/reconcile.d.ts +2 -0
- package/build/src/git/reconcile.js +138 -95
- package/build/src/git/repository.d.ts +27 -19
- package/build/src/git/repository.js +155 -83
- package/build/src/git/scratch.d.ts +7 -3
- package/build/src/git/scratch.js +44 -37
- package/build/src/git/target-placement.d.ts +40 -4
- package/build/src/git/target-placement.js +221 -101
- package/build/src/git/tender.d.ts +6 -5
- package/build/src/git/tender.js +24 -43
- package/build/src/git/terminal-seal.d.ts +1 -1
- package/build/src/git/terminal-seal.js +7 -5
- package/build/src/git/workspace.d.ts +36 -4
- package/build/src/git/workspace.js +53 -18
- package/build/src/identity/selector.js +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.js +1 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +2 -1
- package/build/src/kanshi/read.js +118 -27
- package/build/src/kanshi/report.d.ts +50 -0
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +32 -0
- package/build/src/library/address.d.ts +12 -3
- package/build/src/library/address.js +77 -46
- package/build/src/library/akuma-creation.d.ts +6 -1
- package/build/src/library/akuma-creation.js +86 -13
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.js +4 -4
- package/build/src/library/catalog.d.ts +1 -1
- package/build/src/library/catalog.js +5 -8
- package/build/src/library/contract.d.ts +5 -6
- package/build/src/library/contract.js +11 -35
- package/build/src/library/delivery.d.ts +2 -1
- package/build/src/library/fleet.d.ts +2 -2
- package/build/src/library/fleet.js +35 -71
- package/build/src/library/keiyaku.d.ts +3 -2
- package/build/src/library/keiyaku.js +1 -0
- package/build/src/library/mutation.d.ts +7 -6
- package/build/src/library/mutation.js +23 -25
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/region.d.ts +12 -0
- package/build/src/library/region.js +15 -1
- package/build/src/library/repo.d.ts +4 -16
- package/build/src/library/repo.js +13 -38
- package/build/src/protocol/attempt.js +6 -6
- package/build/src/protocol/bind.js +4 -4
- package/build/src/protocol/intent.d.ts +15 -2
- package/build/src/protocol/intent.js +21 -6
- package/build/src/protocol/operations.d.ts +73 -22
- package/build/src/protocol/operations.js +292 -88
- package/build/src/protocol/outcome.d.ts +9 -3
- package/build/src/protocol/outcome.js +3 -1
- package/build/src/protocol/placement.d.ts +15 -4
- package/build/src/protocol/placement.js +58 -25
- package/build/src/protocol/read/status.d.ts +8 -1
- package/build/src/protocol/read/status.js +61 -14
- package/build/src/runtime/proc/line-rpc.d.ts +3 -8
- package/build/src/runtime/proc/line-rpc.js +16 -43
- package/build/src/runtime/proc/run.d.ts +15 -29
- package/build/src/runtime/proc/run.js +113 -105
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +2 -2
- package/build/src/settings.js +8 -8
- package/build/src/settlement/fence.d.ts +0 -4
- package/build/src/settlement/fence.js +0 -9
- package/build/src/settlement/holder.d.ts +11 -1
- package/build/src/settlement/holder.js +25 -0
- package/build/src/settlement/settle.d.ts +1 -1
- package/build/src/settlement/settle.js +52 -89
- package/build/src/task/board.d.ts +3 -3
- package/build/src/task/board.js +6 -9
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +14 -14
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +19 -7
- package/build/src/task/index.js +42 -23
- package/build/src/task/operations.d.ts +15 -9
- package/build/src/task/operations.js +78 -48
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +2 -2
- package/build/src/task/store.js +40 -25
- package/build/src/verification/execution.d.ts +4 -2
- package/build/src/verification/execution.js +9 -5
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +13 -2
- package/build/src/world.js +81 -48
- package/package.json +3 -2
- package/build/src/protocol/read/audit.d.ts +0 -31
- package/build/src/protocol/read/audit.js +0 -55
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import { promisify } from "node:util";
|
|
3
5
|
const TERMINATION_GRACE_MS = 250;
|
|
4
6
|
const STREAM_TAIL_BYTES = 16 * 1024;
|
|
5
7
|
function tailCapture(limit) {
|
|
@@ -31,25 +33,21 @@ function tailCapture(limit) {
|
|
|
31
33
|
},
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
|
-
function wait(milliseconds) {
|
|
35
|
-
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
36
|
-
}
|
|
37
36
|
function ignoreMissingProcess(error) {
|
|
38
37
|
if (error.code !== "ESRCH")
|
|
39
38
|
throw error;
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
taskkill.once("error", resolve);
|
|
48
|
-
taskkill.once("close", resolve);
|
|
40
|
+
const WINDOWS_TERMINATION_TIMEOUT_MS = 1_000;
|
|
41
|
+
const execFileAsync = promisify(execFile);
|
|
42
|
+
async function terminateWindowsTree(pid) {
|
|
43
|
+
await execFileAsync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
44
|
+
timeout: WINDOWS_TERMINATION_TIMEOUT_MS,
|
|
45
|
+
windowsHide: true,
|
|
49
46
|
});
|
|
50
47
|
}
|
|
51
|
-
export async function
|
|
52
|
-
|
|
48
|
+
export async function terminateOwnedProcess(child, force = false) {
|
|
49
|
+
const pid = child.pid;
|
|
50
|
+
if (pid === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
53
51
|
return;
|
|
54
52
|
if (process.platform === "win32") {
|
|
55
53
|
await terminateWindowsTree(pid);
|
|
@@ -64,6 +62,11 @@ export async function terminateProcessTree(pid, force = false) {
|
|
|
64
62
|
}
|
|
65
63
|
return;
|
|
66
64
|
}
|
|
65
|
+
let exited = false;
|
|
66
|
+
const exit = new Promise((resolve) => {
|
|
67
|
+
child.once("exit", () => { exited = true; resolve(); });
|
|
68
|
+
child.once("close", () => { exited = true; resolve(); });
|
|
69
|
+
});
|
|
67
70
|
try {
|
|
68
71
|
process.kill(-pid, "SIGTERM");
|
|
69
72
|
}
|
|
@@ -71,7 +74,9 @@ export async function terminateProcessTree(pid, force = false) {
|
|
|
71
74
|
ignoreMissingProcess(error);
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
74
|
-
await
|
|
77
|
+
await Promise.race([exit, delay(TERMINATION_GRACE_MS)]);
|
|
78
|
+
if (exited || child.exitCode !== null || child.signalCode !== null)
|
|
79
|
+
return;
|
|
75
80
|
try {
|
|
76
81
|
process.kill(-pid, "SIGKILL");
|
|
77
82
|
}
|
|
@@ -79,49 +84,14 @@ export async function terminateProcessTree(pid, force = false) {
|
|
|
79
84
|
ignoreMissingProcess(error);
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
|
-
function processStartToken(pid) {
|
|
83
|
-
const command = process.platform === "win32"
|
|
84
|
-
? ["powershell.exe", "-NoProfile", "-Command", `(Get-CimInstance Win32_Process -Filter \"ProcessId=${pid}\").CreationDate`]
|
|
85
|
-
: ["ps", "-o", "lstart=", "-p", String(pid)];
|
|
86
|
-
const result = spawnSync(command[0], command.slice(1), { encoding: "utf8", windowsHide: true });
|
|
87
|
-
const token = result.status === 0 ? result.stdout.trim() : "";
|
|
88
|
-
return token.length === 0 ? null : token;
|
|
89
|
-
}
|
|
90
|
-
export function currentProcessCollar() {
|
|
91
|
-
const identity = currentProcessIdentity();
|
|
92
|
-
return { ...identity, processGroup: identity.pid };
|
|
93
|
-
}
|
|
94
|
-
export function currentProcessIdentity() {
|
|
95
|
-
const spawnedAt = processStartToken(process.pid);
|
|
96
|
-
if (spawnedAt === null)
|
|
97
|
-
throw new Error(`cannot read process start identity for ${process.pid}`);
|
|
98
|
-
return { pid: process.pid, spawnedAt };
|
|
99
|
-
}
|
|
100
|
-
export function probeProcessIdentity(identity) {
|
|
101
|
-
try {
|
|
102
|
-
process.kill(identity.pid, 0);
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
const code = error.code;
|
|
106
|
-
if (code === "ESRCH")
|
|
107
|
-
return { kind: "gone" };
|
|
108
|
-
return { kind: "unverifiable", diagnostic: error instanceof Error ? error.message : String(error) };
|
|
109
|
-
}
|
|
110
|
-
const current = processStartToken(identity.pid);
|
|
111
|
-
if (current === null)
|
|
112
|
-
return { kind: "unverifiable", diagnostic: `cannot read process start identity for ${identity.pid}` };
|
|
113
|
-
if (current === identity.spawnedAt)
|
|
114
|
-
return { kind: "alive" };
|
|
115
|
-
return { kind: "replaced" };
|
|
116
|
-
}
|
|
117
87
|
export async function spawnDetachedProcess(input) {
|
|
118
|
-
const log =
|
|
88
|
+
const log = await open(input.log, "a");
|
|
119
89
|
try {
|
|
120
90
|
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
121
91
|
cwd: input.cwd,
|
|
122
92
|
env: input.env,
|
|
123
93
|
detached: true,
|
|
124
|
-
stdio: ["ignore", log, log],
|
|
94
|
+
stdio: ["ignore", log.fd, log.fd],
|
|
125
95
|
windowsHide: true,
|
|
126
96
|
});
|
|
127
97
|
await new Promise((resolve, reject) => {
|
|
@@ -130,48 +100,47 @@ export async function spawnDetachedProcess(input) {
|
|
|
130
100
|
});
|
|
131
101
|
if (child.pid === undefined)
|
|
132
102
|
throw new Error("detached process spawned without a pid");
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
103
|
+
const pid = child.pid;
|
|
104
|
+
let state = "active";
|
|
105
|
+
let termination;
|
|
106
|
+
const invalidate = () => { state = "inert"; };
|
|
107
|
+
child.once("exit", invalidate);
|
|
108
|
+
child.once("close", invalidate);
|
|
109
|
+
return {
|
|
110
|
+
pid,
|
|
111
|
+
terminate(force = false) {
|
|
112
|
+
if (state === "inert")
|
|
113
|
+
return Promise.resolve();
|
|
114
|
+
if (termination !== undefined)
|
|
115
|
+
return termination;
|
|
116
|
+
state = "terminating";
|
|
117
|
+
termination = terminateOwnedProcess(child, force).finally(invalidate);
|
|
118
|
+
return termination;
|
|
119
|
+
},
|
|
120
|
+
release: () => {
|
|
121
|
+
if (state === "inert")
|
|
122
|
+
return;
|
|
123
|
+
state = "inert";
|
|
124
|
+
child.unref();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
138
127
|
}
|
|
139
128
|
finally {
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
export function probeProcessTree(collar) {
|
|
144
|
-
try {
|
|
145
|
-
process.kill(process.platform === "win32" ? collar.pid : -collar.processGroup, 0);
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
const code = error.code;
|
|
149
|
-
if (code === "ESRCH")
|
|
150
|
-
return { kind: "gone" };
|
|
151
|
-
return { kind: "unverifiable", diagnostic: error instanceof Error ? error.message : String(error) };
|
|
129
|
+
await log.close();
|
|
152
130
|
}
|
|
153
|
-
const current = processStartToken(collar.pid);
|
|
154
|
-
if (current === null && process.platform !== "win32")
|
|
155
|
-
return { kind: "alive" };
|
|
156
|
-
if (current === collar.spawnedAt)
|
|
157
|
-
return { kind: "alive" };
|
|
158
|
-
return { kind: "unverifiable", diagnostic: `process ${collar.pid} no longer matches its recorded start identity` };
|
|
159
131
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (after.kind === "gone")
|
|
169
|
-
return "killed";
|
|
170
|
-
return after.kind === "alive" ? "alive-after-sigkill" : "unavailable";
|
|
132
|
+
function terminalOutcome(code, stdout, stderr, consuming) {
|
|
133
|
+
return {
|
|
134
|
+
kind: "terminal",
|
|
135
|
+
code,
|
|
136
|
+
stdout: consuming ? "" : stdout.text,
|
|
137
|
+
stderr: stderr.text,
|
|
138
|
+
truncated: (consuming ? false : stdout.truncated) || stderr.truncated,
|
|
139
|
+
};
|
|
171
140
|
}
|
|
172
|
-
async function executeProcess(input, timeoutMs) {
|
|
141
|
+
async function executeProcess(input, timeoutMs, consumeStdout) {
|
|
173
142
|
if (input.signal?.aborted === true)
|
|
174
|
-
return { kind: "cancelled" };
|
|
143
|
+
return { outcome: { kind: "cancelled" }, pid: null };
|
|
175
144
|
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
176
145
|
cwd: input.cwd,
|
|
177
146
|
env: input.env,
|
|
@@ -181,16 +150,38 @@ async function executeProcess(input, timeoutMs) {
|
|
|
181
150
|
});
|
|
182
151
|
const stdout = tailCapture(STREAM_TAIL_BYTES);
|
|
183
152
|
const stderr = tailCapture(STREAM_TAIL_BYTES);
|
|
184
|
-
child.stdout.on("data", (chunk) => stdout.append(chunk));
|
|
185
|
-
child.stderr.on("data", (chunk) => stderr.append(chunk));
|
|
186
153
|
let stop;
|
|
154
|
+
let streamError;
|
|
187
155
|
let termination;
|
|
188
156
|
const requestStop = (reason) => {
|
|
189
157
|
if (stop !== undefined)
|
|
190
158
|
return;
|
|
191
159
|
stop = reason;
|
|
192
|
-
termination =
|
|
160
|
+
termination = terminateOwnedProcess(child);
|
|
161
|
+
};
|
|
162
|
+
const failStream = (error) => {
|
|
163
|
+
if (streamError !== undefined)
|
|
164
|
+
return;
|
|
165
|
+
streamError = error;
|
|
166
|
+
requestStop("cancelled");
|
|
193
167
|
};
|
|
168
|
+
child.stdout.on("data", (chunk) => {
|
|
169
|
+
if (consumeStdout === undefined) {
|
|
170
|
+
stdout.append(chunk);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
consumeStdout(chunk);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
failStream(error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
child.stderr.on("data", (chunk) => stderr.append(chunk));
|
|
181
|
+
if (consumeStdout !== undefined) {
|
|
182
|
+
child.stdout.once("error", failStream);
|
|
183
|
+
child.stderr.once("error", failStream);
|
|
184
|
+
}
|
|
194
185
|
const cancel = () => requestStop("cancelled");
|
|
195
186
|
input.signal?.addEventListener("abort", cancel, { once: true });
|
|
196
187
|
const timeout = timeoutMs === undefined ? undefined : setTimeout(() => requestStop("timeout"), timeoutMs);
|
|
@@ -202,25 +193,42 @@ async function executeProcess(input, timeoutMs) {
|
|
|
202
193
|
clearTimeout(timeout);
|
|
203
194
|
input.signal?.removeEventListener("abort", cancel);
|
|
204
195
|
await termination;
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
const pid = child.pid ?? null;
|
|
197
|
+
if (terminal.kind === "spawn-error") {
|
|
198
|
+
return { outcome: { kind: "spawn-error", diagnostic: terminal.error.message }, pid };
|
|
199
|
+
}
|
|
200
|
+
if (streamError !== undefined) {
|
|
201
|
+
return {
|
|
202
|
+
outcome: {
|
|
203
|
+
kind: "stream-error",
|
|
204
|
+
diagnostic: streamError instanceof Error ? streamError.message : String(streamError),
|
|
205
|
+
},
|
|
206
|
+
pid,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
207
209
|
if (stop !== undefined)
|
|
208
|
-
return { kind: stop };
|
|
210
|
+
return { outcome: { kind: stop }, pid };
|
|
209
211
|
if (terminal.code === null)
|
|
210
|
-
return { kind: "unknown-exit" };
|
|
212
|
+
return { outcome: { kind: "unknown-exit" }, pid };
|
|
211
213
|
const capturedStdout = stdout.result();
|
|
212
214
|
const capturedStderr = stderr.result();
|
|
213
215
|
return {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
stdout: capturedStdout.text,
|
|
217
|
-
stderr: capturedStderr.text,
|
|
218
|
-
truncated: capturedStdout.truncated || capturedStderr.truncated,
|
|
216
|
+
outcome: terminalOutcome(terminal.code, capturedStdout, capturedStderr, consumeStdout !== undefined),
|
|
217
|
+
pid,
|
|
219
218
|
};
|
|
220
219
|
}
|
|
221
|
-
export function runProcess(input) {
|
|
222
|
-
|
|
220
|
+
export async function runProcess(input) {
|
|
221
|
+
const result = await executeProcess(input, input.timeoutMs);
|
|
222
|
+
if (result.outcome.kind === "stream-error")
|
|
223
|
+
throw new Error("buffered process produced a stream consumer error");
|
|
224
|
+
return result.outcome;
|
|
225
|
+
}
|
|
226
|
+
export async function runProcessToExit(input) {
|
|
227
|
+
const result = await executeProcess(input, undefined);
|
|
228
|
+
if (result.outcome.kind === "stream-error")
|
|
229
|
+
throw new Error("buffered process produced a stream consumer error");
|
|
230
|
+
return result.outcome;
|
|
223
231
|
}
|
|
224
|
-
export function
|
|
225
|
-
return executeProcess(input,
|
|
232
|
+
export function consumeProcessStdout(input, consume) {
|
|
233
|
+
return executeProcess(input, input.timeoutMs, consume);
|
|
226
234
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export type StdioProcessExit = Readonly<{
|
|
3
|
+
code: number | null;
|
|
4
|
+
signal: NodeJS.Signals | null;
|
|
5
|
+
stderr: string;
|
|
6
|
+
}>;
|
|
7
|
+
export type StdioProcess = Readonly<{
|
|
8
|
+
input: Writable;
|
|
9
|
+
output: Readable;
|
|
10
|
+
exited: Promise<StdioProcessExit>;
|
|
11
|
+
endInputAndDrain(timeoutMs?: number): Promise<void>;
|
|
12
|
+
close(force?: boolean): Promise<void>;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function spawnStdioProcess(input: Readonly<{
|
|
15
|
+
argv: readonly [string, ...string[]];
|
|
16
|
+
cwd: string;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
}>): StdioProcess;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { terminateOwnedProcess } from "./run.js";
|
|
4
|
+
const DEFAULT_DRAIN_TIMEOUT_MS = 1_000;
|
|
5
|
+
export function spawnStdioProcess(input) {
|
|
6
|
+
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
7
|
+
cwd: input.cwd,
|
|
8
|
+
env: input.env ?? process.env,
|
|
9
|
+
detached: true,
|
|
10
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
+
windowsHide: true,
|
|
12
|
+
});
|
|
13
|
+
let stderr = "";
|
|
14
|
+
let inputEnded = false;
|
|
15
|
+
let forced;
|
|
16
|
+
child.stderr.setEncoding("utf8");
|
|
17
|
+
child.stderr.on("data", (chunk) => { stderr = `${stderr}${chunk}`.slice(-4_000); });
|
|
18
|
+
const exited = new Promise((resolve) => {
|
|
19
|
+
child.once("close", (code, signal) => resolve({ code, signal, stderr: stderr.trim() }));
|
|
20
|
+
child.once("error", (error) => resolve({ code: null, signal: null, stderr: error.message }));
|
|
21
|
+
});
|
|
22
|
+
const endInput = () => {
|
|
23
|
+
if (inputEnded)
|
|
24
|
+
return;
|
|
25
|
+
inputEnded = true;
|
|
26
|
+
child.stdin.end();
|
|
27
|
+
};
|
|
28
|
+
const forceClose = async () => {
|
|
29
|
+
forced ??= (async () => {
|
|
30
|
+
await terminateOwnedProcess(child, true);
|
|
31
|
+
await exited;
|
|
32
|
+
})();
|
|
33
|
+
await forced;
|
|
34
|
+
};
|
|
35
|
+
const endInputAndDrain = async (timeoutMs = DEFAULT_DRAIN_TIMEOUT_MS) => {
|
|
36
|
+
endInput();
|
|
37
|
+
if (forced !== undefined) {
|
|
38
|
+
await forced;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const timedOut = await Promise.race([
|
|
42
|
+
exited.then(() => false),
|
|
43
|
+
delay(timeoutMs, true, { ref: false }),
|
|
44
|
+
]);
|
|
45
|
+
if (timedOut)
|
|
46
|
+
await forceClose();
|
|
47
|
+
};
|
|
48
|
+
const close = async (force = false) => {
|
|
49
|
+
endInput();
|
|
50
|
+
if (force) {
|
|
51
|
+
await forceClose();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await terminateOwnedProcess(child, false);
|
|
55
|
+
await exited;
|
|
56
|
+
};
|
|
57
|
+
return { input: child.stdin, output: child.stdout, exited, endInputAndDrain, close };
|
|
58
|
+
}
|
package/build/src/settings.d.ts
CHANGED
|
@@ -46,6 +46,6 @@ export declare class SettingsError extends Error {
|
|
|
46
46
|
readonly kind = "settings";
|
|
47
47
|
constructor(message: string);
|
|
48
48
|
}
|
|
49
|
-
export declare function settings(input?: SettingsInput): Settings
|
|
49
|
+
export declare function settings(input?: SettingsInput): Promise<Settings>;
|
|
50
50
|
/** Read only the project scope owned by a materialized candidate snapshot. */
|
|
51
|
-
export declare function projectSettings(root: string): Settings
|
|
51
|
+
export declare function projectSettings(root: string): Promise<Settings>;
|
package/build/src/settings.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
export class SettingsError extends Error {
|
|
@@ -27,10 +27,10 @@ function freezeJson(value) {
|
|
|
27
27
|
function bounded(error) {
|
|
28
28
|
return (error instanceof Error ? error.message : String(error)).slice(0, 500);
|
|
29
29
|
}
|
|
30
|
-
function readScope(path) {
|
|
30
|
+
async function readScope(path) {
|
|
31
31
|
let bytes;
|
|
32
32
|
try {
|
|
33
|
-
bytes =
|
|
33
|
+
bytes = await readFile(path, "utf8");
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
36
|
if (error.code === "ENOENT")
|
|
@@ -91,7 +91,7 @@ function settingsFromScopes(project, user) {
|
|
|
91
91
|
},
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
export function settings(input = {}) {
|
|
94
|
+
export async function settings(input = {}) {
|
|
95
95
|
if (!object(input))
|
|
96
96
|
throw new TypeError("settings input must be an object");
|
|
97
97
|
if (input.root !== undefined && (typeof input.root !== "string" || input.root.trim().length === 0)) {
|
|
@@ -102,13 +102,13 @@ export function settings(input = {}) {
|
|
|
102
102
|
}
|
|
103
103
|
const project = input.root === undefined
|
|
104
104
|
? { state: { kind: "absent" } }
|
|
105
|
-
: readScope(join(resolve(input.root), ".keiyaku", "settings.json"));
|
|
106
|
-
const user = readScope(join(resolve(input.home ?? join(homedir(), ".keiyaku")), "settings.json"));
|
|
105
|
+
: await readScope(join(resolve(input.root), ".keiyaku", "settings.json"));
|
|
106
|
+
const user = await readScope(join(resolve(input.home ?? join(homedir(), ".keiyaku")), "settings.json"));
|
|
107
107
|
return settingsFromScopes(project, user);
|
|
108
108
|
}
|
|
109
109
|
/** Read only the project scope owned by a materialized candidate snapshot. */
|
|
110
|
-
export function projectSettings(root) {
|
|
110
|
+
export async function projectSettings(root) {
|
|
111
111
|
if (typeof root !== "string" || root.trim().length === 0)
|
|
112
112
|
throw new TypeError("settings root must be a nonblank string");
|
|
113
|
-
return settingsFromScopes(readScope(join(resolve(root), ".keiyaku", "settings.json")), { state: { kind: "absent" } });
|
|
113
|
+
return settingsFromScopes(await readScope(join(resolve(root), ".keiyaku", "settings.json")), { state: { kind: "absent" } });
|
|
114
114
|
}
|
|
@@ -3,7 +3,3 @@ import { type GitRepository } from "../git/repository.js";
|
|
|
3
3
|
import type { TaskId } from "../task/identity.js";
|
|
4
4
|
export declare function settlementFencePath(repository: GitRepository, taskId: TaskId): string;
|
|
5
5
|
export declare function acquireTaskSettlementFence(repository: GitRepository, taskId: TaskId): Promise<HeldSqliteTransactionLock>;
|
|
6
|
-
export declare function withTaskSettlementFence<T>(input: Readonly<{
|
|
7
|
-
repository: GitRepository;
|
|
8
|
-
taskId: TaskId;
|
|
9
|
-
}>, action: () => T | Promise<T>): Promise<T>;
|
|
@@ -12,12 +12,3 @@ export function acquireTaskSettlementFence(repository, taskId) {
|
|
|
12
12
|
mode: "immediate",
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
export async function withTaskSettlementFence(input, action) {
|
|
16
|
-
const held = await acquireTaskSettlementFence(input.repository, input.taskId);
|
|
17
|
-
try {
|
|
18
|
-
return await action();
|
|
19
|
-
}
|
|
20
|
-
finally {
|
|
21
|
-
held.close();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TreeUpdate } from "../core/facts/offer.js";
|
|
2
2
|
import { type ContractId } from "../core/facts/types.js";
|
|
3
3
|
import type { GitDecisionObservation } from "../git/observe.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type GitRepository } from "../git/repository.js";
|
|
5
5
|
import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "../git/read-observation.js";
|
|
6
6
|
import { type TaskId } from "../task/identity.js";
|
|
7
7
|
export declare function taskHolderObservationSelection(): GitTreeSelection;
|
|
@@ -28,6 +28,16 @@ export type TaskHolderAdmission<T extends AdmissionOutcome> = Readonly<{
|
|
|
28
28
|
export declare function finishTaskHolderAdmission<T extends AdmissionOutcome>(taskId: TaskId, result: T, close: () => void): TaskHolderAdmission<T>;
|
|
29
29
|
export declare function claimTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, taskId: TaskId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
|
|
30
30
|
export declare function releaseTaskHolder(channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TreeUpdate | null>;
|
|
31
|
+
export type TaskHolderReleasePublication = Readonly<{
|
|
32
|
+
kind: "released";
|
|
33
|
+
}> | Readonly<{
|
|
34
|
+
kind: "not-held";
|
|
35
|
+
}> | Readonly<{
|
|
36
|
+
kind: "non-published";
|
|
37
|
+
diagnostic: string;
|
|
38
|
+
}>;
|
|
39
|
+
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
40
|
+
export declare function publishTaskHolderRelease(repository: GitRepository, channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TaskHolderReleasePublication>;
|
|
31
41
|
export declare function readTaskHolderProjectionFromDecision(channel: GitDecodeChannel, observation: GitDecisionObservation): Promise<TaskHolderProjection>;
|
|
32
42
|
export declare function observeTaskHolderProjection(repository: GitRepository, channel: GitDecodeChannel): Promise<TaskHolderProjection>;
|
|
33
43
|
export declare function releaseTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, channel: GitDecodeChannel, owner: ContractId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
|
|
@@ -1,6 +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, } from "../git/repository.js";
|
|
4
5
|
import { withGitTargetedReadObservation, } from "../git/read-observation.js";
|
|
5
6
|
import { parseTaskId } from "../task/identity.js";
|
|
6
7
|
import { acquireTaskSettlementFence } from "./fence.js";
|
|
@@ -120,6 +121,30 @@ export async function releaseTaskHolder(channel, observation, owner) {
|
|
|
120
121
|
? null
|
|
121
122
|
: update({ ...current, disposition: "released" });
|
|
122
123
|
}
|
|
124
|
+
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
125
|
+
export async function publishTaskHolderRelease(repository, channel, observation, owner) {
|
|
126
|
+
const release = await releaseTaskHolder(channel, observation, owner);
|
|
127
|
+
if (release === null)
|
|
128
|
+
return { kind: "not-held" };
|
|
129
|
+
const tree = await updateGitTree(repository, observation.admission.snapshot.tree, new Map([[release.path, { oid: await writeBlob(repository, release.bytes) }]]));
|
|
130
|
+
const commit = await writeCommit({
|
|
131
|
+
repository,
|
|
132
|
+
tree,
|
|
133
|
+
parent: observation.admission.snapshot.commit,
|
|
134
|
+
message: `release held Task completion: ${owner}`,
|
|
135
|
+
});
|
|
136
|
+
const publication = await updateRefsAtomically(repository, [{
|
|
137
|
+
ref: GIT_REF,
|
|
138
|
+
newOid: commit,
|
|
139
|
+
expectedOid: observation.admission.snapshot.commit,
|
|
140
|
+
}]);
|
|
141
|
+
if (publication.kind === "published")
|
|
142
|
+
return { kind: "released" };
|
|
143
|
+
return {
|
|
144
|
+
kind: "non-published",
|
|
145
|
+
diagnostic: publication.kind === "non-published" ? detail(publication.error) : "Task holder release publication state is unknown",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
123
148
|
export async function readTaskHolderProjectionFromDecision(channel, observation) {
|
|
124
149
|
const read = {
|
|
125
150
|
snapshot: observation.admission.snapshot,
|