cloving 1.0.1 → 1.2.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 +251 -20
- package/assets/watch-fonts/fira-code-OFL.md +93 -0
- package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-OFL.md +93 -0
- package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
- package/assets/watch-fonts/manifest.json +112 -0
- package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
- package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- package/dist/src/authority/commands.d.ts +1 -0
- package/dist/src/authority/commands.js +33 -4
- package/dist/src/authority/dispatch-decision.d.ts +1 -0
- package/dist/src/authority/dispatch-decision.js +7 -4
- package/dist/src/authority/events.js +2 -2
- package/dist/src/authority/handoff-chain.d.ts +24 -0
- package/dist/src/authority/handoff-chain.js +46 -0
- package/dist/src/authority/reconciliation.d.ts +3 -0
- package/dist/src/authority/reconciliation.js +21 -5
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +5 -0
- package/dist/src/broker/actor/authority-queries.d.ts +4 -0
- package/dist/src/broker/actor/authority-queries.js +51 -2
- package/dist/src/broker/actor/authority-session.d.ts +2 -0
- package/dist/src/broker/actor/authority-session.js +3 -4
- package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
- package/dist/src/broker/actor/effect-delivery.js +9 -0
- package/dist/src/broker/authority-admission.js +13 -5
- package/dist/src/chats/analysis-batches.d.ts +42 -0
- package/dist/src/chats/analysis-batches.js +97 -0
- package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
- package/dist/src/chats/analysis-checkpoint.js +31 -0
- package/dist/src/chats/analysis-contract.d.ts +80 -0
- package/dist/src/chats/analysis-contract.js +16 -0
- package/dist/src/chats/analysis-data.d.ts +3 -0
- package/dist/src/chats/analysis-data.js +85 -0
- package/dist/src/chats/analysis-input.d.ts +18 -0
- package/dist/src/chats/analysis-input.js +97 -0
- package/dist/src/chats/analysis-links.d.ts +2 -0
- package/dist/src/chats/analysis-links.js +25 -0
- package/dist/src/chats/analysis-merge.d.ts +16 -0
- package/dist/src/chats/analysis-merge.js +134 -0
- package/dist/src/chats/analysis-prompt.d.ts +18 -0
- package/dist/src/chats/analysis-prompt.js +42 -0
- package/dist/src/chats/analysis-proposal.d.ts +4 -0
- package/dist/src/chats/analysis-proposal.js +41 -0
- package/dist/src/chats/analysis-provenance.d.ts +4 -0
- package/dist/src/chats/analysis-provenance.js +99 -0
- package/dist/src/chats/analysis-shape.d.ts +4 -0
- package/dist/src/chats/analysis-shape.js +235 -0
- package/dist/src/chats/analysis.d.ts +3 -0
- package/dist/src/chats/analysis.js +42 -0
- package/dist/src/chats/corpus-input.d.ts +5 -0
- package/dist/src/chats/corpus-input.js +288 -0
- package/dist/src/chats/destination.js +3 -1
- package/dist/src/chats/import.d.ts +8 -6
- package/dist/src/chats/import.js +195 -23
- package/dist/src/chats/input-file.d.ts +3 -0
- package/dist/src/chats/input-file.js +32 -0
- package/dist/src/chats/normalize.d.ts +11 -2
- package/dist/src/chats/normalize.js +18 -2
- package/dist/src/chats/previous-import.d.ts +5 -0
- package/dist/src/chats/previous-import.js +34 -0
- package/dist/src/chats/reader.d.ts +7 -1
- package/dist/src/chats/reader.js +46 -15
- package/dist/src/chats/redact-import.d.ts +14 -0
- package/dist/src/chats/redact-import.js +141 -0
- package/dist/src/chats/redaction.d.ts +41 -0
- package/dist/src/chats/redaction.js +161 -0
- package/dist/src/chats/reference-mapping.d.ts +16 -0
- package/dist/src/chats/reference-mapping.js +153 -0
- package/dist/src/chats/revision-input.d.ts +3 -0
- package/dist/src/chats/revision-input.js +180 -0
- package/dist/src/chats/revision.d.ts +48 -0
- package/dist/src/chats/revision.js +94 -0
- package/dist/src/chats/selection.d.ts +25 -0
- package/dist/src/chats/selection.js +109 -0
- package/dist/src/chats/source.d.ts +23 -0
- package/dist/src/chats/source.js +135 -0
- package/dist/src/chats/structure.d.ts +57 -0
- package/dist/src/chats/structure.js +216 -0
- package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
- package/dist/src/chats/workflow-generation-projection.js +75 -0
- package/dist/src/chats/workflow-generation-source.d.ts +25 -0
- package/dist/src/chats/workflow-generation-source.js +124 -0
- package/dist/src/chats/workflow-generation-text.d.ts +23 -0
- package/dist/src/chats/workflow-generation-text.js +74 -0
- package/dist/src/chats/workflow-generation.d.ts +69 -0
- package/dist/src/chats/workflow-generation.js +142 -0
- package/dist/src/cli/arguments.d.ts +8 -0
- package/dist/src/cli/arguments.js +94 -13
- package/dist/src/cli/client.d.ts +1 -0
- package/dist/src/cli/client.js +38 -1
- package/dist/src/cli/commands.d.ts +3 -0
- package/dist/src/cli/commands.js +29 -11
- package/dist/src/cli/main.d.ts +2 -1
- package/dist/src/cli/main.js +61 -14
- package/dist/src/cli/render.d.ts +3 -0
- package/dist/src/cli/render.js +122 -7
- package/dist/src/cli/usage-render.d.ts +3 -0
- package/dist/src/cli/usage-render.js +28 -0
- package/dist/src/cli/usage.d.ts +3 -2
- package/dist/src/cli/usage.js +42 -7
- package/dist/src/cli/watch-actions.d.ts +10 -0
- package/dist/src/cli/watch-actions.js +58 -0
- package/dist/src/cli/watch-board-view.d.ts +44 -1
- package/dist/src/cli/watch-board-view.js +137 -18
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +48 -11
- package/dist/src/cli/watch-card-client.d.ts +1 -0
- package/dist/src/cli/watch-card-client.js +44 -0
- package/dist/src/cli/watch-card-forms.d.ts +2 -0
- package/dist/src/cli/watch-card-forms.js +33 -0
- package/dist/src/cli/watch-card-reply.d.ts +5 -0
- package/dist/src/cli/watch-card-reply.js +34 -0
- package/dist/src/cli/watch-card-view.d.ts +40 -0
- package/dist/src/cli/watch-card-view.js +86 -0
- package/dist/src/cli/watch-client.d.ts +1 -0
- package/dist/src/cli/watch-client.js +122 -0
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +61 -67
- package/dist/src/cli/watch-page.d.ts +1 -1
- package/dist/src/cli/watch-page.js +89 -86
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-pr-board.d.ts +3 -0
- package/dist/src/cli/watch-pr-board.js +26 -0
- package/dist/src/cli/watch-pr-command.d.ts +18 -0
- package/dist/src/cli/watch-pr-command.js +74 -0
- package/dist/src/cli/watch-pr-identity.d.ts +32 -0
- package/dist/src/cli/watch-pr-identity.js +87 -0
- package/dist/src/cli/watch-pr-observer.d.ts +20 -0
- package/dist/src/cli/watch-pr-observer.js +150 -0
- package/dist/src/cli/watch-pr-settings.d.ts +8 -0
- package/dist/src/cli/watch-pr-settings.js +47 -0
- package/dist/src/cli/watch-pull-request.d.ts +10 -0
- package/dist/src/cli/watch-pull-request.js +130 -0
- package/dist/src/cli/watch-reads.d.ts +7 -1
- package/dist/src/cli/watch-reads.js +44 -18
- package/dist/src/cli/watch-render.d.ts +29 -2
- package/dist/src/cli/watch-render.js +11 -3
- package/dist/src/cli/watch-run-input.d.ts +14 -0
- package/dist/src/cli/watch-run-input.js +49 -0
- package/dist/src/cli/watch-server.d.ts +3 -0
- package/dist/src/cli/watch-server.js +77 -8
- package/dist/src/cli/watch-start-view.d.ts +18 -0
- package/dist/src/cli/watch-start-view.js +77 -0
- package/dist/src/cli/watch-turn.d.ts +1 -0
- package/dist/src/cli/watch-turn.js +8 -3
- package/dist/src/cli/watch-usage.d.ts +6 -0
- package/dist/src/cli/watch-usage.js +65 -0
- package/dist/src/cli/watch-view.d.ts +22 -0
- package/dist/src/cli/watch-view.js +111 -19
- package/dist/src/cli.js +48 -6
- package/dist/src/daemon/analysis-launch.d.ts +5 -0
- package/dist/src/daemon/analysis-launch.js +81 -0
- package/dist/src/daemon/analysis-token.d.ts +6 -0
- package/dist/src/daemon/analysis-token.js +88 -0
- package/dist/src/daemon/analysis-worker.d.ts +20 -0
- package/dist/src/daemon/analysis-worker.js +271 -0
- package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
- package/dist/src/daemon/chats-batch-binding.js +99 -0
- package/dist/src/daemon/chats-capture.d.ts +18 -0
- package/dist/src/daemon/chats-capture.js +194 -0
- package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
- package/dist/src/daemon/chats-checkpoints.js +186 -0
- package/dist/src/daemon/chats-files.d.ts +42 -0
- package/dist/src/daemon/chats-files.js +252 -0
- package/dist/src/daemon/chats-follow-up.d.ts +38 -0
- package/dist/src/daemon/chats-follow-up.js +180 -0
- package/dist/src/daemon/chats-question.d.ts +15 -0
- package/dist/src/daemon/chats-question.js +93 -0
- package/dist/src/daemon/chats-worker.d.ts +13 -0
- package/dist/src/daemon/chats-worker.js +201 -0
- package/dist/src/daemon/claude-turn.d.ts +11 -0
- package/dist/src/daemon/claude-turn.js +96 -15
- package/dist/src/daemon/claude-worker.d.ts +2 -1
- package/dist/src/daemon/claude-worker.js +92 -45
- package/dist/src/daemon/codex-stream.js +16 -0
- package/dist/src/daemon/daemon.d.ts +8 -1
- package/dist/src/daemon/daemon.js +51 -14
- package/dist/src/daemon/delivery-router.js +6 -0
- package/dist/src/daemon/github-calls.d.ts +4 -0
- package/dist/src/daemon/github-calls.js +16 -7
- package/dist/src/daemon/github-ci.d.ts +6 -0
- package/dist/src/daemon/github-ci.js +170 -0
- package/dist/src/daemon/github-merge-call.d.ts +14 -0
- package/dist/src/daemon/github-merge-call.js +73 -0
- package/dist/src/daemon/github-merge.d.ts +3 -5
- package/dist/src/daemon/github-merge.js +52 -95
- package/dist/src/daemon/github-methods.d.ts +28 -0
- package/dist/src/daemon/github-methods.js +84 -16
- package/dist/src/daemon/github-operation.d.ts +2 -0
- package/dist/src/daemon/github-retry.d.ts +6 -0
- package/dist/src/daemon/github-retry.js +49 -0
- package/dist/src/daemon/github-ticket-status.d.ts +3 -0
- package/dist/src/daemon/github-ticket-status.js +294 -0
- package/dist/src/daemon/github-worker.js +2 -1
- package/dist/src/daemon/handoff-worker.d.ts +10 -0
- package/dist/src/daemon/handoff-worker.js +232 -0
- package/dist/src/daemon/issue-queue.d.ts +2 -0
- package/dist/src/daemon/issue-queue.js +186 -43
- package/dist/src/daemon/notify-journal.d.ts +18 -0
- package/dist/src/daemon/notify-journal.js +108 -0
- package/dist/src/daemon/notify-message.js +2 -1
- package/dist/src/daemon/notify.js +176 -37
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/operation-journal.js +4 -2
- package/dist/src/daemon/retrying-turn.d.ts +12 -0
- package/dist/src/daemon/retrying-turn.js +231 -0
- package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
- package/dist/src/daemon/ticket-dependency-waits.js +60 -0
- package/dist/src/daemon/ticket-registration.d.ts +8 -4
- package/dist/src/daemon/ticket-registration.js +76 -22
- package/dist/src/daemon/ticket-source.d.ts +4 -1
- package/dist/src/daemon/ticket-source.js +8 -2
- package/dist/src/decision-visibility.d.ts +2 -0
- package/dist/src/decision-visibility.js +10 -0
- package/dist/src/execution-pin.d.ts +4 -0
- package/dist/src/execution-pin.js +23 -2
- package/dist/src/host/start-delivery.d.ts +2 -1
- package/dist/src/host/start-delivery.js +1 -1
- package/dist/src/host/step-bridge-host.d.ts +1 -0
- package/dist/src/host/step-bridge-host.js +16 -0
- package/dist/src/host/world-reconciliation.d.ts +3 -0
- package/dist/src/host/world-reconciliation.js +9 -0
- package/dist/src/host/world-runs.js +14 -1
- package/dist/src/issue-identity.d.ts +6 -0
- package/dist/src/issue-identity.js +23 -0
- package/dist/src/service/queue-runs.d.ts +3 -0
- package/dist/src/service/queue-runs.js +39 -0
- package/dist/src/service/registry.d.ts +8 -2
- package/dist/src/service/registry.js +20 -8
- package/dist/src/service/replies.d.ts +2 -0
- package/dist/src/service/run-handlers.js +54 -30
- package/dist/src/service/service.d.ts +3 -0
- package/dist/src/service/service.js +3 -1
- package/dist/src/service/submission-handler.d.ts +1 -0
- package/dist/src/service/submission-handler.js +92 -13
- package/dist/src/service/worker-totals.d.ts +3 -0
- package/dist/src/service/worker-totals.js +52 -0
- package/dist/src/step-bridge.d.ts +9 -0
- package/dist/src/steps/handoff.d.ts +2 -0
- package/dist/src/steps/handoff.js +13 -0
- package/dist/src/ticket-dependencies.d.ts +1 -0
- package/dist/src/ticket-dependencies.js +15 -0
- package/dist/src/ticket-status.d.ts +8 -0
- package/dist/src/ticket-status.js +48 -0
- package/dist/src/token-prices.d.ts +21 -0
- package/dist/src/token-prices.js +108 -0
- package/dist/src/worker-usage.d.ts +31 -0
- package/dist/src/worker-usage.js +44 -0
- package/dist/src/workflow-admission.d.ts +1 -0
- package/dist/src/workflow-candidate.d.ts +29 -0
- package/dist/src/workflow-candidate.js +60 -0
- package/dist/src/workflow-contracts.d.ts +4 -0
- package/dist/src/workflow-contracts.js +32 -5
- package/dist/src/workflow-declaration.d.ts +12 -1
- package/dist/src/workflow-declaration.js +71 -13
- package/dist/src/workflow-discovery.d.ts +18 -0
- package/dist/src/workflow-discovery.js +33 -0
- package/dist/src/workflow-refusals.d.ts +6 -0
- package/dist/src/workflow-refusals.js +39 -0
- package/dist/src/workflows.d.ts +3 -1
- package/dist/src/workflows.js +2 -0
- package/dist/tools/admit-workflow.d.ts +13 -0
- package/dist/tools/admit-workflow.js +94 -0
- package/dist/tools/self-host-processes.js +7 -2
- package/dist/tools/self-host-project.d.ts +17 -0
- package/dist/tools/self-host-project.js +177 -0
- package/dist/tools/self-host-workflows.d.ts +1 -1
- package/dist/tools/self-host-workflows.js +2 -2
- package/dist/tools/self-host.js +30 -84
- package/dist/tools/self-update-host.d.ts +7 -0
- package/dist/tools/self-update-host.js +48 -1
- package/dist/tools/self-update.d.ts +1 -0
- package/dist/tools/self-update.js +85 -13
- package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
- package/dist/tools/workflow-bundle-dependencies.js +93 -0
- package/dist/tools/workflow-bundles.d.ts +20 -0
- package/dist/tools/workflow-bundles.js +173 -0
- package/dist/tools/workflow-candidate-counts.d.ts +8 -0
- package/dist/tools/workflow-candidate-counts.js +251 -0
- package/dist/tools/workflow-candidate-program.d.ts +11 -0
- package/dist/tools/workflow-candidate-program.js +78 -0
- package/dist/tools/workflow-candidate-source.d.ts +6 -0
- package/dist/tools/workflow-candidate-source.js +107 -0
- package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
- package/dist/tools/workflow-candidate-syntax.js +219 -0
- package/dist/tools/workflow-folder-capture.d.ts +17 -0
- package/dist/tools/workflow-folder-capture.js +216 -0
- package/dist/tools/workflow-folders.d.ts +33 -0
- package/dist/tools/workflow-folders.js +150 -0
- package/dist/tools/workflow-source-effects.d.ts +10 -0
- package/dist/tools/workflow-source-effects.js +213 -0
- package/dist/workflows/analyze-chats.d.ts +15 -0
- package/dist/workflows/analyze-chats.declaration.json +22 -0
- package/dist/workflows/analyze-chats.js +250 -0
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.d.ts +1 -7
- package/dist/workflows/develop-ticket.js +47 -141
- package/dist/workflows/issue-labels.d.ts +3 -1
- package/dist/workflows/issue-labels.js +34 -0
- package/dist/workflows/ticket-prompts.d.ts +35 -0
- package/dist/workflows/ticket-prompts.js +143 -0
- package/package.json +25 -5
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
5
5
|
import { send } from "../cli/client.js";
|
|
6
6
|
import { record } from "../protocol.js";
|
|
7
7
|
import { runIdentity } from "../service/submission-handler.js";
|
|
8
|
+
function savedHold(value) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const held = record(value);
|
|
12
|
+
if (typeof held?.["mark"] !== "string" ||
|
|
13
|
+
typeof held["startedAt"] !== "number" ||
|
|
14
|
+
!Number.isFinite(held["startedAt"]) ||
|
|
15
|
+
held["startedAt"] <= 0)
|
|
16
|
+
throw new Error("invalid-queue-commands");
|
|
17
|
+
return { mark: held["mark"], startedAt: held["startedAt"] };
|
|
18
|
+
}
|
|
19
|
+
function identifiers(value) {
|
|
20
|
+
if (!Array.isArray(value) ||
|
|
21
|
+
!value.every((id) => typeof id === "string" && id.length > 0))
|
|
22
|
+
throw new Error("invalid-queue-commands");
|
|
23
|
+
return value.map(String);
|
|
24
|
+
}
|
|
8
25
|
async function journal(directory) {
|
|
9
26
|
const file = join(directory, "queue-commands.json");
|
|
10
27
|
const text = await readFile(file, "utf8").catch((error) => {
|
|
@@ -13,21 +30,26 @@ async function journal(directory) {
|
|
|
13
30
|
throw error;
|
|
14
31
|
});
|
|
15
32
|
const value = JSON.parse(text);
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
const saved = record(value);
|
|
34
|
+
const commands = new Set(identifiers(Array.isArray(value) ? value : saved?.["commands"]));
|
|
35
|
+
const failures = Array.isArray(value) ? [] : identifiers(saved?.["failures"]);
|
|
36
|
+
if (failures.length > 3)
|
|
18
37
|
throw new Error("invalid-queue-commands");
|
|
19
|
-
const
|
|
20
|
-
return {
|
|
38
|
+
const pending = {
|
|
21
39
|
commands,
|
|
40
|
+
failures,
|
|
22
41
|
retryCommandId: undefined,
|
|
42
|
+
selfUpdateHold: savedHold(saved?.["selfUpdateHold"]),
|
|
23
43
|
save: async () => {
|
|
24
|
-
await writeFile(`${file}.tmp`, JSON.stringify(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
await writeFile(`${file}.tmp`, JSON.stringify({
|
|
45
|
+
commands: [...commands],
|
|
46
|
+
failures,
|
|
47
|
+
selfUpdateHold: pending.selfUpdateHold,
|
|
48
|
+
}) + "\n", { mode: 0o600, flush: true });
|
|
28
49
|
await rename(`${file}.tmp`, file);
|
|
29
50
|
},
|
|
30
51
|
};
|
|
52
|
+
return pending;
|
|
31
53
|
}
|
|
32
54
|
function terminal(run) {
|
|
33
55
|
return ["completed", "failed", "cancelled"].includes(String(run["state"]));
|
|
@@ -54,34 +76,79 @@ async function recover(settings, pending) {
|
|
|
54
76
|
await pending.save();
|
|
55
77
|
}
|
|
56
78
|
}
|
|
57
|
-
async function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const run = await runStatus(
|
|
79
|
+
async function trackedRuns(runtimeDirectory, commands) {
|
|
80
|
+
if (commands.length === 0)
|
|
81
|
+
return [];
|
|
82
|
+
if (commands.length === 1) {
|
|
83
|
+
const commandId = String(commands[0]);
|
|
84
|
+
const run = await runStatus(runtimeDirectory, commandId);
|
|
63
85
|
if (run === undefined)
|
|
64
86
|
throw new Error("queue-run-missing");
|
|
65
|
-
|
|
66
|
-
pendingRun = true;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
const outcome = record(run["outcome"]);
|
|
70
|
-
if (run["state"] !== "completed" ||
|
|
71
|
-
outcome?.["kind"] !== "handoff_requested")
|
|
72
|
-
failure ??= JSON.stringify({
|
|
73
|
-
runId: run["runId"],
|
|
74
|
-
state: run["state"],
|
|
75
|
-
outcome: run["outcome"] ?? null,
|
|
76
|
-
});
|
|
77
|
-
pending.commands.delete(commandId);
|
|
78
|
-
await pending.save();
|
|
87
|
+
return [{ commandId, run }];
|
|
79
88
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
const reply = await send(runtimeDirectory, {
|
|
90
|
+
operation: "status",
|
|
91
|
+
parameters: { runIds: commands.map(runIdentity) },
|
|
92
|
+
});
|
|
93
|
+
if (reply.kind === "error")
|
|
94
|
+
throw new Error(reply.error.code);
|
|
95
|
+
const runs = reply.result["runs"];
|
|
96
|
+
if (!Array.isArray(runs))
|
|
97
|
+
throw new Error("invalid-run-list");
|
|
98
|
+
return commands.map((commandId) => {
|
|
99
|
+
const run = runs
|
|
100
|
+
.map(record)
|
|
101
|
+
.find((item) => item?.["runId"] === runIdentity(commandId));
|
|
102
|
+
if (run === undefined)
|
|
103
|
+
throw new Error("queue-run-missing");
|
|
104
|
+
return { commandId, run };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function handedOff(run) {
|
|
108
|
+
const outcome = record(run["outcome"]);
|
|
109
|
+
return (run["state"] === "completed" &&
|
|
110
|
+
outcome?.["kind"] === "handoff_requested" &&
|
|
111
|
+
outcome["issueUpdate"] !== "failed");
|
|
112
|
+
}
|
|
113
|
+
class QueueFailureLimit extends Error {
|
|
114
|
+
}
|
|
115
|
+
function checkFailureLimit(pending) {
|
|
116
|
+
if (pending.failures.length >= 3)
|
|
117
|
+
throw new QueueFailureLimit(`3 runs in a row ended without handoff: ${pending.failures.join(", ")}`);
|
|
118
|
+
}
|
|
119
|
+
async function retired(pending, ending) {
|
|
120
|
+
const { commandId, run } = ending;
|
|
121
|
+
pending.commands.delete(commandId);
|
|
122
|
+
if (handedOff(run))
|
|
123
|
+
pending.failures.length = 0;
|
|
124
|
+
else
|
|
125
|
+
pending.failures.push(runIdentity(commandId));
|
|
126
|
+
await pending.save();
|
|
127
|
+
checkFailureLimit(pending);
|
|
128
|
+
if (handedOff(run))
|
|
129
|
+
return;
|
|
130
|
+
const outcome = record(run["outcome"]);
|
|
131
|
+
process.stderr.write(`Queue run ended ${String(outcome?.["kind"] ?? run["state"])}${outcome?.["issueUpdate"] === "failed" ? "; issueUpdate: failed" : ""}: ${runIdentity(commandId)}; continuing\n`);
|
|
132
|
+
}
|
|
133
|
+
async function completed(settings, pending) {
|
|
134
|
+
checkFailureLimit(pending);
|
|
135
|
+
let pendingRuns = 0;
|
|
136
|
+
const endings = [];
|
|
137
|
+
const tracked = [...pending.commands].filter((commandId) => commandId !== pending.retryCommandId);
|
|
138
|
+
for (const { commandId, run } of await trackedRuns(settings.runtimeDirectory, tracked)) {
|
|
139
|
+
if (terminal(run))
|
|
140
|
+
endings.push({ commandId, run });
|
|
141
|
+
else
|
|
142
|
+
pendingRuns += 1;
|
|
143
|
+
}
|
|
144
|
+
endings.sort((first, second) => Number(first.run["terminalSequence"] ?? first.run["latestSequence"] ?? 0) -
|
|
145
|
+
Number(second.run["terminalSequence"] ?? second.run["latestSequence"] ?? 0));
|
|
146
|
+
for (const ending of endings)
|
|
147
|
+
await retired(pending, ending);
|
|
148
|
+
return pendingRuns;
|
|
83
149
|
}
|
|
84
150
|
async function active(runtimeDirectory) {
|
|
151
|
+
let count = 0;
|
|
85
152
|
let after = "";
|
|
86
153
|
do {
|
|
87
154
|
const reply = await send(runtimeDirectory, {
|
|
@@ -93,17 +160,16 @@ async function active(runtimeDirectory) {
|
|
|
93
160
|
const runs = reply.result["runs"];
|
|
94
161
|
if (!Array.isArray(runs))
|
|
95
162
|
throw new Error("invalid-run-list");
|
|
96
|
-
|
|
163
|
+
count += runs.filter((value) => {
|
|
97
164
|
const run = record(value);
|
|
98
165
|
return run?.["workflowId"] === "develop-issue" && !terminal(run);
|
|
99
|
-
})
|
|
100
|
-
return true;
|
|
166
|
+
}).length;
|
|
101
167
|
const next = reply.result["next"];
|
|
102
168
|
if (next !== null && (typeof next !== "string" || next <= after))
|
|
103
169
|
throw new Error("invalid-run-page");
|
|
104
170
|
after = next;
|
|
105
171
|
} while (after !== null);
|
|
106
|
-
return
|
|
172
|
+
return count;
|
|
107
173
|
}
|
|
108
174
|
async function submit(settings, pending) {
|
|
109
175
|
const commandId = pending.retryCommandId ?? randomUUID();
|
|
@@ -121,38 +187,115 @@ async function submit(settings, pending) {
|
|
|
121
187
|
pending.retryCommandId = undefined;
|
|
122
188
|
if (reply.kind === "result")
|
|
123
189
|
return;
|
|
124
|
-
if (
|
|
190
|
+
if ([
|
|
191
|
+
"resolver-timeout",
|
|
192
|
+
"next-ticket-not-available",
|
|
193
|
+
"ticket-not-available",
|
|
194
|
+
].includes(reply.error.code)) {
|
|
125
195
|
pending.retryCommandId = commandId;
|
|
126
|
-
process.stderr.write(`Queue
|
|
196
|
+
process.stderr.write(`Queue ${reply.error.code}: command ${commandId}; will retry\n`);
|
|
127
197
|
return;
|
|
128
198
|
}
|
|
129
|
-
if (![
|
|
199
|
+
if (![
|
|
200
|
+
"no-ready-ticket",
|
|
201
|
+
"workflow-active",
|
|
202
|
+
"ticket-active",
|
|
203
|
+
"ticket-dependency-open",
|
|
204
|
+
].includes(reply.error.code))
|
|
130
205
|
throw new Error(reply.error.code);
|
|
131
206
|
pending.commands.delete(commandId);
|
|
132
207
|
await pending.save();
|
|
133
208
|
}
|
|
209
|
+
async function pendingUpdate(directory) {
|
|
210
|
+
return readFile(join(directory, "pending-self-update"), "utf8").catch((error) => {
|
|
211
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
212
|
+
return undefined;
|
|
213
|
+
throw error;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async function holdStarted(pending, observed) {
|
|
217
|
+
const { mark, activeRuns } = observed;
|
|
218
|
+
const timestamp = Number(mark);
|
|
219
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0)
|
|
220
|
+
throw new Error("invalid-pending-self-update");
|
|
221
|
+
if (pending.selfUpdateHold?.mark !== mark || activeRuns > 0) {
|
|
222
|
+
pending.selfUpdateHold = { mark, startedAt: Date.now() };
|
|
223
|
+
await pending.save();
|
|
224
|
+
}
|
|
225
|
+
return pending.selfUpdateHold.startedAt;
|
|
226
|
+
}
|
|
227
|
+
function updateHold(directory, pending) {
|
|
228
|
+
let notice;
|
|
229
|
+
return async (activeRuns = 0) => {
|
|
230
|
+
const mark = await pendingUpdate(directory);
|
|
231
|
+
if (mark === undefined && pending.selfUpdateHold !== undefined) {
|
|
232
|
+
pending.selfUpdateHold = undefined;
|
|
233
|
+
await pending.save();
|
|
234
|
+
}
|
|
235
|
+
if (mark === undefined) {
|
|
236
|
+
notice = undefined;
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
const startedAt = await holdStarted(pending, { mark, activeRuns });
|
|
240
|
+
const holding = activeRuns > 0 || Date.now() - startedAt < 30 * 60 * 1000;
|
|
241
|
+
const line = holding
|
|
242
|
+
? "Queue holding for pending self-update\n"
|
|
243
|
+
: "Queue continuing after pending self-update time limit\n";
|
|
244
|
+
const key = mark + line;
|
|
245
|
+
if (notice !== key)
|
|
246
|
+
process.stderr.write(line);
|
|
247
|
+
notice = key;
|
|
248
|
+
return holding;
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async function admission(settings, attempt) {
|
|
252
|
+
const file = join(settings.directory, "queue-submission");
|
|
253
|
+
await writeFile(file, "", { mode: 0o600 });
|
|
254
|
+
try {
|
|
255
|
+
if (!(await attempt.holding()) && !attempt.signal.aborted)
|
|
256
|
+
await submit(settings, attempt.pending);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await rm(file, { force: true });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
134
262
|
async function cycle(settings, signal) {
|
|
135
263
|
const pending = await journal(settings.directory);
|
|
264
|
+
if (settings.resetQueue === true) {
|
|
265
|
+
pending.failures.length = 0;
|
|
266
|
+
await pending.save();
|
|
267
|
+
}
|
|
268
|
+
const holding = updateHold(settings.directory, pending);
|
|
269
|
+
await rm(join(settings.directory, "queue-submission"), { force: true });
|
|
136
270
|
await recover(settings, pending);
|
|
137
271
|
const stopped = () => signal.aborted;
|
|
138
272
|
while (!stopped()) {
|
|
139
273
|
await delay(settings.intervalMilliseconds, undefined, { signal }).catch(() => undefined);
|
|
140
274
|
if (stopped())
|
|
141
275
|
return;
|
|
142
|
-
|
|
276
|
+
const pendingRuns = await completed(settings, pending);
|
|
277
|
+
const lanes = settings.lanes ?? 1;
|
|
278
|
+
if (pendingRuns >= lanes) {
|
|
279
|
+
await holding(pendingRuns);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const activeRuns = await active(settings.runtimeDirectory);
|
|
283
|
+
const held = await holding(activeRuns);
|
|
284
|
+
if (activeRuns >= lanes || activeRuns < pendingRuns)
|
|
143
285
|
continue;
|
|
144
|
-
if (
|
|
286
|
+
if (held)
|
|
145
287
|
continue;
|
|
146
288
|
if (stopped())
|
|
147
289
|
return;
|
|
148
|
-
await
|
|
290
|
+
await admission(settings, { pending, holding, signal });
|
|
149
291
|
}
|
|
150
292
|
}
|
|
151
293
|
export function issueQueue(settings) {
|
|
152
294
|
const stopping = new AbortController();
|
|
153
295
|
const running = cycle(settings, stopping.signal).catch((error) => {
|
|
154
296
|
const reason = error instanceof Error ? error.message : String(error);
|
|
155
|
-
|
|
297
|
+
const diagnostic = error instanceof QueueFailureLimit ? reason : JSON.stringify(reason);
|
|
298
|
+
process.stderr.write(`Queue stopped: ${diagnostic}\n`);
|
|
156
299
|
});
|
|
157
300
|
return {
|
|
158
301
|
stop: async () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Notification } from "./notify-message.ts";
|
|
2
|
+
export interface DeliveryEntry {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly message: Notification | null;
|
|
5
|
+
readonly state: "pending" | "sent" | "failed";
|
|
6
|
+
readonly attempts: number;
|
|
7
|
+
readonly lastAttemptAt: number | null;
|
|
8
|
+
readonly lastFailure: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface RecordedNotifications {
|
|
11
|
+
readonly entries: Map<string, DeliveryEntry>;
|
|
12
|
+
readonly retiredKeys: Set<string>;
|
|
13
|
+
}
|
|
14
|
+
export declare function recordedNotifications(file: string): Promise<RecordedNotifications | undefined>;
|
|
15
|
+
export declare function saveNotifications(file: string, journal: {
|
|
16
|
+
readonly entries: ReadonlyMap<string, DeliveryEntry>;
|
|
17
|
+
readonly retiredKeys: ReadonlySet<string>;
|
|
18
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { open, readFile, rename } from "node:fs/promises";
|
|
2
|
+
import { record } from "../protocol.js";
|
|
3
|
+
export async function recordedNotifications(file) {
|
|
4
|
+
const text = await readFile(file, "utf8").catch((error) => {
|
|
5
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
6
|
+
return undefined;
|
|
7
|
+
throw error;
|
|
8
|
+
});
|
|
9
|
+
if (text === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const value = JSON.parse(text);
|
|
12
|
+
const journal = record(value);
|
|
13
|
+
if (journal?.["version"] === 1 && Array.isArray(journal["entries"])) {
|
|
14
|
+
const entries = journal["entries"].map(deliveryEntry);
|
|
15
|
+
const found = new Map(entries.map((entry) => [entry.key, entry]));
|
|
16
|
+
if (found.size !== entries.length)
|
|
17
|
+
throw new Error("invalid-notify-record");
|
|
18
|
+
return {
|
|
19
|
+
entries: found,
|
|
20
|
+
retiredKeys: recordedKeys(journal["retiredKeys"] ?? []),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const keys = recordedKeys(value);
|
|
24
|
+
return {
|
|
25
|
+
retiredKeys: new Set(),
|
|
26
|
+
entries: new Map([...keys].map((key) => [
|
|
27
|
+
key,
|
|
28
|
+
{
|
|
29
|
+
key,
|
|
30
|
+
message: null,
|
|
31
|
+
state: "sent",
|
|
32
|
+
attempts: 0,
|
|
33
|
+
lastAttemptAt: Date.now(),
|
|
34
|
+
lastFailure: null,
|
|
35
|
+
},
|
|
36
|
+
])),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function recordedKeys(value) {
|
|
40
|
+
if (!Array.isArray(value) ||
|
|
41
|
+
!value.every((key) => typeof key === "string"))
|
|
42
|
+
throw new Error("invalid-notify-record");
|
|
43
|
+
return new Set(value.map(String));
|
|
44
|
+
}
|
|
45
|
+
function notification(value) {
|
|
46
|
+
const message = record(value);
|
|
47
|
+
if (message === undefined)
|
|
48
|
+
return false;
|
|
49
|
+
return (["decision", "blocked", "failed"].includes(String(message["event"])) &&
|
|
50
|
+
["event", "runId", "workflowId", "subject", "body"].every((field) => typeof message[field] === "string"));
|
|
51
|
+
}
|
|
52
|
+
function deliveryState(value) {
|
|
53
|
+
return value === "pending" || value === "sent" || value === "failed";
|
|
54
|
+
}
|
|
55
|
+
function attemptCount(value) {
|
|
56
|
+
return (typeof value === "number" &&
|
|
57
|
+
Number.isInteger(value) &&
|
|
58
|
+
value >= 0 &&
|
|
59
|
+
value <= 5);
|
|
60
|
+
}
|
|
61
|
+
function attemptTime(value) {
|
|
62
|
+
return (value === null ||
|
|
63
|
+
(typeof value === "number" && Number.isSafeInteger(value) && value >= 0));
|
|
64
|
+
}
|
|
65
|
+
function deliveryEntry(value) {
|
|
66
|
+
const { key, message, state, attempts, lastAttemptAt, lastFailure } = record(value) ?? {};
|
|
67
|
+
if (typeof key !== "string" ||
|
|
68
|
+
!deliveryState(state) ||
|
|
69
|
+
!attemptCount(attempts))
|
|
70
|
+
throw new Error("invalid-notify-record");
|
|
71
|
+
if (!attemptTime(lastAttemptAt) ||
|
|
72
|
+
!(lastFailure === null || typeof lastFailure === "string"))
|
|
73
|
+
throw new Error("invalid-notify-record");
|
|
74
|
+
const content = { message, state };
|
|
75
|
+
if (!entryMessage(content))
|
|
76
|
+
throw new Error("invalid-notify-record");
|
|
77
|
+
return {
|
|
78
|
+
key,
|
|
79
|
+
message: content.message,
|
|
80
|
+
state,
|
|
81
|
+
attempts,
|
|
82
|
+
lastAttemptAt,
|
|
83
|
+
lastFailure,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function entryMessage(entry) {
|
|
87
|
+
return entry.message === null
|
|
88
|
+
? entry.state === "sent"
|
|
89
|
+
: notification(entry.message);
|
|
90
|
+
}
|
|
91
|
+
export async function saveNotifications(file, journal) {
|
|
92
|
+
const temporary = await open(`${file}.tmp`, "w", 0o600);
|
|
93
|
+
try {
|
|
94
|
+
await temporary.chmod(0o600);
|
|
95
|
+
await temporary.writeFile(JSON.stringify({
|
|
96
|
+
version: 1,
|
|
97
|
+
entries: [...journal.entries.values()],
|
|
98
|
+
...(journal.retiredKeys.size > 0
|
|
99
|
+
? { retiredKeys: [...journal.retiredKeys] }
|
|
100
|
+
: {}),
|
|
101
|
+
}) + "\n");
|
|
102
|
+
await temporary.sync();
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await temporary.close();
|
|
106
|
+
}
|
|
107
|
+
await rename(`${file}.tmp`, file);
|
|
108
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { operationalDecision } from "../decision-visibility.js";
|
|
1
2
|
import { decisionLines, runStatus } from "../cli/render.js";
|
|
2
3
|
import { record } from "../protocol.js";
|
|
3
4
|
function body(text, commands) {
|
|
@@ -17,7 +18,7 @@ function message(run, content) {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
export function decisionNotification(run, decision) {
|
|
20
|
-
const lines = decisionLines(decision);
|
|
21
|
+
const lines = decisionLines(operationalDecision(decision, String(run["workflowId"])));
|
|
21
22
|
const allowed = decision["allowed"];
|
|
22
23
|
const count = Array.isArray(allowed) ? allowed.length : 0;
|
|
23
24
|
const split = lines.length - count;
|