cloving 1.4.0 → 1.5.0
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 +100 -15
- package/dist/src/broker/actor/authority-health.d.ts +9 -0
- package/dist/src/broker/actor/authority-health.js +24 -0
- package/dist/src/broker/actor/authority-runtime.d.ts +4 -2
- package/dist/src/broker/actor/authority-runtime.js +29 -20
- package/dist/src/broker/actor/authority-session.d.ts +6 -19
- package/dist/src/broker/actor/authority-session.js +66 -88
- package/dist/src/broker/actor/authority-settings.d.ts +2 -1
- package/dist/src/broker/actor/authority-settings.js +4 -1
- package/dist/src/broker/actor/authority-upgrades.d.ts +5 -1
- package/dist/src/broker/actor/authority-upgrades.js +2 -1
- package/dist/src/broker/actor/authority-watchdog.d.ts +6 -2
- package/dist/src/broker/actor/authority-watchdog.js +31 -9
- package/dist/src/broker/actor/authority.js +3 -1
- package/dist/src/broker/actor/projection-memory.d.ts +43 -0
- package/dist/src/broker/actor/projection-memory.js +207 -0
- package/dist/src/broker/actor/runtime-parts.d.ts +5 -0
- package/dist/src/broker/actor/runtime-parts.js +8 -3
- package/dist/src/broker/authority-restore.js +1 -1
- package/dist/src/broker/effect-upgrade.d.ts +1 -0
- package/dist/src/broker/effect-upgrade.js +4 -1
- package/dist/src/cli/arguments.js +1 -0
- package/dist/src/cli/client.d.ts +3 -1
- package/dist/src/cli/client.js +4 -4
- package/dist/src/cli/command-requests.d.ts +30 -1
- package/dist/src/cli/command-requests.js +43 -2
- package/dist/src/cli/commands.js +57 -48
- package/dist/src/cli/config-notice.js +5 -2
- package/dist/src/cli/doctor-retention.d.ts +2 -0
- package/dist/src/cli/doctor-retention.js +93 -0
- package/dist/src/cli/doctor.js +5 -0
- package/dist/src/cli/init-prompt.js +1 -1
- package/dist/src/cli/mcp-server.js +4 -4
- package/dist/src/cli/mcp-tools.d.ts +14 -14
- package/dist/src/cli/mcp-tools.js +212 -50
- package/dist/src/cli/queue-arguments.d.ts +15 -0
- package/dist/src/cli/queue-arguments.js +31 -0
- package/dist/src/cli/queue-render.d.ts +3 -0
- package/dist/src/cli/queue-render.js +26 -0
- package/dist/src/cli/reload-render.d.ts +2 -0
- package/dist/src/cli/reload-render.js +12 -0
- package/dist/src/cli/render.js +37 -8
- package/dist/src/cli/report-github-reader.js +1 -1
- package/dist/src/cli/runtime-arguments.d.ts +17 -3
- package/dist/src/cli/runtime-arguments.js +23 -19
- package/dist/src/cli/toolchain.d.ts +1 -0
- package/dist/src/cli/toolchain.js +1 -0
- package/dist/src/cli/usage.d.ts +2 -2
- package/dist/src/cli/usage.js +40 -5
- package/dist/src/cli/watch-api-document.d.ts +430 -0
- package/dist/src/cli/watch-api-document.js +291 -0
- package/dist/src/cli/watch-api-run.d.ts +56 -0
- package/dist/src/cli/watch-api-run.js +112 -0
- package/dist/src/cli/watch-api.d.ts +17 -0
- package/dist/src/cli/watch-api.js +230 -0
- package/dist/src/cli/watch-header-view.d.ts +1 -0
- package/dist/src/cli/watch-header-view.js +9 -1
- package/dist/src/cli/watch-model.d.ts +1 -0
- package/dist/src/cli/watch-model.js +3 -0
- package/dist/src/cli/watch-page.js +3 -1
- package/dist/src/cli/watch-reads.d.ts +1 -0
- package/dist/src/cli/watch-reads.js +6 -5
- package/dist/src/cli/watch-render.d.ts +1 -0
- package/dist/src/cli/watch-server.js +18 -4
- package/dist/src/cli.js +50 -43
- package/dist/src/configuration-display.js +2 -0
- package/dist/src/configuration-schema.d.ts +5 -0
- package/dist/src/daemon/claude-recovery.js +13 -9
- package/dist/src/daemon/claude-worker.d.ts +3 -3
- package/dist/src/daemon/claude-worker.js +10 -11
- package/dist/src/daemon/daemon-adapters.d.ts +7 -0
- package/dist/src/daemon/daemon-adapters.js +7 -3
- package/dist/src/daemon/daemon-loops.d.ts +2 -0
- package/dist/src/daemon/daemon-loops.js +17 -11
- package/dist/src/daemon/daemon.js +71 -60
- package/dist/src/daemon/github-attach.js +77 -4
- package/dist/src/daemon/github-calls.js +1 -1
- package/dist/src/daemon/github-methods.js +6 -2
- package/dist/src/daemon/issue-queue.d.ts +3 -0
- package/dist/src/daemon/issue-queue.js +66 -73
- package/dist/src/daemon/merge-turns.d.ts +10 -0
- package/dist/src/daemon/merge-turns.js +37 -0
- package/dist/src/daemon/queue-control.d.ts +13 -0
- package/dist/src/daemon/queue-control.js +86 -0
- package/dist/src/daemon/queue-journal.d.ts +20 -0
- package/dist/src/daemon/queue-journal.js +89 -0
- package/dist/src/daemon/record-archive.d.ts +19 -0
- package/dist/src/daemon/record-archive.js +82 -0
- package/dist/src/daemon/record-sweep.d.ts +24 -0
- package/dist/src/daemon/record-sweep.js +147 -0
- package/dist/src/daemon/retrying-turn.js +4 -4
- package/dist/src/daemon/settings-reload.d.ts +26 -0
- package/dist/src/daemon/settings-reload.js +82 -0
- package/dist/src/daemon/turn-details.d.ts +4 -0
- package/dist/src/daemon/turn-details.js +33 -0
- package/dist/src/daemon/worker-profile.d.ts +1 -0
- package/dist/src/daemon/worker-profile.js +4 -0
- package/dist/src/daemon/workflow-generations.d.ts +7 -4
- package/dist/src/daemon/workflow-generations.js +22 -14
- package/dist/src/daemon/workflow-reload.d.ts +41 -0
- package/dist/src/daemon/workflow-reload.js +244 -0
- package/dist/src/daemon/workflow-replay.d.ts +2 -1
- package/dist/src/daemon/workflow-replay.js +7 -3
- package/dist/src/doctor-report.d.ts +2 -2
- package/dist/src/doctor-report.js +2 -0
- package/dist/src/private-config.schema.json +31 -0
- package/dist/src/project-settings.d.ts +15 -1
- package/dist/src/project-settings.js +33 -1
- package/dist/src/queue-state.d.ts +26 -0
- package/dist/src/queue-state.js +34 -0
- package/dist/src/record-retention.d.ts +46 -0
- package/dist/src/record-retention.js +81 -0
- package/dist/src/service/active-tickets.js +5 -0
- package/dist/src/service/dispatcher.d.ts +4 -0
- package/dist/src/service/dispatcher.js +33 -0
- package/dist/src/service/queue-handlers.d.ts +4 -0
- package/dist/src/service/queue-handlers.js +32 -0
- package/dist/src/service/registry.d.ts +28 -2
- package/dist/src/service/registry.js +48 -0
- package/dist/src/service/replies.d.ts +16 -9
- package/dist/src/service/replies.js +5 -2
- package/dist/src/service/run-handlers.js +15 -10
- package/dist/src/service/service.d.ts +7 -3
- package/dist/src/service/service.js +16 -31
- package/dist/src/service/worker-totals.d.ts +4 -0
- package/dist/src/service/worker-totals.js +7 -0
- package/dist/src/service/workflow-reload.d.ts +35 -0
- package/dist/src/service/workflow-reload.js +52 -0
- package/dist/src/storage/decision-snapshots.d.ts +1 -2
- package/dist/src/storage/decision-snapshots.js +1 -7
- package/dist/src/storage/domain-changes.d.ts +1 -1
- package/dist/src/storage/domain-changes.js +1 -0
- package/dist/src/storage/domain-schema.d.ts +4 -1
- package/dist/src/storage/domain-schema.js +6 -2
- package/dist/src/storage/effect-schema.d.ts +1 -0
- package/dist/src/storage/effect-schema.js +4 -0
- package/dist/src/storage/projection-changes.d.ts +12 -0
- package/dist/src/storage/projection-changes.js +194 -0
- package/dist/src/storage/projection-lookups.d.ts +7 -0
- package/dist/src/storage/projection-lookups.js +30 -0
- package/dist/src/workflow-declaration.d.ts +1 -0
- package/dist/src/workflow-declaration.js +3 -0
- package/dist/tools/compile-workflows.js +3 -0
- package/dist/tools/project-configuration.d.ts +7 -2
- package/dist/tools/project-configuration.js +30 -1
- package/dist/tools/self-host-processes.js +37 -13
- package/dist/tools/self-host-project.d.ts +2 -0
- package/dist/tools/self-host-project.js +21 -3
- package/dist/tools/self-host.js +34 -24
- package/dist/tools/workflow-candidate-program.d.ts +4 -2
- package/dist/tools/workflow-candidate-program.js +30 -11
- package/dist/tools/workflow-candidate-source.d.ts +1 -0
- package/dist/tools/workflow-candidate-source.js +11 -3
- package/package.json +2 -2
- package/templates/develop/README.md +81 -4
- package/templates/develop/develop/flow-tests.ts +1 -1
- package/templates/develop/develop/interruption-tests.ts +157 -0
- package/templates/develop/develop/local.ts +1 -0
- package/templates/develop/develop/option-tests.ts +138 -9
- package/templates/develop/develop/red-green.ts +55 -0
- package/templates/develop/develop/report.ts +31 -11
- package/templates/develop/develop/round.ts +1 -0
- package/templates/develop/develop/scenarios.ts +27 -0
- package/templates/develop/develop/turn.ts +26 -6
- package/templates/develop/develop.declaration.json +1 -1
- package/templates/develop/develop.test.ts +4 -0
- package/templates/develop/template.json +29 -2
|
@@ -19,4 +19,5 @@ export declare function Queue(props: {
|
|
|
19
19
|
export declare function Brand(): Shown;
|
|
20
20
|
export declare function HeaderStatus(props: HeaderProps): Shown;
|
|
21
21
|
export declare function HeaderActions(props: HeaderProps): Shown;
|
|
22
|
+
export declare function Version(props: HeaderProps): Shown;
|
|
22
23
|
export declare function Header(props: HeaderProps): Shown;
|
|
@@ -16,7 +16,8 @@ export function queueLanes(model) {
|
|
|
16
16
|
const queue = model.queue;
|
|
17
17
|
if (queue === undefined)
|
|
18
18
|
return null;
|
|
19
|
-
|
|
19
|
+
const state = typeof queue["state"] === "string" ? `${queue["state"]}, ` : "";
|
|
20
|
+
return `Queue: ${state}${String(queue["busy"])} of ${String(queue["lanes"])} lanes busy`;
|
|
20
21
|
}
|
|
21
22
|
export function skippedText(value) {
|
|
22
23
|
const ticket = record(value) ?? {};
|
|
@@ -93,12 +94,19 @@ export function HeaderActions(props) {
|
|
|
93
94
|
}),
|
|
94
95
|
});
|
|
95
96
|
}
|
|
97
|
+
export function Version(props) {
|
|
98
|
+
const version = props.model?.serviceVersion;
|
|
99
|
+
if (version === undefined)
|
|
100
|
+
return null;
|
|
101
|
+
return element("div", { class: "version", children: `cloving ${version}` });
|
|
102
|
+
}
|
|
96
103
|
export function Header(props) {
|
|
97
104
|
return element("header", {
|
|
98
105
|
children: [
|
|
99
106
|
component(Brand, {}),
|
|
100
107
|
component(HeaderStatus, props),
|
|
101
108
|
component(HeaderActions, props),
|
|
109
|
+
component(Version, props),
|
|
102
110
|
],
|
|
103
111
|
});
|
|
104
112
|
}
|
|
@@ -29,6 +29,7 @@ export interface WatchInputs {
|
|
|
29
29
|
readonly client: string;
|
|
30
30
|
readonly updatedAt: string;
|
|
31
31
|
readonly service: string;
|
|
32
|
+
readonly serviceVersion?: string;
|
|
32
33
|
readonly runs: readonly unknown[];
|
|
33
34
|
readonly completionCandidates?: readonly unknown[];
|
|
34
35
|
readonly decisions: readonly unknown[];
|
|
@@ -269,6 +269,9 @@ export function watchModel(inputs) {
|
|
|
269
269
|
startWorkflowId: startWorkflowOf(workflows),
|
|
270
270
|
updatedAt: inputs.updatedAt,
|
|
271
271
|
service: inputs.service,
|
|
272
|
+
...(inputs.serviceVersion === undefined
|
|
273
|
+
? {}
|
|
274
|
+
: { serviceVersion: inputs.serviceVersion }),
|
|
272
275
|
runs: runs.toSorted((first, second) => began(first) < began(second) ? 1 : -1),
|
|
273
276
|
};
|
|
274
277
|
}
|
|
@@ -15,7 +15,7 @@ import { boardWorkflow, watchHash, watchLocation, watchTitle, } from "./watch-na
|
|
|
15
15
|
import { clockTime, completedDetail, counted, elapsed, groupActivity, pageTitle, phaseTime, runName, runTone, selectedRun, toneOf, turnTime, waitingDetail, } from "./watch-render.js";
|
|
16
16
|
import { Command, Decision, Events, Group, Item, Turn, Work, } from "./watch-turn.js";
|
|
17
17
|
import { Banner, BannerDetail, BoardPage, Page, Phases, PullRequest, Review, RefusedWorkflows, Run, } from "./watch-view.js";
|
|
18
|
-
import { Brand, clove, Header, HeaderActions, HeaderStatus, Queue, queueLanes, skippedText, } from "./watch-header-view.js";
|
|
18
|
+
import { Brand, clove, Header, HeaderActions, HeaderStatus, Queue, queueLanes, skippedText, Version, } from "./watch-header-view.js";
|
|
19
19
|
const functions = [
|
|
20
20
|
scheduleStatusLine,
|
|
21
21
|
scheduledText,
|
|
@@ -32,6 +32,7 @@ const functions = [
|
|
|
32
32
|
Brand,
|
|
33
33
|
HeaderStatus,
|
|
34
34
|
HeaderActions,
|
|
35
|
+
Version,
|
|
35
36
|
cardReply,
|
|
36
37
|
cardSubmitKey,
|
|
37
38
|
cardKey,
|
|
@@ -284,6 +285,7 @@ button:hover { background: var(--code-head); }
|
|
|
284
285
|
.events { margin-top: 18px; padding-bottom: 10px; }
|
|
285
286
|
.events > summary { color: var(--code-fg); }
|
|
286
287
|
.down { font-weight: 600; }
|
|
288
|
+
.version { position: fixed; left: 12px; bottom: 8px; font: 12px/1.4 var(--code-font); color: var(--muted); pointer-events: none; }
|
|
287
289
|
@media (max-width: 700px) { body { padding-inline: 12px; } }
|
|
288
290
|
`;
|
|
289
291
|
export function pageDocument(actionToken = null) {
|
|
@@ -20,6 +20,7 @@ export interface WatchRead {
|
|
|
20
20
|
readonly schedules?: ShownSchedules;
|
|
21
21
|
readonly queue?: Fields;
|
|
22
22
|
readonly service: string;
|
|
23
|
+
readonly serviceVersion?: string;
|
|
23
24
|
readonly workflows: readonly string[];
|
|
24
25
|
readonly workflowRefusals: readonly WorkflowRefusal[];
|
|
25
26
|
readonly workflowStages?: WorkflowStages;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { establishedIssue } from "./watch-pr-identity.js";
|
|
2
2
|
import { shownSchedules } from "./schedule-render.js";
|
|
3
|
-
import {
|
|
3
|
+
import { workflowStagesOf, } from "../workflow-declaration.js";
|
|
4
4
|
import { record } from "../protocol.js";
|
|
5
5
|
import { send } from "./client.js";
|
|
6
6
|
import { activeRun, recentRun } from "./watch-board.js";
|
|
@@ -192,8 +192,8 @@ function stoppedIssues(runs, context) {
|
|
|
192
192
|
: [];
|
|
193
193
|
}));
|
|
194
194
|
}
|
|
195
|
-
function
|
|
196
|
-
return
|
|
195
|
+
function versionOf(value) {
|
|
196
|
+
return typeof value === "string" ? { serviceVersion: value } : {};
|
|
197
197
|
}
|
|
198
198
|
async function service(reader, now) {
|
|
199
199
|
const status = await asked(reader, {
|
|
@@ -226,7 +226,8 @@ async function service(reader, now) {
|
|
|
226
226
|
? {}
|
|
227
227
|
: { queue: record(status["queue"]) ?? {} }),
|
|
228
228
|
workflows: list(status["workflows"]).filter((value) => typeof value === "string"),
|
|
229
|
-
workflowStages:
|
|
229
|
+
workflowStages: workflowStagesOf(status["workflowStages"]),
|
|
230
|
+
...versionOf(status["serviceVersion"]),
|
|
230
231
|
workflowRefusals: workflowRefusals(status["workflowRefusals"]),
|
|
231
232
|
summaries,
|
|
232
233
|
runs,
|
|
@@ -252,7 +253,7 @@ async function singleRun(reader, runId) {
|
|
|
252
253
|
return {
|
|
253
254
|
...unavailable,
|
|
254
255
|
service: "up",
|
|
255
|
-
workflowStages:
|
|
256
|
+
workflowStages: workflowStagesOf(metadata["workflowStages"]),
|
|
256
257
|
runs: [value],
|
|
257
258
|
decisions: await decisionsOf(reader),
|
|
258
259
|
};
|
|
@@ -153,6 +153,7 @@ export interface WatchRun {
|
|
|
153
153
|
readonly mergeCommit: string | null;
|
|
154
154
|
}
|
|
155
155
|
export interface WatchModel {
|
|
156
|
+
readonly serviceVersion?: string;
|
|
156
157
|
readonly schedules?: ShownSchedules;
|
|
157
158
|
readonly queue?: Fields;
|
|
158
159
|
readonly workflowRefusals?: readonly WorkflowRefusal[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { watchAction, watchActions, watchHost, } from "./watch-actions.js";
|
|
2
|
+
import { watchApiChange, watchApiReply } from "./watch-api.js";
|
|
2
3
|
import { observedBoard } from "./watch-pr-board.js";
|
|
3
4
|
import { isPhoneAction, phoneAction, phoneRead } from "./watch-phone.js";
|
|
4
5
|
import { watchPrObserver } from "./watch-pr-settings.js";
|
|
@@ -30,6 +31,7 @@ const fontFiles = new Map([
|
|
|
30
31
|
new URL(`assets/watch-fonts/${name}`, import.meta.resolve("#package")),
|
|
31
32
|
]));
|
|
32
33
|
const libraryFile = fileURLToPath(import.meta.resolve("preact"));
|
|
34
|
+
const pageChange = () => "data: change\n\n";
|
|
33
35
|
function parsed(text) {
|
|
34
36
|
try {
|
|
35
37
|
const value = JSON.parse(text);
|
|
@@ -139,6 +141,14 @@ async function answered(watch, request) {
|
|
|
139
141
|
if (font !== null)
|
|
140
142
|
return font;
|
|
141
143
|
const url = new URL(String(request.url), "http://localhost");
|
|
144
|
+
const api = await watchApiReply({
|
|
145
|
+
method: request.method ?? "",
|
|
146
|
+
url,
|
|
147
|
+
model: () => snapshot(watch, null),
|
|
148
|
+
run: (runId) => snapshot(watch, runId),
|
|
149
|
+
});
|
|
150
|
+
if (api !== null)
|
|
151
|
+
return api;
|
|
142
152
|
if (request.method === "GET")
|
|
143
153
|
return readReply(watch, url);
|
|
144
154
|
if (request.method !== "POST" || watch.actions === null)
|
|
@@ -187,13 +197,14 @@ function replied(response, reply) {
|
|
|
187
197
|
response.writeHead(reply.status, {
|
|
188
198
|
"content-type": reply.type,
|
|
189
199
|
"cache-control": reply.cache ?? "no-store",
|
|
200
|
+
...(reply.allow === undefined ? {} : { allow: reply.allow }),
|
|
190
201
|
});
|
|
191
202
|
response.end(reply.body);
|
|
192
203
|
}
|
|
193
204
|
function subscribed(stream, exchange) {
|
|
194
|
-
const { request, response } = exchange;
|
|
205
|
+
const { request, response, message } = exchange;
|
|
195
206
|
const changed = () => {
|
|
196
|
-
response.write(
|
|
207
|
+
response.write(message());
|
|
197
208
|
};
|
|
198
209
|
response.writeHead(200, {
|
|
199
210
|
"content-type": "text/event-stream",
|
|
@@ -223,11 +234,14 @@ export async function watchServer(settings) {
|
|
|
223
234
|
if (watchHost(request) &&
|
|
224
235
|
request.method === "GET" &&
|
|
225
236
|
request.url === "/events") {
|
|
226
|
-
subscribed(stream, { request, response });
|
|
237
|
+
subscribed(stream, { request, response, message: pageChange });
|
|
227
238
|
return;
|
|
228
239
|
}
|
|
229
240
|
answered(watch, request).then((reply) => {
|
|
230
|
-
|
|
241
|
+
if (reply === "stream")
|
|
242
|
+
subscribed(stream, { request, response, message: watchApiChange });
|
|
243
|
+
else
|
|
244
|
+
replied(response, reply);
|
|
231
245
|
}, () => {
|
|
232
246
|
replied(response, { ...notFound, status: 500, body: "failed" });
|
|
233
247
|
});
|
package/dist/src/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { terminateHarness } from "./cli/harness-processes.js";
|
|
|
4
4
|
import { settingsEnvironment } from "./cli/project-settings.js";
|
|
5
5
|
import { constants as systemConstants } from "node:os";
|
|
6
6
|
import { addAbortListener } from "node:events";
|
|
7
|
-
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
7
|
+
import { execFile, execFileSync, spawn, } from "node:child_process";
|
|
8
8
|
import { existsSync, realpathSync } from "node:fs";
|
|
9
9
|
import { parseArgs } from "node:util";
|
|
10
10
|
import { delimiter, dirname, extname, join, resolve } from "node:path";
|
|
@@ -60,43 +60,50 @@ const watchCommand = async (request) => {
|
|
|
60
60
|
listener[Symbol.dispose]();
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
+
function spawnedHarness(request) {
|
|
64
|
+
try {
|
|
65
|
+
return spawn(request.executable, request.arguments, {
|
|
66
|
+
cwd: request.directory,
|
|
67
|
+
env: request.environment,
|
|
68
|
+
stdio: "inherit",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
63
75
|
function startHarness(request) {
|
|
64
76
|
return new Promise((settle) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
terminateHarness(child.pid, {
|
|
74
|
-
snapshot: () => execFileSync("/bin/ps", ["-axo", "pid=,ppid="], {
|
|
75
|
-
encoding: "utf8",
|
|
76
|
-
timeout: 1000,
|
|
77
|
-
}),
|
|
78
|
-
kill: (identifier, signal) => process.kill(identifier, signal),
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
const cleanup = () => {
|
|
82
|
-
process.off("SIGINT", interrupt);
|
|
83
|
-
process.off("SIGTERM", terminate);
|
|
84
|
-
};
|
|
85
|
-
process.on("SIGINT", interrupt);
|
|
86
|
-
process.on("SIGTERM", terminate);
|
|
87
|
-
child.once("error", () => {
|
|
88
|
-
cleanup();
|
|
89
|
-
settle(null);
|
|
77
|
+
const interrupt = () => undefined;
|
|
78
|
+
const terminate = () => {
|
|
79
|
+
terminateHarness(child?.pid, {
|
|
80
|
+
snapshot: () => execFileSync("/bin/ps", ["-axo", "pid=,ppid="], {
|
|
81
|
+
encoding: "utf8",
|
|
82
|
+
timeout: 1000,
|
|
83
|
+
}),
|
|
84
|
+
kill: (identifier, signal) => process.kill(identifier, signal),
|
|
90
85
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
};
|
|
87
|
+
const cleanup = () => {
|
|
88
|
+
process.off("SIGINT", interrupt);
|
|
89
|
+
process.off("SIGTERM", terminate);
|
|
90
|
+
};
|
|
91
|
+
process.on("SIGINT", interrupt);
|
|
92
|
+
process.on("SIGTERM", terminate);
|
|
93
|
+
const child = spawnedHarness(request);
|
|
94
|
+
if (child === undefined) {
|
|
95
|
+
cleanup();
|
|
98
96
|
settle(null);
|
|
97
|
+
return;
|
|
99
98
|
}
|
|
99
|
+
child.once("error", () => {
|
|
100
|
+
cleanup();
|
|
101
|
+
settle(null);
|
|
102
|
+
});
|
|
103
|
+
child.once("close", (status, signal) => {
|
|
104
|
+
cleanup();
|
|
105
|
+
settle(status ?? (signal === null ? 1 : 128 + systemConstants.signals[signal]));
|
|
106
|
+
});
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
109
|
function startArguments(input) {
|
|
@@ -122,16 +129,6 @@ function startArguments(input) {
|
|
|
122
129
|
}
|
|
123
130
|
function toolProcess(input) {
|
|
124
131
|
const program = join(import.meta.dirname, "..", "tools", `${input.tool}${extname(import.meta.filename)}`);
|
|
125
|
-
const child = spawn(process.execPath, [program, ...input.argumentsList], {
|
|
126
|
-
detached: input.tool === "red-green-command",
|
|
127
|
-
stdio: input.tool === "red-green-command"
|
|
128
|
-
? ["inherit", "inherit", "inherit", "ipc"]
|
|
129
|
-
: "inherit",
|
|
130
|
-
env: {
|
|
131
|
-
...process.env,
|
|
132
|
-
PATH: [dirname(process.execPath), process.env["PATH"]].join(delimiter),
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
132
|
let stopping = false;
|
|
136
133
|
const interrupt = () => {
|
|
137
134
|
stopping = true;
|
|
@@ -143,6 +140,16 @@ function toolProcess(input) {
|
|
|
143
140
|
};
|
|
144
141
|
process.on("SIGINT", interrupt);
|
|
145
142
|
process.on("SIGTERM", terminate);
|
|
143
|
+
const child = spawn(process.execPath, [program, ...input.argumentsList], {
|
|
144
|
+
detached: input.tool === "red-green-command",
|
|
145
|
+
stdio: input.tool === "red-green-command"
|
|
146
|
+
? ["inherit", "inherit", "inherit", "ipc"]
|
|
147
|
+
: "inherit",
|
|
148
|
+
env: {
|
|
149
|
+
...process.env,
|
|
150
|
+
PATH: [dirname(process.execPath), process.env["PATH"]].join(delimiter),
|
|
151
|
+
},
|
|
152
|
+
});
|
|
146
153
|
return new Promise((settle) => {
|
|
147
154
|
child.once("close", (status) => {
|
|
148
155
|
setImmediate(() => {
|
|
@@ -51,6 +51,11 @@ export interface Configuration {
|
|
|
51
51
|
readonly workflows?: {
|
|
52
52
|
readonly folder?: string;
|
|
53
53
|
};
|
|
54
|
+
readonly retention?: {
|
|
55
|
+
readonly days?: number | null;
|
|
56
|
+
readonly action?: "archive" | "delete";
|
|
57
|
+
readonly archiveFolder?: string | null;
|
|
58
|
+
};
|
|
54
59
|
}
|
|
55
60
|
export declare function configuration(value: unknown, scope: "project" | "private"): Configuration;
|
|
56
61
|
export declare function settingScope(field: string): "project" | "private";
|
|
@@ -78,9 +78,19 @@ export function recordedTree(started) {
|
|
|
78
78
|
}
|
|
79
79
|
export async function resumePlan(settings, request) {
|
|
80
80
|
const { folder, started, intent, signal } = request;
|
|
81
|
-
const resume = await readResume(folder, started);
|
|
82
81
|
const tree = recordedTree(started);
|
|
83
|
-
if (
|
|
82
|
+
if (tree === undefined)
|
|
83
|
+
return "claude-turn-interrupted";
|
|
84
|
+
const locked = await resumeWorktree({
|
|
85
|
+
...settings,
|
|
86
|
+
directory: tree.path,
|
|
87
|
+
folder,
|
|
88
|
+
signal,
|
|
89
|
+
});
|
|
90
|
+
if (locked !== null)
|
|
91
|
+
return locked.reason;
|
|
92
|
+
const resume = await readResume(folder, started);
|
|
93
|
+
if (resume === undefined)
|
|
84
94
|
return "claude-turn-interrupted";
|
|
85
95
|
const profile = await selectedProfile(settings, intent);
|
|
86
96
|
if (typeof profile === "string")
|
|
@@ -93,13 +103,7 @@ export async function resumePlan(settings, request) {
|
|
|
93
103
|
sessionId: resume.sessionId,
|
|
94
104
|
}) === undefined)
|
|
95
105
|
return "claude-turn-interrupted";
|
|
96
|
-
|
|
97
|
-
...settings,
|
|
98
|
-
directory: tree.path,
|
|
99
|
-
folder,
|
|
100
|
-
signal,
|
|
101
|
-
});
|
|
102
|
-
return locked === null ? { resume, tree, profile, harness } : locked.reason;
|
|
106
|
+
return { resume, tree, profile, harness };
|
|
103
107
|
}
|
|
104
108
|
function priorEnd(record) {
|
|
105
109
|
const field = (name) => recordedField(record, name);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type HarnessSettings } from "../harness-defaults.ts";
|
|
2
|
-
import { type TurnProfiles } from "../turn-profile.ts";
|
|
3
2
|
import type { Delivery, DeliveryIntent } from "../broker/actor/delivery-intents.ts";
|
|
4
3
|
import { type RetrySettings } from "./retrying-turn.ts";
|
|
5
4
|
import { type WorkerReport } from "./operation-journal.ts";
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import type { TurnSettings } from "../project-settings.ts";
|
|
6
|
+
export interface ClaudeWorkerSettings extends Pick<RetrySettings, "retryDelay">, HarnessSettings {
|
|
7
|
+
readonly turnSettings: () => TurnSettings;
|
|
8
8
|
readonly workflowId?: (runId: string) => Promise<string | undefined>;
|
|
9
9
|
readonly repository: string;
|
|
10
10
|
readonly worktrees: string;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { turnHarness } from "../harness-defaults.js";
|
|
2
2
|
import { attachmentEnvironment } from "./attachments.js";
|
|
3
|
-
import { profileCommand
|
|
3
|
+
import { profileCommand } from "../turn-profile.js";
|
|
4
4
|
import {} from "./claude-turn.js";
|
|
5
5
|
import { turnOutcome } from "./turn-report.js";
|
|
6
6
|
import { startRetriedTurn } from "./retrying-turn.js";
|
|
7
7
|
import { located, readRecord as read, replied, reportOf, writeRecord as written, } from "./operation-journal.js";
|
|
8
8
|
import { firstTurn, treeFailure, workTree, } from "./work-tree.js";
|
|
9
9
|
import { recordedHarness, interruptedReply, processAlive, uncertainProcess, resumePlan, recordedField, resumableHarness, recoverTurn, turnIntent, } from "./claude-recovery.js";
|
|
10
|
-
import { selectedProfile } from "./worker-profile.js";
|
|
10
|
+
import { checkedProfiles, selectedProfile } from "./worker-profile.js";
|
|
11
11
|
export const claudeLimits = {
|
|
12
12
|
summaryCharacters: 16_000,
|
|
13
13
|
resultBytes: 48_000,
|
|
@@ -51,12 +51,8 @@ async function resumed(parts, request) {
|
|
|
51
51
|
return interruptedReply(request, "claude-turn-interrupted");
|
|
52
52
|
if (aborted())
|
|
53
53
|
return failure("claude-start-aborted");
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
started,
|
|
57
|
-
intent,
|
|
58
|
-
signal,
|
|
59
|
-
});
|
|
54
|
+
const current = parts.settings.turnSettings();
|
|
55
|
+
const plan = await resumePlan({ ...parts.settings, ...current }, { folder, started, intent, signal });
|
|
60
56
|
if (aborted())
|
|
61
57
|
return failure("claude-start-aborted");
|
|
62
58
|
if (typeof plan === "string")
|
|
@@ -73,6 +69,7 @@ async function resumed(parts, request) {
|
|
|
73
69
|
...plan,
|
|
74
70
|
signal,
|
|
75
71
|
prompt,
|
|
72
|
+
turnSettings: current,
|
|
76
73
|
});
|
|
77
74
|
return { kind: "accepted", receipt: key, worker };
|
|
78
75
|
}
|
|
@@ -105,6 +102,7 @@ async function begun(parts, start) {
|
|
|
105
102
|
prompt,
|
|
106
103
|
}, {
|
|
107
104
|
...parts.settings,
|
|
105
|
+
...start.turnSettings,
|
|
108
106
|
...(start.resume === undefined ? {} : { resume: start.resume }),
|
|
109
107
|
stopping: stopping.controller,
|
|
110
108
|
metadata: {
|
|
@@ -143,7 +141,8 @@ async function delivered(parts, call) {
|
|
|
143
141
|
const known = await reconciled(parts, call);
|
|
144
142
|
if (known.kind !== "absent")
|
|
145
143
|
return known;
|
|
146
|
-
const
|
|
144
|
+
const current = settings.turnSettings();
|
|
145
|
+
const profile = await selectedProfile({ ...settings, ...current }, intent);
|
|
147
146
|
if (typeof profile === "string")
|
|
148
147
|
return failure(profile);
|
|
149
148
|
const prompt = await settings.document(intent.payload);
|
|
@@ -171,6 +170,7 @@ async function delivered(parts, call) {
|
|
|
171
170
|
profile,
|
|
172
171
|
harness: turnHarness(settings, profile),
|
|
173
172
|
signal,
|
|
173
|
+
turnSettings: current,
|
|
174
174
|
});
|
|
175
175
|
return { receipt: place.key, worker };
|
|
176
176
|
}
|
|
@@ -225,8 +225,7 @@ export function claudeWorker(settings) {
|
|
|
225
225
|
if (settings.command.length === 0)
|
|
226
226
|
throw new Error("invalid-claude-command");
|
|
227
227
|
turnHarness(settings);
|
|
228
|
-
|
|
229
|
-
turnHarness(settings, profile);
|
|
228
|
+
checkedProfiles(settings, settings.turnSettings().profiles);
|
|
230
229
|
const parts = {
|
|
231
230
|
settings,
|
|
232
231
|
active: new Map(),
|
|
@@ -12,12 +12,15 @@ import type { LocalCommands } from "../command-settings.ts";
|
|
|
12
12
|
import type { DeclaredWorkflow } from "../workflow-declaration.ts";
|
|
13
13
|
import type { WorkflowRefusal } from "../workflow-refusals.ts";
|
|
14
14
|
import type { HarnessSettings } from "../harness-defaults.ts";
|
|
15
|
+
import type { RetentionSettings } from "../record-retention.ts";
|
|
16
|
+
import { type SettingDigests, type TurnSettingsSource } from "./settings-reload.ts";
|
|
15
17
|
export interface DaemonSettings extends HarnessSettings {
|
|
16
18
|
readonly commands?: LocalCommands;
|
|
17
19
|
readonly commandSeconds?: number;
|
|
18
20
|
readonly fallbackModels?: readonly string[];
|
|
19
21
|
readonly tokenPrices?: TokenPrices;
|
|
20
22
|
readonly profiles?: TurnProfiles;
|
|
23
|
+
readonly settingDigests?: SettingDigests;
|
|
21
24
|
readonly directory: string;
|
|
22
25
|
readonly runtimeDirectory: string;
|
|
23
26
|
readonly repository: string;
|
|
@@ -28,6 +31,7 @@ export interface DaemonSettings extends HarnessSettings {
|
|
|
28
31
|
readonly workflows: Readonly<Record<string, WorkflowFunction | undefined>>;
|
|
29
32
|
readonly handler: WorkflowHandler;
|
|
30
33
|
readonly loadBundle?: WorkflowBundleLoader;
|
|
34
|
+
readonly reloadCommand?: readonly string[];
|
|
31
35
|
readonly command: readonly string[];
|
|
32
36
|
readonly ticketCommand?: readonly string[];
|
|
33
37
|
readonly nextCommand?: readonly string[];
|
|
@@ -35,6 +39,8 @@ export interface DaemonSettings extends HarnessSettings {
|
|
|
35
39
|
readonly notifyMilliseconds?: number;
|
|
36
40
|
readonly queueMilliseconds?: number;
|
|
37
41
|
readonly scheduleClock?: () => number;
|
|
42
|
+
readonly retention?: RetentionSettings;
|
|
43
|
+
readonly retentionClock?: () => number;
|
|
38
44
|
readonly schedules?: unknown;
|
|
39
45
|
readonly queueLanes?: number;
|
|
40
46
|
readonly queueDailySpendLimitUsd?: number;
|
|
@@ -47,6 +53,7 @@ export interface DaemonSettings extends HarnessSettings {
|
|
|
47
53
|
}
|
|
48
54
|
export interface Adapters {
|
|
49
55
|
readonly worker: ClaudeWorker;
|
|
56
|
+
readonly turnSettings: TurnSettingsSource;
|
|
50
57
|
readonly github: GithubWorker;
|
|
51
58
|
readonly runs: StartDelivery;
|
|
52
59
|
readonly handoff: EffectDelivery;
|
|
@@ -7,6 +7,7 @@ import { githubWorker } from "./github-worker.js";
|
|
|
7
7
|
import { commandWorker } from "./command-worker.js";
|
|
8
8
|
import { handoffWorker } from "./handoff-worker.js";
|
|
9
9
|
import { resumableHarness } from "./claude-recovery.js";
|
|
10
|
+
import { turnSettingsSource, } from "./settings-reload.js";
|
|
10
11
|
function githubOf(settings, given) {
|
|
11
12
|
const { directory, repository, checkCommand } = settings;
|
|
12
13
|
return githubWorker({
|
|
@@ -42,11 +43,14 @@ export function adapters(settings, options) {
|
|
|
42
43
|
const { given, generations } = options;
|
|
43
44
|
const document = async (digest) => (await given).document(digest);
|
|
44
45
|
const report = async (effect) => (await given).reportEffect(effect);
|
|
45
|
-
const
|
|
46
|
+
const turnSettings = turnSettingsSource({
|
|
47
|
+
profiles: settings.profiles ?? {},
|
|
46
48
|
fallbackModels: settings.fallbackModels ?? [],
|
|
47
49
|
tokenPrices: settings.tokenPrices ?? {},
|
|
50
|
+
});
|
|
51
|
+
const worker = claudeWorker({
|
|
48
52
|
...workerHarness(settings),
|
|
49
|
-
|
|
53
|
+
turnSettings: turnSettings.current,
|
|
50
54
|
workflowId: async (runId) => (await given).workflow.id(runId),
|
|
51
55
|
repository,
|
|
52
56
|
document,
|
|
@@ -72,7 +76,7 @@ export function adapters(settings, options) {
|
|
|
72
76
|
document,
|
|
73
77
|
report,
|
|
74
78
|
});
|
|
75
|
-
return { worker, github, runs, handoff, commands };
|
|
79
|
+
return { worker, github, runs, handoff, commands, turnSettings };
|
|
76
80
|
}
|
|
77
81
|
function handoffOf(settings, given) {
|
|
78
82
|
return handoffWorker({
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { scheduleConnection } from "./schedule-control.ts";
|
|
2
|
+
import type { queueConnection } from "./queue-control.ts";
|
|
2
3
|
import { workflowSchedules } from "./workflow-schedules.ts";
|
|
3
4
|
interface LoopSettings extends ParametersOfSchedules {
|
|
4
5
|
readonly scheduleConnection?: ReturnType<typeof scheduleConnection>;
|
|
6
|
+
readonly queueConnection?: ReturnType<typeof queueConnection>;
|
|
5
7
|
readonly notifyCommand?: readonly string[];
|
|
6
8
|
readonly notifyMilliseconds?: number;
|
|
7
9
|
readonly queueMilliseconds?: number;
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { workflowSchedules } from "./workflow-schedules.js";
|
|
2
2
|
import { issueQueue } from "./issue-queue.js";
|
|
3
3
|
import { notifyRuns } from "./notify.js";
|
|
4
|
+
async function startedQueue(settings) {
|
|
5
|
+
if (settings.queueMilliseconds === undefined ||
|
|
6
|
+
settings.queueWorkflowId === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
const queue = issueQueue({
|
|
9
|
+
directory: settings.directory,
|
|
10
|
+
runtimeDirectory: settings.runtimeDirectory,
|
|
11
|
+
intervalMilliseconds: settings.queueMilliseconds,
|
|
12
|
+
lanes: settings.queueLanes ?? 1,
|
|
13
|
+
resetQueue: settings.resetQueue ?? false,
|
|
14
|
+
workflowId: settings.queueWorkflowId,
|
|
15
|
+
});
|
|
16
|
+
settings.queueConnection?.connect(queue.port);
|
|
17
|
+
await queue.ready;
|
|
18
|
+
return queue;
|
|
19
|
+
}
|
|
4
20
|
export async function daemonLoops(settings) {
|
|
5
21
|
const schedules = await workflowSchedules(settings);
|
|
6
22
|
settings.scheduleConnection?.connect(schedules.request);
|
|
@@ -15,17 +31,7 @@ export async function daemonLoops(settings) {
|
|
|
15
31
|
? {}
|
|
16
32
|
: { intervalMilliseconds: settings.notifyMilliseconds }),
|
|
17
33
|
});
|
|
18
|
-
const queue = settings
|
|
19
|
-
settings.queueWorkflowId === undefined
|
|
20
|
-
? undefined
|
|
21
|
-
: issueQueue({
|
|
22
|
-
directory: settings.directory,
|
|
23
|
-
runtimeDirectory: settings.runtimeDirectory,
|
|
24
|
-
intervalMilliseconds: settings.queueMilliseconds,
|
|
25
|
-
lanes: settings.queueLanes ?? 1,
|
|
26
|
-
resetQueue: settings.resetQueue ?? false,
|
|
27
|
-
workflowId: settings.queueWorkflowId,
|
|
28
|
-
});
|
|
34
|
+
const queue = await startedQueue(settings);
|
|
29
35
|
return {
|
|
30
36
|
stop: async () => {
|
|
31
37
|
await schedules.stop();
|