@aeolus-fleet/trierarch 0.18.0 → 0.20.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/LICENSE +201 -0
- package/README.md +11 -11
- package/dist/adapters/claude-code.d.ts +5 -3
- package/dist/adapters/claude-code.js +2 -0
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts +5 -3
- package/dist/adapters/codex.js +2 -0
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/flags.d.ts +2 -2
- package/dist/adapters/flags.js +2 -2
- package/dist/adapters/git-workspace.js +2 -2
- package/dist/adapters/git-workspace.js.map +1 -1
- package/dist/adapters/harnesses.d.ts +6 -4
- package/dist/adapters/harnesses.js +11 -5
- package/dist/adapters/harnesses.js.map +1 -1
- package/dist/adapters/json-state.d.ts +6 -4
- package/dist/adapters/json-state.js +28 -23
- package/dist/adapters/json-state.js.map +1 -1
- package/dist/adapters/rest-fleet.d.ts +4 -3
- package/dist/adapters/rest-fleet.js +24 -13
- package/dist/adapters/rest-fleet.js.map +1 -1
- package/dist/adapters/service.js +1 -1
- package/dist/adapters/style.d.ts +3 -3
- package/dist/adapters/style.js +1 -2
- package/dist/adapters/style.js.map +1 -1
- package/dist/cli/config-check.d.ts +3 -3
- package/dist/cli/config-check.js +1 -1
- package/dist/cli/config-check.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/list.d.ts +4 -3
- package/dist/cli/list.js +1 -1
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/main.js +2 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.d.ts +12 -7
- package/dist/cli/run.js +53 -26
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +3 -2
- package/dist/cli/status.js +6 -5
- package/dist/cli/status.js.map +1 -1
- package/dist/core/check-settings.d.ts +25 -0
- package/dist/core/check-settings.js +52 -0
- package/dist/core/check-settings.js.map +1 -0
- package/dist/core/entry.d.ts +24 -29
- package/dist/core/entry.js +3 -1
- package/dist/core/entry.js.map +1 -1
- package/dist/core/handle-delivery.d.ts +9 -14
- package/dist/core/handle-delivery.js +4 -133
- package/dist/core/handle-delivery.js.map +1 -1
- package/dist/core/machine.d.ts +12 -0
- package/dist/core/machine.js +15 -0
- package/dist/core/machine.js.map +1 -0
- package/dist/core/options-schema.d.ts +7 -0
- package/dist/core/options-schema.js +10 -0
- package/dist/core/options-schema.js.map +1 -0
- package/dist/core/ports.d.ts +72 -9
- package/dist/core/reconciler.d.ts +50 -12
- package/dist/core/reconciler.js +129 -25
- package/dist/core/reconciler.js.map +1 -1
- package/dist/core/report-self.d.ts +16 -0
- package/dist/core/report-self.js +52 -0
- package/dist/core/report-self.js.map +1 -0
- package/dist/core/run-pass.d.ts +8 -7
- package/dist/core/run-pass.js +140 -70
- package/dist/core/run-pass.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
- package/dist/core/answers.d.ts +0 -4
- package/dist/core/answers.js +0 -32
- package/dist/core/answers.js.map +0 -1
- package/dist/core/check-want.d.ts +0 -32
- package/dist/core/check-want.js +0 -80
- package/dist/core/check-want.js.map +0 -1
package/dist/cli/run.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { setTimeout as wait } from 'node:timers/promises';
|
|
2
2
|
import { loadConfiguration, readCrewFile, writeRunningFile } from '../adapters/files.js';
|
|
3
3
|
import { createGitWorkspace } from '../adapters/git-workspace.js';
|
|
4
|
-
import { adapterFlagsOf, createHarnesses } from '../adapters/harnesses.js';
|
|
4
|
+
import { adapterFlagsOf, createHarnesses, riskyFlagsOf } from '../adapters/harnesses.js';
|
|
5
5
|
import { createJsonState } from '../adapters/json-state.js';
|
|
6
6
|
import { createRestFleet } from '../adapters/rest-fleet.js';
|
|
7
7
|
import { createTmux } from '../adapters/tmux.js';
|
|
8
8
|
import { runningVersion } from '../adapters/version.js';
|
|
9
9
|
import { createHandleDelivery } from '../core/handle-delivery.js';
|
|
10
|
+
import { machineOf } from '../core/machine.js';
|
|
11
|
+
import { createReportSelf } from '../core/report-self.js';
|
|
10
12
|
import { createRunPass } from '../core/run-pass.js';
|
|
11
13
|
/** How often the loop runs a pass: restarts, wakes and releases wait at most this long. */
|
|
12
14
|
export const PASS_INTERVAL_MS = 5000;
|
|
13
15
|
/**
|
|
14
16
|
* `aeolus-trierarch run`: the loop. It receives what comes to the trierarch's
|
|
15
|
-
* ship and handles each
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* ship and handles each delivery, and runs a pass every few seconds beside it,
|
|
18
|
+
* until it is told to stop. A failure is logged and the loop goes on: the next
|
|
19
|
+
* pass starts from the saved state.
|
|
18
20
|
*/
|
|
19
21
|
export async function runTrierarch(input) {
|
|
20
22
|
const { paths, homeDirectory, env, signal, logger } = input;
|
|
@@ -26,42 +28,67 @@ export async function runTrierarch(input) {
|
|
|
26
28
|
const workspace = createGitWorkspace({ configuration, root: configuration.worktreeRoot ?? paths.worktrees });
|
|
27
29
|
const state = createJsonState(paths.state);
|
|
28
30
|
const clock = { now: () => new Date() };
|
|
29
|
-
const setup = {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const setup = {
|
|
32
|
+
configuration,
|
|
33
|
+
version: runningVersion(),
|
|
34
|
+
adapterFlags: adapterFlagsOf(configuration),
|
|
35
|
+
riskyFlags: riskyFlagsOf(configuration),
|
|
36
|
+
machine: machineOf({ platform: process.platform, arch: process.arch }),
|
|
37
|
+
};
|
|
38
|
+
const handle = createHandleDelivery({ fleet, logger });
|
|
39
|
+
const runPass = createRunPass({ fleet, harnesses, processes: tmux, workspace, state, setup, clock, logger });
|
|
40
|
+
const reportSelf = createReportSelf({ fleet, processes: tmux, state, setup });
|
|
41
|
+
// Each pass ends with the trierarch's own report: the first at start, then only when it changed.
|
|
42
|
+
const pass = async () => {
|
|
43
|
+
await runPass();
|
|
44
|
+
await reportSelf();
|
|
45
|
+
};
|
|
32
46
|
await writeRunningFile(paths.running, { pid: process.pid, version: setup.version });
|
|
33
47
|
const found = Object.entries(plugins).map(([harness, plugin]) => `${harness} at ${plugin.root}`);
|
|
34
48
|
logger.info(`aeolus-trierarch ${setup.version} runs for ${crew.fleetUrl}, with the aeolus plugin for ${found.join(' and ') || 'no harness'}`);
|
|
35
49
|
await loop({ receive: (stop) => fleet.receive(stop), handle, pass, signal, logger, intervalMs: PASS_INTERVAL_MS });
|
|
36
50
|
}
|
|
37
51
|
/**
|
|
38
|
-
* Receives and handles what came, and runs a pass
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
52
|
+
* Receives and handles what came, and runs a pass every `intervalMs`, until
|
|
53
|
+
* `signal` stops it. The two run side by side: a receive the fleet holds for
|
|
54
|
+
* its long poll never delays a pass, so restarts and wakes act within the
|
|
55
|
+
* interval (#250). Handling a delivery touches no state (a ping gets pong,
|
|
56
|
+
* anything else is acknowledged), so the two never contend. A failure is
|
|
57
|
+
* logged and its side waits an interval before it goes on. The stop ends a
|
|
58
|
+
* receive the fleet still holds, and every wait, at once: a service manager
|
|
59
|
+
* that stops the trierarch sees it gone in a moment, not after the next long
|
|
60
|
+
* poll.
|
|
42
61
|
*/
|
|
43
62
|
export async function loop(at) {
|
|
44
63
|
const { signal, logger, intervalMs } = at;
|
|
45
64
|
// Asked afresh each time: a stop comes from outside while the loop waits.
|
|
46
65
|
const isStopped = () => signal.aborted;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
const pause = () => wait(intervalMs, undefined, { signal }).catch(() => undefined);
|
|
67
|
+
/** Runs one step after another until the stop, logging a failure and pausing after it; a paced one waits an interval after each. */
|
|
68
|
+
const repeat = async (step, pacing) => {
|
|
69
|
+
while (!isStopped()) {
|
|
70
|
+
try {
|
|
71
|
+
await step();
|
|
72
|
+
if (pacing.isPaced) {
|
|
73
|
+
await pause();
|
|
74
|
+
}
|
|
52
75
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (isStopped()) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
logger.warn(`The loop failed and goes on: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
|
+
await pause();
|
|
56
82
|
}
|
|
57
83
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
};
|
|
85
|
+
await Promise.all([
|
|
86
|
+
repeat(async () => {
|
|
87
|
+
for (const delivery of await at.receive(signal)) {
|
|
88
|
+
await at.handle(delivery);
|
|
61
89
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
90
|
+
}, { isPaced: false }),
|
|
91
|
+
repeat(at.pass, { isPaced: true }),
|
|
92
|
+
]);
|
|
66
93
|
}
|
|
67
94
|
//# sourceMappingURL=run.js.map
|
package/dist/cli/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/status.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TrierarchConfiguration } from '@aeolus-fleet/common';
|
|
2
|
+
import { type EntryState } from '../core/entry.js';
|
|
2
3
|
import type { CrewFile, RunningFile } from '../adapters/files.js';
|
|
3
4
|
import type { Service, ServiceStatus } from '../adapters/service.js';
|
|
4
5
|
import type { KeptWorktree } from '../core/entry.js';
|
|
@@ -33,7 +34,7 @@ export interface StatusReport {
|
|
|
33
34
|
readonly cap: number;
|
|
34
35
|
};
|
|
35
36
|
};
|
|
36
|
-
readonly entries: Readonly<Record<
|
|
37
|
+
readonly entries: Readonly<Record<EntryState, number>>;
|
|
37
38
|
readonly kept: readonly KeptWorktree[];
|
|
38
39
|
readonly orphans: readonly string[];
|
|
39
40
|
}
|
package/dist/cli/status.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ENTRY_STATES } from '../core/entry.js';
|
|
2
3
|
import { FleetRefusal } from '../adapters/rest-fleet.js';
|
|
3
4
|
import { PLAIN, stateTone } from '../adapters/style.js';
|
|
4
5
|
export function leaseFrom(whoami) {
|
|
@@ -19,7 +20,7 @@ export async function inspectStatus(at) {
|
|
|
19
20
|
const [service, lease, sessions, state, running] = await Promise.all([at.service.status(), at.lease(), at.processes.list(), at.state.load(), at.running()]);
|
|
20
21
|
// Only the service's own process counts: a file left by an earlier process, or by a `run` outside the service, says nothing of it.
|
|
21
22
|
const runningVersion = service.isRunning && running !== undefined && running.pid === service.pid ? running.version : undefined;
|
|
22
|
-
const entries = {
|
|
23
|
+
const entries = { crewing: 0, running: 0, restarting: 0, crashed: 0, releasing: 0 };
|
|
23
24
|
for (const entry of Object.values(state.entries)) {
|
|
24
25
|
entries[entry.state] += 1;
|
|
25
26
|
}
|
|
@@ -34,7 +35,7 @@ export async function inspectStatus(at) {
|
|
|
34
35
|
},
|
|
35
36
|
entries,
|
|
36
37
|
kept: state.kept,
|
|
37
|
-
orphans: state.orphans,
|
|
38
|
+
orphans: state.orphans.map((orphan) => orphan.path),
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
function describeService(service, style) {
|
|
@@ -76,7 +77,7 @@ export function describeStatus(report, style = PLAIN) {
|
|
|
76
77
|
line('Version', describeVersion(report, style)),
|
|
77
78
|
line('Service', describeService(report.service, style)),
|
|
78
79
|
line('Fleet', describeFleet(report.fleet, style)),
|
|
79
|
-
line('Caps', `${String(report.caps.ships.used)} of ${String(report.caps.ships.cap)} ships
|
|
80
|
+
line('Caps', `${String(report.caps.ships.used)} of ${String(report.caps.ships.cap)} ships crewed here, ${String(report.caps.running.used)} of ${String(report.caps.running.cap)} sessions running`),
|
|
80
81
|
line('Entries', counted.length === 0 ? 'none' : counted.map(([state, count]) => style.tone(stateToneOf(state), `${state} ${String(count)}`)).join(', ')),
|
|
81
82
|
line('Kept worktrees', report.kept.length === 0 ? 'none' : style.tone('busy', report.kept.map((kept) => `${kept.path} (${kept.shipId})`).join(', '))),
|
|
82
83
|
line('Orphans', report.orphans.length === 0 ? 'none' : style.tone('busy', report.orphans.join(', '))),
|
|
@@ -84,7 +85,7 @@ export function describeStatus(report, style = PLAIN) {
|
|
|
84
85
|
}
|
|
85
86
|
/** The tone of a state counted under entries, as its name is a key of the report. */
|
|
86
87
|
function stateToneOf(state) {
|
|
87
|
-
const parsed =
|
|
88
|
+
const parsed = z.enum(ENTRY_STATES).safeParse(state);
|
|
88
89
|
return parsed.success ? stateTone(parsed.data) : 'quiet';
|
|
89
90
|
}
|
|
90
91
|
//# sourceMappingURL=status.js.map
|
package/dist/cli/status.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/cli/status.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/cli/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CrewSettings, type ShipId, type TrierarchConfiguration } from '@aeolus-fleet/common';
|
|
2
|
+
import type { TrierarchState } from './entry.js';
|
|
3
|
+
import { type Result } from './shared/result.js';
|
|
4
|
+
/** Why the trierarch cannot crew settings: the field at fault, when one is, and a reason argo can read. */
|
|
5
|
+
export interface Refusal {
|
|
6
|
+
readonly field?: string;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
}
|
|
9
|
+
/** Settings this trierarch can crew: parsed, with each option resolved to its value name. */
|
|
10
|
+
export interface CheckedSettings {
|
|
11
|
+
readonly settings: CrewSettings;
|
|
12
|
+
readonly options: Readonly<Record<string, string>>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Checks a crew request's settings again before crewing (decision 0027):
|
|
16
|
+
* the crew settings schema, then what this trierarch's configuration offers
|
|
17
|
+
* (harness, repository or folder, each option and its value), and one folder
|
|
18
|
+
* per ship. The trierarch plugin checked them already; this machine's
|
|
19
|
+
* configuration may have changed since.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkSettings(raw: unknown, context: {
|
|
22
|
+
shipId: ShipId;
|
|
23
|
+
configuration: TrierarchConfiguration;
|
|
24
|
+
state: TrierarchState;
|
|
25
|
+
}): Result<CheckedSettings, Refusal>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { crewSettingsSchema } from '@aeolus-fleet/common';
|
|
2
|
+
import { err, ok } from './shared/result.js';
|
|
3
|
+
/** The field an issue names, as `options.model`, when it names one. */
|
|
4
|
+
function fieldOf(path) {
|
|
5
|
+
return path.length === 0 ? {} : { field: path.map(String).join('.') };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Checks a crew request's settings again before crewing (decision 0027):
|
|
9
|
+
* the crew settings schema, then what this trierarch's configuration offers
|
|
10
|
+
* (harness, repository or folder, each option and its value), and one folder
|
|
11
|
+
* per ship. The trierarch plugin checked them already; this machine's
|
|
12
|
+
* configuration may have changed since.
|
|
13
|
+
*/
|
|
14
|
+
export function checkSettings(raw, context) {
|
|
15
|
+
const parsed = crewSettingsSchema.safeParse(raw);
|
|
16
|
+
if (!parsed.success) {
|
|
17
|
+
const [issue] = parsed.error.issues;
|
|
18
|
+
return err({ ...fieldOf(issue?.path ?? []), reason: issue?.message ?? 'The settings do not parse' });
|
|
19
|
+
}
|
|
20
|
+
const settings = parsed.data;
|
|
21
|
+
const { configuration, state, shipId } = context;
|
|
22
|
+
const harness = configuration.harnesses[settings.harness];
|
|
23
|
+
if (harness === undefined) {
|
|
24
|
+
return err({ field: 'harness', reason: `This trierarch offers no harness ${settings.harness}` });
|
|
25
|
+
}
|
|
26
|
+
const { workspace } = settings;
|
|
27
|
+
if (workspace.kind === 'worktree' && !(workspace.repository in configuration.repositories)) {
|
|
28
|
+
return err({ field: 'workspace.repository', reason: `This trierarch offers no repository ${workspace.repository}` });
|
|
29
|
+
}
|
|
30
|
+
if (workspace.kind === 'folder') {
|
|
31
|
+
if (!(workspace.name in configuration.folders)) {
|
|
32
|
+
return err({ field: 'workspace.name', reason: `This trierarch offers no folder ${workspace.name}` });
|
|
33
|
+
}
|
|
34
|
+
const crewing = Object.values(state.entries).find((entry) => entry.shipId !== shipId && entry.workspace.kind === 'folder' && entry.workspace.name === workspace.name);
|
|
35
|
+
if (crewing !== undefined) {
|
|
36
|
+
return err({ field: 'workspace.name', reason: `The folder ${workspace.name} crews ${crewing.shipId} already: one folder crews one ship` });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const options = {};
|
|
40
|
+
for (const [name, value] of Object.entries(settings.options)) {
|
|
41
|
+
const option = harness.options[name];
|
|
42
|
+
if (option === undefined) {
|
|
43
|
+
return err({ field: `options.${name}`, reason: `${settings.harness} has no option ${name}` });
|
|
44
|
+
}
|
|
45
|
+
if (typeof value !== 'string' || !(value in option.values)) {
|
|
46
|
+
return err({ field: `options.${name}`, reason: `${name} must be one of ${Object.keys(option.values).join(', ')}` });
|
|
47
|
+
}
|
|
48
|
+
options[name] = value;
|
|
49
|
+
}
|
|
50
|
+
return ok({ settings, options });
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=check-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-settings.js","sourceRoot":"","sources":["../../src/core/check-settings.ts"],"names":[],"mappings":"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"}
|
package/dist/core/entry.d.ts
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
|
-
import type { ShipId,
|
|
1
|
+
import type { ShipId, TrierarchWorkspace } from '@aeolus-fleet/common';
|
|
2
2
|
/**
|
|
3
|
-
* The trierarch's saved state (docs/trierarch.md):
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* secret: a session's crew token lives only in its folder's
|
|
3
|
+
* The trierarch's saved state (docs/trierarch.md): an entry per ship it
|
|
4
|
+
* crews, by ship id, the worktrees it kept, the orphans it found, and the
|
|
5
|
+
* settings versions it told argo it cannot crew. Plain data, saved whole as
|
|
6
|
+
* JSON, never a secret: a session's crew token lives only in its folder's
|
|
7
|
+
* identity file. The crew requests themselves live in the fleet.
|
|
7
8
|
*/
|
|
8
9
|
export interface TrierarchState {
|
|
9
10
|
readonly entries: Readonly<Record<string, Entry>>;
|
|
10
|
-
/** Each applied message's answers, by message id, so a message applied before changes nothing. */
|
|
11
|
-
readonly applied: Readonly<Record<string, readonly Outgoing[]>>;
|
|
12
11
|
readonly kept: readonly KeptWorktree[];
|
|
13
|
-
readonly orphans: readonly
|
|
12
|
+
readonly orphans: readonly Orphan[];
|
|
13
|
+
/** By ship id: the settings version argo was told this trierarch cannot crew, so it is told once. */
|
|
14
|
+
readonly refused: Readonly<Record<string, number>>;
|
|
14
15
|
}
|
|
15
|
-
/**
|
|
16
|
+
/** Where an entry stands: the crew statuses the trierarch writes, and releasing while it ends. */
|
|
17
|
+
export declare const ENTRY_STATES: readonly ["crewing", "running", "restarting", "crashed", "releasing"];
|
|
18
|
+
export type EntryState = (typeof ENTRY_STATES)[number];
|
|
19
|
+
/** One ship the trierarch crews, from the crew request assigned to it, with the settings of the version it crewed. */
|
|
16
20
|
export interface Entry {
|
|
17
21
|
readonly shipId: ShipId;
|
|
18
22
|
/** The ship's name, from the fleet when it is crewed: its session is named after it. */
|
|
19
23
|
readonly shipName?: string;
|
|
24
|
+
/** The settings version this entry crews with: a new one crews the ship again. */
|
|
25
|
+
readonly settingsVersion: number;
|
|
20
26
|
readonly harness: string;
|
|
21
27
|
readonly workspace: TrierarchWorkspace;
|
|
22
28
|
readonly squadron?: string;
|
|
23
29
|
/** Given on the first start only. */
|
|
24
30
|
readonly firstPrompt?: string;
|
|
25
|
-
/** Option name to value name, as the
|
|
31
|
+
/** Option name to value name, as the settings picked them. */
|
|
26
32
|
readonly options: Readonly<Record<string, string>>;
|
|
27
|
-
|
|
28
|
-
readonly requester: ShipId;
|
|
29
|
-
readonly state: TrierarchEntryState;
|
|
33
|
+
readonly state: EntryState;
|
|
30
34
|
/** When the entry took its state, ISO 8601. */
|
|
31
35
|
readonly since: string;
|
|
32
36
|
/** When its session exited within the restart window, oldest first, ISO 8601. */
|
|
@@ -36,37 +40,28 @@ export interface Entry {
|
|
|
36
40
|
/** Where its workspace is, once made. */
|
|
37
41
|
readonly folder?: string;
|
|
38
42
|
readonly hasStarted: boolean;
|
|
39
|
-
/** The release a message asked for, answered once the ship is released. */
|
|
40
|
-
readonly release?: {
|
|
41
|
-
readonly messageId: string;
|
|
42
|
-
readonly sender: ShipId;
|
|
43
|
-
readonly isForced: boolean;
|
|
44
|
-
};
|
|
45
43
|
/** The inbox count last seen, and whether a wake waits for the session to turn idle. */
|
|
46
44
|
readonly wake: {
|
|
47
45
|
readonly waiting: number;
|
|
48
46
|
readonly isPending: boolean;
|
|
49
47
|
};
|
|
50
48
|
}
|
|
51
|
-
/** A worktree kept because it had changes, until a human
|
|
49
|
+
/** A worktree kept because it had changes, until a human clears it: reported by its ship and repository, never its path. */
|
|
52
50
|
export interface KeptWorktree {
|
|
53
51
|
readonly shipId: ShipId;
|
|
52
|
+
readonly repository: string;
|
|
54
53
|
readonly path: string;
|
|
55
54
|
}
|
|
56
|
-
/** A
|
|
57
|
-
export interface
|
|
58
|
-
readonly
|
|
59
|
-
readonly
|
|
60
|
-
/** The protocol name, such as `wanted`: its content type is `trierarchContentType(name)`. */
|
|
55
|
+
/** A worktree under the root with no assigned request: reported by its repository and name, never its path. */
|
|
56
|
+
export interface Orphan {
|
|
57
|
+
readonly path: string;
|
|
58
|
+
readonly repository: string;
|
|
61
59
|
readonly name: string;
|
|
62
|
-
readonly payload: Readonly<Record<string, unknown>>;
|
|
63
|
-
/** The same each time this message is sent again, so the fleet stores it once. */
|
|
64
|
-
readonly idempotencyKey: string;
|
|
65
60
|
}
|
|
66
61
|
export declare const EMPTY_STATE: TrierarchState;
|
|
67
62
|
/** The entry in a new state since now. */
|
|
68
63
|
export declare function withState(entry: Entry, change: {
|
|
69
|
-
state:
|
|
64
|
+
state: EntryState;
|
|
70
65
|
now: Date;
|
|
71
66
|
}): Entry;
|
|
72
67
|
/** The state with one entry put in, or replaced. */
|
package/dist/core/entry.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** Where an entry stands: the crew statuses the trierarch writes, and releasing while it ends. */
|
|
2
|
+
export const ENTRY_STATES = ['crewing', 'running', 'restarting', 'crashed', 'releasing'];
|
|
3
|
+
export const EMPTY_STATE = { entries: {}, kept: [], orphans: [], refused: {} };
|
|
2
4
|
/** The entry in a new state since now. */
|
|
3
5
|
export function withState(entry, change) {
|
|
4
6
|
return { ...entry, state: change.state, since: change.now.toISOString() };
|
package/dist/core/entry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/core/entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/core/entry.ts"],"names":[],"mappings":"AAiBA,kGAAkG;AAClG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,CAAU,CAAC;AA8ClG,MAAM,CAAC,MAAM,WAAW,GAAmB,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAE/F,0CAA0C;AAC1C,MAAM,UAAU,SAAS,CAAC,KAAY,EAAE,MAAwC;IAC9E,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;AAC5E,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,QAAQ,CAAC,KAAqB,EAAE,KAAY;IAC1D,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;AAC5E,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,KAAqB,EAAE,MAAc;IAC/D,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC;IAClG,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import type { Delivery, FleetPort, Logger
|
|
2
|
-
import type { Clock } from './shared/clock.js';
|
|
1
|
+
import type { Delivery, FleetPort, Logger } from './ports.js';
|
|
3
2
|
/**
|
|
4
|
-
* Use case: a
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* Use case: a message arrives at the trierarch's own ship. Its ship stays a
|
|
4
|
+
* normal ship (docs/trierarch.md): a ping gets pong. A trierarch takes work
|
|
5
|
+
* only through the crew requests assigned to it, never from a message, so
|
|
6
|
+
* any other delivery is acknowledged, logged as not handled, and acted on
|
|
7
|
+
* never: no ship can start or stop a session on the machine by messaging it
|
|
8
|
+
* (#301).
|
|
9
9
|
*/
|
|
10
10
|
export type HandleDelivery = (delivery: Delivery) => Promise<void>;
|
|
11
|
-
export
|
|
11
|
+
export declare function createHandleDelivery(deps: {
|
|
12
12
|
fleet: FleetPort;
|
|
13
|
-
workspace: WorkspacePort;
|
|
14
|
-
state: StatePort;
|
|
15
|
-
setup: TrierarchSetup;
|
|
16
|
-
clock: Clock;
|
|
17
13
|
logger: Logger;
|
|
18
|
-
}
|
|
19
|
-
export declare function createHandleDelivery(deps: HandleDeliveryDeps): HandleDelivery;
|
|
14
|
+
}): HandleDelivery;
|
|
@@ -1,141 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { describedOf, listedOf } from './answers.js';
|
|
4
|
-
import { checkWant, fieldOf, newEntry } from './check-want.js';
|
|
5
|
-
import { putEntry, removeEntry, withState } from './entry.js';
|
|
6
|
-
const CONTENT_TYPE = /^application\/vnd\.aeolus\.trierarch\.(\w+)\+json$/;
|
|
7
|
-
const shipIdOnly = z.looseObject({ shipId: idSchema('ship') });
|
|
1
|
+
import { isPingContentType } from '@aeolus-fleet/common';
|
|
8
2
|
export function createHandleDelivery(deps) {
|
|
9
3
|
return async (delivery) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (before !== undefined) {
|
|
13
|
-
await deps.fleet.ack(delivery.deliveryId);
|
|
14
|
-
await Promise.all(before.map((answer) => deps.fleet.send(answer)));
|
|
4
|
+
if (isPingContentType(delivery.contentType)) {
|
|
5
|
+
await deps.fleet.pong(delivery.deliveryId);
|
|
15
6
|
return;
|
|
16
7
|
}
|
|
17
|
-
const { state: next, answers } = await apply(state, { delivery, deps });
|
|
18
|
-
await deps.state.save({ ...next, applied: { ...next.applied, [delivery.messageId]: answers } });
|
|
19
8
|
await deps.fleet.ack(delivery.deliveryId);
|
|
20
|
-
|
|
21
|
-
await deps.fleet.send(answer);
|
|
22
|
-
}
|
|
9
|
+
deps.logger.warn(`Not handled: ${delivery.contentType} from ${delivery.senderShipId}. A trierarch takes work only through the crew requests assigned to it.`);
|
|
23
10
|
};
|
|
24
11
|
}
|
|
25
|
-
async function apply(state, context) {
|
|
26
|
-
const { delivery, deps } = context;
|
|
27
|
-
const name = CONTENT_TYPE.exec(delivery.contentType)?.[1] ?? '';
|
|
28
|
-
const payload = parsedJson(delivery.payload);
|
|
29
|
-
const answer = answerTo(delivery);
|
|
30
|
-
switch (name) {
|
|
31
|
-
case 'describe':
|
|
32
|
-
return { state, answers: [answer('described', describedOf(deps.setup, state))] };
|
|
33
|
-
case 'list':
|
|
34
|
-
return { state, answers: [answer('listed', listedOf(state))] };
|
|
35
|
-
case 'want':
|
|
36
|
-
return want(state, { payload, context });
|
|
37
|
-
case 'release':
|
|
38
|
-
return release(state, { payload, context });
|
|
39
|
-
default:
|
|
40
|
-
deps.logger.warn(`A message the trierarch does not read: ${delivery.contentType}, from ${delivery.senderShipId}`);
|
|
41
|
-
return { state, answers: [] };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/** The payload as JSON, or undefined when it is none: the schemas then refuse it. */
|
|
45
|
-
function parsedJson(payload) {
|
|
46
|
-
try {
|
|
47
|
-
const parsed = JSON.parse(payload);
|
|
48
|
-
return parsed;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function answerTo(delivery) {
|
|
55
|
-
return (name, payload) => ({
|
|
56
|
-
to: delivery.senderShipId,
|
|
57
|
-
inReplyTo: delivery.messageId,
|
|
58
|
-
name,
|
|
59
|
-
payload,
|
|
60
|
-
idempotencyKey: `trierarch:${delivery.messageId}:${name}`,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
function refused(context, refusal) {
|
|
64
|
-
return answerTo(context.delivery)('refused', {
|
|
65
|
-
shipId: refusal.shipId,
|
|
66
|
-
...(refusal.field !== undefined && { field: refusal.field }),
|
|
67
|
-
reason: refusal.reason,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
/** A command whose ship id does not parse cannot be answered: refused names a ship. */
|
|
71
|
-
function unanswerable(state, context) {
|
|
72
|
-
context.deps.logger.warn(`A command with no ship id the trierarch can answer, from ${context.delivery.senderShipId}`);
|
|
73
|
-
return { state, answers: [] };
|
|
74
|
-
}
|
|
75
|
-
async function want(state, at) {
|
|
76
|
-
const { payload, context } = at;
|
|
77
|
-
const { deps, delivery } = context;
|
|
78
|
-
const target = shipIdOnly.safeParse(payload);
|
|
79
|
-
if (!target.success) {
|
|
80
|
-
return unanswerable(state, context);
|
|
81
|
-
}
|
|
82
|
-
const { shipId } = target.data;
|
|
83
|
-
const existing = state.entries[shipId];
|
|
84
|
-
if (existing !== undefined && existing.state !== 'crashed') {
|
|
85
|
-
return { state, answers: [refused(context, { shipId, field: 'shipId', reason: `${shipId} is on the wanted list already` })] };
|
|
86
|
-
}
|
|
87
|
-
const checked = checkWant(payload, { configuration: deps.setup.configuration, state: removeEntry(state, shipId) });
|
|
88
|
-
if (!checked.isOk) {
|
|
89
|
-
return { state, answers: [refused(context, { shipId, ...checked.error })] };
|
|
90
|
-
}
|
|
91
|
-
const now = deps.clock.now();
|
|
92
|
-
const entry = newEntry(checked.value, { requester: delivery.senderShipId, now });
|
|
93
|
-
const wanted = answerTo(delivery)('wanted', { shipId });
|
|
94
|
-
if (existing !== undefined) {
|
|
95
|
-
// Wanted again after it crashed: its session starts again in its folder, with its lease still held.
|
|
96
|
-
const again = { ...entry, state: 'running', hasStarted: existing.hasStarted, ...(existing.folder !== undefined && { folder: existing.folder }) };
|
|
97
|
-
return { state: putEntry(state, again), answers: [wanted] };
|
|
98
|
-
}
|
|
99
|
-
const ship = await deps.fleet.ship(shipId);
|
|
100
|
-
switch (ship.kind) {
|
|
101
|
-
case 'notFound':
|
|
102
|
-
return { state, answers: [refused(context, { shipId, field: 'shipId', reason: `The fleet has no ship ${shipId}` })] };
|
|
103
|
-
case 'retired':
|
|
104
|
-
return { state, answers: [refused(context, { shipId, field: 'shipId', reason: `${shipId} is retired` })] };
|
|
105
|
-
case 'crewed':
|
|
106
|
-
return { state, answers: [refused(context, { shipId, field: 'shipId', reason: `${ship.name} is crewed by another session` })] };
|
|
107
|
-
case 'awaitingCrew':
|
|
108
|
-
return { state: putEntry(state, entry), answers: [wanted] };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async function release(state, at) {
|
|
112
|
-
const { payload, context } = at;
|
|
113
|
-
const { deps, delivery } = context;
|
|
114
|
-
const parsed = releaseCommandSchema.safeParse(payload);
|
|
115
|
-
if (!parsed.success) {
|
|
116
|
-
const target = shipIdOnly.safeParse(payload);
|
|
117
|
-
if (!target.success) {
|
|
118
|
-
return unanswerable(state, context);
|
|
119
|
-
}
|
|
120
|
-
const [issue] = parsed.error.issues;
|
|
121
|
-
return { state, answers: [refused(context, { shipId: target.data.shipId, ...fieldOf(issue?.path), reason: issue?.message ?? 'The release does not parse' })] };
|
|
122
|
-
}
|
|
123
|
-
const { shipId, force = false } = parsed.data;
|
|
124
|
-
const entry = state.entries[shipId];
|
|
125
|
-
if (entry !== undefined) {
|
|
126
|
-
const releasing = { ...withState(entry, { state: 'releasing', now: deps.clock.now() }), release: { messageId: delivery.messageId, sender: delivery.senderShipId, isForced: force } };
|
|
127
|
-
return { state: putEntry(state, releasing), answers: [] };
|
|
128
|
-
}
|
|
129
|
-
const kept = state.kept.find((each) => each.shipId === shipId);
|
|
130
|
-
if (kept === undefined) {
|
|
131
|
-
return { state, answers: [refused(context, { shipId, field: 'shipId', reason: `${shipId} is not on the wanted list` })] };
|
|
132
|
-
}
|
|
133
|
-
const answer = answerTo(delivery);
|
|
134
|
-
if (!force) {
|
|
135
|
-
return { state, answers: [answer('released', { shipId, workspace: 'kept', path: kept.path })] };
|
|
136
|
-
}
|
|
137
|
-
// A release with force clears a kept worktree.
|
|
138
|
-
await deps.workspace.remove(kept.path);
|
|
139
|
-
return { state: { ...state, kept: state.kept.filter((each) => each !== kept) }, answers: [answer('released', { shipId, workspace: 'removed' })] };
|
|
140
|
-
}
|
|
141
12
|
//# sourceMappingURL=handle-delivery.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-delivery.js","sourceRoot":"","sources":["../../src/core/handle-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"handle-delivery.js","sourceRoot":"","sources":["../../src/core/handle-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAczD,MAAM,UAAU,oBAAoB,CAAC,IAA0C;IAC7E,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE;QACxB,IAAI,iBAAiB,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,QAAQ,CAAC,WAAW,SAAS,QAAQ,CAAC,YAAY,yEAAyE,CAAC,CAAC;IAChK,CAAC,CAAC;AACJ,CAAC"}
|