@ours.network/fleet 2.2.5 → 2.3.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/dist/application/task-room-service.d.ts +11 -0
- package/dist/application/task-room-service.js +37 -14
- package/dist/atomic-file.d.ts +2 -0
- package/dist/atomic-file.js +2 -1
- package/dist/build-info.json +4 -4
- package/dist/config.d.ts +6 -0
- package/dist/config.js +6 -3
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +37 -0
- package/dist/owner-channel/commands.js +25 -12
- package/dist/reports/tasks.js +4 -3
- package/dist/rooms-tasks/add-member.js +17 -6
- package/dist/rooms-tasks/cli.js +79 -5
- package/dist/rooms-tasks/config.js +11 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +15 -0
- package/dist/rooms-tasks/layout-member-retirement.js +26 -1
- package/dist/rooms-tasks/layout-service.d.ts +2 -1
- package/dist/rooms-tasks/layout-service.js +10 -4
- package/dist/rooms-tasks/layout.d.ts +4 -2
- package/dist/rooms-tasks/layout.js +21 -4
- package/dist/rooms-tasks/member-naming.d.ts +43 -0
- package/dist/rooms-tasks/member-naming.js +186 -0
- package/dist/rooms-tasks/member-overrides.js +1 -0
- package/dist/rooms-tasks/mission-keys.d.ts +83 -0
- package/dist/rooms-tasks/mission-keys.js +250 -0
- package/dist/rooms-tasks/provision.d.ts +4 -0
- package/dist/rooms-tasks/provision.js +37 -3
- package/dist/rooms-tasks/task-lists.d.ts +18 -2
- package/dist/rooms-tasks/task-lists.js +60 -5
- package/dist/rooms-tasks/task-room-name.d.ts +7 -2
- package/dist/rooms-tasks/task-room-name.js +13 -4
- package/dist/rooms-tasks/task-state.d.ts +40 -0
- package/dist/rooms-tasks/task-state.js +288 -45
- package/dist/rooms-tasks/types.d.ts +35 -1
- package/dist/rooms-tasks/types.js +1 -1
- package/dist/web/runtime.js +4 -1
- package/dist/web/server.js +62 -27
- package/package.json +5 -5
|
@@ -174,6 +174,15 @@ export interface TaskRecord {
|
|
|
174
174
|
notification_manifest?: import('./notification-manifest.js').TaskNotificationManifest;
|
|
175
175
|
workspace?: import('./workspace.js').OwnedWorkspace;
|
|
176
176
|
task_id: string;
|
|
177
|
+
/**
|
|
178
|
+
* Immutable human mission key (`MSN-1`), issued once from a durable
|
|
179
|
+
* per-prefix high-water mark. Absent only before the one-time migration.
|
|
180
|
+
*/
|
|
181
|
+
key?: string;
|
|
182
|
+
/** Set when the key was backfilled for a task that existed before mission keys. */
|
|
183
|
+
key_from_migration?: true;
|
|
184
|
+
/** Durable per-mission agent name allocations; absent until first allocation or migration. */
|
|
185
|
+
member_naming?: MemberNaming;
|
|
177
186
|
/** Stable organizational list identifier. Missing legacy values mean `default`. */
|
|
178
187
|
list_id: string;
|
|
179
188
|
/** Derived presentation field; repositories must never persist it. */
|
|
@@ -211,9 +220,30 @@ export interface TaskRecord {
|
|
|
211
220
|
terminal_intent?: TaskTerminalIntent;
|
|
212
221
|
deletion?: TaskDeletionIntent;
|
|
213
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Role-scoped agent names: `<KEY>-<Stem>` for the first member of a stem,
|
|
225
|
+
* `<KEY>-<Stem>-<n>` (n >= 2) after it. Counters only grow; an allocation is
|
|
226
|
+
* reused by provisioning, recovery and restart, never by a replacement.
|
|
227
|
+
*/
|
|
228
|
+
export interface MemberNameAllocation {
|
|
229
|
+
name: string;
|
|
230
|
+
/** Counter bucket: the case-folded truncated stem. Absent on frozen legacy names. */
|
|
231
|
+
bucket?: string;
|
|
232
|
+
ordinal?: number;
|
|
233
|
+
label_source?: 'role_override' | 'template_ref' | 'room_role' | 'participant_key' | 'slot' | 'fallback';
|
|
234
|
+
/** A historical name kept exactly as launched before mission keys existed. */
|
|
235
|
+
frozen?: true;
|
|
236
|
+
}
|
|
237
|
+
export interface MemberNaming {
|
|
238
|
+
version: 2;
|
|
239
|
+
counters: Record<string, number>;
|
|
240
|
+
allocations: Record<string, MemberNameAllocation>;
|
|
241
|
+
}
|
|
214
242
|
export interface TaskListRecord {
|
|
215
243
|
list_id: string;
|
|
216
244
|
name: string;
|
|
245
|
+
/** Prefix for new mission keys; absent only before the one-time key migration. */
|
|
246
|
+
prefix?: string;
|
|
217
247
|
built_in: boolean;
|
|
218
248
|
created_at: string;
|
|
219
249
|
}
|
|
@@ -387,6 +417,8 @@ export interface TemplateSnapshotMember extends TemplateMemberSlot {
|
|
|
387
417
|
id: string;
|
|
388
418
|
hash: string;
|
|
389
419
|
};
|
|
420
|
+
/** Named Role of the member's Agent Template, sealed at plan time for agent naming. */
|
|
421
|
+
role_label?: string;
|
|
390
422
|
}
|
|
391
423
|
export interface TemplateSnapshot extends Omit<TemplateDefinition, 'members'> {
|
|
392
424
|
members: TemplateSnapshotMember[];
|
|
@@ -418,13 +450,15 @@ export interface TasksConfig {
|
|
|
418
450
|
create_mode?: 'start' | 'backlog';
|
|
419
451
|
close_room_on_done?: boolean;
|
|
420
452
|
retain_completed_for?: string;
|
|
453
|
+
/** Default list prefix (default MSN); may change only before the first mission key. */
|
|
454
|
+
default_prefix?: string;
|
|
421
455
|
}
|
|
422
456
|
export type RoomTemplatesConfig = Record<string, TemplateDefinition>;
|
|
423
457
|
export declare const ROOMS_KEYS: readonly ["cowork", "owner", "defaults"];
|
|
424
458
|
export declare const ROOMS_OWNER_KEYS: readonly ["provider", "public_invite", "public_invite_file", "expected_cid", "role"];
|
|
425
459
|
export declare const ROOMS_COWORK_KEYS: readonly ["config"];
|
|
426
460
|
export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
|
|
427
|
-
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
|
|
461
|
+
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for", "default_prefix"];
|
|
428
462
|
export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
|
|
429
463
|
export declare const TEMPLATE_ROOM_KEYS: readonly ["quiet_membership", "anonymous"];
|
|
430
464
|
/** `agent` is accepted only so validation can emit its actionable migration error. */
|
|
@@ -23,7 +23,7 @@ export const ROOMS_KEYS = ['cowork', 'owner', 'defaults'];
|
|
|
23
23
|
export const ROOMS_OWNER_KEYS = ['provider', 'public_invite', 'public_invite_file', 'expected_cid', 'role'];
|
|
24
24
|
export const ROOMS_COWORK_KEYS = ['config'];
|
|
25
25
|
export const ROOMS_DEFAULTS_KEYS = ['template', 'attach_owner', 'close_when_task_done'];
|
|
26
|
-
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for'];
|
|
26
|
+
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for', 'default_prefix'];
|
|
27
27
|
export const TEMPLATE_KEYS = ['version', 'description', 'room', 'contract', 'members', 'override_builtin'];
|
|
28
28
|
export const TEMPLATE_ROOM_KEYS = ['quiet_membership', 'anonymous'];
|
|
29
29
|
/** `agent` is accepted only so validation can emit its actionable migration error. */
|
package/dist/web/runtime.js
CHANGED
|
@@ -193,13 +193,16 @@ export async function startWebConsole(options) {
|
|
|
193
193
|
const topologyPromote = new TopologyPromoteService({
|
|
194
194
|
drafts: topologyDrafts, configuration, topology: readTopology,
|
|
195
195
|
});
|
|
196
|
+
const taskRooms = new TaskRoomApplicationService(options.configPath, { binPath: () => options.binPath });
|
|
197
|
+
// One-time, lock-guarded mission key migration before serving; failure leaves it to the next mutation.
|
|
198
|
+
await taskRooms.prepareMissionKeys().catch(error => console.warn(`mission key migration deferred: ${error.message}`));
|
|
196
199
|
let server;
|
|
197
200
|
try {
|
|
198
201
|
server = await buildWebServer({
|
|
199
202
|
query, repository, logs, commands, creation, removal, audit, events, watchdogs, configuration, observeRole,
|
|
200
203
|
resourceScope: { configPath: configuration.path, subscribeMutations: listener => events.subscribeResources(listener) },
|
|
201
204
|
invalidateResourceCaches: () => { watchdogConfigProvider.invalidate(); watchdogFindings.invalidate(); },
|
|
202
|
-
taskRooms
|
|
205
|
+
taskRooms,
|
|
203
206
|
roomLayouts: new RoomLayoutDefinitions(options.configPath),
|
|
204
207
|
layoutBindings: new LayoutBindingGrants(new RoomLayoutService(options.configPath).supervisor()),
|
|
205
208
|
presetProvenance: new PresetProvenance(options.configPath),
|
package/dist/web/server.js
CHANGED
|
@@ -26,7 +26,8 @@ import { WebAuth } from './auth.js';
|
|
|
26
26
|
import { FleetEventBus } from './events.js';
|
|
27
27
|
import { ROLE_NAME_RE } from '../config.js';
|
|
28
28
|
import { TaskListError } from '../rooms-tasks/task-lists.js';
|
|
29
|
-
import { acknowledgeTaskProgress, acknowledgeTaskReview, contributionUnread, TaskStateError } from '../rooms-tasks/task-state.js';
|
|
29
|
+
import { acknowledgeTaskProgress, acknowledgeTaskReview, contributionUnread, resolveTaskRef, TaskStateError } from '../rooms-tasks/task-state.js';
|
|
30
|
+
import { MissionKeyError } from '../rooms-tasks/mission-keys.js';
|
|
30
31
|
import { appendTaskProgress, getTaskReview, taskProgressSnapshot, publicProgressEntry, publicReview, readTaskReviewArtifact, setTaskReviewMarkdown, taskContributionSummary, TaskReviewError, } from '../rooms-tasks/task-review.js';
|
|
31
32
|
import { isProvider } from '../subscriptions/store.js';
|
|
32
33
|
const ONBOARDING_WORK = ['coordination', 'development', 'review'];
|
|
@@ -321,6 +322,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
321
322
|
? 'conflict' : 'invalid_request';
|
|
322
323
|
throw new FleetError(code, error.message);
|
|
323
324
|
}
|
|
325
|
+
if (error instanceof MissionKeyError)
|
|
326
|
+
throw new FleetError(error.code === 'invalid_prefix' ? 'invalid_request' : 'conflict', error.message, { details: { reason: error.code } });
|
|
324
327
|
if (error instanceof TaskStateError && 'accepted' in error && error.accepted === false)
|
|
325
328
|
return Promise.reject(Object.assign(new FleetError('invalid_request', error.message), { memberNotAccepted: true }));
|
|
326
329
|
if (error instanceof TaskStateError) {
|
|
@@ -329,6 +332,20 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
329
332
|
throw error;
|
|
330
333
|
}
|
|
331
334
|
};
|
|
335
|
+
/** Canonical task ID for a `:id` param holding an ID or a mission key; call only after authentication. */
|
|
336
|
+
const taskRef = (request, options = {}) => {
|
|
337
|
+
const ref = String(request.params.id ?? '');
|
|
338
|
+
try {
|
|
339
|
+
return resolveTaskRef(ref, options);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (error instanceof MissionKeyError)
|
|
343
|
+
throw new FleetError('resource_not_found', error.message);
|
|
344
|
+
if (error instanceof TaskStateError)
|
|
345
|
+
throw new FleetError(error.message.startsWith('task not found:') ? 'resource_not_found' : 'invalid_request', error.message);
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
332
349
|
const reviewApi = async (fn) => taskApi(async () => {
|
|
333
350
|
try {
|
|
334
351
|
return await fn();
|
|
@@ -345,7 +362,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
345
362
|
});
|
|
346
363
|
/** Browser task payloads carry Progress/To review counts; bodies have their own routes. */
|
|
347
364
|
const wireTask = (task) => {
|
|
348
|
-
const { progress: _progress, review: _review, ...rest } = task;
|
|
365
|
+
const { progress: _progress, review: _review, member_naming: _naming, ...rest } = task;
|
|
349
366
|
return { ...rest, ...taskContributionSummary(task) };
|
|
350
367
|
};
|
|
351
368
|
const wireTaskResult = (result) => {
|
|
@@ -517,15 +534,29 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
517
534
|
});
|
|
518
535
|
app.post('/api/v1/task-lists', async (request, reply) => {
|
|
519
536
|
auth.authenticate(request, true);
|
|
520
|
-
const
|
|
521
|
-
const
|
|
537
|
+
const body = request.body;
|
|
538
|
+
const name = String(body?.name ?? '');
|
|
539
|
+
if (body?.prefix !== undefined && typeof body.prefix !== 'string')
|
|
540
|
+
throw new FleetError('invalid_request', 'prefix must be a string');
|
|
541
|
+
const list = await taskApi(() => requireTaskRooms().createTaskList({ actor: { kind: 'local_control', surface: 'web' }, name,
|
|
542
|
+
prefix: body?.prefix }));
|
|
522
543
|
reply.code(201);
|
|
523
544
|
return { list };
|
|
524
545
|
});
|
|
546
|
+
app.get('/api/v1/task-lists/prefix-suggestion', async (request) => {
|
|
547
|
+
auth.authenticate(request);
|
|
548
|
+
const name = String(request.query.name ?? '');
|
|
549
|
+
return { prefix: await taskApi(() => requireTaskRooms().suggestTaskListPrefix(name)) };
|
|
550
|
+
});
|
|
525
551
|
app.patch('/api/v1/task-lists/:name', async (request) => {
|
|
526
552
|
auth.authenticate(request, true);
|
|
527
553
|
const name = request.params.name;
|
|
528
|
-
const
|
|
554
|
+
const body = request.body;
|
|
555
|
+
if (body?.prefix !== undefined && typeof body.prefix !== 'string')
|
|
556
|
+
throw new FleetError('invalid_request', 'prefix must be a string');
|
|
557
|
+
if (body?.name === undefined && typeof body?.prefix === 'string')
|
|
558
|
+
return { list: await taskApi(() => requireTaskRooms().setTaskListPrefix({ actor: { kind: 'local_control', surface: 'web' }, name, prefix: body.prefix })) };
|
|
559
|
+
const newName = String(body?.name ?? '');
|
|
529
560
|
return { list: await taskApi(() => requireTaskRooms().renameTaskList({ actor: { kind: 'local_control', surface: 'web' }, name, newName })) };
|
|
530
561
|
});
|
|
531
562
|
app.delete('/api/v1/task-lists/:name', async (request) => {
|
|
@@ -540,11 +571,13 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
540
571
|
});
|
|
541
572
|
app.delete('/api/v1/tasks/:id', async (request, reply) => {
|
|
542
573
|
auth.authenticate(request, true);
|
|
543
|
-
const
|
|
574
|
+
const submitted = request.params.id;
|
|
544
575
|
const confirm = request.query.confirm;
|
|
545
|
-
// Exact-
|
|
546
|
-
if (confirm !==
|
|
547
|
-
throw new FleetError('invalid_request', 'confirm must exactly repeat the task id');
|
|
576
|
+
// Exact raw ref-twice confirmation is validated before existence or idempotency.
|
|
577
|
+
if (confirm !== submitted)
|
|
578
|
+
throw new FleetError('invalid_request', 'confirm must exactly repeat the task id or key');
|
|
579
|
+
// A deleted key still maps to its canonical ID, so a repeated DELETE stays idempotent.
|
|
580
|
+
const taskId = taskRef(request, { allowDeleted: true });
|
|
548
581
|
const accepted = await taskApi(() => requireTaskRooms().requestTaskDeletion({
|
|
549
582
|
actor: { kind: 'local_control', surface: 'web' }, taskId,
|
|
550
583
|
}));
|
|
@@ -559,7 +592,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
559
592
|
return {
|
|
560
593
|
task_id: taskId, accepted: true, deletion: 'pending',
|
|
561
594
|
...(outcome.error ? { error: outcome.error } : {}),
|
|
562
|
-
recovery: `Repeat DELETE /api/v1/tasks/${
|
|
595
|
+
recovery: `Repeat DELETE /api/v1/tasks/${submitted}?confirm=${submitted}.`,
|
|
563
596
|
};
|
|
564
597
|
});
|
|
565
598
|
app.post('/api/v1/tasks', async (request, reply) => {
|
|
@@ -588,7 +621,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
588
621
|
});
|
|
589
622
|
app.patch('/api/v1/tasks/:id/list', async (request) => {
|
|
590
623
|
auth.authenticate(request, true);
|
|
591
|
-
const taskId = request
|
|
624
|
+
const taskId = taskRef(request);
|
|
592
625
|
const list = String(request.body?.list ?? '');
|
|
593
626
|
return { task: wireTask(await taskApi(() => requireTaskRooms().moveTask({ actor: { kind: 'local_control', surface: 'web' }, taskId, list }))) };
|
|
594
627
|
});
|
|
@@ -598,17 +631,17 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
598
631
|
if (typeof body?.brief !== 'string' || typeof body.expectedBrief !== 'string' || body.brief.length > 100_000)
|
|
599
632
|
throw new FleetError('invalid_request', 'brief and expectedBrief must be strings; brief maximum is 100000 characters');
|
|
600
633
|
return { task: wireTask(await taskApi(() => requireTaskRooms().editTaskDescription({
|
|
601
|
-
actor: { kind: 'local_control', surface: 'web' }, taskId: request
|
|
634
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: taskRef(request),
|
|
602
635
|
brief: body.brief, expectedBrief: body.expectedBrief,
|
|
603
636
|
}))) };
|
|
604
637
|
});
|
|
605
638
|
app.get('/api/v1/tasks/:id', async (request) => {
|
|
606
639
|
auth.authenticate(request);
|
|
607
|
-
return wireTaskResult(await taskApi(() => requireTaskRooms().getTask(request
|
|
640
|
+
return wireTaskResult(await taskApi(() => requireTaskRooms().getTask(taskRef(request))));
|
|
608
641
|
});
|
|
609
642
|
app.get('/api/v1/tasks/:id/progress', async (request) => {
|
|
610
643
|
auth.authenticate(request);
|
|
611
|
-
return { progress: await reviewApi(() => taskProgressSnapshot(request
|
|
644
|
+
return { progress: await reviewApi(() => taskProgressSnapshot(taskRef(request))) };
|
|
612
645
|
});
|
|
613
646
|
app.post('/api/v1/tasks/:id/progress', async (request, reply) => {
|
|
614
647
|
auth.authenticate(request, true);
|
|
@@ -617,7 +650,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
617
650
|
const requestKey = typeof body?.requestKey === 'string' ? body.requestKey : typeof header === 'string' ? header : undefined;
|
|
618
651
|
if (typeof body?.text !== 'string' || (body.requestKey !== undefined && typeof body.requestKey !== 'string'))
|
|
619
652
|
throw new FleetError('invalid_request', 'text must be a string; requestKey, when given, a string');
|
|
620
|
-
const result = await reviewApi(() => appendTaskProgress(request
|
|
653
|
+
const result = await reviewApi(() => appendTaskProgress(taskRef(request), {
|
|
621
654
|
text: body.text, requestKey, author: { kind: 'local_control', surface: 'web' },
|
|
622
655
|
}));
|
|
623
656
|
if (!result.replayed)
|
|
@@ -630,7 +663,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
630
663
|
const ids = request.body?.ids;
|
|
631
664
|
if (!Array.isArray(ids) || ids.length > 200 || ids.some(id => typeof id !== 'string' || id.length > 64))
|
|
632
665
|
throw new FleetError('invalid_request', 'ids must be at most 200 progress entry IDs');
|
|
633
|
-
return { unread: await reviewApi(() => acknowledgeTaskProgress(request
|
|
666
|
+
return { unread: await reviewApi(() => acknowledgeTaskProgress(taskRef(request), ids)) };
|
|
634
667
|
});
|
|
635
668
|
/** Acknowledge the To review revision the browser fetched and rendered. */
|
|
636
669
|
app.post('/api/v1/tasks/:id/review/read', async (request) => {
|
|
@@ -638,11 +671,11 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
638
671
|
const revision = request.body?.revision;
|
|
639
672
|
if (!Number.isSafeInteger(revision) || revision < 0)
|
|
640
673
|
throw new FleetError('invalid_request', 'revision must be a non-negative integer');
|
|
641
|
-
return { unread: await reviewApi(() => acknowledgeTaskReview(request
|
|
674
|
+
return { unread: await reviewApi(() => acknowledgeTaskReview(taskRef(request), revision)) };
|
|
642
675
|
});
|
|
643
676
|
app.get('/api/v1/tasks/:id/review', async (request) => {
|
|
644
677
|
auth.authenticate(request);
|
|
645
|
-
const review = await reviewApi(() => getTaskReview(request
|
|
678
|
+
const review = await reviewApi(() => getTaskReview(taskRef(request)));
|
|
646
679
|
return { review: review ? publicReview(review) : null };
|
|
647
680
|
});
|
|
648
681
|
app.put('/api/v1/tasks/:id/review', async (request) => {
|
|
@@ -650,7 +683,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
650
683
|
const body = request.body;
|
|
651
684
|
if (typeof body?.markdown !== 'string' || typeof body.expectedMarkdown !== 'string')
|
|
652
685
|
throw new FleetError('invalid_request', 'markdown and expectedMarkdown must be strings');
|
|
653
|
-
const review = await reviewApi(() => setTaskReviewMarkdown(request
|
|
686
|
+
const review = await reviewApi(() => setTaskReviewMarkdown(taskRef(request), {
|
|
654
687
|
markdown: body.markdown, expectedMarkdown: body.expectedMarkdown,
|
|
655
688
|
author: { kind: 'local_control', surface: 'web' },
|
|
656
689
|
}));
|
|
@@ -658,7 +691,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
658
691
|
});
|
|
659
692
|
app.get('/api/v1/tasks/:id/review/artifacts/:artifactId', async (request, reply) => {
|
|
660
693
|
auth.authenticate(request);
|
|
661
|
-
const { artifact, bytes } = await reviewApi(() => readTaskReviewArtifact(request
|
|
694
|
+
const { artifact, bytes } = await reviewApi(() => readTaskReviewArtifact(taskRef(request), request.params.artifactId));
|
|
662
695
|
reply.header('Cache-Control', 'private, no-store');
|
|
663
696
|
reply.header('X-Content-Type-Options', 'nosniff');
|
|
664
697
|
reply.header('Content-Security-Policy', "default-src 'none'; sandbox");
|
|
@@ -669,7 +702,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
669
702
|
auth.authenticate(request, true);
|
|
670
703
|
const body = request.body;
|
|
671
704
|
try {
|
|
672
|
-
const result = await taskApi(() => requireTaskRooms().addMember(request
|
|
705
|
+
const result = await taskApi(() => requireTaskRooms().addMember(taskRef(request), body));
|
|
673
706
|
reply.code(result.state === 'running' ? 202 : 200);
|
|
674
707
|
return result;
|
|
675
708
|
}
|
|
@@ -681,7 +714,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
681
714
|
});
|
|
682
715
|
app.get('/api/v1/tasks/:id/member-additions/:requestId', async (request) => {
|
|
683
716
|
auth.authenticate(request);
|
|
684
|
-
const {
|
|
717
|
+
const { requestId } = request.params;
|
|
718
|
+
const id = taskRef(request);
|
|
685
719
|
return taskApi(() => requireTaskRooms().memberAddition(id, requestId));
|
|
686
720
|
});
|
|
687
721
|
app.patch('/api/v1/tasks/:id/layout', async (request) => {
|
|
@@ -691,18 +725,19 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
691
725
|
if (!name(body?.layout) || !(body.expectedLayout === null || name(body.expectedLayout)))
|
|
692
726
|
throw new FleetError('invalid_request', 'layout must be a layout name; expectedLayout a layout name or null');
|
|
693
727
|
const task = await taskApi(() => requireTaskRooms().setTaskLayout({ actor: { kind: 'local_control', surface: 'web' },
|
|
694
|
-
taskId: request
|
|
728
|
+
taskId: taskRef(request), layout: body.layout, expectedLayout: body.expectedLayout }));
|
|
695
729
|
await audit.record({ requestId: request.id, browser: session.id, action: 'task.layout.set', result: 'succeeded' });
|
|
696
730
|
return { task: wireTask(requireTaskRooms().withLayoutRooms(task)) };
|
|
697
731
|
});
|
|
698
732
|
app.get('/api/v1/tasks/:id/layout', async (request) => {
|
|
699
733
|
auth.authenticate(request);
|
|
700
|
-
return { layout: await taskApi(async () => requireTaskRooms().taskLayout(request
|
|
734
|
+
return { layout: await taskApi(async () => requireTaskRooms().taskLayout(taskRef(request))) };
|
|
701
735
|
});
|
|
702
736
|
for (const operation of ['open', 'close']) {
|
|
703
737
|
app.post(`/api/v1/tasks/:id/layout/rooms/:room/${operation}`, async (request, reply) => {
|
|
704
738
|
const session = auth.authenticate(request, true);
|
|
705
|
-
const {
|
|
739
|
+
const { room } = request.params;
|
|
740
|
+
const id = taskRef(request);
|
|
706
741
|
const accepted = await taskApi(() => requireTaskRooms().launchTaskLayoutOperation({
|
|
707
742
|
actor: { kind: 'local_control', surface: 'web' }, taskId: id, room,
|
|
708
743
|
operation: operation === 'open' ? 'open' : 'close-room',
|
|
@@ -715,7 +750,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
715
750
|
app.post('/api/v1/tasks/:id/layout/cleanup', async (request, reply) => {
|
|
716
751
|
auth.authenticate(request, true);
|
|
717
752
|
const accepted = await taskApi(() => requireTaskRooms().retryTaskLayoutCleanup({
|
|
718
|
-
actor: { kind: 'local_control', surface: 'web' }, taskId: request
|
|
753
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: taskRef(request),
|
|
719
754
|
}));
|
|
720
755
|
reply.code(202);
|
|
721
756
|
return { operation: accepted };
|
|
@@ -761,7 +796,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
761
796
|
for (const action of ['start', 'block', 'unblock', 'review', 'finish', 'cancel']) {
|
|
762
797
|
app.post(`/api/v1/tasks/:id/${action}`, async (request, reply) => {
|
|
763
798
|
auth.authenticate(request, true);
|
|
764
|
-
const taskId = request
|
|
799
|
+
const taskId = taskRef(request);
|
|
765
800
|
const body = (request.body ?? {});
|
|
766
801
|
const input = { actor: { kind: 'local_control', surface: 'web' }, taskId };
|
|
767
802
|
const api = requireTaskRooms();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@agentclientprotocol/sdk": "^1.3.0",
|
|
51
51
|
"@fastify/websocket": "^11.2.0",
|
|
52
|
-
"@ours.network/cli": "4.
|
|
53
|
-
"@ours.network/mcp": "2.0.
|
|
54
|
-
"@ours.network/sdk": "5.
|
|
52
|
+
"@ours.network/cli": "4.2.0",
|
|
53
|
+
"@ours.network/mcp": "2.0.3",
|
|
54
|
+
"@ours.network/sdk": "5.2.0",
|
|
55
55
|
"commander": "^12.1.0",
|
|
56
56
|
"fastify": "^5.4.0",
|
|
57
57
|
"qrcode": "1.5.4",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@agentclientprotocol/codex-acp": "1.10.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@ours.network/daemon": "5.
|
|
68
|
+
"@ours.network/daemon": "5.2.0",
|
|
69
69
|
"@types/node": "^22.20.4",
|
|
70
70
|
"@types/qrcode": "^1.5.6",
|
|
71
71
|
"@types/ws": "^8.18.1",
|