@ours.network/fleet 1.0.2 → 1.0.4
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/README.md +4 -4
- package/dist/application/role-command-service.d.ts +29 -1
- package/dist/application/role-command-service.js +41 -2
- package/dist/application/role-creation-service.d.ts +32 -1
- package/dist/application/role-creation-service.js +56 -10
- package/dist/application/role-removal-service.d.ts +19 -0
- package/dist/application/role-removal-service.js +13 -3
- package/dist/application/session-mutations.d.ts +7 -0
- package/dist/application/session-mutations.js +8 -0
- package/dist/application/task-room-service.d.ts +227 -0
- package/dist/application/task-room-service.js +529 -0
- package/dist/build-info.json +4 -4
- package/dist/cli.js +39 -15
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +7 -6
- package/dist/harness/codex-app-server-proxy.d.ts +1 -1
- package/dist/harness/codex-app-server-proxy.js +23 -9
- package/dist/harness/types.d.ts +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/owner-channel/channel.d.ts +5 -0
- package/dist/owner-channel/channel.js +143 -18
- package/dist/owner-channel/commands.d.ts +26 -1
- package/dist/owner-channel/commands.js +66 -128
- package/dist/rooms-tasks/cli.js +181 -462
- package/dist/rooms-tasks/provision.js +71 -8
- package/dist/rooms-tasks/types.d.ts +2 -0
- package/dist/runner.js +19 -44
- package/dist/session/acp.d.ts +8 -3
- package/dist/session/acp.js +24 -8
- package/dist/session/control.d.ts +10 -1
- package/dist/session/control.js +22 -21
- package/dist/watchdog/query.d.ts +2 -0
- package/dist/watchdog/query.js +7 -3
- package/dist/web/server.js +2 -2
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { RoomStateError } from '../rooms-tasks/room-state.js';
|
|
|
4
4
|
import { TaskStateError } from '../rooms-tasks/task-state.js';
|
|
5
5
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from '../rooms-tasks/markdown.js';
|
|
6
6
|
import { OWNER_COMMENT_LABEL, ownerNotices } from './notices.js';
|
|
7
|
+
import { TaskRoomApplicationError } from '../application/task-room-service.js';
|
|
7
8
|
/** A malformed invocation; the dispatcher answers it with annotated help. */
|
|
8
9
|
class OwnerCommandUsageError extends Error {
|
|
9
10
|
}
|
|
@@ -53,6 +54,12 @@ function ownerTaskFailure(error) {
|
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
function ownerRoomFailure(error) {
|
|
57
|
+
if (error instanceof TaskRoomApplicationError
|
|
58
|
+
&& (error.code === 'room_not_found' || error.code === 'room_record_not_found'))
|
|
59
|
+
return {
|
|
60
|
+
kind: 'not_found', detail: `Room ${error.fields.room} was not found.`,
|
|
61
|
+
action: 'Run /room list to find a valid room ID.',
|
|
62
|
+
};
|
|
56
63
|
if (error instanceof RoomStateError) {
|
|
57
64
|
const missing = /^room not found: ([A-Za-z0-9_-]{1,128})$/u.exec(error.message);
|
|
58
65
|
if (missing)
|
|
@@ -228,9 +235,8 @@ export const ownerCommands = [
|
|
|
228
235
|
name: 'tasks', usage: '/tasks [state]',
|
|
229
236
|
summary: 'list tasks (optionally filter by state)',
|
|
230
237
|
execute: async (ctx, args) => {
|
|
231
|
-
const { listTasks } = await import('../rooms-tasks/task-state.js');
|
|
232
238
|
const stateFilter = args?.trim() || undefined;
|
|
233
|
-
const tasks = listTasks(stateFilter && stateFilter !== 'all' ? { state: stateFilter } : undefined);
|
|
239
|
+
const tasks = ctx.listTasks(stateFilter && stateFilter !== 'all' ? { state: stateFilter } : undefined);
|
|
234
240
|
await ctx.reply(renderMarkdownList({
|
|
235
241
|
icon: '📋', title: 'Tasks', empty: 'No tasks found.',
|
|
236
242
|
records: tasks.map(taskListRecord),
|
|
@@ -250,8 +256,7 @@ export const ownerCommands = [
|
|
|
250
256
|
const rest = firstLineTokens.slice(1);
|
|
251
257
|
const trailingLines = argLines.slice(1).join('\n').trim() || undefined;
|
|
252
258
|
const showTask = async (id) => {
|
|
253
|
-
const {
|
|
254
|
-
const t = getTask(id);
|
|
259
|
+
const { task: t } = ctx.getTask(id);
|
|
255
260
|
await ctx.reply(renderMarkdownResult({
|
|
256
261
|
icon: '📋', title: 'Task details',
|
|
257
262
|
fields: [
|
|
@@ -278,14 +283,9 @@ export const ownerCommands = [
|
|
|
278
283
|
const titleParts = rest.filter(r => r !== '--backlog' && !r.startsWith('--template=') && r !== '--no-room');
|
|
279
284
|
if (!titleParts.length)
|
|
280
285
|
throw new OwnerCommandUsageError('usage: /task create [--backlog] [--template=<name>|--no-room] <title>');
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
origin: { type: 'owner_channel' },
|
|
285
|
-
start: !backlog,
|
|
286
|
-
...(template ? { template: { name: template, version: 1, content_hash: '' } } : {}),
|
|
287
|
-
...(noRoom ? { no_room: true } : {}),
|
|
288
|
-
...(trailingLines ? { brief: trailingLines } : {}),
|
|
286
|
+
const t = await ctx.createTask({
|
|
287
|
+
title: titleParts.join(' '), origin: { type: 'owner_channel' },
|
|
288
|
+
backlog, template, noRoom, brief: trailingLines,
|
|
289
289
|
});
|
|
290
290
|
await ctx.reply(taskAction('Task created', t, [
|
|
291
291
|
{ label: 'Title', value: t.title },
|
|
@@ -295,7 +295,6 @@ export const ownerCommands = [
|
|
|
295
295
|
break;
|
|
296
296
|
}
|
|
297
297
|
case 'list': {
|
|
298
|
-
const { listTasks } = await import('../rooms-tasks/task-state.js');
|
|
299
298
|
const filter = rest[0];
|
|
300
299
|
const stateMap = {
|
|
301
300
|
backlog: 'backlog', active: 'active', blocked: 'active',
|
|
@@ -304,7 +303,7 @@ export const ownerCommands = [
|
|
|
304
303
|
const stateFilter = filter && stateMap[filter]
|
|
305
304
|
? { state: stateMap[filter] }
|
|
306
305
|
: undefined;
|
|
307
|
-
const tasks = listTasks(stateFilter);
|
|
306
|
+
const tasks = ctx.listTasks(stateFilter);
|
|
308
307
|
if (filter === 'blocked') {
|
|
309
308
|
const blocked = tasks.filter(t => t.blocked);
|
|
310
309
|
await ctx.reply(renderMarkdownList({
|
|
@@ -328,33 +327,29 @@ export const ownerCommands = [
|
|
|
328
327
|
case 'start': {
|
|
329
328
|
if (!rest[0])
|
|
330
329
|
throw new OwnerCommandUsageError('usage: /task start <id>');
|
|
331
|
-
const
|
|
332
|
-
const t = startTask(rest[0]);
|
|
330
|
+
const t = await ctx.startTask(rest[0]);
|
|
333
331
|
await ctx.reply(taskAction('Task started', t));
|
|
334
332
|
break;
|
|
335
333
|
}
|
|
336
334
|
case 'block': {
|
|
337
335
|
if (!rest[0] || !rest[1])
|
|
338
336
|
throw new OwnerCommandUsageError('usage: /task block <id> <reason>');
|
|
339
|
-
const { blockTask } = await import('../rooms-tasks/task-state.js');
|
|
340
337
|
const reason = rest.slice(1).join(' ');
|
|
341
|
-
const t = blockTask(rest[0], reason);
|
|
338
|
+
const t = ctx.blockTask(rest[0], reason);
|
|
342
339
|
await ctx.reply(taskAction('Task blocked', t, [{ label: 'Reason', value: reason }]));
|
|
343
340
|
break;
|
|
344
341
|
}
|
|
345
342
|
case 'unblock': {
|
|
346
343
|
if (!rest[0])
|
|
347
344
|
throw new OwnerCommandUsageError('usage: /task unblock <id>');
|
|
348
|
-
const
|
|
349
|
-
const t = unblockTask(rest[0]);
|
|
345
|
+
const t = ctx.unblockTask(rest[0]);
|
|
350
346
|
await ctx.reply(taskAction('Task unblocked', t));
|
|
351
347
|
break;
|
|
352
348
|
}
|
|
353
349
|
case 'review': {
|
|
354
350
|
if (!rest[0])
|
|
355
351
|
throw new OwnerCommandUsageError('usage: /task review <id>');
|
|
356
|
-
const
|
|
357
|
-
const t = reviewTask(rest[0]);
|
|
352
|
+
const t = ctx.reviewTask(rest[0]);
|
|
358
353
|
await ctx.reply(taskAction('Task ready for review', t));
|
|
359
354
|
break;
|
|
360
355
|
}
|
|
@@ -374,8 +369,7 @@ export const ownerCommands = [
|
|
|
374
369
|
case 'delete': {
|
|
375
370
|
if (rest.length !== 2 || rest[0] !== rest[1])
|
|
376
371
|
throw new OwnerCommandUsageError('destructive: /task delete <id> <id> — provide the task ID twice');
|
|
377
|
-
const
|
|
378
|
-
const deleted = deleteTask(rest[0]);
|
|
372
|
+
const deleted = ctx.deleteTask(rest[0]);
|
|
379
373
|
await ctx.reply(renderMarkdownResult({
|
|
380
374
|
icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
|
|
381
375
|
fields: [{ label: 'ID', value: rest[0], kind: 'code' }],
|
|
@@ -385,40 +379,7 @@ export const ownerCommands = [
|
|
|
385
379
|
case 'recover': {
|
|
386
380
|
if (!rest[0])
|
|
387
381
|
throw new OwnerCommandUsageError('usage: /task recover <id>');
|
|
388
|
-
|
|
389
|
-
const { getRoomRecord } = await import('../rooms-tasks/room-state.js');
|
|
390
|
-
const t = getTask(rest[0]);
|
|
391
|
-
if (t.terminal_intent?.status === 'pending') {
|
|
392
|
-
await ctx.terminalTask(t.task_id, t.terminal_intent.kind, t.terminal_intent.outcome);
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
const room = t.room_id ? getRoomRecord(t.room_id) : undefined;
|
|
396
|
-
const hints = [];
|
|
397
|
-
if (t.state === 'provisioning' && room) {
|
|
398
|
-
if (room.provisioning_detail === 'waiting_cowork')
|
|
399
|
-
hints.push('Cowork socket unreachable');
|
|
400
|
-
if (room.provisioning_detail === 'waiting_owner_invite')
|
|
401
|
-
hints.push('Owner invite missing or invalid');
|
|
402
|
-
if (room.provisioning_detail === 'owner_cid_mismatch')
|
|
403
|
-
hints.push('Owner CID mismatch');
|
|
404
|
-
if (room.provisioning_detail === 'member_failed')
|
|
405
|
-
hints.push(`Member failed at step ${room.saga.step_index}`);
|
|
406
|
-
}
|
|
407
|
-
await ctx.reply(renderMarkdownResult({
|
|
408
|
-
icon: '🛟', title: 'Task recovery',
|
|
409
|
-
fields: [
|
|
410
|
-
{ label: 'Task', value: t.task_id, kind: 'code' },
|
|
411
|
-
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
412
|
-
...(room ? [
|
|
413
|
-
{ label: 'Room', value: room.room_id, kind: 'code' },
|
|
414
|
-
{ label: 'Room status', value: roomStatus(room.state), kind: 'markdown' },
|
|
415
|
-
{ label: 'Saga', value: room.saga.phase, kind: 'code' },
|
|
416
|
-
] : []),
|
|
417
|
-
],
|
|
418
|
-
sections: hints.length
|
|
419
|
-
? [{ heading: 'Next steps', items: hints }]
|
|
420
|
-
: [{ heading: 'Result', items: ['No automated recovery action is available.'] }],
|
|
421
|
-
}));
|
|
382
|
+
await ctx.recoverTask(rest[0]);
|
|
422
383
|
break;
|
|
423
384
|
}
|
|
424
385
|
default:
|
|
@@ -442,11 +403,11 @@ export const ownerCommands = [
|
|
|
442
403
|
{
|
|
443
404
|
name: 'rooms', summary: 'list rooms',
|
|
444
405
|
execute: noArgs('/rooms', async (ctx) => {
|
|
445
|
-
const
|
|
446
|
-
const rooms = listRoomRecords().filter(room => room.state === 'active' || room.state === 'provisioning');
|
|
406
|
+
const rooms = await ctx.listRoomQueries();
|
|
447
407
|
await ctx.reply(renderMarkdownList({
|
|
448
408
|
icon: '🏠', title: 'Rooms', empty: 'No rooms found.',
|
|
449
|
-
records: rooms.map(
|
|
409
|
+
records: rooms.map(room => `${roomStatus(room.state)} ${markdownCode(room.room_id)} — ${markdownProse(room.room_name)}`
|
|
410
|
+
+ (room.orchestration?.task_id ? ` — Task ${markdownCode(room.orchestration.task_id)}` : '')),
|
|
450
411
|
}));
|
|
451
412
|
}),
|
|
452
413
|
},
|
|
@@ -463,24 +424,28 @@ export const ownerCommands = [
|
|
|
463
424
|
const rest = firstLineTokens.slice(1);
|
|
464
425
|
const roomTrailingLines = argLines.slice(1).join('\n').trim() || undefined;
|
|
465
426
|
const showRoom = async (id) => {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
427
|
+
let detail;
|
|
428
|
+
try {
|
|
429
|
+
detail = await ctx.getRoomQuery(id);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (!(error instanceof TaskRoomApplicationError && error.code === 'room_not_found'))
|
|
433
|
+
throw error;
|
|
434
|
+
return ctx.reply(renderMarkdownFailure({ kind: 'not_found', subject: `/room show ${id}`,
|
|
435
|
+
detail: `Room ${id} was not found.`, action: 'Run /room list to find a valid room ID.' }));
|
|
436
|
+
}
|
|
437
|
+
const { room, orchestration: r } = detail;
|
|
473
438
|
await ctx.reply(renderMarkdownResult({
|
|
474
439
|
icon: '🏠', title: 'Room details',
|
|
475
440
|
fields: [
|
|
476
|
-
{ label: 'ID', value:
|
|
477
|
-
{ label: 'Name', value:
|
|
478
|
-
{ label: 'Status', value: roomStatus(
|
|
479
|
-
{ label: 'Saga', value: `${r.saga.phase} (step ${r.saga.step_index})` },
|
|
480
|
-
...(r
|
|
481
|
-
...(r
|
|
482
|
-
...(r
|
|
483
|
-
{ label: 'Created', value: r.created_at, kind: 'code' },
|
|
441
|
+
{ label: 'ID', value: room.room_id, kind: 'code' },
|
|
442
|
+
{ label: 'Name', value: room.room_name },
|
|
443
|
+
{ label: 'Status', value: roomStatus(room.state), kind: 'markdown' },
|
|
444
|
+
...(r ? [{ label: 'Saga', value: `${r.saga.phase} (step ${r.saga.step_index})` }] : []),
|
|
445
|
+
...(r?.task_id ? [{ label: 'Task', value: r.task_id, kind: 'code' }] : []),
|
|
446
|
+
...(r?.provisioning_detail ? [{ label: 'Detail', value: r.provisioning_detail }] : []),
|
|
447
|
+
...(r?.saga.error ? [{ label: 'Last error', value: 'Provisioning failure recorded; inspect role logs.' }] : []),
|
|
448
|
+
...(r ? [{ label: 'Created', value: r.created_at, kind: 'code' }] : []),
|
|
484
449
|
],
|
|
485
450
|
}));
|
|
486
451
|
};
|
|
@@ -492,22 +457,16 @@ export const ownerCommands = [
|
|
|
492
457
|
const nameParts = rest.filter(r => !r.startsWith('--'));
|
|
493
458
|
if (!nameParts.length)
|
|
494
459
|
throw new OwnerCommandUsageError('usage: /room create --template=<name> <room name>');
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (
|
|
460
|
+
let r;
|
|
461
|
+
try {
|
|
462
|
+
r = await ctx.createRoom({ name: nameParts.join(' '), template: templateName,
|
|
463
|
+
goal: roomTrailingLines });
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (error instanceof TaskRoomApplicationError && error.code === 'template_not_found')
|
|
502
467
|
throw new OwnerCommandUsageError(`unknown template: ${templateName}`);
|
|
503
|
-
|
|
468
|
+
throw error;
|
|
504
469
|
}
|
|
505
|
-
const r = createRoomRecord({
|
|
506
|
-
room_id: randomUUID().replace(/-/g, '').slice(0, 16),
|
|
507
|
-
room_name: nameParts.join(' '),
|
|
508
|
-
...(templateSnapshot ? { template_snapshot: templateSnapshot } : {}),
|
|
509
|
-
...(roomTrailingLines ? { goal: roomTrailingLines } : {}),
|
|
510
|
-
});
|
|
511
470
|
await ctx.reply(roomAction('Room created', r, [
|
|
512
471
|
{ label: 'Name', value: r.room_name },
|
|
513
472
|
...(templateName ? [{ label: 'Template', value: templateName, kind: 'code' }] : []),
|
|
@@ -515,14 +474,14 @@ export const ownerCommands = [
|
|
|
515
474
|
break;
|
|
516
475
|
}
|
|
517
476
|
case 'list': {
|
|
518
|
-
const { listRoomRecords } = await import('../rooms-tasks/room-state.js');
|
|
519
477
|
const filter = rest[0];
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
478
|
+
if (filter && !['active', 'provisioning', 'all'].includes(filter))
|
|
479
|
+
throw new OwnerCommandUsageError('usage: /room list [active|provisioning|all]');
|
|
480
|
+
const rooms = await ctx.listRoomQueries(filter === 'active' || filter === 'provisioning' ? { state: filter } : undefined);
|
|
523
481
|
await ctx.reply(renderMarkdownList({
|
|
524
482
|
icon: '🏠', title: 'Rooms', empty: 'No rooms found.',
|
|
525
|
-
records: rooms.map(
|
|
483
|
+
records: rooms.map(room => `${roomStatus(room.state)} ${markdownCode(room.room_id)} — ${markdownProse(room.room_name)}`
|
|
484
|
+
+ (room.orchestration?.task_id ? ` — Task ${markdownCode(room.orchestration.task_id)}` : '')),
|
|
526
485
|
}));
|
|
527
486
|
break;
|
|
528
487
|
}
|
|
@@ -542,32 +501,7 @@ export const ownerCommands = [
|
|
|
542
501
|
case 'recover': {
|
|
543
502
|
if (!rest[0])
|
|
544
503
|
throw new OwnerCommandUsageError('usage: /room recover <id>');
|
|
545
|
-
|
|
546
|
-
const r = getRoomRecord(rest[0]);
|
|
547
|
-
if (!r) {
|
|
548
|
-
await ctx.reply(renderMarkdownFailure({
|
|
549
|
-
kind: 'not_found', subject: `/room recover ${rest[0]}`,
|
|
550
|
-
detail: `Room ${rest[0]} was not found.`, action: 'Run /room list to find a valid room ID.',
|
|
551
|
-
}));
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
if (r.state === 'closing' || r.state === 'closed') {
|
|
555
|
-
await ctx.closeRoom(r.room_id);
|
|
556
|
-
break;
|
|
557
|
-
}
|
|
558
|
-
await ctx.reply(renderMarkdownResult({
|
|
559
|
-
icon: '🛟', title: 'Room recovery',
|
|
560
|
-
fields: [
|
|
561
|
-
{ label: 'Room', value: r.room_id, kind: 'code' },
|
|
562
|
-
{ label: 'Status', value: roomStatus(r.state), kind: 'markdown' },
|
|
563
|
-
{ label: 'Saga', value: r.saga.phase, kind: 'code' },
|
|
564
|
-
...(r.saga.error ? [{ label: 'Last error', value: 'Provisioning failure recorded; inspect role logs.' }] : []),
|
|
565
|
-
...(r.provisioning_detail ? [{ label: 'Detail', value: r.provisioning_detail }] : []),
|
|
566
|
-
],
|
|
567
|
-
sections: [{ heading: 'Next step', items: [r.saga.error
|
|
568
|
-
? 'Run ours-fleet room recover from the CLI for full recovery.'
|
|
569
|
-
: 'No recovery action is needed.'] }],
|
|
570
|
-
}));
|
|
504
|
+
await ctx.recoverRoom(rest[0]);
|
|
571
505
|
break;
|
|
572
506
|
}
|
|
573
507
|
default:
|
|
@@ -592,8 +526,7 @@ export const ownerCommands = [
|
|
|
592
526
|
name: 'templates', aliases: ['template-list'],
|
|
593
527
|
summary: 'list available room templates',
|
|
594
528
|
execute: noArgs('/templates', async (ctx) => {
|
|
595
|
-
const
|
|
596
|
-
const templates = listTemplates({});
|
|
529
|
+
const templates = ctx.listTemplateQueries();
|
|
597
530
|
if (!templates.length)
|
|
598
531
|
return ctx.reply('📐 No templates.');
|
|
599
532
|
const lines = templates.map(t => {
|
|
@@ -613,10 +546,15 @@ export const ownerCommands = [
|
|
|
613
546
|
const parts = args.trim().split(/\s+/);
|
|
614
547
|
const sub = parts[0];
|
|
615
548
|
const showTemplate = async (nameStr) => {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
549
|
+
let t;
|
|
550
|
+
try {
|
|
551
|
+
t = ctx.getTemplateQuery(nameStr);
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
if (!(error instanceof TaskRoomApplicationError && error.code === 'template_not_found'))
|
|
555
|
+
throw error;
|
|
619
556
|
return ctx.reply(`⚠️ template not found: ${nameStr}`);
|
|
557
|
+
}
|
|
620
558
|
const lines = [
|
|
621
559
|
`📐 Template: ${t.name}@${t.version}`,
|
|
622
560
|
`Description: ${t.description}`,
|
|
@@ -635,8 +573,7 @@ export const ownerCommands = [
|
|
|
635
573
|
break;
|
|
636
574
|
}
|
|
637
575
|
case 'list': {
|
|
638
|
-
const
|
|
639
|
-
const templates = listTemplates({});
|
|
576
|
+
const templates = ctx.listTemplateQueries();
|
|
640
577
|
if (!templates.length)
|
|
641
578
|
return ctx.reply('📐 No templates.');
|
|
642
579
|
const lines = templates.map(t => {
|
|
@@ -710,5 +647,6 @@ export function fleetCliOps(role, configPath) {
|
|
|
710
647
|
}),
|
|
711
648
|
closeRoom: roomId => launchFleetWorker(['room', '_delete', roomId], `room-delete-${roomId}`, configPath),
|
|
712
649
|
settleTask: taskId => launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, configPath),
|
|
650
|
+
recoverTask: taskId => launchFleetWorker(['task', '_recover', taskId], `task-recover-${taskId}`, configPath),
|
|
713
651
|
};
|
|
714
652
|
}
|