@ours.network/fleet 0.19.0-nightly.1 → 0.19.0-nightly.3
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/dist/build-info.json +4 -4
- package/dist/cli.js +5 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +44 -3
- package/dist/owner-channel/commands.js +89 -0
- package/dist/rooms-tasks/cli.d.ts +4 -0
- package/dist/rooms-tasks/cli.js +690 -0
- package/dist/rooms-tasks/config.d.ts +15 -0
- package/dist/rooms-tasks/config.js +171 -0
- package/dist/rooms-tasks/cowork-adapter.d.ts +77 -0
- package/dist/rooms-tasks/cowork-adapter.js +272 -0
- package/dist/rooms-tasks/index.d.ts +7 -0
- package/dist/rooms-tasks/index.js +7 -0
- package/dist/rooms-tasks/room-state.d.ts +23 -0
- package/dist/rooms-tasks/room-state.js +118 -0
- package/dist/rooms-tasks/task-state.d.ts +31 -0
- package/dist/rooms-tasks/task-state.js +173 -0
- package/dist/rooms-tasks/templates.d.ts +6 -0
- package/dist/rooms-tasks/templates.js +80 -0
- package/dist/rooms-tasks/types.d.ts +153 -0
- package/dist/rooms-tasks/types.js +20 -0
- package/package.json +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
4
|
+
import { stateRoot } from '../paths.js';
|
|
5
|
+
export const roomsDir = () => join(stateRoot(), 'rooms');
|
|
6
|
+
function roomPath(id) { return join(roomsDir(), `${id}.json`); }
|
|
7
|
+
export class RoomStateError extends Error {
|
|
8
|
+
}
|
|
9
|
+
function readRoom(id) {
|
|
10
|
+
const p = roomPath(id);
|
|
11
|
+
if (!existsSync(p))
|
|
12
|
+
throw new RoomStateError(`room not found: ${id}`);
|
|
13
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
14
|
+
}
|
|
15
|
+
function writeRoom(record) {
|
|
16
|
+
mkdirSync(roomsDir(), { recursive: true });
|
|
17
|
+
replaceFileAtomically(roomPath(record.room_id), JSON.stringify(record, null, 2) + '\n');
|
|
18
|
+
}
|
|
19
|
+
export function createRoomRecord(input) {
|
|
20
|
+
const existing = getRoomRecord(input.room_id);
|
|
21
|
+
if (existing)
|
|
22
|
+
return existing;
|
|
23
|
+
const record = {
|
|
24
|
+
room_id: input.room_id,
|
|
25
|
+
room_name: input.room_name,
|
|
26
|
+
room_identity_cid: input.room_identity_cid,
|
|
27
|
+
task_id: input.task_id,
|
|
28
|
+
template_snapshot: input.template_snapshot,
|
|
29
|
+
saga: { phase: 'persist_intent', step_index: 0 },
|
|
30
|
+
member_seats: [],
|
|
31
|
+
state: 'provisioning',
|
|
32
|
+
created_at: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
writeRoom(record);
|
|
35
|
+
return record;
|
|
36
|
+
}
|
|
37
|
+
export function getRoomRecord(id) {
|
|
38
|
+
try {
|
|
39
|
+
return readRoom(id);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function listRoomRecords(filter) {
|
|
46
|
+
const dir = roomsDir();
|
|
47
|
+
if (!existsSync(dir))
|
|
48
|
+
return [];
|
|
49
|
+
const states = filter?.state
|
|
50
|
+
? (Array.isArray(filter.state) ? filter.state : [filter.state])
|
|
51
|
+
: undefined;
|
|
52
|
+
const rooms = [];
|
|
53
|
+
for (const f of readdirSync(dir).filter(f => f.endsWith('.json'))) {
|
|
54
|
+
try {
|
|
55
|
+
const r = JSON.parse(readFileSync(join(dir, f), 'utf8'));
|
|
56
|
+
if (!states || states.includes(r.state))
|
|
57
|
+
rooms.push(r);
|
|
58
|
+
}
|
|
59
|
+
catch { /* skip corrupt */ }
|
|
60
|
+
}
|
|
61
|
+
return rooms.sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
62
|
+
}
|
|
63
|
+
export function advanceSaga(id, phase, stepIndex, detail) {
|
|
64
|
+
const r = readRoom(id);
|
|
65
|
+
r.saga = { phase, step_index: stepIndex ?? 0 };
|
|
66
|
+
if (detail)
|
|
67
|
+
r.provisioning_detail = detail;
|
|
68
|
+
else
|
|
69
|
+
delete r.provisioning_detail;
|
|
70
|
+
writeRoom(r);
|
|
71
|
+
return r;
|
|
72
|
+
}
|
|
73
|
+
export function setSagaError(id, error, hint, detail) {
|
|
74
|
+
const r = readRoom(id);
|
|
75
|
+
r.saga.error = error;
|
|
76
|
+
r.saga.recovery_hint = hint;
|
|
77
|
+
if (detail)
|
|
78
|
+
r.provisioning_detail = detail;
|
|
79
|
+
writeRoom(r);
|
|
80
|
+
return r;
|
|
81
|
+
}
|
|
82
|
+
export function setRoomIdentity(id, roomIdentityCid) {
|
|
83
|
+
const r = readRoom(id);
|
|
84
|
+
r.room_identity_cid = roomIdentityCid;
|
|
85
|
+
writeRoom(r);
|
|
86
|
+
return r;
|
|
87
|
+
}
|
|
88
|
+
export function setOwnerSeat(id, ownerSeatCid, inviteFingerprint) {
|
|
89
|
+
const r = readRoom(id);
|
|
90
|
+
r.owner_seat_cid = ownerSeatCid;
|
|
91
|
+
r.owner_invite_fingerprint = inviteFingerprint;
|
|
92
|
+
writeRoom(r);
|
|
93
|
+
return r;
|
|
94
|
+
}
|
|
95
|
+
export function updateMemberSeats(id, seats) {
|
|
96
|
+
const r = readRoom(id);
|
|
97
|
+
r.member_seats = seats;
|
|
98
|
+
writeRoom(r);
|
|
99
|
+
return r;
|
|
100
|
+
}
|
|
101
|
+
export function activateRoom(id) {
|
|
102
|
+
const r = readRoom(id);
|
|
103
|
+
r.state = 'active';
|
|
104
|
+
r.saga = { phase: 'completed', step_index: 0 };
|
|
105
|
+
delete r.provisioning_detail;
|
|
106
|
+
r.activated_at = new Date().toISOString();
|
|
107
|
+
writeRoom(r);
|
|
108
|
+
return r;
|
|
109
|
+
}
|
|
110
|
+
export function closeRoom(id) {
|
|
111
|
+
const r = readRoom(id);
|
|
112
|
+
if (r.state === 'closed')
|
|
113
|
+
return r;
|
|
114
|
+
r.state = 'closed';
|
|
115
|
+
r.closed_at = new Date().toISOString();
|
|
116
|
+
writeRoom(r);
|
|
117
|
+
return r;
|
|
118
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TaskRecord, TaskState, TaskOrigin, TaskTemplateRef, TaskOutcome, TaskMemberRole } from './types.js';
|
|
2
|
+
export declare const tasksDir: () => string;
|
|
3
|
+
export declare class TaskStateError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export interface CreateTaskInput {
|
|
6
|
+
title: string;
|
|
7
|
+
brief?: string;
|
|
8
|
+
brief_file?: string;
|
|
9
|
+
template?: TaskTemplateRef;
|
|
10
|
+
origin: TaskOrigin;
|
|
11
|
+
idempotency_key?: string;
|
|
12
|
+
start?: boolean;
|
|
13
|
+
room_id?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function createTask(input: CreateTaskInput): TaskRecord;
|
|
16
|
+
export declare function getTask(id: string): TaskRecord;
|
|
17
|
+
export declare function listTasks(filter?: {
|
|
18
|
+
state?: TaskState | TaskState[];
|
|
19
|
+
}): TaskRecord[];
|
|
20
|
+
export declare function findByIdempotencyKey(key: string): TaskRecord | undefined;
|
|
21
|
+
export declare function startTask(id: string): TaskRecord;
|
|
22
|
+
export declare function activateTask(id: string): TaskRecord;
|
|
23
|
+
export declare function blockTask(id: string, reason: string): TaskRecord;
|
|
24
|
+
export declare function unblockTask(id: string): TaskRecord;
|
|
25
|
+
export declare function reviewTask(id: string): TaskRecord;
|
|
26
|
+
export declare function completeTask(id: string, outcome?: TaskOutcome): TaskRecord;
|
|
27
|
+
export declare function cancelTask(id: string): TaskRecord;
|
|
28
|
+
export declare function failTask(id: string, error: string): TaskRecord;
|
|
29
|
+
export declare function updateTaskRoom(id: string, roomId: string, roomIdentityCid: string): TaskRecord;
|
|
30
|
+
export declare function updateTaskMembers(id: string, members: TaskMemberRole[]): TaskRecord;
|
|
31
|
+
export declare function deleteTask(id: string): void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
5
|
+
import { stateRoot } from '../paths.js';
|
|
6
|
+
import { TASK_TERMINAL_STATES, TASK_CANCELLABLE_STATES } from './types.js';
|
|
7
|
+
export const tasksDir = () => join(stateRoot(), 'tasks');
|
|
8
|
+
function taskPath(id) { return join(tasksDir(), `${id}.json`); }
|
|
9
|
+
function generateTaskId() {
|
|
10
|
+
const ts = Date.now().toString(36).padStart(9, '0');
|
|
11
|
+
const rand = randomUUID().replace(/-/g, '').slice(0, 8);
|
|
12
|
+
return `${ts}${rand}`;
|
|
13
|
+
}
|
|
14
|
+
export class TaskStateError extends Error {
|
|
15
|
+
}
|
|
16
|
+
function readTask(id) {
|
|
17
|
+
const p = taskPath(id);
|
|
18
|
+
if (!existsSync(p))
|
|
19
|
+
throw new TaskStateError(`task not found: ${id}`);
|
|
20
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
function writeTask(record) {
|
|
23
|
+
mkdirSync(tasksDir(), { recursive: true });
|
|
24
|
+
replaceFileAtomically(taskPath(record.task_id), JSON.stringify(record, null, 2) + '\n');
|
|
25
|
+
}
|
|
26
|
+
// ── Lifecycle transitions ───────────────────────────────────────────────
|
|
27
|
+
const VALID_TRANSITIONS = {
|
|
28
|
+
backlog: ['provisioning', 'cancelled'],
|
|
29
|
+
provisioning: ['active', 'failed', 'cancelled'],
|
|
30
|
+
active: ['review', 'failed', 'cancelled'],
|
|
31
|
+
review: ['done', 'failed', 'cancelled'],
|
|
32
|
+
done: [],
|
|
33
|
+
cancelled: [],
|
|
34
|
+
failed: [],
|
|
35
|
+
};
|
|
36
|
+
function assertTransition(from, to) {
|
|
37
|
+
if (!VALID_TRANSITIONS[from].includes(to))
|
|
38
|
+
throw new TaskStateError(`cannot transition from '${from}' to '${to}'`);
|
|
39
|
+
}
|
|
40
|
+
export function createTask(input) {
|
|
41
|
+
const key = input.idempotency_key ?? randomUUID();
|
|
42
|
+
const existing = findByIdempotencyKey(key);
|
|
43
|
+
if (existing)
|
|
44
|
+
return existing;
|
|
45
|
+
const state = input.start === false ? 'backlog' : 'provisioning';
|
|
46
|
+
const record = {
|
|
47
|
+
task_id: generateTaskId(),
|
|
48
|
+
title: input.title,
|
|
49
|
+
brief: input.brief,
|
|
50
|
+
brief_file: input.brief_file,
|
|
51
|
+
state,
|
|
52
|
+
template: input.template,
|
|
53
|
+
room_id: input.room_id,
|
|
54
|
+
member_roles: [],
|
|
55
|
+
origin: input.origin,
|
|
56
|
+
idempotency_key: key,
|
|
57
|
+
created_at: new Date().toISOString(),
|
|
58
|
+
started_at: state === 'provisioning' ? new Date().toISOString() : undefined,
|
|
59
|
+
};
|
|
60
|
+
writeTask(record);
|
|
61
|
+
return record;
|
|
62
|
+
}
|
|
63
|
+
export function getTask(id) { return readTask(id); }
|
|
64
|
+
export function listTasks(filter) {
|
|
65
|
+
const dir = tasksDir();
|
|
66
|
+
if (!existsSync(dir))
|
|
67
|
+
return [];
|
|
68
|
+
const states = filter?.state
|
|
69
|
+
? (Array.isArray(filter.state) ? filter.state : [filter.state])
|
|
70
|
+
: undefined;
|
|
71
|
+
const tasks = [];
|
|
72
|
+
for (const f of readdirSync(dir).filter(f => f.endsWith('.json'))) {
|
|
73
|
+
try {
|
|
74
|
+
const t = JSON.parse(readFileSync(join(dir, f), 'utf8'));
|
|
75
|
+
if (!states || states.includes(t.state))
|
|
76
|
+
tasks.push(t);
|
|
77
|
+
}
|
|
78
|
+
catch { /* corrupt file, skip */ }
|
|
79
|
+
}
|
|
80
|
+
return tasks.sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
81
|
+
}
|
|
82
|
+
export function findByIdempotencyKey(key) {
|
|
83
|
+
return listTasks().find(t => t.idempotency_key === key);
|
|
84
|
+
}
|
|
85
|
+
export function startTask(id) {
|
|
86
|
+
const t = readTask(id);
|
|
87
|
+
assertTransition(t.state, 'provisioning');
|
|
88
|
+
t.state = 'provisioning';
|
|
89
|
+
t.started_at = new Date().toISOString();
|
|
90
|
+
writeTask(t);
|
|
91
|
+
return t;
|
|
92
|
+
}
|
|
93
|
+
export function activateTask(id) {
|
|
94
|
+
const t = readTask(id);
|
|
95
|
+
assertTransition(t.state, 'active');
|
|
96
|
+
t.state = 'active';
|
|
97
|
+
delete t.blocked;
|
|
98
|
+
writeTask(t);
|
|
99
|
+
return t;
|
|
100
|
+
}
|
|
101
|
+
export function blockTask(id, reason) {
|
|
102
|
+
const t = readTask(id);
|
|
103
|
+
if (TASK_TERMINAL_STATES.includes(t.state))
|
|
104
|
+
throw new TaskStateError(`cannot block a '${t.state}' task`);
|
|
105
|
+
t.blocked = { reason, at: new Date().toISOString() };
|
|
106
|
+
writeTask(t);
|
|
107
|
+
return t;
|
|
108
|
+
}
|
|
109
|
+
export function unblockTask(id) {
|
|
110
|
+
const t = readTask(id);
|
|
111
|
+
if (!t.blocked)
|
|
112
|
+
throw new TaskStateError('task is not blocked');
|
|
113
|
+
delete t.blocked;
|
|
114
|
+
writeTask(t);
|
|
115
|
+
return t;
|
|
116
|
+
}
|
|
117
|
+
export function reviewTask(id) {
|
|
118
|
+
const t = readTask(id);
|
|
119
|
+
assertTransition(t.state, 'review');
|
|
120
|
+
t.state = 'review';
|
|
121
|
+
delete t.blocked;
|
|
122
|
+
writeTask(t);
|
|
123
|
+
return t;
|
|
124
|
+
}
|
|
125
|
+
export function completeTask(id, outcome) {
|
|
126
|
+
const t = readTask(id);
|
|
127
|
+
assertTransition(t.state, 'done');
|
|
128
|
+
t.state = 'done';
|
|
129
|
+
t.ended_at = new Date().toISOString();
|
|
130
|
+
delete t.blocked;
|
|
131
|
+
if (outcome)
|
|
132
|
+
t.outcome = outcome;
|
|
133
|
+
writeTask(t);
|
|
134
|
+
return t;
|
|
135
|
+
}
|
|
136
|
+
export function cancelTask(id) {
|
|
137
|
+
const t = readTask(id);
|
|
138
|
+
if (!TASK_CANCELLABLE_STATES.includes(t.state))
|
|
139
|
+
throw new TaskStateError(`cannot cancel a '${t.state}' task`);
|
|
140
|
+
t.state = 'cancelled';
|
|
141
|
+
t.ended_at = new Date().toISOString();
|
|
142
|
+
delete t.blocked;
|
|
143
|
+
writeTask(t);
|
|
144
|
+
return t;
|
|
145
|
+
}
|
|
146
|
+
export function failTask(id, error) {
|
|
147
|
+
const t = readTask(id);
|
|
148
|
+
assertTransition(t.state, 'failed');
|
|
149
|
+
t.state = 'failed';
|
|
150
|
+
t.ended_at = new Date().toISOString();
|
|
151
|
+
t.outcome = { summary: error };
|
|
152
|
+
delete t.blocked;
|
|
153
|
+
writeTask(t);
|
|
154
|
+
return t;
|
|
155
|
+
}
|
|
156
|
+
export function updateTaskRoom(id, roomId, roomIdentityCid) {
|
|
157
|
+
const t = readTask(id);
|
|
158
|
+
t.room_id = roomId;
|
|
159
|
+
t.room_identity_cid = roomIdentityCid;
|
|
160
|
+
writeTask(t);
|
|
161
|
+
return t;
|
|
162
|
+
}
|
|
163
|
+
export function updateTaskMembers(id, members) {
|
|
164
|
+
const t = readTask(id);
|
|
165
|
+
t.member_roles = members;
|
|
166
|
+
writeTask(t);
|
|
167
|
+
return t;
|
|
168
|
+
}
|
|
169
|
+
export function deleteTask(id) {
|
|
170
|
+
const p = taskPath(id);
|
|
171
|
+
if (existsSync(p))
|
|
172
|
+
rmSync(p);
|
|
173
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TemplateDefinition, TemplateSnapshot } from './types.js';
|
|
2
|
+
export declare const BUILTIN_TEMPLATES: readonly TemplateDefinition[];
|
|
3
|
+
export declare function hashTemplate(t: TemplateDefinition): string;
|
|
4
|
+
export declare function snapshotTemplate(t: TemplateDefinition): TemplateSnapshot;
|
|
5
|
+
export declare function resolveTemplate(name: string, customTemplates: Record<string, TemplateDefinition>): TemplateDefinition | undefined;
|
|
6
|
+
export declare function listTemplates(customTemplates: Record<string, TemplateDefinition>): TemplateDefinition[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const DEVELOPMENT_TEAM = {
|
|
3
|
+
name: 'development-team',
|
|
4
|
+
version: 1,
|
|
5
|
+
builtin: true,
|
|
6
|
+
description: 'Four equal Developer peers: implement, review, test, and cross-validate',
|
|
7
|
+
room: { quiet_membership: false, anonymous: false },
|
|
8
|
+
contract: [
|
|
9
|
+
'Work in the room. Preserve evidence. Review independently before consensus.',
|
|
10
|
+
'Every material change needs independent review from another peer.',
|
|
11
|
+
'Peers reproduce, test, stress, criticize, and cross-validate each other\'s work.',
|
|
12
|
+
'Evidence and decisions are posted into the Room; detailed scratch work stays in role worklogs.',
|
|
13
|
+
'Completion requires the room\'s agreed acceptance criteria, not merely one agent declaring success.',
|
|
14
|
+
].join('\n'),
|
|
15
|
+
members: [
|
|
16
|
+
{ slot: 'developer', role: 'Developer', count: 4, role_ref: 'Developer' },
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
const RESEARCH_DECISION = {
|
|
20
|
+
name: 'research-decision',
|
|
21
|
+
version: 1,
|
|
22
|
+
builtin: true,
|
|
23
|
+
description: 'Two Researchers, one Critic, one Synthesizer for evidence-based decisions',
|
|
24
|
+
room: { quiet_membership: false, anonymous: false },
|
|
25
|
+
contract: [
|
|
26
|
+
'Researchers gather independent evidence. Critic attacks assumptions and missing cases.',
|
|
27
|
+
'Synthesizer produces the final decision record with cited evidence, alternatives, and recommendation.',
|
|
28
|
+
'No code mutation is implied by this template.',
|
|
29
|
+
'Completion requires cited evidence, alternatives considered, and an explicit recommendation.',
|
|
30
|
+
].join('\n'),
|
|
31
|
+
members: [
|
|
32
|
+
{ slot: 'researcher', role: 'Researcher', count: 2, role_ref: 'Researcher' },
|
|
33
|
+
{ slot: 'critic', role: 'Critic', count: 1, role_ref: 'Critic' },
|
|
34
|
+
{ slot: 'synthesizer', role: 'Synthesizer', count: 1, role_ref: 'Synthesizer' },
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
export const BUILTIN_TEMPLATES = [DEVELOPMENT_TEAM, RESEARCH_DECISION];
|
|
38
|
+
export function hashTemplate(t) {
|
|
39
|
+
const canonical = JSON.stringify({
|
|
40
|
+
name: t.name, version: t.version, description: t.description,
|
|
41
|
+
room: t.room, contract: t.contract, members: t.members,
|
|
42
|
+
});
|
|
43
|
+
return createHash('sha256').update(canonical).digest('hex');
|
|
44
|
+
}
|
|
45
|
+
export function snapshotTemplate(t) {
|
|
46
|
+
return { ...t, content_hash: hashTemplate(t) };
|
|
47
|
+
}
|
|
48
|
+
export function resolveTemplate(name, customTemplates) {
|
|
49
|
+
const atIdx = name.lastIndexOf('@');
|
|
50
|
+
const baseName = atIdx > 0 ? name.slice(0, atIdx) : name;
|
|
51
|
+
const versionStr = atIdx > 0 ? name.slice(atIdx + 1) : undefined;
|
|
52
|
+
const version = versionStr ? parseInt(versionStr, 10) : undefined;
|
|
53
|
+
const custom = customTemplates[baseName];
|
|
54
|
+
if (custom) {
|
|
55
|
+
if (version !== undefined && custom.version !== version)
|
|
56
|
+
return undefined;
|
|
57
|
+
return custom;
|
|
58
|
+
}
|
|
59
|
+
const builtin = BUILTIN_TEMPLATES.find(b => b.name === baseName);
|
|
60
|
+
if (builtin) {
|
|
61
|
+
if (version !== undefined && builtin.version !== version)
|
|
62
|
+
return undefined;
|
|
63
|
+
return builtin;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
export function listTemplates(customTemplates) {
|
|
68
|
+
const result = [];
|
|
69
|
+
const overridden = new Set();
|
|
70
|
+
for (const [name, t] of Object.entries(customTemplates)) {
|
|
71
|
+
result.push({ ...t, name });
|
|
72
|
+
if (BUILTIN_TEMPLATES.some(b => b.name === name))
|
|
73
|
+
overridden.add(name);
|
|
74
|
+
}
|
|
75
|
+
for (const b of BUILTIN_TEMPLATES) {
|
|
76
|
+
if (!overridden.has(b.name))
|
|
77
|
+
result.push(b);
|
|
78
|
+
}
|
|
79
|
+
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
80
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rooms & Tasks domain types for ours-fleet.
|
|
3
|
+
*
|
|
4
|
+
* Task and Room are Fleet orchestration records. Cowork owns room content,
|
|
5
|
+
* membership, and archive. Messenger Server owns owner presentation.
|
|
6
|
+
* Fleet stores only IDs, CIDs, orchestration state, and saga cursors.
|
|
7
|
+
*/
|
|
8
|
+
export type TaskState = 'backlog' | 'provisioning' | 'active' | 'review' | 'done' | 'cancelled' | 'failed';
|
|
9
|
+
export declare const TASK_TERMINAL_STATES: readonly TaskState[];
|
|
10
|
+
export declare const TASK_CANCELLABLE_STATES: readonly TaskState[];
|
|
11
|
+
export interface TaskBlocked {
|
|
12
|
+
reason: string;
|
|
13
|
+
at: string;
|
|
14
|
+
}
|
|
15
|
+
export interface TaskTemplateRef {
|
|
16
|
+
name: string;
|
|
17
|
+
version: number;
|
|
18
|
+
content_hash: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TaskMemberRole {
|
|
21
|
+
name: string;
|
|
22
|
+
identity_cid: string;
|
|
23
|
+
slot: string;
|
|
24
|
+
cowork_role: string;
|
|
25
|
+
}
|
|
26
|
+
export interface TaskOrigin {
|
|
27
|
+
type: 'cli' | 'owner_channel';
|
|
28
|
+
owner_cid?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TaskOutcome {
|
|
31
|
+
summary: string;
|
|
32
|
+
artifacts?: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface TaskRecord {
|
|
35
|
+
task_id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
brief?: string;
|
|
38
|
+
brief_file?: string;
|
|
39
|
+
state: TaskState;
|
|
40
|
+
blocked?: TaskBlocked;
|
|
41
|
+
template?: TaskTemplateRef;
|
|
42
|
+
room_id?: string;
|
|
43
|
+
room_identity_cid?: string;
|
|
44
|
+
member_roles: TaskMemberRole[];
|
|
45
|
+
origin: TaskOrigin;
|
|
46
|
+
idempotency_key: string;
|
|
47
|
+
created_at: string;
|
|
48
|
+
started_at?: string;
|
|
49
|
+
ended_at?: string;
|
|
50
|
+
outcome?: TaskOutcome;
|
|
51
|
+
}
|
|
52
|
+
export type RoomOrchestrationState = 'provisioning' | 'active' | 'closing' | 'closed';
|
|
53
|
+
export type SagaPhase = 'persist_intent' | 'create_room' | 'attach_owner' | 'create_members' | 'join_role_groups' | 'wait_seats' | 'launch_work' | 'activate' | 'completed' | 'failed';
|
|
54
|
+
export interface SagaCursor {
|
|
55
|
+
phase: SagaPhase;
|
|
56
|
+
step_index: number;
|
|
57
|
+
error?: string;
|
|
58
|
+
recovery_hint?: string;
|
|
59
|
+
}
|
|
60
|
+
export type ProvisioningDetail = 'waiting_cowork' | 'waiting_owner_invite' | 'owner_cid_mismatch' | 'member_failed' | 'waiting_seats' | 'uncertain';
|
|
61
|
+
export interface RoomMemberSeat {
|
|
62
|
+
role_name: string;
|
|
63
|
+
identity_cid: string;
|
|
64
|
+
slot: string;
|
|
65
|
+
cowork_role: string;
|
|
66
|
+
seat_state: 'pending' | 'active' | 'removed';
|
|
67
|
+
}
|
|
68
|
+
export interface RoomOrchestrationRecord {
|
|
69
|
+
room_id: string;
|
|
70
|
+
room_identity_cid?: string;
|
|
71
|
+
room_name: string;
|
|
72
|
+
task_id?: string;
|
|
73
|
+
template_snapshot?: TemplateSnapshot;
|
|
74
|
+
saga: SagaCursor;
|
|
75
|
+
provisioning_detail?: ProvisioningDetail;
|
|
76
|
+
owner_seat_cid?: string;
|
|
77
|
+
owner_invite_fingerprint?: string;
|
|
78
|
+
member_seats: RoomMemberSeat[];
|
|
79
|
+
state: RoomOrchestrationState;
|
|
80
|
+
created_at: string;
|
|
81
|
+
activated_at?: string;
|
|
82
|
+
closed_at?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface TemplateMemberSlot {
|
|
85
|
+
slot: string;
|
|
86
|
+
role: string;
|
|
87
|
+
count: number;
|
|
88
|
+
role_ref: string;
|
|
89
|
+
overrides?: TemplateRoleOverrides;
|
|
90
|
+
}
|
|
91
|
+
export interface TemplateRoleOverrides {
|
|
92
|
+
harness?: string;
|
|
93
|
+
model?: string;
|
|
94
|
+
model_chain?: string[];
|
|
95
|
+
permissions?: Record<string, unknown>;
|
|
96
|
+
isolation?: Record<string, unknown>;
|
|
97
|
+
cwd?: string;
|
|
98
|
+
env?: Record<string, string>;
|
|
99
|
+
persona?: string;
|
|
100
|
+
mission?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface TemplateRoomConfig {
|
|
103
|
+
quiet_membership?: boolean;
|
|
104
|
+
anonymous?: boolean;
|
|
105
|
+
}
|
|
106
|
+
export interface TemplateDefinition {
|
|
107
|
+
name: string;
|
|
108
|
+
version: number;
|
|
109
|
+
description: string;
|
|
110
|
+
builtin?: boolean;
|
|
111
|
+
room?: TemplateRoomConfig;
|
|
112
|
+
contract?: string;
|
|
113
|
+
members: TemplateMemberSlot[];
|
|
114
|
+
}
|
|
115
|
+
export interface TemplateSnapshot extends TemplateDefinition {
|
|
116
|
+
content_hash: string;
|
|
117
|
+
}
|
|
118
|
+
export interface RoomsOwnerConfig {
|
|
119
|
+
provider: string;
|
|
120
|
+
public_invite?: string;
|
|
121
|
+
public_invite_file?: string;
|
|
122
|
+
expected_cid: string;
|
|
123
|
+
role: string;
|
|
124
|
+
}
|
|
125
|
+
export interface RoomsCoworkConfig {
|
|
126
|
+
config?: string;
|
|
127
|
+
}
|
|
128
|
+
export interface RoomsDefaults {
|
|
129
|
+
template?: string;
|
|
130
|
+
attach_owner?: boolean;
|
|
131
|
+
close_when_task_done?: boolean;
|
|
132
|
+
}
|
|
133
|
+
export interface RoomsConfig {
|
|
134
|
+
provider: string;
|
|
135
|
+
cowork?: RoomsCoworkConfig;
|
|
136
|
+
owner: RoomsOwnerConfig;
|
|
137
|
+
defaults?: RoomsDefaults;
|
|
138
|
+
}
|
|
139
|
+
export interface TasksConfig {
|
|
140
|
+
default_room_template?: string;
|
|
141
|
+
create_mode?: 'start' | 'backlog';
|
|
142
|
+
close_room_on_done?: boolean;
|
|
143
|
+
retain_completed_for?: string;
|
|
144
|
+
}
|
|
145
|
+
export type RoomTemplatesConfig = Record<string, TemplateDefinition>;
|
|
146
|
+
export declare const ROOMS_KEYS: readonly ["provider", "cowork", "owner", "defaults"];
|
|
147
|
+
export declare const ROOMS_OWNER_KEYS: readonly ["provider", "public_invite", "public_invite_file", "expected_cid", "role"];
|
|
148
|
+
export declare const ROOMS_COWORK_KEYS: readonly ["config"];
|
|
149
|
+
export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
|
|
150
|
+
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
|
|
151
|
+
export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
|
|
152
|
+
export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "role_ref", "overrides"];
|
|
153
|
+
export declare const TEMPLATE_OVERRIDE_KEYS: readonly ["harness", "model", "model_chain", "permissions", "isolation", "cwd", "env", "persona", "mission"];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rooms & Tasks domain types for ours-fleet.
|
|
3
|
+
*
|
|
4
|
+
* Task and Room are Fleet orchestration records. Cowork owns room content,
|
|
5
|
+
* membership, and archive. Messenger Server owns owner presentation.
|
|
6
|
+
* Fleet stores only IDs, CIDs, orchestration state, and saga cursors.
|
|
7
|
+
*/
|
|
8
|
+
export const TASK_TERMINAL_STATES = ['done', 'cancelled', 'failed'];
|
|
9
|
+
export const TASK_CANCELLABLE_STATES = ['backlog', 'provisioning', 'active', 'review'];
|
|
10
|
+
// ── Validation keys ─────────────────────────────────────────────────────
|
|
11
|
+
export const ROOMS_KEYS = ['provider', 'cowork', 'owner', 'defaults'];
|
|
12
|
+
export const ROOMS_OWNER_KEYS = ['provider', 'public_invite', 'public_invite_file', 'expected_cid', 'role'];
|
|
13
|
+
export const ROOMS_COWORK_KEYS = ['config'];
|
|
14
|
+
export const ROOMS_DEFAULTS_KEYS = ['template', 'attach_owner', 'close_when_task_done'];
|
|
15
|
+
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for'];
|
|
16
|
+
export const TEMPLATE_KEYS = ['version', 'description', 'room', 'contract', 'members', 'override_builtin'];
|
|
17
|
+
export const TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', 'role_ref', 'overrides'];
|
|
18
|
+
export const TEMPLATE_OVERRIDE_KEYS = [
|
|
19
|
+
'harness', 'model', 'model_chain', 'permissions', 'isolation', 'cwd', 'env', 'persona', 'mission',
|
|
20
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "0.19.0-nightly.
|
|
3
|
+
"version": "0.19.0-nightly.3",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|