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.
Files changed (167) hide show
  1. package/README.md +100 -15
  2. package/dist/src/broker/actor/authority-health.d.ts +9 -0
  3. package/dist/src/broker/actor/authority-health.js +24 -0
  4. package/dist/src/broker/actor/authority-runtime.d.ts +4 -2
  5. package/dist/src/broker/actor/authority-runtime.js +29 -20
  6. package/dist/src/broker/actor/authority-session.d.ts +6 -19
  7. package/dist/src/broker/actor/authority-session.js +66 -88
  8. package/dist/src/broker/actor/authority-settings.d.ts +2 -1
  9. package/dist/src/broker/actor/authority-settings.js +4 -1
  10. package/dist/src/broker/actor/authority-upgrades.d.ts +5 -1
  11. package/dist/src/broker/actor/authority-upgrades.js +2 -1
  12. package/dist/src/broker/actor/authority-watchdog.d.ts +6 -2
  13. package/dist/src/broker/actor/authority-watchdog.js +31 -9
  14. package/dist/src/broker/actor/authority.js +3 -1
  15. package/dist/src/broker/actor/projection-memory.d.ts +43 -0
  16. package/dist/src/broker/actor/projection-memory.js +207 -0
  17. package/dist/src/broker/actor/runtime-parts.d.ts +5 -0
  18. package/dist/src/broker/actor/runtime-parts.js +8 -3
  19. package/dist/src/broker/authority-restore.js +1 -1
  20. package/dist/src/broker/effect-upgrade.d.ts +1 -0
  21. package/dist/src/broker/effect-upgrade.js +4 -1
  22. package/dist/src/cli/arguments.js +1 -0
  23. package/dist/src/cli/client.d.ts +3 -1
  24. package/dist/src/cli/client.js +4 -4
  25. package/dist/src/cli/command-requests.d.ts +30 -1
  26. package/dist/src/cli/command-requests.js +43 -2
  27. package/dist/src/cli/commands.js +57 -48
  28. package/dist/src/cli/config-notice.js +5 -2
  29. package/dist/src/cli/doctor-retention.d.ts +2 -0
  30. package/dist/src/cli/doctor-retention.js +93 -0
  31. package/dist/src/cli/doctor.js +5 -0
  32. package/dist/src/cli/init-prompt.js +1 -1
  33. package/dist/src/cli/mcp-server.js +4 -4
  34. package/dist/src/cli/mcp-tools.d.ts +14 -14
  35. package/dist/src/cli/mcp-tools.js +212 -50
  36. package/dist/src/cli/queue-arguments.d.ts +15 -0
  37. package/dist/src/cli/queue-arguments.js +31 -0
  38. package/dist/src/cli/queue-render.d.ts +3 -0
  39. package/dist/src/cli/queue-render.js +26 -0
  40. package/dist/src/cli/reload-render.d.ts +2 -0
  41. package/dist/src/cli/reload-render.js +12 -0
  42. package/dist/src/cli/render.js +37 -8
  43. package/dist/src/cli/report-github-reader.js +1 -1
  44. package/dist/src/cli/runtime-arguments.d.ts +17 -3
  45. package/dist/src/cli/runtime-arguments.js +23 -19
  46. package/dist/src/cli/toolchain.d.ts +1 -0
  47. package/dist/src/cli/toolchain.js +1 -0
  48. package/dist/src/cli/usage.d.ts +2 -2
  49. package/dist/src/cli/usage.js +40 -5
  50. package/dist/src/cli/watch-api-document.d.ts +430 -0
  51. package/dist/src/cli/watch-api-document.js +291 -0
  52. package/dist/src/cli/watch-api-run.d.ts +56 -0
  53. package/dist/src/cli/watch-api-run.js +112 -0
  54. package/dist/src/cli/watch-api.d.ts +17 -0
  55. package/dist/src/cli/watch-api.js +230 -0
  56. package/dist/src/cli/watch-header-view.d.ts +1 -0
  57. package/dist/src/cli/watch-header-view.js +9 -1
  58. package/dist/src/cli/watch-model.d.ts +1 -0
  59. package/dist/src/cli/watch-model.js +3 -0
  60. package/dist/src/cli/watch-page.js +3 -1
  61. package/dist/src/cli/watch-reads.d.ts +1 -0
  62. package/dist/src/cli/watch-reads.js +6 -5
  63. package/dist/src/cli/watch-render.d.ts +1 -0
  64. package/dist/src/cli/watch-server.js +18 -4
  65. package/dist/src/cli.js +50 -43
  66. package/dist/src/configuration-display.js +2 -0
  67. package/dist/src/configuration-schema.d.ts +5 -0
  68. package/dist/src/daemon/claude-recovery.js +13 -9
  69. package/dist/src/daemon/claude-worker.d.ts +3 -3
  70. package/dist/src/daemon/claude-worker.js +10 -11
  71. package/dist/src/daemon/daemon-adapters.d.ts +7 -0
  72. package/dist/src/daemon/daemon-adapters.js +7 -3
  73. package/dist/src/daemon/daemon-loops.d.ts +2 -0
  74. package/dist/src/daemon/daemon-loops.js +17 -11
  75. package/dist/src/daemon/daemon.js +71 -60
  76. package/dist/src/daemon/github-attach.js +77 -4
  77. package/dist/src/daemon/github-calls.js +1 -1
  78. package/dist/src/daemon/github-methods.js +6 -2
  79. package/dist/src/daemon/issue-queue.d.ts +3 -0
  80. package/dist/src/daemon/issue-queue.js +66 -73
  81. package/dist/src/daemon/merge-turns.d.ts +10 -0
  82. package/dist/src/daemon/merge-turns.js +37 -0
  83. package/dist/src/daemon/queue-control.d.ts +13 -0
  84. package/dist/src/daemon/queue-control.js +86 -0
  85. package/dist/src/daemon/queue-journal.d.ts +20 -0
  86. package/dist/src/daemon/queue-journal.js +89 -0
  87. package/dist/src/daemon/record-archive.d.ts +19 -0
  88. package/dist/src/daemon/record-archive.js +82 -0
  89. package/dist/src/daemon/record-sweep.d.ts +24 -0
  90. package/dist/src/daemon/record-sweep.js +147 -0
  91. package/dist/src/daemon/retrying-turn.js +4 -4
  92. package/dist/src/daemon/settings-reload.d.ts +26 -0
  93. package/dist/src/daemon/settings-reload.js +82 -0
  94. package/dist/src/daemon/turn-details.d.ts +4 -0
  95. package/dist/src/daemon/turn-details.js +33 -0
  96. package/dist/src/daemon/worker-profile.d.ts +1 -0
  97. package/dist/src/daemon/worker-profile.js +4 -0
  98. package/dist/src/daemon/workflow-generations.d.ts +7 -4
  99. package/dist/src/daemon/workflow-generations.js +22 -14
  100. package/dist/src/daemon/workflow-reload.d.ts +41 -0
  101. package/dist/src/daemon/workflow-reload.js +244 -0
  102. package/dist/src/daemon/workflow-replay.d.ts +2 -1
  103. package/dist/src/daemon/workflow-replay.js +7 -3
  104. package/dist/src/doctor-report.d.ts +2 -2
  105. package/dist/src/doctor-report.js +2 -0
  106. package/dist/src/private-config.schema.json +31 -0
  107. package/dist/src/project-settings.d.ts +15 -1
  108. package/dist/src/project-settings.js +33 -1
  109. package/dist/src/queue-state.d.ts +26 -0
  110. package/dist/src/queue-state.js +34 -0
  111. package/dist/src/record-retention.d.ts +46 -0
  112. package/dist/src/record-retention.js +81 -0
  113. package/dist/src/service/active-tickets.js +5 -0
  114. package/dist/src/service/dispatcher.d.ts +4 -0
  115. package/dist/src/service/dispatcher.js +33 -0
  116. package/dist/src/service/queue-handlers.d.ts +4 -0
  117. package/dist/src/service/queue-handlers.js +32 -0
  118. package/dist/src/service/registry.d.ts +28 -2
  119. package/dist/src/service/registry.js +48 -0
  120. package/dist/src/service/replies.d.ts +16 -9
  121. package/dist/src/service/replies.js +5 -2
  122. package/dist/src/service/run-handlers.js +15 -10
  123. package/dist/src/service/service.d.ts +7 -3
  124. package/dist/src/service/service.js +16 -31
  125. package/dist/src/service/worker-totals.d.ts +4 -0
  126. package/dist/src/service/worker-totals.js +7 -0
  127. package/dist/src/service/workflow-reload.d.ts +35 -0
  128. package/dist/src/service/workflow-reload.js +52 -0
  129. package/dist/src/storage/decision-snapshots.d.ts +1 -2
  130. package/dist/src/storage/decision-snapshots.js +1 -7
  131. package/dist/src/storage/domain-changes.d.ts +1 -1
  132. package/dist/src/storage/domain-changes.js +1 -0
  133. package/dist/src/storage/domain-schema.d.ts +4 -1
  134. package/dist/src/storage/domain-schema.js +6 -2
  135. package/dist/src/storage/effect-schema.d.ts +1 -0
  136. package/dist/src/storage/effect-schema.js +4 -0
  137. package/dist/src/storage/projection-changes.d.ts +12 -0
  138. package/dist/src/storage/projection-changes.js +194 -0
  139. package/dist/src/storage/projection-lookups.d.ts +7 -0
  140. package/dist/src/storage/projection-lookups.js +30 -0
  141. package/dist/src/workflow-declaration.d.ts +1 -0
  142. package/dist/src/workflow-declaration.js +3 -0
  143. package/dist/tools/compile-workflows.js +3 -0
  144. package/dist/tools/project-configuration.d.ts +7 -2
  145. package/dist/tools/project-configuration.js +30 -1
  146. package/dist/tools/self-host-processes.js +37 -13
  147. package/dist/tools/self-host-project.d.ts +2 -0
  148. package/dist/tools/self-host-project.js +21 -3
  149. package/dist/tools/self-host.js +34 -24
  150. package/dist/tools/workflow-candidate-program.d.ts +4 -2
  151. package/dist/tools/workflow-candidate-program.js +30 -11
  152. package/dist/tools/workflow-candidate-source.d.ts +1 -0
  153. package/dist/tools/workflow-candidate-source.js +11 -3
  154. package/package.json +2 -2
  155. package/templates/develop/README.md +81 -4
  156. package/templates/develop/develop/flow-tests.ts +1 -1
  157. package/templates/develop/develop/interruption-tests.ts +157 -0
  158. package/templates/develop/develop/local.ts +1 -0
  159. package/templates/develop/develop/option-tests.ts +138 -9
  160. package/templates/develop/develop/red-green.ts +55 -0
  161. package/templates/develop/develop/report.ts +31 -11
  162. package/templates/develop/develop/round.ts +1 -0
  163. package/templates/develop/develop/scenarios.ts +27 -0
  164. package/templates/develop/develop/turn.ts +26 -6
  165. package/templates/develop/develop.declaration.json +1 -1
  166. package/templates/develop/develop.test.ts +4 -0
  167. package/templates/develop/template.json +29 -2
@@ -0,0 +1,207 @@
1
+ import { eligibleIntents } from "../../authority/reducer.js";
2
+ import { loadProjections, } from "../../storage/decision-snapshots.js";
3
+ import { messageOutcomes, recordedTimes, runTimes, storedWorkflows, submittedWorkflows, } from "../../storage/projection-lookups.js";
4
+ import { changedProjection, followingSequence, projectedChange, } from "../../storage/projection-changes.js";
5
+ import { currentState, intentKind, } from "../authority-restore.js";
6
+ const pendingLimit = 1_000;
7
+ export function projectionMemory(options) {
8
+ const memory = {
9
+ held: undefined,
10
+ stale: null,
11
+ recorded: new Map(),
12
+ logger: options.logger,
13
+ };
14
+ return {
15
+ record: ({ messageId, change }) => {
16
+ memory.recorded.delete(messageId);
17
+ memory.recorded.set(messageId, projectedChange(change));
18
+ if (memory.recorded.size <= pendingLimit)
19
+ return;
20
+ memory.recorded.clear();
21
+ memory.held = undefined;
22
+ },
23
+ load: (database) => database.connection(async (connection) => {
24
+ memory.recorded.clear();
25
+ memory.held = await rebuilt(connection);
26
+ }),
27
+ read: (database) => database.connection(async (connection) => {
28
+ const held = await advanced(memory, connection);
29
+ return held.projection;
30
+ }),
31
+ };
32
+ }
33
+ async function advanced(memory, connection) {
34
+ const recorded = [...memory.recorded];
35
+ memory.recorded.clear();
36
+ try {
37
+ memory.held = await updated(memory, { recorded, connection });
38
+ }
39
+ catch (error) {
40
+ memory.held = undefined;
41
+ memory.stale = `update failed: ${String(Reflect.get(Object(error), "message"))}`;
42
+ throw error;
43
+ }
44
+ return memory.held;
45
+ }
46
+ async function updated(memory, update) {
47
+ const { recorded, connection } = update;
48
+ if (memory.held === undefined)
49
+ return rebuiltAfter(memory, connection);
50
+ if (recorded.length === 0)
51
+ return memory.held;
52
+ const outcomes = await messageOutcomes(connection, recorded.map(([messageId]) => messageId));
53
+ const committed = [];
54
+ let sequence = memory.held.snapshot.sequence;
55
+ for (const [messageId, change] of recorded) {
56
+ const outcome = outcomes.get(messageId) ?? "missing";
57
+ if (outcome === "rolled-back")
58
+ continue;
59
+ const reason = mismatch({ messageId, change, outcome, sequence });
60
+ if (reason !== null) {
61
+ memory.stale = reason;
62
+ return rebuiltAfter(memory, connection);
63
+ }
64
+ committed.push(change);
65
+ sequence = followingSequence(sequence, change);
66
+ }
67
+ return applied(memory.held, { changes: committed, connection });
68
+ }
69
+ async function rebuiltAfter(memory, connection) {
70
+ const held = await rebuilt(connection);
71
+ if (memory.stale !== null)
72
+ memory.logger.warn({
73
+ event: "authority-projection-rebuilt",
74
+ reason: memory.stale,
75
+ });
76
+ memory.stale = null;
77
+ return held;
78
+ }
79
+ function mismatch(recorded) {
80
+ const { messageId, change, outcome, sequence } = recorded;
81
+ if (outcome === "missing")
82
+ return `message ${messageId} is not stored`;
83
+ if (change.previous === sequence)
84
+ return null;
85
+ return `sequence ${String(change.previous)} does not follow ${String(sequence)}`;
86
+ }
87
+ async function rebuilt(connection) {
88
+ const snapshot = await loadProjections(connection);
89
+ return withProjection({
90
+ snapshot,
91
+ times: await runTimes(connection),
92
+ workflows: new Map([
93
+ ...snapshot.details.flatMap((row) => row.submission === null ? [] : [[row.submission, null]]),
94
+ ...(await submittedWorkflows(connection)),
95
+ ]),
96
+ });
97
+ }
98
+ async function applied(previous, input) {
99
+ const { changes } = input;
100
+ if (changes.length === 0)
101
+ return previous;
102
+ return withProjection({
103
+ snapshot: changedProjection(previous.snapshot, changes),
104
+ times: await changedTimes(previous.times, input),
105
+ workflows: await changedWorkflows(previous.workflows, input),
106
+ });
107
+ }
108
+ async function changedTimes(previous, input) {
109
+ const runs = new Map(input.changes.flatMap((change) => change.runs.map((row) => [row.runId, row.sequence])));
110
+ const carried = new Map(input.changes.flatMap((change) => change.events.map((event) => [event.sequence, event.recordedAt])));
111
+ const stored = await recordedTimes(input.connection, [
112
+ ...new Set([...runs.values()].filter((sequence) => !carried.has(sequence))),
113
+ ]);
114
+ const times = new Map(previous);
115
+ for (const [runId, sequence] of runs) {
116
+ const time = carried.get(sequence) ?? stored.get(sequence);
117
+ if (time === undefined)
118
+ times.delete(runId);
119
+ else
120
+ times.set(runId, time);
121
+ }
122
+ return times;
123
+ }
124
+ async function changedWorkflows(previous, input) {
125
+ const submissions = [
126
+ ...new Set(input.changes.flatMap((change) => change.details.flatMap((row) => row.submission === null || previous.has(row.submission)
127
+ ? []
128
+ : [row.submission]))),
129
+ ];
130
+ const stored = await storedWorkflows(input.connection, submissions);
131
+ return new Map([
132
+ ...previous,
133
+ ...submissions.map((submission) => [submission, stored.get(submission) ?? null]),
134
+ ]);
135
+ }
136
+ function withProjection(parts) {
137
+ return { ...parts, projection: projection(parts) };
138
+ }
139
+ function remembered(compute) {
140
+ let value;
141
+ return () => {
142
+ value ??= { current: compute() };
143
+ return value.current;
144
+ };
145
+ }
146
+ function projection(parts) {
147
+ const { snapshot, times, workflows } = parts;
148
+ const state = remembered(() => currentState(snapshot));
149
+ return {
150
+ snapshot,
151
+ state,
152
+ eligible: remembered(() => eligible({ snapshot, state: state() })),
153
+ operations: remembered(() => operationRecords(snapshot)),
154
+ sequences: remembered(() => sequences({ snapshot, times })),
155
+ workflowIds: (runIds) => workflowIds({ snapshot, workflows, runIds }),
156
+ };
157
+ }
158
+ function sequences(parts) {
159
+ const timed = parts.snapshot.runs.flatMap((row) => {
160
+ const time = parts.times.get(row.runId);
161
+ return time === undefined ? [] : [{ row, time }];
162
+ });
163
+ return {
164
+ latest: parts.snapshot.sequence,
165
+ runs: Object.fromEntries(timed.map(({ row }) => [row.runId, row.sequence])),
166
+ times: Object.fromEntries(timed.map(({ row, time }) => [row.runId, time])),
167
+ };
168
+ }
169
+ function workflowIds(parts) {
170
+ const wanted = parts.runIds === undefined ? null : new Set(parts.runIds);
171
+ return Object.fromEntries(parts.snapshot.details.flatMap((row) => {
172
+ const workflowId = row.submission === null ? null : parts.workflows.get(row.submission);
173
+ if (typeof workflowId !== "string")
174
+ return [];
175
+ return wanted === null || wanted.has(row.runId)
176
+ ? [[row.runId, workflowId]]
177
+ : [];
178
+ }));
179
+ }
180
+ export function operationRecords(snapshot) {
181
+ const deliveries = Map.groupBy(snapshot.deliveries, (row) => row.operationId);
182
+ return snapshot.outbox.map((row) => ({
183
+ operationId: row.operationId,
184
+ kind: intentKind(row.kind),
185
+ runId: row.runId,
186
+ createdSequence: row.createdSequence,
187
+ releasedSequence: row.releasedSequence,
188
+ deliveries: (deliveries.get(row.operationId) ?? []).map(({ effectId, outcome, receipt, purpose, epoch }) => ({
189
+ effectId,
190
+ outcome,
191
+ receipt,
192
+ purpose,
193
+ epoch,
194
+ })),
195
+ }));
196
+ }
197
+ function eligible(parts) {
198
+ return eligibleIntents({
199
+ ...parts.state,
200
+ effects: {
201
+ operations: Object.fromEntries(parts.snapshot.pending.map((operationId) => [
202
+ operationId,
203
+ { status: "dispatch-pending" },
204
+ ])),
205
+ },
206
+ });
207
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ActorClass, type Database, type InstrumentationEvent, type SolidObjectsConfiguration, type SolidObjectsRuntime } from "solid-objects";
2
+ import { type DomainChange } from "../../storage/domain-changes.ts";
2
3
  import { type AuthorityTiming } from "./authority-settings.ts";
3
4
  import type { StrandingWatchdog } from "./authority-watchdog.ts";
4
5
  export interface FailedDelivery {
@@ -20,4 +21,8 @@ export declare function registeredRuntime(registration: {
20
21
  readonly database: Database;
21
22
  readonly actor: ActorClass;
22
23
  readonly settings: Partial<SolidObjectsConfiguration>;
24
+ readonly record: (recorded: {
25
+ readonly messageId: string;
26
+ readonly change: DomainChange;
27
+ }) => void;
23
28
  }): SolidObjectsRuntime;
@@ -1,5 +1,5 @@
1
1
  import { createRuntime, } from "solid-objects";
2
- import { persistChange } from "../../storage/domain-changes.js";
2
+ import { persistChange, } from "../../storage/domain-changes.js";
3
3
  import { persistAction } from "./authority-actor.js";
4
4
  import { brokerAuthorization } from "./authority-policy.js";
5
5
  import { librarySettings } from "./authority-settings.js";
@@ -26,9 +26,14 @@ export function runtimeSettings(parts) {
26
26
  };
27
27
  }
28
28
  export function registeredRuntime(registration) {
29
- const { database, actor, settings } = registration;
29
+ const { database, actor, settings, record } = registration;
30
30
  const runtime = createRuntime({ database, ...settings });
31
31
  runtime.register(actor);
32
- runtime.registerCommitAction(persistAction, (change, context) => persistChange(change, context.connection));
32
+ runtime.registerCommitAction(persistAction, async (change, context) => {
33
+ record({
34
+ messageId: context.messageId,
35
+ change: await persistChange(change, context.connection),
36
+ });
37
+ });
33
38
  return runtime;
34
39
  }
@@ -133,7 +133,7 @@ export function currentState(snapshot) {
133
133
  return {
134
134
  ...projectedState(snapshot),
135
135
  ...decisionState(snapshot),
136
- ...commandWaitState(snapshot.waits ?? []),
136
+ ...commandWaitState(snapshot.waits),
137
137
  };
138
138
  }
139
139
  export function projectedState(snapshot) {
@@ -3,3 +3,4 @@ import type { AuthorityState } from "../authority/reducer.ts";
3
3
  export declare function upgradeEffects(database: Database, state: AuthorityState): Promise<void>;
4
4
  export declare function upgradeSpendingIndexes(database: Database): Promise<void>;
5
5
  export declare function upgradeEventIndexes(database: Database): Promise<void>;
6
+ export declare function upgradeRunIndexes(database: Database): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { currentVersion, effectedVersion, spendingVersion, upgradeSchema, } from "../storage/domain-schema.js";
1
+ import { currentVersion, effectedVersion, eventIndexVersion, spendingVersion, upgradeSchema, } from "../storage/domain-schema.js";
2
2
  import { persistEffects } from "../storage/effect-changes.js";
3
3
  import { effectRows } from "./effect-restore.js";
4
4
  export async function upgradeEffects(database, state) {
@@ -11,5 +11,8 @@ export async function upgradeSpendingIndexes(database) {
11
11
  await database.transaction((connection) => upgradeSchema(connection, spendingVersion));
12
12
  }
13
13
  export async function upgradeEventIndexes(database) {
14
+ await database.transaction((connection) => upgradeSchema(connection, eventIndexVersion));
15
+ }
16
+ export async function upgradeRunIndexes(database) {
14
17
  await database.transaction((connection) => upgradeSchema(connection, currentVersion));
15
18
  }
@@ -12,6 +12,7 @@ const options = {
12
12
  version: { type: "boolean", short: "v" },
13
13
  input: { type: "string" },
14
14
  json: { type: "boolean" },
15
+ active: { type: "boolean" },
15
16
  prompt: { type: "string" },
16
17
  ticket: { type: "string" },
17
18
  next: { type: "boolean" },
@@ -7,7 +7,9 @@ export interface ClientRequest extends ServiceRequest {
7
7
  readonly signal?: AbortSignal;
8
8
  }
9
9
  export declare function send(runtimeDirectory: string, input: ClientRequest): Promise<Reply>;
10
- export declare function listRuns(runtimeDirectory: string): Promise<Reply>;
10
+ export declare function listRuns(runtimeDirectory: string, selection?: {
11
+ readonly active?: true;
12
+ }): Promise<Reply>;
11
13
  export declare function mutate(runtimeDirectory: string, mutation: {
12
14
  readonly request: ServiceRequest;
13
15
  readonly signal: Promise<void>;
@@ -2,7 +2,7 @@ import net from "node:net";
2
2
  import { join } from "node:path";
3
3
  import { socketAddress } from "../broker/socket-address.js";
4
4
  import { parseReplyLine, record, requestLine, } from "../protocol.js";
5
- const mutations = ["run", "decision", "run-command"];
5
+ const mutations = ["run", "decision", "run-command", "queue-command"];
6
6
  function unavailable(runtimeDirectory) {
7
7
  return {
8
8
  kind: "error",
@@ -101,10 +101,10 @@ function listedRuns(result) {
101
101
  ? runs.map(record).filter((run) => run !== undefined)
102
102
  : [];
103
103
  }
104
- export async function listRuns(runtimeDirectory) {
104
+ export async function listRuns(runtimeDirectory, selection = {}) {
105
105
  const first = await send(runtimeDirectory, {
106
106
  operation: "status",
107
- parameters: { limit: 500 },
107
+ parameters: { ...selection, limit: 500 },
108
108
  });
109
109
  if (first.kind === "error")
110
110
  return first;
@@ -123,7 +123,7 @@ export async function listRuns(runtimeDirectory) {
123
123
  after = next;
124
124
  const page = await send(runtimeDirectory, {
125
125
  operation: "status",
126
- parameters: { limit: 500, after },
126
+ parameters: { ...selection, limit: 500, after },
127
127
  });
128
128
  if (page.kind === "error")
129
129
  return page;
@@ -1,12 +1,41 @@
1
1
  import { type Fields, type Reply, type ServiceError, type ServiceRequest } from "../protocol.ts";
2
2
  import { type ClientRequest } from "./client.ts";
3
+ import type { DecisionCommand, RunCommand, RunSource } from "./runtime-arguments.ts";
3
4
  export interface ReadCommand {
4
- readonly kind: "status" | "questions" | "events";
5
+ readonly kind: "status" | "questions" | "events" | "reload";
5
6
  readonly runId?: string | null;
6
7
  readonly limit?: number | null;
7
8
  readonly after?: string | number | null;
9
+ readonly active?: boolean;
8
10
  readonly follow?: boolean;
9
11
  }
12
+ export type SubmittedSource = Exclude<RunSource, {
13
+ readonly kind: "input";
14
+ }> | {
15
+ readonly kind: "input";
16
+ readonly submission: Fields;
17
+ };
18
+ export declare function runRequest(run: {
19
+ readonly workflowId: string;
20
+ readonly source: SubmittedSource;
21
+ readonly commandId: string;
22
+ }): ServiceRequest;
23
+ export interface DecisionRequest {
24
+ readonly command: DecisionCommand;
25
+ readonly reference: string;
26
+ readonly text: string | null;
27
+ readonly expectedRevision: number;
28
+ readonly generation: number;
29
+ readonly subjectDigest: string;
30
+ readonly commandId: string;
31
+ }
32
+ export declare function decisionRequest(decision: DecisionRequest): ServiceRequest;
33
+ export declare function runCommandRequest(request: {
34
+ readonly command: RunCommand;
35
+ readonly runId: string;
36
+ readonly expectedRevision: number;
37
+ readonly commandId: string;
38
+ }): ServiceRequest;
10
39
  export declare function readCommandRequest(command: ReadCommand): ServiceRequest;
11
40
  export declare function readCommandReply(runtimeDirectory: string, request: ClientRequest): Promise<Reply>;
12
41
  export declare function errorObject(error: ServiceError, format?: {
@@ -2,11 +2,52 @@ import {} from "../protocol.js";
2
2
  import { send } from "./client.js";
3
3
  import { withCiReruns } from "./github-ci-progress.js";
4
4
  import { statusJson } from "./render.js";
5
+ function submission(source) {
6
+ if (source.kind === "input")
7
+ return source.submission;
8
+ return source.kind === "prompt"
9
+ ? { prompt: source.text }
10
+ : { ticket: source.id };
11
+ }
12
+ export function runRequest(run) {
13
+ return {
14
+ operation: "run",
15
+ parameters: {
16
+ workflowId: run.workflowId,
17
+ source: run.source.kind,
18
+ submission: submission(run.source),
19
+ commandId: run.commandId,
20
+ },
21
+ };
22
+ }
23
+ export function decisionRequest(decision) {
24
+ return {
25
+ operation: "decision",
26
+ parameters: Object.fromEntries(Object.entries(decision).filter(([, value]) => value !== null)),
27
+ };
28
+ }
29
+ export function runCommandRequest(request) {
30
+ return {
31
+ operation: "run-command",
32
+ parameters: {
33
+ command: request.command,
34
+ runId: request.runId,
35
+ expectedRevision: request.expectedRevision,
36
+ commandId: request.commandId,
37
+ },
38
+ };
39
+ }
5
40
  export function readCommandRequest(command) {
6
- const { kind, runId, limit, after, follow } = command;
41
+ const { kind, runId, limit, after, active, follow } = command;
7
42
  const parameters = kind === "questions"
8
43
  ? { runId }
9
- : { runId, limit, after, ...(follow === undefined ? {} : { follow }) };
44
+ : {
45
+ runId,
46
+ limit,
47
+ after,
48
+ ...(active === true ? { active } : {}),
49
+ ...(follow === undefined ? {} : { follow }),
50
+ };
10
51
  return {
11
52
  operation: kind,
12
53
  parameters: Object.fromEntries(Object.entries(parameters).filter(([, value]) => value !== null && value !== undefined)),
@@ -1,6 +1,7 @@
1
- import { commandJson, errorObject, readCommandReply, readCommandRequest, } from "./command-requests.js";
1
+ import { commandJson, decisionRequest, errorObject, readCommandReply, readCommandRequest, runCommandRequest, runRequest, } from "./command-requests.js";
2
2
  import { reportCommand } from "./report.js";
3
3
  import { scheduleServiceRequest } from "./schedule-arguments.js";
4
+ import { queueServiceRequest } from "./queue-arguments.js";
4
5
  import { withCiReruns } from "./github-ci-progress.js";
5
6
  import { readFile } from "node:fs/promises";
6
7
  import { resolve } from "node:path";
@@ -55,7 +56,8 @@ function identified(environment, invocation) {
55
56
  environment.terminal.error(`Command ID: ${commandId}\n`);
56
57
  return { json, commandId, announced: !json };
57
58
  }
58
- async function inputFile(environment, file) {
59
+ async function inputFile(environment, input) {
60
+ const { file, commandId } = input;
59
61
  let text;
60
62
  try {
61
63
  text = await readFile(resolve(environment.workingDirectory, file), "utf8");
@@ -69,8 +71,9 @@ async function inputFile(environment, file) {
69
71
  },
70
72
  };
71
73
  }
74
+ let submission;
72
75
  try {
73
- return { kind: "read", submission: JSON.parse(text) };
76
+ submission = record(JSON.parse(text));
74
77
  }
75
78
  catch {
76
79
  return {
@@ -81,37 +84,37 @@ async function inputFile(environment, file) {
81
84
  },
82
85
  };
83
86
  }
84
- }
85
- function typed(source) {
86
- return source.kind === "prompt"
87
- ? { prompt: source.text }
88
- : { ticket: source.id };
87
+ if (submission === undefined)
88
+ return {
89
+ kind: "error",
90
+ error: {
91
+ commandId,
92
+ detail: "The submission is not an object.",
93
+ code: "invalid-submission",
94
+ },
95
+ };
96
+ return { kind: "read", source: { kind: "input", submission } };
89
97
  }
90
98
  async function run(environment, invocation) {
91
99
  const format = identified(environment, invocation);
92
- const { commandId } = format;
93
100
  const { source } = invocation;
94
101
  const read = source.kind === "input"
95
- ? await inputFile(environment, source.file)
96
- : { kind: "read", submission: typed(source) };
102
+ ? await inputFile(environment, {
103
+ file: source.file,
104
+ commandId: format.commandId,
105
+ })
106
+ : { kind: "read", source };
97
107
  if (read.kind === "error")
98
108
  return failed(environment, { error: read.error, format });
99
109
  return mutated(environment, {
100
110
  format,
101
- request: {
102
- operation: "run",
103
- parameters: {
104
- workflowId: invocation.workflowId,
105
- source: source.kind,
106
- submission: read.submission,
107
- commandId,
108
- },
109
- },
111
+ request: runRequest({
112
+ workflowId: invocation.workflowId,
113
+ source: read.source,
114
+ commandId: format.commandId,
115
+ }),
110
116
  });
111
117
  }
112
- function present(values) {
113
- return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== null));
114
- }
115
118
  async function summarized(environment) {
116
119
  const pending = await send(environment.runtimeDirectory, {
117
120
  operation: "questions",
@@ -121,11 +124,11 @@ async function summarized(environment) {
121
124
  environment.terminal.out(pendingSummary(pending.result));
122
125
  }
123
126
  async function status(environment, invocation) {
124
- const { runId, limit, after, json } = invocation;
127
+ const { runId, limit, after, active, json } = invocation;
125
128
  if (!json && runId === null)
126
129
  await summarized(environment);
127
130
  if (!json && runId === null && limit === null && after === null) {
128
- const reply = await withCiReruns(environment.runtimeDirectory, await listRuns(environment.runtimeDirectory));
131
+ const reply = await withCiReruns(environment.runtimeDirectory, await listRuns(environment.runtimeDirectory, active ? { active } : {}));
129
132
  return presented(environment, {
130
133
  format: { json },
131
134
  reply: reply.kind === "result"
@@ -140,37 +143,29 @@ async function status(environment, invocation) {
140
143
  }
141
144
  function decision(environment, invocation) {
142
145
  const format = identified(environment, invocation);
143
- const { commandId } = format;
144
146
  return mutated(environment, {
145
147
  format,
146
- request: {
147
- operation: "decision",
148
- parameters: present({
149
- command: invocation.command,
150
- reference: invocation.reference,
151
- text: invocation.text,
152
- expectedRevision: invocation.expectedRevision,
153
- generation: invocation.generation,
154
- subjectDigest: invocation.subjectDigest,
155
- commandId,
156
- }),
157
- },
148
+ request: decisionRequest({
149
+ command: invocation.command,
150
+ reference: invocation.reference,
151
+ text: invocation.text,
152
+ expectedRevision: invocation.expectedRevision,
153
+ generation: invocation.generation,
154
+ subjectDigest: invocation.subjectDigest,
155
+ commandId: format.commandId,
156
+ }),
158
157
  });
159
158
  }
160
159
  function runCommand(environment, invocation) {
161
160
  const format = identified(environment, invocation);
162
- const { commandId } = format;
163
161
  return mutated(environment, {
164
162
  format,
165
- request: {
166
- operation: "run-command",
167
- parameters: {
168
- command: invocation.command,
169
- runId: invocation.runId,
170
- expectedRevision: invocation.expectedRevision,
171
- commandId,
172
- },
173
- },
163
+ request: runCommandRequest({
164
+ command: invocation.command,
165
+ runId: invocation.runId,
166
+ expectedRevision: invocation.expectedRevision,
167
+ commandId: format.commandId,
168
+ }),
174
169
  });
175
170
  }
176
171
  async function events(environment, invocation) {
@@ -216,6 +211,8 @@ function located(invocation, environment) {
216
211
  format: { json: invocation.json },
217
212
  request: scheduleServiceRequest(invocation),
218
213
  });
214
+ if (invocation.kind === "queue")
215
+ return queue(environment, invocation);
219
216
  if (invocation.kind === "report")
220
217
  return reportCommand(invocation, environment);
221
218
  if (invocation.kind === "run")
@@ -235,6 +232,18 @@ function located(invocation, environment) {
235
232
  request: readCommandRequest(invocation),
236
233
  });
237
234
  }
235
+ function queue(environment, invocation) {
236
+ if (invocation.action === "show")
237
+ return requested(environment, {
238
+ format: { json: invocation.json },
239
+ request: { operation: "queue", parameters: {} },
240
+ });
241
+ const format = identified(environment, invocation);
242
+ return mutated(environment, {
243
+ format,
244
+ request: queueServiceRequest(invocation, format.commandId),
245
+ });
246
+ }
238
247
  function watch(environment, invocation) {
239
248
  return watchServer({
240
249
  actions: environment.watchActions ?? "on",
@@ -1,6 +1,7 @@
1
1
  import net from "node:net";
2
2
  import { join } from "node:path";
3
3
  import { socketAddress } from "../broker/socket-address.js";
4
+ import { reloadedSettingFields } from "../project-settings.js";
4
5
  import { runtimeDirectory } from "./runtime-directory.js";
5
6
  function serviceDirectory(repository) {
6
7
  try {
@@ -40,7 +41,9 @@ export async function configNotice(repository, input) {
40
41
  const source = input.entry?.source;
41
42
  const hidden = source === "environment" ||
42
43
  (input.scope === "project" && source === "private");
43
- return hidden
44
- ? `The ${source} source hides this file value. A restart keeps the value of that source.`
44
+ if (hidden)
45
+ return `The ${source} source hides this file value. A restart keeps the value of that source.`;
46
+ return reloadedSettingFields.includes(String(input.entry?.field))
47
+ ? "Run cloving reload to use the new value."
45
48
  : "Restart the service to use the new value.";
46
49
  }
@@ -0,0 +1,2 @@
1
+ import type { DoctorObservation } from "../doctor-report.ts";
2
+ export declare function retentionObservation(state: string, environment: Readonly<NodeJS.ProcessEnv>): Promise<DoctorObservation>;