@aeolus-fleet/trierarch 0.17.3 → 0.19.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 +18 -16
- package/dist/adapters/claude-code.d.ts +11 -1
- package/dist/adapters/claude-code.js +32 -10
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts +10 -1
- package/dist/adapters/codex.js +15 -4
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/command-line.d.ts +15 -0
- package/dist/adapters/command-line.js +17 -0
- package/dist/adapters/command-line.js.map +1 -0
- package/dist/adapters/files.d.ts +9 -0
- package/dist/adapters/files.js +16 -0
- package/dist/adapters/files.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 +1 -1
- package/dist/adapters/harnesses.d.ts +10 -2
- package/dist/adapters/harnesses.js +26 -3
- package/dist/adapters/harnesses.js.map +1 -1
- package/dist/adapters/json-state.d.ts +6 -4
- package/dist/adapters/json-state.js +21 -21
- package/dist/adapters/json-state.js.map +1 -1
- package/dist/adapters/log.d.ts +39 -0
- package/dist/adapters/log.js +65 -0
- package/dist/adapters/log.js.map +1 -0
- package/dist/adapters/paths.d.ts +1 -0
- package/dist/adapters/paths.js +1 -0
- package/dist/adapters/paths.js.map +1 -1
- package/dist/adapters/rest-fleet.d.ts +4 -3
- package/dist/adapters/rest-fleet.js +38 -18
- package/dist/adapters/rest-fleet.js.map +1 -1
- package/dist/adapters/service.js +18 -15
- package/dist/adapters/service.js.map +1 -1
- package/dist/adapters/style.d.ts +18 -0
- package/dist/adapters/style.js +38 -0
- package/dist/adapters/style.js.map +1 -0
- package/dist/cli/config-check.d.ts +11 -2
- package/dist/cli/config-check.js +26 -5
- package/dist/cli/config-check.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.js +41 -15
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/list.d.ts +7 -4
- package/dist/cli/list.js +18 -8
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/main.js +33 -16
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/prompter.d.ts +4 -0
- package/dist/cli/prompter.js +18 -3
- package/dist/cli/prompter.js.map +1 -1
- package/dist/cli/run.d.ts +13 -8
- package/dist/cli/run.js +48 -27
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +13 -4
- package/dist/cli/status.js +43 -17
- 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 +18 -30
- 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/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 +60 -6
- package/dist/core/reconciler.d.ts +32 -12
- package/dist/core/reconciler.js +107 -23
- package/dist/core/reconciler.js.map +1 -1
- package/dist/core/report-self.d.ts +16 -0
- package/dist/core/report-self.js +41 -0
- package/dist/core/report-self.js.map +1 -0
- package/dist/core/run-pass.d.ts +8 -4
- package/dist/core/run-pass.js +135 -56
- 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 -27
- 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/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
12
|
readonly orphans: readonly string[];
|
|
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,21 @@ 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. */
|
|
52
50
|
export interface KeptWorktree {
|
|
53
51
|
readonly shipId: ShipId;
|
|
54
52
|
readonly path: string;
|
|
55
53
|
}
|
|
56
|
-
/** A message the trierarch sends: an answer in reply to a command, or a notice. */
|
|
57
|
-
export interface Outgoing {
|
|
58
|
-
readonly to: ShipId;
|
|
59
|
-
readonly inReplyTo?: string;
|
|
60
|
-
/** The protocol name, such as `wanted`: its content type is `trierarchContentType(name)`. */
|
|
61
|
-
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
|
-
}
|
|
66
54
|
export declare const EMPTY_STATE: TrierarchState;
|
|
67
55
|
/** The entry in a new state since now. */
|
|
68
56
|
export declare function withState(entry: Entry, change: {
|
|
69
|
-
state:
|
|
57
|
+
state: EntryState;
|
|
70
58
|
now: Date;
|
|
71
59
|
}): Entry;
|
|
72
60
|
/** 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;AAsClG,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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TrierarchConfiguration, TrierarchReportDetails } from '@aeolus-fleet/common';
|
|
2
|
+
/**
|
|
3
|
+
* A harness's options as a JSON Schema, as the trierarch's report details
|
|
4
|
+
* give it (docs/trierarch.md, "What a trierarch reports"): each option one of
|
|
5
|
+
* its value names, with its default when it has one.
|
|
6
|
+
*/
|
|
7
|
+
export declare function optionsSchemaOf(harness: TrierarchConfiguration['harnesses'][string]): TrierarchReportDetails['harnesses'][number]['options'];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A harness's options as a JSON Schema, as the trierarch's report details
|
|
3
|
+
* give it (docs/trierarch.md, "What a trierarch reports"): each option one of
|
|
4
|
+
* its value names, with its default when it has one.
|
|
5
|
+
*/
|
|
6
|
+
export function optionsSchemaOf(harness) {
|
|
7
|
+
const properties = Object.fromEntries(Object.entries(harness.options).map(([name, option]) => [name, { enum: Object.keys(option.values), ...(option.default !== undefined && { default: option.default }) }]));
|
|
8
|
+
return { type: 'object', properties, additionalProperties: false };
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=options-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options-schema.js","sourceRoot":"","sources":["../../src/core/options-schema.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAoD;IAClF,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CACnC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CACxK,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC;AACrE,CAAC"}
|
package/dist/core/ports.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { ShipId, TrierarchConfiguration, TrierarchWorkspace } from '@aeolus-fleet/common';
|
|
2
|
-
import type {
|
|
1
|
+
import type { CrewStatus, ShipId, TrierarchConfiguration, TrierarchReportDetails, TrierarchWorkspace } from '@aeolus-fleet/common';
|
|
2
|
+
import type { TrierarchState } from './entry.js';
|
|
3
|
+
import type { Action } from './reconciler.js';
|
|
3
4
|
/**
|
|
4
5
|
* The trierarch's ports (docs/architecture.md, "The trierarch"). Adapters
|
|
5
6
|
* implement them in D2b: REST for the fleet, tmux for processes, git worktree
|
|
6
7
|
* and folders for workspaces, Claude Code for the harness, a JSON file for
|
|
7
8
|
* state. Each throws only for a system failure.
|
|
8
9
|
*/
|
|
9
|
-
/** Where a ship stands in the fleet, as `fleet.ship` answers the trierarch (
|
|
10
|
+
/** Where a ship stands in the fleet, as `fleet.ship` answers the trierarch (crew:run, for a ship assigned to it). */
|
|
10
11
|
export type FleetShipStatus = {
|
|
11
12
|
readonly kind: 'awaitingCrew';
|
|
12
13
|
readonly name: string;
|
|
@@ -33,9 +34,32 @@ export type InboxAnswer = {
|
|
|
33
34
|
} | {
|
|
34
35
|
readonly kind: 'leaseEnded';
|
|
35
36
|
};
|
|
37
|
+
/** A crew request assigned to the trierarch's ship, as `fleet.assignedCrewRequests` answers: its settings unread. */
|
|
38
|
+
export interface AssignedRequest {
|
|
39
|
+
readonly shipId: ShipId;
|
|
40
|
+
readonly settings: unknown;
|
|
41
|
+
readonly settingsVersion: number;
|
|
42
|
+
/** How the trierarch last said the crew stands; releasing once the requester removed it; null until any. */
|
|
43
|
+
readonly status: CrewStatus | null;
|
|
44
|
+
}
|
|
45
|
+
/** A plain-text report to argo, the operator, stored once under its idempotency key. */
|
|
46
|
+
export interface ArgoReport {
|
|
47
|
+
readonly text: string;
|
|
48
|
+
readonly idempotencyKey: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The fleet as the trierarch's own ship calls it, with `crew:run`: it reaches
|
|
52
|
+
* only the ships whose crew requests are assigned to it (decision 0029).
|
|
53
|
+
*/
|
|
36
54
|
export interface FleetPort {
|
|
37
55
|
/** The fleet's URL, written into each session's identity. */
|
|
38
56
|
readonly url: string;
|
|
57
|
+
/** The crew requests assigned to the trierarch's ship, oldest ship first. */
|
|
58
|
+
assignedRequests(): Promise<readonly AssignedRequest[]>;
|
|
59
|
+
/** Says how its crew of an assigned ship stands. */
|
|
60
|
+
writeStatus(shipId: ShipId, status: CrewStatus): Promise<void>;
|
|
61
|
+
/** Confirms it released the ship of a releasing request, which then goes. */
|
|
62
|
+
confirmRelease(shipId: ShipId): Promise<void>;
|
|
39
63
|
ship(shipId: ShipId): Promise<FleetShipStatus>;
|
|
40
64
|
/** A new starting prompt's secret; the trierarch keeps it only until it registers. */
|
|
41
65
|
getStartingPrompt(shipId: ShipId): Promise<{
|
|
@@ -51,7 +75,10 @@ export interface FleetPort {
|
|
|
51
75
|
release(shipId: ShipId): Promise<void>;
|
|
52
76
|
/** Acknowledges a delivery to the trierarch's own ship. */
|
|
53
77
|
ack(deliveryId: string): Promise<void>;
|
|
54
|
-
|
|
78
|
+
/** Answers a ping to the trierarch's own ship. */
|
|
79
|
+
pong(deliveryId: string): Promise<void>;
|
|
80
|
+
/** Sends argo a report, as the trierarch's own ship. */
|
|
81
|
+
reportToArgo(report: ArgoReport): Promise<void>;
|
|
55
82
|
/** A session's inbox, asked with its crew token. */
|
|
56
83
|
inbox(crewToken: string): Promise<InboxAnswer>;
|
|
57
84
|
/** Reports on a ship's behalf, with its session's crew token. */
|
|
@@ -60,6 +87,14 @@ export interface FleetPort {
|
|
|
60
87
|
state: 'blocked' | 'working';
|
|
61
88
|
note: string;
|
|
62
89
|
}): Promise<void>;
|
|
90
|
+
/** Reports as the trierarch's own ship: what it does, and its details. */
|
|
91
|
+
reportSelf(report: SelfReport): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
/** The trierarch's own report: idle or working, how many sessions run, and its details (docs/trierarch.md). */
|
|
94
|
+
export interface SelfReport {
|
|
95
|
+
readonly state: 'idle' | 'working';
|
|
96
|
+
readonly note: string;
|
|
97
|
+
readonly details: TrierarchReportDetails;
|
|
63
98
|
}
|
|
64
99
|
/** What a session's folder identity holds for the trierarch: written through the aeolus plugin, never a copy of it. */
|
|
65
100
|
export interface Identity {
|
|
@@ -80,7 +115,7 @@ export interface HarnessPort {
|
|
|
80
115
|
/** The crew token of the folder's identity, when there is one. */
|
|
81
116
|
crewTokenOf(folder: string): Promise<string | undefined>;
|
|
82
117
|
removeIdentity(folder: string): Promise<void>;
|
|
83
|
-
/** Starts the harness in the folder: the first start with the
|
|
118
|
+
/** Starts the harness in the folder: the first start with the settings' first prompt, later ones continuing. */
|
|
84
119
|
launch(session: {
|
|
85
120
|
shipId: ShipId;
|
|
86
121
|
/** With the workspace, what the session is named after where the harness names one. */
|
|
@@ -130,11 +165,30 @@ export interface StatePort {
|
|
|
130
165
|
load(): Promise<TrierarchState>;
|
|
131
166
|
save(state: TrierarchState): Promise<void>;
|
|
132
167
|
}
|
|
168
|
+
/** What the loop did for a ship, and how it came out: one line of the trierarch's log (aeolus-trierarch logs). */
|
|
169
|
+
export interface LoggedAction {
|
|
170
|
+
readonly time: Date;
|
|
171
|
+
readonly shipId: ShipId;
|
|
172
|
+
/** Known once the ship was first crewed. */
|
|
173
|
+
readonly shipName?: string;
|
|
174
|
+
/** What the loop did for the ship: an action it carried out, or leave when another session crews the ship. */
|
|
175
|
+
readonly action: Exclude<Action['kind'], 'argo' | 'status' | 'refuse'> | 'leave';
|
|
176
|
+
readonly outcome: string;
|
|
177
|
+
/** For a failure: what happens next, and where to look. */
|
|
178
|
+
readonly next?: string;
|
|
179
|
+
}
|
|
133
180
|
export interface Logger {
|
|
134
181
|
warn(message: string): void;
|
|
182
|
+
action(logged: LoggedAction): void;
|
|
183
|
+
}
|
|
184
|
+
/** A flag a harness adapter adds itself, as mechanism, never the operator's: on every launch or on a restart only. */
|
|
185
|
+
export interface AdapterFlag {
|
|
186
|
+
readonly flag: string;
|
|
187
|
+
readonly when: 'always' | 'restart';
|
|
135
188
|
}
|
|
136
|
-
/**
|
|
189
|
+
/** The trierarch's configuration, its version, and the flags each harness's adapter adds itself. */
|
|
137
190
|
export interface TrierarchSetup {
|
|
138
191
|
readonly configuration: TrierarchConfiguration;
|
|
139
192
|
readonly version: string;
|
|
193
|
+
readonly adapterFlags: Readonly<Record<string, readonly AdapterFlag[]>>;
|
|
140
194
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { ShipId, TrierarchConfiguration } from '@aeolus-fleet/common';
|
|
2
|
-
import { type
|
|
3
|
-
import
|
|
1
|
+
import type { CrewStatus, ShipId, TrierarchConfiguration } from '@aeolus-fleet/common';
|
|
2
|
+
import { type Refusal } from './check-settings.js';
|
|
3
|
+
import { type Entry, type TrierarchState } from './entry.js';
|
|
4
|
+
import type { ArgoReport, AssignedRequest, InboxAnswer, ObservedSession, ObservedWorktree, Turn } from './ports.js';
|
|
4
5
|
/**
|
|
5
6
|
* The Reconciler (docs/architecture.md, "The trierarch"): a pure function
|
|
6
|
-
* from the
|
|
7
|
-
*
|
|
8
|
-
* decides; the run pass only
|
|
7
|
+
* from the crew requests assigned to the trierarch, its saved state, what
|
|
8
|
+
* runs and the time to the actions that make them match, and the state those
|
|
9
|
+
* actions start from. It is the only place that decides; the run pass only
|
|
10
|
+
* carries the actions out.
|
|
9
11
|
*/
|
|
10
12
|
/** What the trierarch saw this pass. */
|
|
11
13
|
export interface Observed {
|
|
@@ -18,7 +20,7 @@ export interface Observed {
|
|
|
18
20
|
}>>;
|
|
19
21
|
}
|
|
20
22
|
export type Action =
|
|
21
|
-
/** Crew the ship: starting prompt, register, workspace, identity, launch. Resumed
|
|
23
|
+
/** Crew the ship: starting prompt, register, workspace, identity, launch. Resumed while its own lease may still hold the ship. */
|
|
22
24
|
{
|
|
23
25
|
readonly kind: 'crew';
|
|
24
26
|
readonly shipId: ShipId;
|
|
@@ -35,24 +37,41 @@ export type Action =
|
|
|
35
37
|
readonly kind: 'wake';
|
|
36
38
|
readonly shipId: ShipId;
|
|
37
39
|
}
|
|
38
|
-
/**
|
|
40
|
+
/** Its request was removed: stop, end the lease, the workspace, then confirm. */
|
|
39
41
|
| {
|
|
40
42
|
readonly kind: 'release';
|
|
41
43
|
readonly shipId: ShipId;
|
|
42
44
|
}
|
|
43
|
-
/**
|
|
45
|
+
/** A removed request it does not crew: confirm alone. */
|
|
44
46
|
| {
|
|
45
|
-
readonly kind: '
|
|
47
|
+
readonly kind: 'confirm';
|
|
46
48
|
readonly shipId: ShipId;
|
|
49
|
+
}
|
|
50
|
+
/** Its request is no longer assigned here: stop, remove the identity of the entry it was; the worktree stays as an orphan. */
|
|
51
|
+
| {
|
|
52
|
+
readonly kind: 'forget';
|
|
53
|
+
readonly shipId: ShipId;
|
|
54
|
+
readonly entry: Entry;
|
|
47
55
|
} | {
|
|
48
|
-
readonly kind: '
|
|
49
|
-
readonly
|
|
56
|
+
readonly kind: 'status';
|
|
57
|
+
readonly shipId: ShipId;
|
|
58
|
+
readonly status: CrewStatus;
|
|
50
59
|
}
|
|
51
60
|
/** Report on the ship's behalf, with its session's crew token. */
|
|
52
61
|
| {
|
|
53
62
|
readonly kind: 'report';
|
|
54
63
|
readonly shipId: ShipId;
|
|
55
64
|
readonly note: string;
|
|
65
|
+
}
|
|
66
|
+
/** Tell argo it cannot crew this settings version. */
|
|
67
|
+
| {
|
|
68
|
+
readonly kind: 'refuse';
|
|
69
|
+
readonly shipId: ShipId;
|
|
70
|
+
readonly settingsVersion: number;
|
|
71
|
+
readonly refusal: Refusal;
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: 'argo';
|
|
74
|
+
readonly report: ArgoReport;
|
|
56
75
|
};
|
|
57
76
|
export interface Reconciled {
|
|
58
77
|
readonly state: TrierarchState;
|
|
@@ -60,6 +79,7 @@ export interface Reconciled {
|
|
|
60
79
|
}
|
|
61
80
|
/** What reconciling needs beside the state and what was seen. */
|
|
62
81
|
export interface ReconcileContext {
|
|
82
|
+
readonly requests: readonly AssignedRequest[];
|
|
63
83
|
readonly observed: Observed;
|
|
64
84
|
readonly now: Date;
|
|
65
85
|
readonly configuration: TrierarchConfiguration;
|