@ours.network/fleet 1.0.4 → 1.1.0-nightly.1
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 +41 -50
- package/dist/application/capabilities.d.ts +0 -1
- package/dist/application/capabilities.js +3 -13
- package/dist/application/errors.d.ts +1 -1
- package/dist/application/fleet-query-service.d.ts +0 -3
- package/dist/application/fleet-query-service.js +1 -24
- package/dist/application/role-creation-service.d.ts +5 -5
- package/dist/application/role-creation-service.js +5 -7
- package/dist/application/role-repository.d.ts +1 -2
- package/dist/application/role-repository.js +7 -7
- package/dist/application/session-control.d.ts +0 -12
- package/dist/application/session-control.js +0 -42
- package/dist/application/session-mutations.d.ts +5 -5
- package/dist/application/task-room-service.d.ts +37 -2
- package/dist/application/task-room-service.js +49 -7
- package/dist/application/types.d.ts +0 -7
- package/dist/atomic-file.d.ts +5 -3
- package/dist/atomic-file.js +32 -8
- package/dist/briefing.js +1 -4
- package/dist/build-info.json +5 -5
- package/dist/cli.js +9 -23
- package/dist/config.d.ts +7 -7
- package/dist/config.js +13 -34
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +46 -30
- package/dist/doctor.js +0 -7
- package/dist/fleet-proxy.d.ts +1 -1
- package/dist/harness/acp-session-transport.d.ts +8 -0
- package/dist/harness/acp-session-transport.js +3 -0
- package/dist/harness/agent-session.d.ts +33 -0
- package/dist/harness/agent-session.js +1 -0
- package/dist/harness/claude-code-session.d.ts +18 -0
- package/dist/harness/claude-code-session.js +28 -0
- package/dist/harness/claude-code.d.ts +2 -1
- package/dist/harness/claude-code.js +29 -81
- package/dist/harness/codex-session.d.ts +18 -0
- package/dist/harness/codex-session.js +28 -0
- package/dist/harness/codex.d.ts +2 -1
- package/dist/harness/codex.js +35 -45
- package/dist/harness/types.d.ts +6 -38
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1 -3
- package/dist/isolation/resources.d.ts +3 -7
- package/dist/isolation/resources.js +3 -7
- package/dist/model-env.d.ts +2 -3
- package/dist/model-env.js +2 -3
- package/dist/monitor.d.ts +1 -24
- package/dist/monitor.js +5 -140
- package/dist/owner-channel/attachments.d.ts +2 -4
- package/dist/owner-channel/attachments.js +6 -39
- package/dist/owner-channel/channel.d.ts +2 -2
- package/dist/owner-channel/channel.js +16 -3
- package/dist/owner-channel/commands.d.ts +18 -1
- package/dist/owner-channel/commands.js +73 -4
- package/dist/rooms-tasks/cli.js +121 -2
- package/dist/rooms-tasks/task-lists.d.ts +19 -0
- package/dist/rooms-tasks/task-lists.js +96 -0
- package/dist/rooms-tasks/task-state.d.ts +3 -0
- package/dist/rooms-tasks/task-state.js +281 -175
- package/dist/rooms-tasks/types.d.ts +11 -1
- package/dist/runner.d.ts +5 -15
- package/dist/runner.js +26 -102
- package/dist/session/acp.d.ts +2 -2
- package/dist/session/acp.js +2 -3
- package/dist/session/activity.d.ts +2 -3
- package/dist/session/activity.js +2 -3
- package/dist/session/arbiter.d.ts +4 -4
- package/dist/session/control.d.ts +3 -3
- package/dist/session/types.d.ts +11 -5
- package/dist/spawn.js +8 -9
- package/dist/supervisor/none.d.ts +1 -1
- package/dist/supervisor/none.js +2 -2
- package/dist/tmux.d.ts +1 -14
- package/dist/tmux.js +1 -51
- package/dist/watchdog/config.js +6 -4
- package/dist/watchdog/run.js +8 -23
- package/dist/web/auth.d.ts +1 -1
- package/dist/web/runtime.js +6 -15
- package/dist/web/server.d.ts +3 -3
- package/dist/web/server.js +89 -15
- package/dist/web/topology-promote.js +2 -5
- package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
- package/dist/web-app/assets/{index-BCBK78hw.js → index-DhMDVRU1.js} +6 -6
- package/dist/web-app/index.html +2 -2
- package/package.json +3 -8
- package/dist/session/tmux.d.ts +0 -28
- package/dist/session/tmux.js +0 -80
- package/dist/web/terminal/bridge.d.ts +0 -27
- package/dist/web/terminal/bridge.js +0 -317
- package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
- package/dist/web-app/assets/index-DuC-xnX4.css +0 -1
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createRoomRecord, getRoomRecord, advanceSaga, setOwnerSeat, setSagaErro
|
|
|
8
8
|
import { createCoworkAdapter, CoworkProtocolError } from './cowork-adapter.js';
|
|
9
9
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
10
10
|
import { TaskRoomApplicationError, TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
11
|
+
import { TaskListError } from './task-lists.js';
|
|
11
12
|
class TaskRoomPublicError extends Error {
|
|
12
13
|
code;
|
|
13
14
|
fields;
|
|
@@ -128,6 +129,21 @@ function dieTaskRoom(e) {
|
|
|
128
129
|
process.stderr.write(`${output}\n`);
|
|
129
130
|
process.exit(1);
|
|
130
131
|
}
|
|
132
|
+
if (e instanceof TaskListError) {
|
|
133
|
+
const notFound = e.code === 'list_not_found';
|
|
134
|
+
const conflict = ['duplicate_name', 'reserved_name', 'default_immutable',
|
|
135
|
+
'destination_required', 'same_destination'].includes(e.code);
|
|
136
|
+
const output = renderMarkdownFailure({
|
|
137
|
+
kind: notFound ? 'not_found' : conflict ? 'state' : 'usage',
|
|
138
|
+
subject: 'ours-fleet task list',
|
|
139
|
+
detail: e.message,
|
|
140
|
+
action: notFound ? 'Run ours-fleet task lists to find a valid list.'
|
|
141
|
+
: conflict ? 'Choose a different list name or an explicit valid destination.'
|
|
142
|
+
: 'Use a valid NFC-normalized name of at most 64 Unicode code points.',
|
|
143
|
+
});
|
|
144
|
+
process.stderr.write(`${output}\n`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
131
147
|
const taskMissing = e instanceof TaskStateError
|
|
132
148
|
? /^task not found: ([A-Za-z0-9_-]{1,128})$/u.exec(e.message) : null;
|
|
133
149
|
const roomMissing = e instanceof RoomStateError
|
|
@@ -156,6 +172,7 @@ function dieTaskRoom(e) {
|
|
|
156
172
|
const taskListMarkdown = (tasks, title = 'Tasks') => renderMarkdownList({
|
|
157
173
|
icon: '📋', title, empty: 'No tasks found.',
|
|
158
174
|
records: tasks.map(t => `${taskStatus(t.state)} ${markdownCode(t.task_id)} — ${markdownProse(t.title)}`
|
|
175
|
+
+ ` — List ${markdownCode(t.list_name ?? 'default')}`
|
|
159
176
|
+ (t.blocked ? ` — 🚧 Blocked: ${markdownProse(t.blocked.reason)}` : '')),
|
|
160
177
|
});
|
|
161
178
|
const taskActionMarkdown = (title, task, fields = []) => renderMarkdownResult({
|
|
@@ -163,6 +180,7 @@ const taskActionMarkdown = (title, task, fields = []) => renderMarkdownResult({
|
|
|
163
180
|
fields: [
|
|
164
181
|
{ label: 'ID', value: task.task_id, kind: 'code' },
|
|
165
182
|
{ label: 'Status', value: taskStatus(task.state), kind: 'markdown' },
|
|
183
|
+
{ label: 'List', value: task.list_name ?? 'default', kind: 'code' },
|
|
166
184
|
...fields,
|
|
167
185
|
],
|
|
168
186
|
});
|
|
@@ -435,6 +453,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
435
453
|
.option('--backlog', 'create in backlog (do not start immediately)')
|
|
436
454
|
.option('--no-room', 'create task without a room')
|
|
437
455
|
.option('--idempotency-key <key>', 'idempotency key')
|
|
456
|
+
.option('--list <name>', 'task list (default: default)')
|
|
438
457
|
.option('--json', 'JSON output')
|
|
439
458
|
.action(async (opts) => {
|
|
440
459
|
try {
|
|
@@ -443,6 +462,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
443
462
|
brief: opts.brief, briefFile: opts.briefFile, template: opts.template,
|
|
444
463
|
backlog: opts.backlog, noRoom: opts.room === false,
|
|
445
464
|
idempotencyKey: opts.idempotencyKey, origin: { type: 'cli' },
|
|
465
|
+
list: opts.list,
|
|
446
466
|
});
|
|
447
467
|
if (opts.json) {
|
|
448
468
|
console.log(JSON.stringify({ schema_version: 1, task: record }, null, 2));
|
|
@@ -464,6 +484,8 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
464
484
|
cOpt(taskCmd.command('list'))
|
|
465
485
|
.description('list tasks')
|
|
466
486
|
.option('--state <state>', 'filter by state (backlog|active|provisioning|review|done|cancelled|failed|all)')
|
|
487
|
+
.option('--list <name>', 'filter by task list')
|
|
488
|
+
.option('--group-by-list', 'group deterministic results by list (JSON)')
|
|
467
489
|
.option('--json', 'JSON output')
|
|
468
490
|
.action((opts) => {
|
|
469
491
|
try {
|
|
@@ -471,9 +493,13 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
471
493
|
if (opts.state && opts.state !== 'all') {
|
|
472
494
|
stateFilter = opts.state;
|
|
473
495
|
}
|
|
474
|
-
const
|
|
496
|
+
const service = taskRoomService(opts.configuration);
|
|
497
|
+
const filter = { ...(stateFilter ? { state: stateFilter } : {}), ...(opts.list ? { list: opts.list } : {}) };
|
|
498
|
+
const tasks = service.listTasks(filter);
|
|
475
499
|
if (opts.json) {
|
|
476
|
-
console.log(JSON.stringify(
|
|
500
|
+
console.log(JSON.stringify(opts.groupByList
|
|
501
|
+
? { schema_version: 1, groups: service.groupedTasks(filter) }
|
|
502
|
+
: { schema_version: 1, tasks }, null, 2));
|
|
477
503
|
return;
|
|
478
504
|
}
|
|
479
505
|
console.log(taskListMarkdown(tasks));
|
|
@@ -484,6 +510,99 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
484
510
|
dieTaskRoom(e);
|
|
485
511
|
}
|
|
486
512
|
});
|
|
513
|
+
taskCmd.command('lists')
|
|
514
|
+
.description('list named task lists')
|
|
515
|
+
.option('--json', 'JSON output')
|
|
516
|
+
.action((opts) => {
|
|
517
|
+
try {
|
|
518
|
+
const lists = taskRoomService().listTaskLists();
|
|
519
|
+
if (opts.json) {
|
|
520
|
+
console.log(JSON.stringify({ schema_version: 1, lists }, null, 2));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
console.log(renderMarkdownList({ icon: '📚', title: 'Task lists', empty: 'No task lists found.',
|
|
524
|
+
records: lists.map(list => `${markdownCode(list.name)}${list.built_in ? ' — built-in' : ''}`) }));
|
|
525
|
+
}
|
|
526
|
+
catch (e) {
|
|
527
|
+
if (opts.json)
|
|
528
|
+
die(e);
|
|
529
|
+
dieTaskRoom(e);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
taskCmd.command('list-create <name>')
|
|
533
|
+
.description('create a named task list')
|
|
534
|
+
.option('--json', 'JSON output')
|
|
535
|
+
.action(async (name, opts) => {
|
|
536
|
+
try {
|
|
537
|
+
const list = await taskRoomService().createTaskList({ actor: { kind: 'local_control', surface: 'cli' }, name });
|
|
538
|
+
if (opts.json) {
|
|
539
|
+
console.log(JSON.stringify({ schema_version: 1, list }, null, 2));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
console.log(renderMarkdownResult({ icon: '📚', title: 'Task list created', fields: [{ label: 'Name', value: list.name }] }));
|
|
543
|
+
}
|
|
544
|
+
catch (e) {
|
|
545
|
+
if (opts.json)
|
|
546
|
+
die(e);
|
|
547
|
+
dieTaskRoom(e);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
taskCmd.command('list-rename <name> <new-name>')
|
|
551
|
+
.description('rename a named task list')
|
|
552
|
+
.option('--json', 'JSON output')
|
|
553
|
+
.action(async (name, newName, opts) => {
|
|
554
|
+
try {
|
|
555
|
+
const list = await taskRoomService().renameTaskList({ actor: { kind: 'local_control', surface: 'cli' }, name, newName });
|
|
556
|
+
if (opts.json) {
|
|
557
|
+
console.log(JSON.stringify({ schema_version: 1, list }, null, 2));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
console.log(renderMarkdownResult({ icon: '📚', title: 'Task list renamed', fields: [{ label: 'Name', value: list.name }] }));
|
|
561
|
+
}
|
|
562
|
+
catch (e) {
|
|
563
|
+
if (opts.json)
|
|
564
|
+
die(e);
|
|
565
|
+
dieTaskRoom(e);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
taskCmd.command('list-delete <name>')
|
|
569
|
+
.description('delete a task list; non-empty lists require --move-to')
|
|
570
|
+
.option('--move-to <name>', 'destination for assigned tasks')
|
|
571
|
+
.option('--json', 'JSON output')
|
|
572
|
+
.action(async (name, opts) => {
|
|
573
|
+
try {
|
|
574
|
+
const result = await taskRoomService().deleteTaskList({ actor: { kind: 'local_control', surface: 'cli' }, name, destination: opts.moveTo });
|
|
575
|
+
if (opts.json) {
|
|
576
|
+
console.log(JSON.stringify({ schema_version: 1, ...result }, null, 2));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
console.log(renderMarkdownResult({ icon: '🗑️', title: 'Task list deleted', fields: [{ label: 'Name', value: result.deleted.name }, { label: 'Tasks moved', value: String(result.moved) }] }));
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
if (opts.json)
|
|
583
|
+
die(e);
|
|
584
|
+
dieTaskRoom(e);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
taskCmd.command('move <id>')
|
|
588
|
+
.description('move a task to another list without changing its lifecycle')
|
|
589
|
+
.requiredOption('--list <name>', 'destination task list')
|
|
590
|
+
.option('--json', 'JSON output')
|
|
591
|
+
.action(async (id, opts) => {
|
|
592
|
+
try {
|
|
593
|
+
const task = await taskRoomService().moveTask({ actor: { kind: 'local_control', surface: 'cli' }, taskId: id, list: opts.list });
|
|
594
|
+
if (opts.json) {
|
|
595
|
+
console.log(JSON.stringify({ schema_version: 1, task }, null, 2));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
console.log(taskActionMarkdown('Task moved', task));
|
|
599
|
+
}
|
|
600
|
+
catch (e) {
|
|
601
|
+
if (opts.json)
|
|
602
|
+
die(e);
|
|
603
|
+
dieTaskRoom(e);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
487
606
|
taskCmd.command('show <id>')
|
|
488
607
|
.description('show task details')
|
|
489
608
|
.option('--json', 'JSON output')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TaskListRecord } from './types.js';
|
|
2
|
+
export declare const DEFAULT_TASK_LIST_ID = "default";
|
|
3
|
+
export declare const TASK_LIST_NAME_MAX_CODE_POINTS = 64;
|
|
4
|
+
export declare class TaskListError extends Error {
|
|
5
|
+
readonly code: 'invalid_name' | 'duplicate_name' | 'reserved_name' | 'list_not_found' | 'default_immutable' | 'destination_required' | 'same_destination';
|
|
6
|
+
constructor(code: 'invalid_name' | 'duplicate_name' | 'reserved_name' | 'list_not_found' | 'default_immutable' | 'destination_required' | 'same_destination', message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare const taskListsPath: () => string;
|
|
9
|
+
export declare const taskListsLockPath: () => string;
|
|
10
|
+
export declare const withTaskListsLock: <T>(fn: () => T | Promise<T>) => Promise<T>;
|
|
11
|
+
export declare function normalizeTaskListName(input: string): string;
|
|
12
|
+
export declare function readTaskLists(): TaskListRecord[];
|
|
13
|
+
export declare function resolveTaskList(name: string, lists?: TaskListRecord[]): TaskListRecord;
|
|
14
|
+
/** Caller must hold withTaskListsLock. */
|
|
15
|
+
export declare function createTaskListLocked(name: string): TaskListRecord;
|
|
16
|
+
/** Caller must hold withTaskListsLock. */
|
|
17
|
+
export declare function renameTaskListLocked(currentName: string, nextName: string): TaskListRecord;
|
|
18
|
+
/** Caller must hold withTaskListsLock. */
|
|
19
|
+
export declare function deleteTaskListRecordLocked(listId: string): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
5
|
+
import { stateRoot } from '../paths.js';
|
|
6
|
+
export const DEFAULT_TASK_LIST_ID = 'default';
|
|
7
|
+
export const TASK_LIST_NAME_MAX_CODE_POINTS = 64;
|
|
8
|
+
const CREATED_AT = '1970-01-01T00:00:00.000Z';
|
|
9
|
+
export class TaskListError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = 'TaskListError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const taskListsPath = () => join(stateRoot(), 'task-lists.json');
|
|
18
|
+
export const taskListsLockPath = () => join(stateRoot(), 'task-lists.lock');
|
|
19
|
+
export const withTaskListsLock = (fn) => withFileLock(taskListsLockPath(), fn);
|
|
20
|
+
const defaultList = () => ({
|
|
21
|
+
list_id: DEFAULT_TASK_LIST_ID, name: 'default', built_in: true, created_at: CREATED_AT,
|
|
22
|
+
});
|
|
23
|
+
const compareNames = (a, b) => {
|
|
24
|
+
if (a.list_id === DEFAULT_TASK_LIST_ID)
|
|
25
|
+
return b.list_id === DEFAULT_TASK_LIST_ID ? 0 : -1;
|
|
26
|
+
if (b.list_id === DEFAULT_TASK_LIST_ID)
|
|
27
|
+
return 1;
|
|
28
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : a.list_id.localeCompare(b.list_id);
|
|
29
|
+
};
|
|
30
|
+
export function normalizeTaskListName(input) {
|
|
31
|
+
if (typeof input !== 'string')
|
|
32
|
+
throw new TaskListError('invalid_name', 'list name must be a string');
|
|
33
|
+
const normalized = input.normalize('NFC');
|
|
34
|
+
if (normalized.trim() !== normalized || normalized.length === 0)
|
|
35
|
+
throw new TaskListError('invalid_name', 'list name must be non-empty with no leading or trailing whitespace');
|
|
36
|
+
if ([...normalized].length > TASK_LIST_NAME_MAX_CODE_POINTS)
|
|
37
|
+
throw new TaskListError('invalid_name', `list name must be at most ${TASK_LIST_NAME_MAX_CODE_POINTS} Unicode code points`);
|
|
38
|
+
if (/[\p{Cc}\p{Cf}\/\\]/u.test(normalized))
|
|
39
|
+
throw new TaskListError('invalid_name', 'list name contains a forbidden control, format, or path character');
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
export function readTaskLists() {
|
|
43
|
+
const path = taskListsPath();
|
|
44
|
+
if (!existsSync(path))
|
|
45
|
+
return [defaultList()];
|
|
46
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
47
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.lists))
|
|
48
|
+
throw new Error('invalid task list registry');
|
|
49
|
+
const custom = parsed.lists.filter(item => item.list_id !== DEFAULT_TASK_LIST_ID);
|
|
50
|
+
return [defaultList(), ...custom].sort(compareNames);
|
|
51
|
+
}
|
|
52
|
+
function writeTaskLists(lists) {
|
|
53
|
+
const custom = lists.filter(item => item.list_id !== DEFAULT_TASK_LIST_ID).sort(compareNames);
|
|
54
|
+
replaceFileAtomically(taskListsPath(), JSON.stringify({ version: 1, lists: custom }, null, 2) + '\n');
|
|
55
|
+
}
|
|
56
|
+
export function resolveTaskList(name, lists = readTaskLists()) {
|
|
57
|
+
const normalized = normalizeTaskListName(name);
|
|
58
|
+
const found = lists.find(item => item.name === normalized);
|
|
59
|
+
if (!found)
|
|
60
|
+
throw new TaskListError('list_not_found', `task list not found: ${normalized}`);
|
|
61
|
+
return found;
|
|
62
|
+
}
|
|
63
|
+
/** Caller must hold withTaskListsLock. */
|
|
64
|
+
export function createTaskListLocked(name) {
|
|
65
|
+
const normalized = normalizeTaskListName(name);
|
|
66
|
+
if (normalized === 'default')
|
|
67
|
+
throw new TaskListError('reserved_name', "'default' is a reserved built-in list");
|
|
68
|
+
const lists = readTaskLists();
|
|
69
|
+
if (lists.some(item => item.name === normalized))
|
|
70
|
+
throw new TaskListError('duplicate_name', `task list already exists: ${normalized}`);
|
|
71
|
+
const record = {
|
|
72
|
+
list_id: `list_${randomUUID().replace(/-/g, '')}`, name: normalized,
|
|
73
|
+
built_in: false, created_at: new Date().toISOString(),
|
|
74
|
+
};
|
|
75
|
+
writeTaskLists([...lists, record]);
|
|
76
|
+
return record;
|
|
77
|
+
}
|
|
78
|
+
/** Caller must hold withTaskListsLock. */
|
|
79
|
+
export function renameTaskListLocked(currentName, nextName) {
|
|
80
|
+
const lists = readTaskLists();
|
|
81
|
+
const current = resolveTaskList(currentName, lists);
|
|
82
|
+
if (current.built_in)
|
|
83
|
+
throw new TaskListError('default_immutable', "the 'default' list cannot be renamed");
|
|
84
|
+
const normalized = normalizeTaskListName(nextName);
|
|
85
|
+
if (normalized === 'default')
|
|
86
|
+
throw new TaskListError('reserved_name', "'default' is a reserved built-in list");
|
|
87
|
+
if (lists.some(item => item.list_id !== current.list_id && item.name === normalized))
|
|
88
|
+
throw new TaskListError('duplicate_name', `task list already exists: ${normalized}`);
|
|
89
|
+
const renamed = { ...current, name: normalized };
|
|
90
|
+
writeTaskLists(lists.map(item => item.list_id === current.list_id ? renamed : item));
|
|
91
|
+
return renamed;
|
|
92
|
+
}
|
|
93
|
+
/** Caller must hold withTaskListsLock. */
|
|
94
|
+
export function deleteTaskListRecordLocked(listId) {
|
|
95
|
+
writeTaskLists(readTaskLists().filter(item => item.list_id !== listId));
|
|
96
|
+
}
|
|
@@ -12,12 +12,15 @@ export interface CreateTaskInput {
|
|
|
12
12
|
start?: boolean;
|
|
13
13
|
no_room?: boolean;
|
|
14
14
|
room_id?: string;
|
|
15
|
+
listId?: string;
|
|
15
16
|
}
|
|
16
17
|
export declare function createTask(input: CreateTaskInput): TaskRecord;
|
|
17
18
|
export declare function getTask(id: string): TaskRecord;
|
|
18
19
|
export declare function listTasks(filter?: {
|
|
19
20
|
state?: TaskState | TaskState[];
|
|
21
|
+
listId?: string;
|
|
20
22
|
}): TaskRecord[];
|
|
23
|
+
export declare function moveTaskToList(id: string, listId: string): TaskRecord;
|
|
21
24
|
export declare function findByIdempotencyKey(key: string): TaskRecord | undefined;
|
|
22
25
|
export declare function startTask(id: string): TaskRecord;
|
|
23
26
|
export declare function activateTask(id: string): TaskRecord;
|