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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { failed, result } from "./replies.js";
|
|
2
|
+
export function workflowFields(context) {
|
|
3
|
+
const inventory = context.workflowCatalog?.inventory();
|
|
4
|
+
const stages = context.registry.workflowStages;
|
|
5
|
+
return {
|
|
6
|
+
...(inventory?.bundleDigest === undefined
|
|
7
|
+
? {}
|
|
8
|
+
: { bundleDigest: inventory.bundleDigest }),
|
|
9
|
+
workflows: context.registry.workflowIds,
|
|
10
|
+
workflowRefusals: inventory?.workflowRefusals ?? [],
|
|
11
|
+
...(Object.keys(stages).length === 0 ? {} : { workflowStages: stages }),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function applied(catalog, reload) {
|
|
15
|
+
if (reload.kind === "refused")
|
|
16
|
+
return failed(reload.code, { detail: reload.detail });
|
|
17
|
+
const previousBundleDigest = catalog.inventory().bundleDigest ?? null;
|
|
18
|
+
if (reload.kind === "reloaded")
|
|
19
|
+
catalog.replace(reload);
|
|
20
|
+
else
|
|
21
|
+
catalog.replaceRefusals(reload.workflowRefusals);
|
|
22
|
+
return result("reload", {
|
|
23
|
+
status: reload.kind,
|
|
24
|
+
previousBundleDigest,
|
|
25
|
+
bundleDigest: reload.kind === "reloaded" ? reload.bundleDigest : previousBundleDigest,
|
|
26
|
+
workflows: catalog.registry.workflowIds,
|
|
27
|
+
workflowRefusals: reload.workflowRefusals,
|
|
28
|
+
reloadedFields: reload.reloadedFields,
|
|
29
|
+
restartFields: reload.restartFields,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function reloadOperation(parts) {
|
|
33
|
+
let reloading = false;
|
|
34
|
+
return async () => {
|
|
35
|
+
if (reloading)
|
|
36
|
+
return failed("reload-in-progress", {
|
|
37
|
+
detail: "A reload is in work. Wait for its reply.",
|
|
38
|
+
});
|
|
39
|
+
reloading = true;
|
|
40
|
+
try {
|
|
41
|
+
return applied(parts.catalog, await parts.port());
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
reloading = false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function reloadUnavailable() {
|
|
49
|
+
return Promise.resolve(failed("reload-unavailable", {
|
|
50
|
+
detail: "This service cannot reload the workflow files. Restart it to read them.",
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
@@ -6,7 +6,7 @@ export interface ProjectionSnapshot extends ProjectionRows, DecisionRows {
|
|
|
6
6
|
readonly deliveries: readonly AttemptRow[];
|
|
7
7
|
readonly pending: readonly string[];
|
|
8
8
|
readonly sequence: number;
|
|
9
|
-
readonly waits
|
|
9
|
+
readonly waits: readonly CommandWaitRow[];
|
|
10
10
|
}
|
|
11
11
|
export interface DecidedSnapshot extends DomainSnapshot, DecisionRows {
|
|
12
12
|
readonly documents: readonly DocumentRow[];
|
|
@@ -17,7 +17,6 @@ export interface EffectedSnapshot extends DecidedSnapshot {
|
|
|
17
17
|
export interface CurrentSnapshot extends EffectedSnapshot {
|
|
18
18
|
readonly waits: readonly CommandWaitRow[];
|
|
19
19
|
}
|
|
20
|
-
export declare function loadOperationSnapshot(connection: SqlConnection): Promise<Pick<ProjectionSnapshot, "outbox" | "deliveries">>;
|
|
21
20
|
export declare function loadProjections(connection: SqlConnection): Promise<ProjectionSnapshot>;
|
|
22
21
|
export declare function loadDecided(connection: SqlConnection): Promise<DecidedSnapshot>;
|
|
23
22
|
export declare function loadEffected(connection: SqlConnection): Promise<EffectedSnapshot>;
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { loadCommandWaits } from "./command-wait-rows.js";
|
|
2
2
|
import { loadDecisions, loadDocuments, } from "./decision-rows.js";
|
|
3
|
-
import { latestSequence, loadAttempts, loadSnapshot,
|
|
3
|
+
import { latestSequence, loadAttempts, loadSnapshot, projectionRows, text, } from "./domain-rows.js";
|
|
4
4
|
import { loadEffects } from "./effect-rows.js";
|
|
5
|
-
export async function loadOperationSnapshot(connection) {
|
|
6
|
-
return {
|
|
7
|
-
outbox: (await connection.all("SELECT operation_id, kind, run_id, created_sequence, released_sequence FROM outbox ORDER BY created_sequence, rowid")).map(outboxRow),
|
|
8
|
-
deliveries: await loadAttempts(connection),
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
5
|
export async function loadProjections(connection) {
|
|
12
6
|
return {
|
|
13
7
|
waits: await loadCommandWaits(connection),
|
|
@@ -43,7 +43,7 @@ export interface DomainChange extends EffectRows {
|
|
|
43
43
|
readonly documents: readonly DocumentRow[];
|
|
44
44
|
readonly completesAdoption: boolean;
|
|
45
45
|
}
|
|
46
|
-
export declare function persistChange(value: unknown, connection: SqlConnection): Promise<
|
|
46
|
+
export declare function persistChange(value: unknown, connection: SqlConnection): Promise<DomainChange>;
|
|
47
47
|
export declare function gateParameters(row: GateRow): readonly unknown[];
|
|
48
48
|
export declare function detailParameters(row: DetailRow): readonly unknown[];
|
|
49
49
|
export declare function decisionStatements(change: Pick<DomainChange, "gates" | "details" | "documents">): readonly Statement[];
|
|
@@ -10,6 +10,7 @@ export async function persistChange(value, connection) {
|
|
|
10
10
|
throw new Error("stale-authority-sequence");
|
|
11
11
|
for (const statement of statements(change))
|
|
12
12
|
await connection.run(statement.sql, statement.parameters);
|
|
13
|
+
return change;
|
|
13
14
|
}
|
|
14
15
|
function statements(change) {
|
|
15
16
|
return [
|
|
@@ -6,7 +6,8 @@ export declare const effectedVersion = 4;
|
|
|
6
6
|
export declare const waitingVersion = 5;
|
|
7
7
|
export declare const checkedVersion = 6;
|
|
8
8
|
export declare const spendingVersion = 7;
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const eventIndexVersion = 8;
|
|
10
|
+
export declare const currentVersion = 9;
|
|
10
11
|
export declare const baselineSchema = "\nCREATE TABLE schema_migrations (\n version INTEGER PRIMARY KEY,\n checksum TEXT NOT NULL\n) STRICT;\nCREATE TABLE events (\n sequence INTEGER PRIMARY KEY,\n type TEXT NOT NULL,\n run_id TEXT NOT NULL,\n command_id TEXT NOT NULL UNIQUE,\n recorded_at TEXT NOT NULL,\n request TEXT NOT NULL,\n payload TEXT NOT NULL\n) STRICT;\nCREATE TABLE definitions (\n pin TEXT PRIMARY KEY,\n manifest TEXT NOT NULL\n) STRICT;\nCREATE TABLE runs (\n run_id TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n revision INTEGER NOT NULL,\n workflow TEXT NOT NULL REFERENCES definitions (pin) DEFERRABLE INITIALLY DEFERRED,\n paused_from TEXT,\n gates TEXT NOT NULL,\n sequence INTEGER NOT NULL REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE TABLE commands (\n command_id TEXT PRIMARY KEY,\n request TEXT NOT NULL,\n sequence INTEGER NOT NULL UNIQUE REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE TABLE outbox (\n operation_id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n run_id TEXT NOT NULL REFERENCES runs (run_id) DEFERRABLE INITIALLY DEFERRED,\n created_sequence INTEGER NOT NULL REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED,\n released_sequence INTEGER REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\n";
|
|
11
12
|
export type SchemaRefusal = {
|
|
12
13
|
readonly kind: "refused";
|
|
@@ -29,6 +30,8 @@ export type SchemaState = {
|
|
|
29
30
|
readonly kind: "checked";
|
|
30
31
|
} | {
|
|
31
32
|
readonly kind: "indexed";
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: "event-indexed";
|
|
32
35
|
} | {
|
|
33
36
|
readonly kind: "current";
|
|
34
37
|
} | SchemaRefusal;
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
3
|
import { integer, optionalInteger, optionalText, text, } from "./domain-rows.js";
|
|
4
4
|
import { commandWaitSchema, commandWaitCheckSchema, } from "./command-wait-rows.js";
|
|
5
|
-
import { effectSchema, spendingIndexes } from "./effect-schema.js";
|
|
5
|
+
import { effectSchema, runIndexes, spendingIndexes } from "./effect-schema.js";
|
|
6
6
|
export const baselineVersion = 1;
|
|
7
7
|
export const adoptedVersion = 2;
|
|
8
8
|
export const decidedVersion = 3;
|
|
@@ -10,7 +10,8 @@ export const effectedVersion = 4;
|
|
|
10
10
|
export const waitingVersion = 5;
|
|
11
11
|
export const checkedVersion = 6;
|
|
12
12
|
export const spendingVersion = 7;
|
|
13
|
-
export const
|
|
13
|
+
export const eventIndexVersion = 8;
|
|
14
|
+
export const currentVersion = 9;
|
|
14
15
|
export const baselineSchema = `
|
|
15
16
|
CREATE TABLE schema_migrations (
|
|
16
17
|
version INTEGER PRIMARY KEY,
|
|
@@ -116,6 +117,7 @@ const schemas = [
|
|
|
116
117
|
commandWaitCheckSchema,
|
|
117
118
|
spendingIndexes,
|
|
118
119
|
"CREATE INDEX events_run_type_sequence ON events (run_id, type, sequence);",
|
|
120
|
+
runIndexes,
|
|
119
121
|
];
|
|
120
122
|
const checksums = schemas.map((schema) => createHash("sha256").update(schema).digest("hex"));
|
|
121
123
|
const versionKinds = [
|
|
@@ -126,6 +128,7 @@ const versionKinds = [
|
|
|
126
128
|
"waiting",
|
|
127
129
|
"checked",
|
|
128
130
|
"indexed",
|
|
131
|
+
"event-indexed",
|
|
129
132
|
"current",
|
|
130
133
|
];
|
|
131
134
|
export async function inspectSchema(connection) {
|
|
@@ -159,6 +162,7 @@ export async function createSchema(connection) {
|
|
|
159
162
|
await upgradeSchema(connection, waitingVersion);
|
|
160
163
|
await upgradeSchema(connection, checkedVersion);
|
|
161
164
|
await upgradeSchema(connection, spendingVersion);
|
|
165
|
+
await upgradeSchema(connection, eventIndexVersion);
|
|
162
166
|
await upgradeSchema(connection, currentVersion);
|
|
163
167
|
}
|
|
164
168
|
export async function upgradeSchema(connection, version) {
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export declare const effectSchema = "\nCREATE TABLE run_budgets (\n run_id TEXT PRIMARY KEY REFERENCES runs (run_id) DEFERRABLE INITIALLY DEFERRED,\n effect_limit INTEGER NOT NULL,\n spent INTEGER NOT NULL CHECK (spent <= effect_limit),\n capabilities TEXT NOT NULL,\n exhausted_sequence INTEGER REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE TABLE operations (\n operation_id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES runs (run_id) DEFERRABLE INITIALLY DEFERRED,\n capability TEXT NOT NULL,\n method TEXT NOT NULL,\n target TEXT NOT NULL,\n payload TEXT NOT NULL,\n input_digest TEXT NOT NULL,\n units INTEGER NOT NULL,\n lease_key TEXT NOT NULL,\n recovery_limit INTEGER NOT NULL,\n status TEXT NOT NULL,\n reason TEXT,\n attempts INTEGER NOT NULL,\n receipt TEXT,\n worker TEXT,\n result TEXT,\n requested_sequence INTEGER NOT NULL REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED,\n wait_sequence INTEGER REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED,\n settled_sequence INTEGER REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE INDEX operations_run ON operations (run_id);\nCREATE INDEX operations_waiting ON operations (wait_sequence) WHERE status = 'waiting';\nCREATE TABLE leases (\n resource_key TEXT PRIMARY KEY,\n operation_id TEXT NOT NULL REFERENCES operations (operation_id) DEFERRABLE INITIALLY DEFERRED,\n generation INTEGER NOT NULL CHECK (generation > 0),\n expires_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('held', 'expired', 'released')),\n granted_sequence INTEGER NOT NULL REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE INDEX leases_due ON leases (expires_at) WHERE state = 'held';\nCREATE TABLE effect_evidence (\n sequence INTEGER PRIMARY KEY REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED,\n run_id TEXT NOT NULL,\n operation_id TEXT NOT NULL,\n disposition TEXT NOT NULL,\n reason TEXT\n) STRICT;\nCREATE TABLE stopped_operations (\n operation_id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES runs (run_id) DEFERRABLE INITIALLY DEFERRED,\n sequence INTEGER NOT NULL REFERENCES events (sequence) DEFERRABLE INITIALLY DEFERRED\n) STRICT;\nCREATE TABLE dispatch_attempts (\n effect_id TEXT PRIMARY KEY REFERENCES deliveries (effect_id) DEFERRABLE INITIALLY DEFERRED,\n purpose TEXT NOT NULL,\n lease_generation INTEGER,\n authority_epoch INTEGER NOT NULL\n) STRICT;\n";
|
|
2
2
|
export declare const spendingIndexes = "\nCREATE INDEX events_spend_window ON events (julianday(recorded_at));\nCREATE INDEX operations_worker_settled ON operations (settled_sequence) WHERE capability = 'claude';\nCREATE INDEX operations_worker_open ON operations (requested_sequence) WHERE capability = 'claude' AND settled_sequence IS NULL AND status NOT IN ('refused', 'completed', 'failed', 'stopped', 'cancelled');\n";
|
|
3
|
+
export declare const runIndexes = "\nCREATE INDEX effect_evidence_run_sequence ON effect_evidence (run_id, sequence);\nCREATE INDEX stopped_operations_run ON stopped_operations (run_id);\n";
|
|
@@ -62,3 +62,7 @@ CREATE INDEX events_spend_window ON events (julianday(recorded_at));
|
|
|
62
62
|
CREATE INDEX operations_worker_settled ON operations (settled_sequence) WHERE capability = 'claude';
|
|
63
63
|
CREATE INDEX operations_worker_open ON operations (requested_sequence) WHERE capability = 'claude' AND settled_sequence IS NULL AND status NOT IN ('refused', 'completed', 'failed', 'stopped', 'cancelled');
|
|
64
64
|
`;
|
|
65
|
+
export const runIndexes = `
|
|
66
|
+
CREATE INDEX effect_evidence_run_sequence ON effect_evidence (run_id, sequence);
|
|
67
|
+
CREATE INDEX stopped_operations_run ON stopped_operations (run_id);
|
|
68
|
+
`;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProjectionSnapshot } from "./decision-snapshots.ts";
|
|
2
|
+
import type { DomainChange } from "./domain-changes.ts";
|
|
3
|
+
import type { EventRow } from "./domain-rows.ts";
|
|
4
|
+
import type { OperationRow } from "./effect-rows.ts";
|
|
5
|
+
export declare function binaryOrder(left: string, right: string): number;
|
|
6
|
+
export interface ProjectedChange extends Pick<DomainChange, "previous" | "sequence" | "definitions" | "runs" | "commands" | "added" | "released" | "staged" | "closed" | "gates" | "details" | "waits"> {
|
|
7
|
+
readonly events: readonly Pick<EventRow, "sequence" | "recordedAt">[];
|
|
8
|
+
readonly operations: readonly Pick<OperationRow, "operationId" | "status">[];
|
|
9
|
+
}
|
|
10
|
+
export declare function projectedChange(change: DomainChange): ProjectedChange;
|
|
11
|
+
export declare function followingSequence(sequence: number, change: Pick<ProjectedChange, "events">): number;
|
|
12
|
+
export declare function changedProjection(snapshot: ProjectionSnapshot, changes: readonly ProjectedChange[]): ProjectionSnapshot;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
export function binaryOrder(left, right) {
|
|
2
|
+
return Buffer.compare(Buffer.from(left), Buffer.from(right));
|
|
3
|
+
}
|
|
4
|
+
function upserted(rows, upsert) {
|
|
5
|
+
if (upsert.rows.length === 0)
|
|
6
|
+
return rows;
|
|
7
|
+
const next = [...rows];
|
|
8
|
+
const positions = new Map(next.map((row, index) => [upsert.key(row), index]));
|
|
9
|
+
for (const row of upsert.rows) {
|
|
10
|
+
const key = upsert.key(row);
|
|
11
|
+
const position = positions.get(key) ?? next.length;
|
|
12
|
+
positions.set(key, position);
|
|
13
|
+
next[position] = row;
|
|
14
|
+
}
|
|
15
|
+
return next.sort(upsert.order);
|
|
16
|
+
}
|
|
17
|
+
function definitions(rows, added) {
|
|
18
|
+
const known = new Set(rows.map((row) => row.pin));
|
|
19
|
+
const fresh = added.filter((row) => {
|
|
20
|
+
if (known.has(row.pin))
|
|
21
|
+
return false;
|
|
22
|
+
known.add(row.pin);
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
25
|
+
return upserted(rows, {
|
|
26
|
+
rows: fresh,
|
|
27
|
+
key: (row) => row.pin,
|
|
28
|
+
order: (left, right) => binaryOrder(left.pin, right.pin),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function runs(rows, change) {
|
|
32
|
+
return upserted(rows, {
|
|
33
|
+
rows: change,
|
|
34
|
+
key: (row) => row.runId,
|
|
35
|
+
order: (left, right) => binaryOrder(left.runId, right.runId),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function commands(rows, added) {
|
|
39
|
+
if (added.length === 0)
|
|
40
|
+
return rows;
|
|
41
|
+
return [...rows, ...added].sort((left, right) => left.sequence - right.sequence);
|
|
42
|
+
}
|
|
43
|
+
function appendGuarded(table, entry) {
|
|
44
|
+
const key = table.key(entry.row);
|
|
45
|
+
if (entry.open)
|
|
46
|
+
table.open.set(key, [...(table.open.get(key) ?? []), table.rows.length]);
|
|
47
|
+
table.rows.push(entry.row);
|
|
48
|
+
}
|
|
49
|
+
function guarded(rows, fields) {
|
|
50
|
+
const table = { rows: [], open: new Map(), key: fields.key };
|
|
51
|
+
for (const row of rows)
|
|
52
|
+
appendGuarded(table, { row, open: fields.open(row) });
|
|
53
|
+
return table;
|
|
54
|
+
}
|
|
55
|
+
function settleGuarded(table, settlement) {
|
|
56
|
+
for (const index of table.open.get(settlement.key) ?? []) {
|
|
57
|
+
const row = table.rows[index];
|
|
58
|
+
if (row === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
table.rows[index] = settlement.update(row);
|
|
61
|
+
}
|
|
62
|
+
table.open.delete(settlement.key);
|
|
63
|
+
}
|
|
64
|
+
function outbox(rows, changes) {
|
|
65
|
+
if (changes.every((change) => change.added.length + change.released.length === 0))
|
|
66
|
+
return rows;
|
|
67
|
+
const table = guarded(rows, {
|
|
68
|
+
key: (row) => row.operationId,
|
|
69
|
+
open: (row) => row.releasedSequence === null,
|
|
70
|
+
});
|
|
71
|
+
for (const change of changes) {
|
|
72
|
+
for (const row of change.added)
|
|
73
|
+
appendGuarded(table, {
|
|
74
|
+
row: { ...row, releasedSequence: null },
|
|
75
|
+
open: true,
|
|
76
|
+
});
|
|
77
|
+
for (const { operationId, releasedSequence } of change.released)
|
|
78
|
+
settleGuarded(table, {
|
|
79
|
+
key: operationId,
|
|
80
|
+
update: (row) => ({ ...row, releasedSequence }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return table.rows.sort((left, right) => left.createdSequence - right.createdSequence);
|
|
84
|
+
}
|
|
85
|
+
function stagedAttempt(row, sequence) {
|
|
86
|
+
return {
|
|
87
|
+
effectId: row.effectId,
|
|
88
|
+
operationId: row.operationId,
|
|
89
|
+
inputDigest: row.inputDigest,
|
|
90
|
+
stagedSequence: sequence,
|
|
91
|
+
outcome: null,
|
|
92
|
+
receipt: null,
|
|
93
|
+
purpose: row.purpose,
|
|
94
|
+
leaseGeneration: row.leaseGeneration,
|
|
95
|
+
epoch: row.authorityEpoch,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function deliveries(rows, changes) {
|
|
99
|
+
if (changes.every((change) => change.closed.length + change.staged.length === 0))
|
|
100
|
+
return rows;
|
|
101
|
+
const table = guarded(rows, {
|
|
102
|
+
key: (row) => row.effectId,
|
|
103
|
+
open: (row) => row.outcome === null,
|
|
104
|
+
});
|
|
105
|
+
for (const change of changes) {
|
|
106
|
+
for (const { effectId, outcome, receipt } of change.closed)
|
|
107
|
+
settleGuarded(table, {
|
|
108
|
+
key: effectId,
|
|
109
|
+
update: (row) => ({ ...row, outcome, receipt }),
|
|
110
|
+
});
|
|
111
|
+
for (const row of change.staged)
|
|
112
|
+
appendGuarded(table, {
|
|
113
|
+
row: stagedAttempt(row, change.sequence),
|
|
114
|
+
open: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return table.rows.sort((left, right) => left.stagedSequence - right.stagedSequence);
|
|
118
|
+
}
|
|
119
|
+
function gates(rows, change) {
|
|
120
|
+
return upserted(rows, {
|
|
121
|
+
rows: change,
|
|
122
|
+
key: (row) => JSON.stringify([row.runId, row.gateId]),
|
|
123
|
+
order: (left, right) => left.openedSequence - right.openedSequence ||
|
|
124
|
+
binaryOrder(left.gateId, right.gateId),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function details(rows, change) {
|
|
128
|
+
return upserted(rows, {
|
|
129
|
+
rows: change,
|
|
130
|
+
key: (row) => row.runId,
|
|
131
|
+
order: (left, right) => binaryOrder(left.runId, right.runId),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function waits(rows, change) {
|
|
135
|
+
return upserted(rows, {
|
|
136
|
+
rows: change,
|
|
137
|
+
key: (row) => JSON.stringify([row.runId, row.name]),
|
|
138
|
+
order: (left, right) => binaryOrder(left.runId, right.runId) ||
|
|
139
|
+
binaryOrder(left.name, right.name),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function pending(operations, change) {
|
|
143
|
+
if (change.length === 0)
|
|
144
|
+
return operations;
|
|
145
|
+
const next = new Set(operations);
|
|
146
|
+
for (const row of change)
|
|
147
|
+
if (row.status === "dispatch-pending")
|
|
148
|
+
next.add(row.operationId);
|
|
149
|
+
else
|
|
150
|
+
next.delete(row.operationId);
|
|
151
|
+
return [...next].sort(binaryOrder);
|
|
152
|
+
}
|
|
153
|
+
export function projectedChange(change) {
|
|
154
|
+
return {
|
|
155
|
+
previous: change.previous,
|
|
156
|
+
sequence: change.sequence,
|
|
157
|
+
definitions: change.definitions,
|
|
158
|
+
events: change.events.map(({ sequence, recordedAt }) => ({
|
|
159
|
+
sequence,
|
|
160
|
+
recordedAt,
|
|
161
|
+
})),
|
|
162
|
+
runs: change.runs,
|
|
163
|
+
commands: change.commands,
|
|
164
|
+
added: change.added,
|
|
165
|
+
released: change.released,
|
|
166
|
+
staged: change.staged,
|
|
167
|
+
closed: change.closed,
|
|
168
|
+
gates: change.gates,
|
|
169
|
+
details: change.details,
|
|
170
|
+
waits: change.waits,
|
|
171
|
+
operations: change.operations.map(({ operationId, status }) => ({
|
|
172
|
+
operationId,
|
|
173
|
+
status,
|
|
174
|
+
})),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export function followingSequence(sequence, change) {
|
|
178
|
+
return Math.max(sequence, ...change.events.map((event) => event.sequence));
|
|
179
|
+
}
|
|
180
|
+
export function changedProjection(snapshot, changes) {
|
|
181
|
+
const rows = (select) => changes.flatMap(select);
|
|
182
|
+
return {
|
|
183
|
+
definitions: definitions(snapshot.definitions, rows((change) => change.definitions)),
|
|
184
|
+
runs: runs(snapshot.runs, rows((change) => change.runs)),
|
|
185
|
+
commands: commands(snapshot.commands, rows((change) => change.commands)),
|
|
186
|
+
outbox: outbox(snapshot.outbox, changes),
|
|
187
|
+
gates: gates(snapshot.gates, rows((change) => change.gates)),
|
|
188
|
+
details: details(snapshot.details, rows((change) => change.details)),
|
|
189
|
+
waits: waits(snapshot.waits, rows((change) => change.waits)),
|
|
190
|
+
deliveries: deliveries(snapshot.deliveries, changes),
|
|
191
|
+
pending: pending(snapshot.pending, rows((change) => change.operations)),
|
|
192
|
+
sequence: changes.reduce(followingSequence, snapshot.sequence),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SqlConnection } from "./domain-rows.ts";
|
|
2
|
+
export type MessageOutcome = "committed" | "rolled-back" | "missing";
|
|
3
|
+
export declare function messageOutcomes(connection: SqlConnection, messageIds: readonly string[]): Promise<ReadonlyMap<string, MessageOutcome>>;
|
|
4
|
+
export declare function runTimes(connection: SqlConnection): Promise<ReadonlyMap<string, string>>;
|
|
5
|
+
export declare function recordedTimes(connection: SqlConnection, sequences: readonly number[]): Promise<ReadonlyMap<number, string>>;
|
|
6
|
+
export declare function submittedWorkflows(connection: SqlConnection): Promise<ReadonlyMap<string, string>>;
|
|
7
|
+
export declare function storedWorkflows(connection: SqlConnection, digests: readonly string[]): Promise<ReadonlyMap<string, string | null>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { integer, optionalText, text, } from "./domain-rows.js";
|
|
2
|
+
const workflowColumn = "json_extract(s.body, '$.workflowId') AS workflow_id FROM documents s";
|
|
3
|
+
const workflowFilter = "json_valid(s.body) AND json_type(s.body, '$.workflowId') = 'text'";
|
|
4
|
+
export async function messageOutcomes(connection, messageIds) {
|
|
5
|
+
const rows = await connection.all("SELECT id, completed_at_ms IS NOT NULL AND rejection IS NULL AND error IS NULL AS committed FROM solid_messages WHERE id IN (SELECT value FROM json_each(?))", [JSON.stringify(messageIds)]);
|
|
6
|
+
return new Map(rows.map((row) => [
|
|
7
|
+
text(row, "id"),
|
|
8
|
+
integer(row, "committed") === 1 ? "committed" : "rolled-back",
|
|
9
|
+
]));
|
|
10
|
+
}
|
|
11
|
+
export async function runTimes(connection) {
|
|
12
|
+
const rows = await connection.all("SELECT r.run_id, e.recorded_at FROM runs r JOIN events e ON e.sequence = r.sequence");
|
|
13
|
+
return new Map(rows.map((row) => [text(row, "run_id"), text(row, "recorded_at")]));
|
|
14
|
+
}
|
|
15
|
+
export async function recordedTimes(connection, sequences) {
|
|
16
|
+
if (sequences.length === 0)
|
|
17
|
+
return new Map();
|
|
18
|
+
const rows = await connection.all("SELECT sequence, recorded_at FROM events WHERE sequence IN (SELECT value FROM json_each(?))", [JSON.stringify(sequences)]);
|
|
19
|
+
return new Map(rows.map((row) => [integer(row, "sequence"), text(row, "recorded_at")]));
|
|
20
|
+
}
|
|
21
|
+
export async function submittedWorkflows(connection) {
|
|
22
|
+
const rows = await connection.all(`SELECT DISTINCT s.digest, ${workflowColumn} JOIN run_details d ON d.submission = s.digest WHERE ${workflowFilter}`);
|
|
23
|
+
return new Map(rows.map((row) => [text(row, "digest"), text(row, "workflow_id")]));
|
|
24
|
+
}
|
|
25
|
+
export async function storedWorkflows(connection, digests) {
|
|
26
|
+
if (digests.length === 0)
|
|
27
|
+
return new Map();
|
|
28
|
+
const rows = await connection.all(`SELECT s.digest, ${workflowColumn} WHERE s.digest IN (SELECT value FROM json_each(?)) AND ${workflowFilter}`, [JSON.stringify(digests)]);
|
|
29
|
+
return new Map(rows.map((row) => [text(row, "digest"), optionalText(row, "workflow_id")]));
|
|
30
|
+
}
|
|
@@ -31,6 +31,7 @@ export interface DeclaredWorkflow extends WorkflowDeclaration {
|
|
|
31
31
|
}
|
|
32
32
|
export type WorkflowQueueCode = "queue-needs-ticket-field" | "queue-input-incomplete";
|
|
33
33
|
export declare function workflowQueueRefusals(value: unknown): readonly WorkflowQueueCode[];
|
|
34
|
+
export declare function workflowStagesOf(value: unknown): WorkflowStages;
|
|
34
35
|
export declare function isWorkflowStages(value: unknown): value is readonly WorkflowStage[];
|
|
35
36
|
export declare function isWorkflowNames(value: unknown): value is readonly string[];
|
|
36
37
|
export declare function isCommandWaits(value: unknown, permissions: {
|
|
@@ -89,6 +89,9 @@ function isStage(value) {
|
|
|
89
89
|
function unique(values) {
|
|
90
90
|
return new Set(values).size === values.length;
|
|
91
91
|
}
|
|
92
|
+
export function workflowStagesOf(value) {
|
|
93
|
+
return Object.fromEntries(Object.entries(record(value) ?? {}).flatMap(([workflowId, stages]) => isWorkflowStages(stages) ? [[workflowId, stages]] : []));
|
|
94
|
+
}
|
|
92
95
|
export function isWorkflowStages(value) {
|
|
93
96
|
if (!Array.isArray(value) || value.length < 1 || value.length > 12)
|
|
94
97
|
return false;
|
|
@@ -8,6 +8,9 @@ if (!sourceDirectory || !outputDirectory) {
|
|
|
8
8
|
}
|
|
9
9
|
const output = resolve(outputDirectory);
|
|
10
10
|
class WorkflowBuilder extends BaseBuilder {
|
|
11
|
+
async getInputFiles() {
|
|
12
|
+
return (await super.getInputFiles()).toSorted();
|
|
13
|
+
}
|
|
11
14
|
async build() {
|
|
12
15
|
const inputFiles = await this.getInputFiles();
|
|
13
16
|
await mkdir(output, { recursive: true });
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { resolveSettings } from "../src/project-settings.ts";
|
|
1
|
+
import { resolveSettings, type TurnSettings } from "../src/project-settings.ts";
|
|
2
|
+
import { type TurnProfiles } from "../src/turn-profile.ts";
|
|
3
|
+
type ProjectConfiguration = ReturnType<typeof resolveSettings>;
|
|
2
4
|
export declare function topLevel(given: string): string;
|
|
3
5
|
export declare function projectRoot(given: string): Promise<string>;
|
|
4
|
-
export declare function projectConfiguration(repository: string, environment: Readonly<NodeJS.ProcessEnv>): Promise<
|
|
6
|
+
export declare function projectConfiguration(repository: string, environment: Readonly<NodeJS.ProcessEnv>): Promise<ProjectConfiguration>;
|
|
7
|
+
export declare function configuredTurnSettings(configuration: ProjectConfiguration): Promise<TurnSettings>;
|
|
8
|
+
export declare function configurationDigests(configuration: ProjectConfiguration, profiles: TurnProfiles): Readonly<Record<string, string>>;
|
|
9
|
+
export {};
|
|
@@ -3,8 +3,10 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { readFile, realpath } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, join, resolve } from "node:path";
|
|
6
|
-
import { configurationRoot, resolveSettings } from "../src/project-settings.js";
|
|
6
|
+
import { configurationRoot, resolveSettings, settingDigests, } from "../src/project-settings.js";
|
|
7
7
|
import { repositoryFileName } from "../src/repository-identity.js";
|
|
8
|
+
import { parseFallbackModels, parseTokenPrices } from "../src/token-prices.js";
|
|
9
|
+
import { isProfilesFile } from "../src/turn-profile.js";
|
|
8
10
|
async function settingsFile(file) {
|
|
9
11
|
try {
|
|
10
12
|
return await readFile(file.path, "utf8");
|
|
@@ -45,3 +47,30 @@ export async function projectConfiguration(repository, environment) {
|
|
|
45
47
|
}),
|
|
46
48
|
});
|
|
47
49
|
}
|
|
50
|
+
async function configuredProfiles(configuration) {
|
|
51
|
+
const path = configuration.environment["CLOVING_PROFILES"];
|
|
52
|
+
if (path === undefined)
|
|
53
|
+
return configuration.profiles;
|
|
54
|
+
try {
|
|
55
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
56
|
+
if (isProfilesFile(value))
|
|
57
|
+
return value.profiles;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new Error("invalid-profiles");
|
|
61
|
+
}
|
|
62
|
+
throw new Error("invalid-profiles");
|
|
63
|
+
}
|
|
64
|
+
export async function configuredTurnSettings(configuration) {
|
|
65
|
+
const { environment } = configuration;
|
|
66
|
+
const tokenPrices = parseTokenPrices(environment["CLOVING_TOKEN_PRICES"]);
|
|
67
|
+
const fallbackModels = parseFallbackModels(environment["CLOVING_FALLBACK_MODELS"]);
|
|
68
|
+
return {
|
|
69
|
+
profiles: await configuredProfiles(configuration),
|
|
70
|
+
fallbackModels,
|
|
71
|
+
tokenPrices,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function configurationDigests(configuration, profiles) {
|
|
75
|
+
return settingDigests({ ...configuration, profiles }, (text) => createHash("sha256").update(text).digest("hex"));
|
|
76
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile, spawn } from "node:child_process";
|
|
1
|
+
import { execFile, spawn, } from "node:child_process";
|
|
2
2
|
import { addAbortListener } from "node:events";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import { extname, join } from "node:path";
|
|
@@ -10,7 +10,7 @@ function launched(argumentsList, settings) {
|
|
|
10
10
|
const child = spawn(process.execPath, argumentsList, {
|
|
11
11
|
cwd: settings.directory,
|
|
12
12
|
env: settings.environment,
|
|
13
|
-
stdio: "inherit",
|
|
13
|
+
stdio: settings.stdio ?? "inherit",
|
|
14
14
|
});
|
|
15
15
|
const ended = new Promise((settle, reject) => {
|
|
16
16
|
child.once("error", reject);
|
|
@@ -124,25 +124,48 @@ function hostedChildren(settings) {
|
|
|
124
124
|
name: "daemon",
|
|
125
125
|
directory: settings.project,
|
|
126
126
|
environment: settings.environment ?? process.env,
|
|
127
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
127
128
|
});
|
|
128
|
-
const watch = settings.watchPort === "off"
|
|
129
|
-
? []
|
|
130
|
-
: [
|
|
131
|
-
productChild(settings, [
|
|
132
|
-
"cli",
|
|
133
|
-
"watch",
|
|
134
|
-
"--port",
|
|
135
|
-
settings.watchPort,
|
|
136
|
-
]),
|
|
137
|
-
];
|
|
138
129
|
const relay = settings.phoneRelay === "off" ? [] : [productChild(settings, ["relay"])];
|
|
139
|
-
return { daemon, programs: [daemon, ...
|
|
130
|
+
return { daemon, programs: [daemon, ...relay] };
|
|
131
|
+
}
|
|
132
|
+
function daemonStart(daemon) {
|
|
133
|
+
return new Promise((settle) => {
|
|
134
|
+
daemon.child.once("message", () => {
|
|
135
|
+
settle("started");
|
|
136
|
+
});
|
|
137
|
+
daemon.ended.then(() => {
|
|
138
|
+
settle("ended");
|
|
139
|
+
}, () => {
|
|
140
|
+
settle("ended");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function watchAfterStart(settings, hosting) {
|
|
145
|
+
if (settings.watchPort === "off")
|
|
146
|
+
return Promise.resolve();
|
|
147
|
+
return daemonStart(hosting.daemon).then((start) => {
|
|
148
|
+
if (start === "ended" || hosting.stopped())
|
|
149
|
+
return;
|
|
150
|
+
const watch = productChild(settings, [
|
|
151
|
+
"cli",
|
|
152
|
+
"watch",
|
|
153
|
+
"--port",
|
|
154
|
+
settings.watchPort,
|
|
155
|
+
]);
|
|
156
|
+
hosting.programs.splice(1, 0, watch);
|
|
157
|
+
});
|
|
140
158
|
}
|
|
141
159
|
export async function hostedPrograms(settings) {
|
|
142
160
|
const { daemon, programs } = hostedChildren(settings);
|
|
143
161
|
const stopping = new AbortController();
|
|
144
162
|
let updating = false;
|
|
145
163
|
let shutdown;
|
|
164
|
+
const watching = watchAfterStart(settings, {
|
|
165
|
+
daemon,
|
|
166
|
+
programs,
|
|
167
|
+
stopped: () => shutdown !== undefined,
|
|
168
|
+
});
|
|
146
169
|
const stop = () => (shutdown ??= stopped(programs, settings.stopMilliseconds ?? 60000));
|
|
147
170
|
const signal = (signal) => {
|
|
148
171
|
stopping.abort();
|
|
@@ -178,6 +201,7 @@ export async function hostedPrograms(settings) {
|
|
|
178
201
|
}
|
|
179
202
|
finally {
|
|
180
203
|
stopping.abort();
|
|
204
|
+
await watching;
|
|
181
205
|
await stop();
|
|
182
206
|
await polling;
|
|
183
207
|
process.off("SIGINT", interrupt);
|
|
@@ -8,6 +8,7 @@ interface ProjectSettings {
|
|
|
8
8
|
readonly home: string;
|
|
9
9
|
readonly xdgConfigHome?: string | undefined;
|
|
10
10
|
readonly userFolder?: string | undefined;
|
|
11
|
+
readonly recordedProject?: string;
|
|
11
12
|
}
|
|
12
13
|
export interface RegisteredWorkflows {
|
|
13
14
|
readonly workflows: readonly WorkflowListing[];
|
|
@@ -18,4 +19,5 @@ export interface RegisteredWorkflows {
|
|
|
18
19
|
readonly workflowRefusals: readonly WorkflowRefusal[];
|
|
19
20
|
}
|
|
20
21
|
export declare function preparedProject(settings: ProjectSettings): Promise<RegisteredWorkflows>;
|
|
22
|
+
export declare function preparedBundle(settings: ProjectSettings): Promise<RegisteredWorkflows>;
|
|
21
23
|
export {};
|