@ours.network/fleet 2.2.3 → 2.2.5
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 +2 -0
- package/dist/application/task-room-service.d.ts +2 -2
- package/dist/application/task-room-service.js +4 -5
- package/dist/briefing.js +8 -2
- package/dist/build-info.json +4 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +11 -3
- package/dist/notifications/mission-ownership.d.ts +3 -4
- package/dist/notifications/mission-ownership.js +4 -5
- package/dist/notifications/task-producer.d.ts +1 -2
- package/dist/notifications/task-producer.js +9 -17
- package/dist/preset-migration.js +12 -0
- package/dist/rooms-tasks/cli.js +19 -5
- package/dist/rooms-tasks/layout-service.d.ts +1 -1
- package/dist/rooms-tasks/layout-service.js +1 -1
- package/dist/rooms-tasks/layout.d.ts +2 -2
- package/dist/rooms-tasks/layout.js +9 -3
- package/dist/rooms-tasks/task-review.d.ts +28 -1
- package/dist/rooms-tasks/task-review.js +56 -3
- package/dist/rooms-tasks/task-tools.d.ts +1 -1
- package/dist/rooms-tasks/task-tools.js +23 -7
- package/dist/web/resource-views.js +8 -0
- package/docs/task-progress-review.md +75 -21
- package/package.json +1 -1
- package/presets/fleet/roles/Coordinator.yaml +3 -2
- package/presets/fleet/roles/Critic.yaml +3 -1
- package/presets/fleet/roles/Developer.yaml +15 -0
- package/presets/fleet/roles/Engineer.yaml +15 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +31 -15
- package/presets/manifest.json +1 -1
|
@@ -16,8 +16,7 @@ export class TaskNotificationProducer {
|
|
|
16
16
|
offset = 0;
|
|
17
17
|
stopped = false;
|
|
18
18
|
shutdown = new AbortController();
|
|
19
|
-
|
|
20
|
-
bootstrapAt = 0;
|
|
19
|
+
bootstrapAttempted = false;
|
|
21
20
|
constructor(config, warn = () => { }, request = fetch,
|
|
22
21
|
/** Existing tasks and their layout room CIDs, registered once per start before own-room OFF can be chosen. */
|
|
23
22
|
missions) {
|
|
@@ -68,21 +67,14 @@ export class TaskNotificationProducer {
|
|
|
68
67
|
// Deletion first: a retired task's notifications go before any other delivery work in this pass.
|
|
69
68
|
if (convertRetiredTaskNotifications() || await drainMissionCleanup(this.config, this.warn, this.request))
|
|
70
69
|
this.retry();
|
|
71
|
-
if (this.missions && !this.
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.bootstrapped = true;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
this.warn('Mission room registration pending; own-room message preference stays unavailable');
|
|
83
|
-
this.bootstrapAt = Date.now() + 30_000;
|
|
84
|
-
this.retry(30_000);
|
|
85
|
-
}
|
|
70
|
+
if (this.missions && !this.bootstrapAttempted && !this.stopped) {
|
|
71
|
+
// One startup attempt. Association failure neither blocks task events nor schedules another registration.
|
|
72
|
+
this.bootstrapAttempted = true;
|
|
73
|
+
try {
|
|
74
|
+
await bootstrapMissionRooms(this.config, this.missions.tasks, this.missions.layoutRooms, this.request);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
this.warn('Mission room registration failed; own-room message preference may be unavailable');
|
|
86
78
|
}
|
|
87
79
|
}
|
|
88
80
|
const pending = pendingTaskNotifications(this.warn);
|
package/dist/preset-migration.js
CHANGED
|
@@ -99,6 +99,16 @@ const REVIEW_PACKAGE_FINGERPRINTS = Object.freeze({
|
|
|
99
99
|
'roles/Coordinator.yaml': ['1fa72470f37330a96debdfa6a64004280edcf318fafd5f6a5065a06ee7e87acc'],
|
|
100
100
|
'agent_templates/LocalCoordinator.yaml': ['931908da43df7c85dd2b106c99c765a3aeb4a97361fa00ce110e3ac93221ce32'],
|
|
101
101
|
});
|
|
102
|
+
// The packaged revision-12 charters before the 400-character Progress rules, the single Progress
|
|
103
|
+
// writer and the current-request To review lifecycle with clear. Untouched copies adopt the current
|
|
104
|
+
// text; edited ones stay.
|
|
105
|
+
const CURRENT_REQUEST_FINGERPRINTS = Object.freeze({
|
|
106
|
+
'roles/Developer.yaml': ['684b4cb278b92746ba3abe29523adb9ab585e0831f8519b17bc17f41a3e992a4'],
|
|
107
|
+
'roles/Critic.yaml': ['b5b1d00074e6540160b14da256609b42600fa7d94ba776f8d5e7b42771669c41'],
|
|
108
|
+
'roles/Engineer.yaml': ['79991291c354229a71cdbd7191ad132ea91499b5326b1386c7abe8ce1444f714'],
|
|
109
|
+
'roles/LocalCoordinator.yaml': ['0fdbb9b56fedcf1897ad8044d6d88eaefcd5ccc48176860da3f7a1efe22da51d'],
|
|
110
|
+
'roles/Coordinator.yaml': ['c9038f3e33068058884e2ae52f6d569c22c4d56fcdb345e9381866c6de38d9a9'],
|
|
111
|
+
});
|
|
102
112
|
const CURRENT_ROLE_DEFAULTS = new Set([
|
|
103
113
|
'roles/Coordinator.yaml', 'roles/LocalCoordinator.yaml', 'roles/Developer.yaml', 'roles/Critic.yaml',
|
|
104
114
|
'roles/Engineer.yaml',
|
|
@@ -232,6 +242,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
|
|
|
232
242
|
...Object.keys(REREADING_CONTINUITY_LOOP_FINGERPRINTS),
|
|
233
243
|
...Object.keys(PEER_WAIT_REMINDER_FINGERPRINTS),
|
|
234
244
|
...Object.keys(REVIEW_PACKAGE_FINGERPRINTS),
|
|
245
|
+
...Object.keys(CURRENT_REQUEST_FINGERPRINTS),
|
|
235
246
|
]);
|
|
236
247
|
for (const relative of known) {
|
|
237
248
|
const path = join(root, relative);
|
|
@@ -249,6 +260,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
|
|
|
249
260
|
...(REREADING_CONTINUITY_LOOP_FINGERPRINTS[relative] ?? []),
|
|
250
261
|
...(PEER_WAIT_REMINDER_FINGERPRINTS[relative] ?? []),
|
|
251
262
|
...(REVIEW_PACKAGE_FINGERPRINTS[relative] ?? []),
|
|
263
|
+
...(CURRENT_REQUEST_FINGERPRINTS[relative] ?? []),
|
|
252
264
|
].some(expected => expected !== undefined && actual === expected);
|
|
253
265
|
if (recognized)
|
|
254
266
|
(CURRENT_ROLE_DEFAULTS.has(relative) ? replacements : removals).push(path);
|
package/dist/rooms-tasks/cli.js
CHANGED
|
@@ -39,13 +39,13 @@ function cliAnonymousOverride(argv) {
|
|
|
39
39
|
throw new Error('--anonymous and --no-anonymous are mutually exclusive');
|
|
40
40
|
return enabled ? true : disabled ? false : undefined;
|
|
41
41
|
}
|
|
42
|
-
import { getTask, getDeletingTask, TaskStateError, } from './task-state.js';
|
|
42
|
+
import { getTask, getDeletingTask, TaskStateError, reviewRevision, } from './task-state.js';
|
|
43
43
|
import { getRoomRecord, RoomStateError, } from './room-state.js';
|
|
44
44
|
import { createCoworkAdapter } from './cowork-adapter.js';
|
|
45
45
|
import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList, renderMarkdownResult, roomStatus, taskStatus, } from './markdown.js';
|
|
46
46
|
import { recordTaskProvisioningOutcome, TaskRoomApplicationError, TaskRoomApplicationService, } from '../application/task-room-service.js';
|
|
47
47
|
import { TaskListError } from './task-lists.js';
|
|
48
|
-
import { appendTaskProgress, attachTaskReviewArtifact, detachTaskReviewArtifact, getTaskReview, listTaskProgress, openTaskWorkspaceFile, publicProgressEntry, publicReview, setTaskReviewMarkdown, TaskReviewError, } from './task-review.js';
|
|
48
|
+
import { appendTaskProgress, attachTaskReviewArtifact, clearTaskReview, detachTaskReviewArtifact, getTaskReview, listTaskProgress, openTaskWorkspaceFile, publicProgressEntry, publicReview, setTaskReviewMarkdown, TaskReviewError, } from './task-review.js';
|
|
49
49
|
import { verifiedFleetCaller } from '../fleet-command-audit.js';
|
|
50
50
|
import { beginFleetAuditCollection, consumeFleetAuditCollection, recordFleetAuditFailure, recordFleetAuditPresentation, recordFleetAuditResource, } from '../fleet-command-audit.js';
|
|
51
51
|
import { renderAgentConfiguration } from '../lifecycle-summary.js';
|
|
@@ -1108,6 +1108,8 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1108
1108
|
.option('--attach <path>', 'retain a file from the task workspace in the package (repeatable)', (value, all) => [...all, value], [])
|
|
1109
1109
|
.option('--detach <artifact-id>', 'remove a file from the package (repeatable)', (value, all) => [...all, value], [])
|
|
1110
1110
|
.option('--request-key <key>', 'retry key for a single --attach: repeating it with the same file returns the original artifact')
|
|
1111
|
+
.option('--clear', 'remove the resolved request: all Markdown and files, replaced by --markdown[-file] if given (needs --expected-revision)')
|
|
1112
|
+
.option('--expected-revision <n>', 'package revision you last read; --clear is refused if it changed since')
|
|
1111
1113
|
.option('--json', 'JSON output')
|
|
1112
1114
|
.action(async (id, opts) => {
|
|
1113
1115
|
try {
|
|
@@ -1115,10 +1117,22 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1115
1117
|
throw new Error('use --markdown or --markdown-file, not both');
|
|
1116
1118
|
if (opts.requestKey !== undefined && opts.attach.length !== 1)
|
|
1117
1119
|
throw new Error('--request-key needs exactly one --attach');
|
|
1120
|
+
if (!!opts.clear !== (opts.expectedRevision !== undefined))
|
|
1121
|
+
throw new TaskReviewError('invalid_request', '--clear and --expected-revision go together');
|
|
1122
|
+
if (opts.clear && opts.detach.length)
|
|
1123
|
+
throw new TaskReviewError('invalid_request', '--clear already removes every file; drop --detach');
|
|
1124
|
+
if (opts.clear && opts.attach.length)
|
|
1125
|
+
throw new TaskReviewError('invalid_request', '--clear cannot attach in the same call; attach the next request\'s files after reading the package again');
|
|
1126
|
+
const expectedRevision = opts.expectedRevision !== undefined ? Number(opts.expectedRevision) : undefined;
|
|
1127
|
+
if (expectedRevision !== undefined && (!/^\d+$/.test(opts.expectedRevision) || !Number.isSafeInteger(expectedRevision)))
|
|
1128
|
+
throw new TaskReviewError('invalid_request', '--expected-revision must be a non-negative integer');
|
|
1118
1129
|
const markdown = opts.markdownFile !== undefined ? readFileSync(opts.markdownFile, 'utf8') : opts.markdown;
|
|
1119
|
-
const author = markdown !== undefined || opts.attach.length || opts.detach.length ? cliContributionAuthor(id) : undefined;
|
|
1130
|
+
const author = markdown !== undefined || opts.attach.length || opts.detach.length || opts.clear ? cliContributionAuthor(id) : undefined;
|
|
1120
1131
|
const attached = [];
|
|
1121
|
-
|
|
1132
|
+
// A clear with Markdown replaces the request in one fenced change; nothing can land in between.
|
|
1133
|
+
if (opts.clear)
|
|
1134
|
+
clearTaskReview(id, { expectedRevision: expectedRevision, author: author, markdown });
|
|
1135
|
+
else if (markdown !== undefined)
|
|
1122
1136
|
setTaskReviewMarkdown(id, { markdown, expectedMarkdown: getTaskReview(id)?.markdown ?? '', author: author });
|
|
1123
1137
|
for (const artifactId of opts.detach)
|
|
1124
1138
|
detachTaskReviewArtifact(id, artifactId, { author: author });
|
|
@@ -1136,7 +1150,7 @@ export function registerTaskCommands(parent, cOpt) {
|
|
|
1136
1150
|
return;
|
|
1137
1151
|
}
|
|
1138
1152
|
const files = review.artifacts.map(artifact => `- \`${artifact.id}\` ${artifact.name} (${artifact.mime}, ${artifact.size} bytes)`).join('\n');
|
|
1139
|
-
console.log(`## To review — task ${id}\n\n${review.markdown || '_No review Markdown yet._'}\n\n### Files\n\n${files || '_No files._'}`
|
|
1153
|
+
console.log(`## To review — task ${id} (revision ${reviewRevision(review)})\n\n${review.markdown || '_No review Markdown yet._'}\n\n### Files\n\n${files || '_No files._'}`
|
|
1140
1154
|
+ (attached.some(item => item.replayed) ? '\n\n(The attached file was already recorded.)' : ''));
|
|
1141
1155
|
}
|
|
1142
1156
|
catch (e) {
|
|
@@ -30,7 +30,7 @@ export declare class RoomLayoutService {
|
|
|
30
30
|
supervisor(id?: string, templates?: Record<string, AgentTemplateDefinition>): NativeLayoutSupervisor;
|
|
31
31
|
private owner;
|
|
32
32
|
private engine;
|
|
33
|
-
/** A task's layout rooms
|
|
33
|
+
/** A task's layout rooms attempt notification registration before Owner admission. */
|
|
34
34
|
private beforeOwner;
|
|
35
35
|
open(id: string, cleanup?: boolean): RoomLayout;
|
|
36
36
|
/** `scope` lets an owner (e.g. a Task) snapshot an adjusted definition and pin agent working directories. */
|
|
@@ -180,7 +180,7 @@ export class RoomLayoutService {
|
|
|
180
180
|
assertLayoutFile(path);
|
|
181
181
|
return new RoomLayout(path, () => createCoworkAdapter(), this.supervisor(id), cleanup ? undefined : this.owner(), this.beforeOwner(id));
|
|
182
182
|
}
|
|
183
|
-
/** A task's layout rooms
|
|
183
|
+
/** A task's layout rooms attempt notification registration before Owner admission. */
|
|
184
184
|
beforeOwner(id) {
|
|
185
185
|
const taskId = /^task-([0-9a-z]{9}[0-9a-f]{8})$/.exec(id)?.[1];
|
|
186
186
|
return taskId ? async (roomCid) => (await import('../notifications/mission-ownership.js')).associateRoomNotifications(taskId, roomCid) : undefined;
|
|
@@ -104,12 +104,12 @@ export declare class RoomLayout {
|
|
|
104
104
|
private file;
|
|
105
105
|
private supervisor;
|
|
106
106
|
private owner?;
|
|
107
|
-
/**
|
|
107
|
+
/** Best-effort notification registration before the Owner joins each room. */
|
|
108
108
|
private beforeOwner?;
|
|
109
109
|
private coworkAdapter?;
|
|
110
110
|
private readonly coworkFactory;
|
|
111
111
|
constructor(file: string, cowork: CoworkAdapter | (() => CoworkAdapter), supervisor: LayoutSupervisor, owner?: LayoutOwner | undefined,
|
|
112
|
-
/**
|
|
112
|
+
/** Best-effort notification registration before the Owner joins each room. */
|
|
113
113
|
beforeOwner?: ((roomCid: string) => Promise<void>) | undefined);
|
|
114
114
|
private get cowork();
|
|
115
115
|
snapshot(): RoomLayoutState;
|
|
@@ -43,7 +43,7 @@ export class RoomLayout {
|
|
|
43
43
|
coworkAdapter;
|
|
44
44
|
coworkFactory;
|
|
45
45
|
constructor(file, cowork, supervisor, owner,
|
|
46
|
-
/**
|
|
46
|
+
/** Best-effort notification registration before the Owner joins each room. */
|
|
47
47
|
beforeOwner) {
|
|
48
48
|
this.file = file;
|
|
49
49
|
this.supervisor = supervisor;
|
|
@@ -165,8 +165,14 @@ export class RoomLayout {
|
|
|
165
165
|
activation_requirements: !spec.members.length && s.owner ? [{ role: s.owner.role, count: 1 }]
|
|
166
166
|
: [...new Set(spec.members.map(roleFor))].map(role => ({ role, count: spec.members.filter(m => roleFor(m) === role).length })),
|
|
167
167
|
}), native => { room.native = native; s.order.push(key); });
|
|
168
|
-
if (s.owner && !room.owner_ready && this.beforeOwner)
|
|
169
|
-
|
|
168
|
+
if (s.owner && !room.owner_ready && this.beforeOwner) {
|
|
169
|
+
try {
|
|
170
|
+
await this.beforeOwner(room.native.identity_cid);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
console.warn('Mission room notification registration failed; continuing room admission with ordinary notifications');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
170
176
|
if (s.owner && !room.owner_ready)
|
|
171
177
|
await this.mutation(s, `owner:${key}`, async () => {
|
|
172
178
|
await this.cowork.acceptInvite(room.native.room_id, this.owner.invite, { role: s.owner.role, expected_cid: s.owner.cid });
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { TaskContributionAuthor, TaskProgressEntry, TaskRecord, TaskReview, TaskReviewArtifact } from './types.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Maximum length of a new Progress entry, in Unicode code points of the exact
|
|
4
|
+
* submitted text (no normalization or trimming). A combining sequence or a
|
|
5
|
+
* joined emoji counts as each of its code points. Stored entries are not
|
|
6
|
+
* re-checked, so older, longer entries still read and display.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PROGRESS_TEXT_MAX = 400;
|
|
3
9
|
export declare const PROGRESS_MAX_ENTRIES = 200;
|
|
4
10
|
export declare const REVIEW_MARKDOWN_MAX = 50000;
|
|
5
11
|
export declare const REVIEW_MAX_ARTIFACTS = 20;
|
|
@@ -15,6 +21,8 @@ export declare function listTaskProgress(taskId: string): TaskProgressEntry[];
|
|
|
15
21
|
export declare function taskProgressSnapshot(taskId: string): Array<Omit<TaskProgressEntry, 'request_key'> & {
|
|
16
22
|
unread: boolean;
|
|
17
23
|
}>;
|
|
24
|
+
/** Progress length in Unicode code points: a character outside the BMP counts once, not as two UTF-16 units. */
|
|
25
|
+
export declare function progressTextLength(text: string): number;
|
|
18
26
|
/**
|
|
19
27
|
* Append one milestone. Text is kept exactly as written; whitespace-only text is
|
|
20
28
|
* rejected. A retry with the same author-scoped request_key returns the original
|
|
@@ -39,6 +47,25 @@ export declare function setTaskReviewMarkdown(taskId: string, input: {
|
|
|
39
47
|
expectedMarkdown: string;
|
|
40
48
|
author: TaskContributionAuthor;
|
|
41
49
|
}): TaskReview;
|
|
50
|
+
/**
|
|
51
|
+
* Clear the resolved To review request: Markdown and every attached file, in
|
|
52
|
+
* one change fenced by the whole package revision the writer last read. Any
|
|
53
|
+
* Markdown edit, attach or detach since then is a conflict and nothing is
|
|
54
|
+
* removed. Clearing an already empty package at the expected revision succeeds
|
|
55
|
+
* unchanged; after a successful clear, read the package again before retrying.
|
|
56
|
+
* Like any change, a clear gets the next revision, so read markers, unread and
|
|
57
|
+
* push behave as for any update. Only the files this change removed are
|
|
58
|
+
* deleted; a file attached after it stays. With `markdown`, the same change
|
|
59
|
+
* writes the next request, so no other writer can slip in between.
|
|
60
|
+
*/
|
|
61
|
+
export declare function clearTaskReview(taskId: string, input: {
|
|
62
|
+
expectedRevision: number;
|
|
63
|
+
author: TaskContributionAuthor;
|
|
64
|
+
markdown?: string;
|
|
65
|
+
}): {
|
|
66
|
+
review: TaskReview | undefined;
|
|
67
|
+
removed: TaskReviewArtifact[];
|
|
68
|
+
};
|
|
42
69
|
export declare const reviewStoreRoot: () => string;
|
|
43
70
|
export declare function reviewStoreDirectory(taskId: string): string;
|
|
44
71
|
/** A file being read; `verify` fails when it changed since it was opened. */
|
|
@@ -14,7 +14,13 @@ import { stateRoot } from '../paths.js';
|
|
|
14
14
|
import { MAX_FILE_BYTES, openDirectory } from '../file-delivery/reader.js';
|
|
15
15
|
import { contributionUnread, getTask, reviewRevision, TaskStateError, updateTaskContribution } from './task-state.js';
|
|
16
16
|
import { assertWorkspacePresent } from './workspace.js';
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Maximum length of a new Progress entry, in Unicode code points of the exact
|
|
19
|
+
* submitted text (no normalization or trimming). A combining sequence or a
|
|
20
|
+
* joined emoji counts as each of its code points. Stored entries are not
|
|
21
|
+
* re-checked, so older, longer entries still read and display.
|
|
22
|
+
*/
|
|
23
|
+
export const PROGRESS_TEXT_MAX = 400;
|
|
18
24
|
export const PROGRESS_MAX_ENTRIES = 200;
|
|
19
25
|
export const REVIEW_MARKDOWN_MAX = 50_000;
|
|
20
26
|
export const REVIEW_MAX_ARTIFACTS = 20;
|
|
@@ -83,6 +89,13 @@ export function taskProgressSnapshot(taskId) {
|
|
|
83
89
|
...publicProgressEntry(entry), unread: !!reads && !seen.has(entry.id),
|
|
84
90
|
}));
|
|
85
91
|
}
|
|
92
|
+
/** Progress length in Unicode code points: a character outside the BMP counts once, not as two UTF-16 units. */
|
|
93
|
+
export function progressTextLength(text) {
|
|
94
|
+
let length = 0;
|
|
95
|
+
for (const _ of text)
|
|
96
|
+
length++;
|
|
97
|
+
return length;
|
|
98
|
+
}
|
|
86
99
|
/**
|
|
87
100
|
* Append one milestone. Text is kept exactly as written; whitespace-only text is
|
|
88
101
|
* rejected. A retry with the same author-scoped request_key returns the original
|
|
@@ -92,8 +105,10 @@ export function appendTaskProgress(taskId, input) {
|
|
|
92
105
|
assertTaskId(taskId);
|
|
93
106
|
if (typeof input.text !== 'string' || !input.text.trim())
|
|
94
107
|
throw new TaskReviewError('invalid_request', 'progress text is required');
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
const length = progressTextLength(input.text);
|
|
109
|
+
if (length > PROGRESS_TEXT_MAX)
|
|
110
|
+
throw new TaskReviewError('invalid_request', `progress text is ${length} Unicode characters (code points); the maximum is ${PROGRESS_TEXT_MAX}. `
|
|
111
|
+
+ 'Nothing was saved. Keep one fact per entry and shorten it, or record distinct facts as separate entries.');
|
|
97
112
|
checkRequestKey(input.requestKey);
|
|
98
113
|
return updateTaskContribution(taskId, task => {
|
|
99
114
|
const progress = task.progress ?? [];
|
|
@@ -149,6 +164,44 @@ export function setTaskReviewMarkdown(taskId, input) {
|
|
|
149
164
|
return { value: task.review, changed: true };
|
|
150
165
|
});
|
|
151
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Clear the resolved To review request: Markdown and every attached file, in
|
|
169
|
+
* one change fenced by the whole package revision the writer last read. Any
|
|
170
|
+
* Markdown edit, attach or detach since then is a conflict and nothing is
|
|
171
|
+
* removed. Clearing an already empty package at the expected revision succeeds
|
|
172
|
+
* unchanged; after a successful clear, read the package again before retrying.
|
|
173
|
+
* Like any change, a clear gets the next revision, so read markers, unread and
|
|
174
|
+
* push behave as for any update. Only the files this change removed are
|
|
175
|
+
* deleted; a file attached after it stays. With `markdown`, the same change
|
|
176
|
+
* writes the next request, so no other writer can slip in between.
|
|
177
|
+
*/
|
|
178
|
+
export function clearTaskReview(taskId, input) {
|
|
179
|
+
const dir = reviewStoreDirectory(taskId);
|
|
180
|
+
if (!Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0)
|
|
181
|
+
throw new TaskReviewError('invalid_request', 'expected revision must be a non-negative integer');
|
|
182
|
+
const markdown = input.markdown ?? '';
|
|
183
|
+
if (typeof markdown !== 'string')
|
|
184
|
+
throw new TaskReviewError('invalid_request', 'markdown must be a string');
|
|
185
|
+
if (markdown.length > REVIEW_MARKDOWN_MAX)
|
|
186
|
+
throw new TaskReviewError('invalid_request', `review markdown maximum is ${REVIEW_MARKDOWN_MAX} characters`);
|
|
187
|
+
const result = updateTaskContribution(taskId, task => {
|
|
188
|
+
if (reviewRevision(task.review) !== input.expectedRevision)
|
|
189
|
+
throw new TaskReviewError('conflict', 'The review package changed elsewhere. Read it again before clearing it.');
|
|
190
|
+
const empty = !task.review || (!task.review.markdown && !task.review.artifacts.length);
|
|
191
|
+
if (empty && !markdown)
|
|
192
|
+
return { value: { review: task.review, removed: [] }, changed: false };
|
|
193
|
+
const removed = task.review?.artifacts ?? [];
|
|
194
|
+
task.review = { revision: nextRevision(task.review), markdown, updated_at: new Date().toISOString(),
|
|
195
|
+
updated_by: input.author, artifacts: [] };
|
|
196
|
+
return { value: { review: task.review, removed }, changed: true };
|
|
197
|
+
});
|
|
198
|
+
for (const artifact of result.removed)
|
|
199
|
+
try {
|
|
200
|
+
removeStoredFile(dir, artifact.id);
|
|
201
|
+
}
|
|
202
|
+
catch { /* an unreferenced copy is swept later */ }
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
152
205
|
// ── Review artifact store ──────────────────────────────────────────────
|
|
153
206
|
export const reviewStoreRoot = () => join(stateRoot(), 'task-review-artifacts');
|
|
154
207
|
export function reviewStoreDirectory(taskId) {
|
|
@@ -9,7 +9,7 @@ export interface TaskToolBinding {
|
|
|
9
9
|
/** Launched as a task-owned temporary member (layout participant). */
|
|
10
10
|
taskOwned: boolean;
|
|
11
11
|
}
|
|
12
|
-
export declare const TASK_TOOL_NAMES: readonly ["fleet_task_progress_list", "fleet_task_progress_append", "fleet_task_review_show", "fleet_task_review_set", "fleet_task_review_detach"];
|
|
12
|
+
export declare const TASK_TOOL_NAMES: readonly ["fleet_task_progress_list", "fleet_task_progress_append", "fleet_task_review_show", "fleet_task_review_set", "fleet_task_review_clear", "fleet_task_review_detach"];
|
|
13
13
|
export declare const PROGRESS_PAGE_MAX = 50;
|
|
14
14
|
/** One chronological page of the log; `next_after` continues after the last returned entry. */
|
|
15
15
|
export declare function progressPage(entries: TaskProgressEntry[], input: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { appendTaskProgress, detachTaskReviewArtifact, getTaskReview, listTaskProgress, publicProgressEntry, publicReview, setTaskReviewMarkdown, TaskReviewError, PROGRESS_TEXT_MAX, REVIEW_MARKDOWN_MAX, } from './task-review.js';
|
|
2
|
+
import { appendTaskProgress, detachTaskReviewArtifact, getTaskReview, listTaskProgress, publicProgressEntry, publicReview, setTaskReviewMarkdown, clearTaskReview, TaskReviewError, PROGRESS_TEXT_MAX, REVIEW_MARKDOWN_MAX, } from './task-review.js';
|
|
3
3
|
import { getTask, TaskStateError } from './task-state.js';
|
|
4
4
|
export const TASK_TOOL_NAMES = Object.freeze([
|
|
5
5
|
'fleet_task_progress_list', 'fleet_task_progress_append',
|
|
6
|
-
'fleet_task_review_show', 'fleet_task_review_set', 'fleet_task_review_detach',
|
|
6
|
+
'fleet_task_review_show', 'fleet_task_review_set', 'fleet_task_review_clear', 'fleet_task_review_detach',
|
|
7
7
|
]);
|
|
8
8
|
/** Compact JSON: a result is carried twice (text and structured) inside one bounded bridge frame. */
|
|
9
9
|
function result(value) {
|
|
@@ -97,11 +97,13 @@ export function registerTaskTools(server, binding, admit) {
|
|
|
97
97
|
},
|
|
98
98
|
}, async (args) => run(bound => ({ task_id: bound.taskId, ...progressPage(listTaskProgress(bound.taskId), args) })));
|
|
99
99
|
server.registerTool('fleet_task_progress_append', {
|
|
100
|
-
description: 'Append ONE significant, verified
|
|
101
|
-
+ 'measured result with
|
|
100
|
+
description: 'Append ONE significant, verified fact the Owner should know to your task\'s Progress log: delivered behavior, root cause, '
|
|
101
|
+
+ 'measured result with a valid comparison, PR or publication, important blocker, changed scope, or a required Owner decision '
|
|
102
|
+
+ '(e.g. "Owner review required: choose the deployment option in To review."). Lead with the result and name the actor; '
|
|
103
|
+
+ 'one fact per entry, in the task\'s language. Not for routine status, handoffs or review chatter. '
|
|
102
104
|
+ 'Entries are permanent and shown to the Owner as written. Repeating a call with the same request_key and text returns the original entry.',
|
|
103
105
|
inputSchema: {
|
|
104
|
-
text: z.string().describe(`
|
|
106
|
+
text: z.string().describe(`Entry text, kept exactly as written: at most ${PROGRESS_TEXT_MAX} Unicode characters (code points); longer text is refused, not cut.`),
|
|
105
107
|
request_key: z.string().optional().describe('Stable key for this milestone so a retried call cannot add it twice (e.g. "pr-opened-123").'),
|
|
106
108
|
},
|
|
107
109
|
}, async (args) => run((bound, author) => {
|
|
@@ -116,8 +118,9 @@ export function registerTaskTools(server, binding, admit) {
|
|
|
116
118
|
return { task_id: bound.taskId, review: review ? publicReview(review) : null };
|
|
117
119
|
}));
|
|
118
120
|
server.registerTool('fleet_task_review_set', {
|
|
119
|
-
description: 'Replace the Markdown of your task\'s To review package: the
|
|
120
|
-
+ '(outcome, clickable PR/spec links, validation and CI, independent verdict, limits
|
|
121
|
+
description: 'Replace the Markdown of your task\'s To review package: the one current request the Owner reviews without reading the room or task description '
|
|
122
|
+
+ '(final acceptance: outcome, clickable PR/spec links, validation and CI, independent verdict, limits; or one Owner decision with its options). '
|
|
123
|
+
+ 'Not an archive: clear a resolved request with fleet_task_review_clear. Attached files are kept. '
|
|
121
124
|
+ 'Pass the Markdown you last read as expected_markdown ("" when empty); a concurrent change is refused instead of overwritten.',
|
|
122
125
|
inputSchema: {
|
|
123
126
|
markdown: z.string().describe(`Review Markdown (max ${REVIEW_MARKDOWN_MAX} characters). Links use the same safe rules as task descriptions.`),
|
|
@@ -127,6 +130,19 @@ export function registerTaskTools(server, binding, admit) {
|
|
|
127
130
|
task_id: bound.taskId,
|
|
128
131
|
review: publicReview(setTaskReviewMarkdown(bound.taskId, { markdown: args.markdown, expectedMarkdown: args.expected_markdown, author })),
|
|
129
132
|
})));
|
|
133
|
+
server.registerTool('fleet_task_review_clear', {
|
|
134
|
+
description: 'Clear your task\'s resolved To review request after the Owner answered it in the room: removes the Markdown and every attached file in one change. '
|
|
135
|
+
+ 'Pass the revision from your last fleet_task_review_show; if the package changed since, nothing is removed and you get a conflict. '
|
|
136
|
+
+ 'Record the Owner\'s decision in Progress or the room first. Pass markdown to write the next request in the same fenced change.',
|
|
137
|
+
inputSchema: {
|
|
138
|
+
expected_revision: z.number().int().describe('review.revision as you last read it with fleet_task_review_show.'),
|
|
139
|
+
markdown: z.string().optional().describe(`Optional Markdown of the next request (max ${REVIEW_MARKDOWN_MAX} characters), written in the same change.`),
|
|
140
|
+
},
|
|
141
|
+
}, async (args) => run((bound, author) => {
|
|
142
|
+
const cleared = clearTaskReview(bound.taskId, { expectedRevision: args.expected_revision, author, markdown: args.markdown });
|
|
143
|
+
return { task_id: bound.taskId, review: cleared.review ? publicReview(cleared.review) : null,
|
|
144
|
+
removed: cleared.removed.map(({ request_key: _key, ...artifact }) => artifact) };
|
|
145
|
+
}));
|
|
130
146
|
server.registerTool('fleet_task_review_detach', {
|
|
131
147
|
description: 'Remove one file from your task\'s To review package by artifact_id (from fleet_task_review_show).',
|
|
132
148
|
inputSchema: { artifact_id: z.string() },
|
|
@@ -417,6 +417,14 @@ export class FleetResourceViews {
|
|
|
417
417
|
// None changes role membership/configuration. Keep these as full wakes.
|
|
418
418
|
if (structural && parts.length === 2 && ['.session-events.jsonl', '.session-events.jsonl.1', '.subscription-usage.json', '.notify-cursor', '.monitor-state.json', '.monitor-status'].includes(parts[1]))
|
|
419
419
|
return false;
|
|
420
|
+
// A starting or running member also commits its supervisor/session status
|
|
421
|
+
// (restart ledger, exit, daemon recovery, control endpoint, monitor owner) and
|
|
422
|
+
// runner bookkeeping no projection reads. Neither changes membership; while
|
|
423
|
+
// members start, fencing on them left no stable directory capture.
|
|
424
|
+
if (structural && parts.length === 2 && ['.restart-ledger.json', '.exit-status', '.daemon-recovery.json', '.control-token', '.control.sock', '.monitor-owner', '.booted', '.supervisor-run.json', '.subscription-pin.json', '.session-ownership.json', '.managed-recovery.json', '.acp-session-id', '.self-reminders.json', 'supervisor.log', 'WORKLOG.md'].includes(parts[1]))
|
|
425
|
+
return false;
|
|
426
|
+
if (structural && parts.length >= 2 && ['.ours-bridge', '.task-supervisor'].includes(parts[1]))
|
|
427
|
+
return false;
|
|
420
428
|
if (parts.length < 3 || parts[1] !== '.conversation')
|
|
421
429
|
continue;
|
|
422
430
|
if (structural && (descriptor.resource === 'directory' || descriptor.resource === 'roles'))
|
|
@@ -8,18 +8,59 @@ before this feature, or that never use it, have neither and behave as before.
|
|
|
8
8
|
browser response also includes `unread` per entry from the same task snapshot,
|
|
9
9
|
based on persisted entry-ID acknowledgments. Legacy records without read state
|
|
10
10
|
report all existing entries as read; retry keys remain private. It is
|
|
11
|
-
for significant verified
|
|
12
|
-
a measured
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
for significant verified facts the Owner should know: delivered behavior, a
|
|
12
|
+
concrete root cause, a measured improvement with a valid comparison, a PR or
|
|
13
|
+
publication milestone, an important blocker, a changed scope or a required
|
|
14
|
+
Owner decision. It is not for room chat, routine status, handoffs or internal
|
|
15
|
+
review rounds. Each entry holds one fact in at most 400 characters (see
|
|
16
|
+
[Limits](#limits-retries-and-concurrency)), leads with the result or the
|
|
17
|
+
required action, names the actor and uses the task's language. Entries cannot
|
|
18
|
+
be edited or removed.
|
|
19
|
+
- **To review** holds one *current* request for human review or decision: the
|
|
20
|
+
final acceptance package, or one specific Owner decision that blocks or steers
|
|
21
|
+
the work. It is not ongoing agent review, an evidence archive or a growing list
|
|
22
|
+
of materials. Markdown plus retained files gather everything needed for that
|
|
23
|
+
decision in one place. The final package lets the Owner review without reading
|
|
24
|
+
the room or the task description: concise outcome, clickable PR and
|
|
25
|
+
specification links, attached documents, validation and required-CI results
|
|
19
26
|
for the exact heads, the independent verdict where one applies, material limits
|
|
20
27
|
and any Owner decision. Research and document tasks attach their documents and
|
|
21
28
|
need no PR.
|
|
22
29
|
|
|
30
|
+
### Progress examples
|
|
31
|
+
|
|
32
|
+
| Instead of | Write |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| "Started looking into the flaky test, will report back." | Nothing: routine status. |
|
|
35
|
+
| "After a lot of investigation we think the cache might be the cause." | "Developer found the root cause: the cache key omits the workspace ID." |
|
|
36
|
+
| "Critic reviewed PR #42 and asked for two changes, then approved." | "Critic approved PR #42 after the review rounds." (only the outcome) |
|
|
37
|
+
| A 900-character summary of three results | Three entries, one result each. |
|
|
38
|
+
| — | "Owner review required: choose the deployment option in To review." |
|
|
39
|
+
|
|
40
|
+
### To review lifecycle
|
|
41
|
+
|
|
42
|
+
1. **Request.** The responsible agent writes the request into To review
|
|
43
|
+
(`fleet_task_review_set`, attachments with the CLI) and appends a Progress
|
|
44
|
+
entry that sends the Owner there, for example "Owner review required: choose
|
|
45
|
+
the deployment option in To review." Final acceptance also uses the Review
|
|
46
|
+
lifecycle; an interim decision never marks unfinished work done.
|
|
47
|
+
2. **Answer.** Opening To review marks it read but does not resolve it. The
|
|
48
|
+
Owner's answer in the room does.
|
|
49
|
+
3. **Clear.** The agent that owns the request records the decision in Progress or
|
|
50
|
+
the room, reads the package `revision` with `fleet_task_review_show`, and
|
|
51
|
+
calls `fleet_task_review_clear` with that `expected_revision` (CLI:
|
|
52
|
+
`ours-fleet task review-package TASK --clear --expected-revision N`). The next
|
|
53
|
+
request replaces the cleared one, in the same change when `markdown` (CLI:
|
|
54
|
+
`--markdown-file`) is given; requests do not accumulate.
|
|
55
|
+
|
|
56
|
+
A clear removes the Markdown and every attached file in one change and only when
|
|
57
|
+
the package revision is still the one the writer read. Any Markdown edit, attach
|
|
58
|
+
or detach since then, including an edit back to the same Markdown, makes it a
|
|
59
|
+
conflict and nothing is removed. A file attached after the clear committed is
|
|
60
|
+
kept. Clearing an empty package at its current revision changes nothing. Like
|
|
61
|
+
every package change, a clear gets the next revision: readers see an updated
|
|
62
|
+
package until they view it, and read markers stay valid.
|
|
63
|
+
|
|
23
64
|
The web App shows both as compact **Progress** and **To review** buttons with
|
|
24
65
|
counts on the task screen, each opening its own view. Review links use the same
|
|
25
66
|
safe Markdown and link rules as the task description.
|
|
@@ -30,10 +71,10 @@ The packaged default roles divide the work so milestones are not duplicated:
|
|
|
30
71
|
|
|
31
72
|
| Role | Progress | To review |
|
|
32
73
|
| --- | --- | --- |
|
|
33
|
-
| LocalCoordinator |
|
|
34
|
-
| Developer, Engineer |
|
|
35
|
-
| Critic |
|
|
36
|
-
| Coordinator (Fleet Coordinator) | Not by default. | Before `task review`, check that the package
|
|
74
|
+
| LocalCoordinator | The only writer when present: extracts Owner-relevant facts from room discussion during turns it already takes. Its 15-minute continuity pass never writes Progress. | Makes sure members prepare the package before a Review handoff; hands it to the Fleet Coordinator. Directs the Owner to an interim decision request and clears it after the answer. |
|
|
75
|
+
| Developer, Engineer | Writer only in a room without a LocalCoordinator; otherwise posts such facts in the room. | Prepare and keep the package current before reporting completion or asking for Review; clear a request they wrote once the Owner answered. |
|
|
76
|
+
| Critic | Never; posts findings in the room. | Check that the package matches what was verified; withhold sign-off while it is missing, stale or misleading. Review rounds stay in the room. |
|
|
77
|
+
| Coordinator (Fleet Coordinator) | Not by default. | Before `task review`, check that the package is one current acceptance request and attaches the original description; if not, ask the room to complete it instead of writing it. Then replace the description with only the PR links (or deliverable links when there are no PRs). Finish only when the Owner accepts or asks. |
|
|
37
78
|
|
|
38
79
|
This is default instruction policy, not an access rule: any agent bound to the
|
|
39
80
|
task, and the local operator, can use the surfaces below. Preparing a package
|
|
@@ -49,12 +90,13 @@ to review, including the original task description as an attached file. Package
|
|
|
49
90
|
authors save and attach that file before the handoff, the Critic checks it, and
|
|
50
91
|
the Coordinator replaces the description only once the package has it.
|
|
51
92
|
|
|
52
|
-
Fresh installations get these instructions with preset revision
|
|
93
|
+
Fresh installations get these instructions with preset revision 13. An existing
|
|
53
94
|
configuration keeps its installed copies until you run
|
|
54
95
|
`ours-fleet migrate-role-defaults -c FILE` (a zero-write plan) and then with
|
|
55
|
-
`--write`: untouched revision-11 copies of the Developer, Critic,
|
|
56
|
-
LocalCoordinator and Coordinator roles and the
|
|
57
|
-
adopt the new text, and edited copies are
|
|
96
|
+
`--write`: untouched revision-11 and revision-12 copies of the Developer, Critic,
|
|
97
|
+
Engineer, LocalCoordinator and Coordinator roles, and the revision-11
|
|
98
|
+
LocalCoordinator Agent Template, adopt the new text, and edited copies are
|
|
99
|
+
preserved byte-for-byte. Running
|
|
58
100
|
agents keep the instructions they started with until they are restarted.
|
|
59
101
|
|
|
60
102
|
## Agent tools
|
|
@@ -69,7 +111,8 @@ agent that is a listed task member must match that member's identity CID.
|
|
|
69
111
|
| `fleet_task_progress_list` | Read the log oldest first, in pages; pass `after` = the previous page's `next_after`. |
|
|
70
112
|
| `fleet_task_progress_append` | Append one entry (`text`, optional `request_key`). |
|
|
71
113
|
| `fleet_task_review_show` | Read the package Markdown and file list. |
|
|
72
|
-
| `fleet_task_review_set` | Replace the Markdown; pass the Markdown you last read as `expected_markdown` (`""` when empty). |
|
|
114
|
+
| `fleet_task_review_set` | Replace the Markdown; pass the Markdown you last read as `expected_markdown` (`""` when empty). Attached files are kept. |
|
|
115
|
+
| `fleet_task_review_clear` | Clear the resolved request: Markdown and every file, only if `expected_revision` is still current. Optional `markdown` writes the next request in the same change. Returns the package and the removed files. |
|
|
73
116
|
| `fleet_task_review_detach` | Remove one file by `artifact_id`. |
|
|
74
117
|
|
|
75
118
|
Files are attached with the CLI below. It runs in the agent's own process, so the
|
|
@@ -85,6 +128,7 @@ ours-fleet task review-package TASK [--json]
|
|
|
85
128
|
ours-fleet task review-package TASK --markdown-file review.md
|
|
86
129
|
ours-fleet task review-package TASK --attach docs/spec.md [--request-key KEY]
|
|
87
130
|
ours-fleet task review-package TASK --detach ARTIFACT_ID
|
|
131
|
+
ours-fleet task review-package TASK --clear --expected-revision N [--markdown-file next.md]
|
|
88
132
|
```
|
|
89
133
|
|
|
90
134
|
Run inside a managed agent, the supervisor-verified caller is recorded as the
|
|
@@ -92,7 +136,11 @@ author (with its identity CID); otherwise the author is the local operator.
|
|
|
92
136
|
`--text`, `--text-file`, `--markdown`, `--markdown-file` and `--attach` values are
|
|
93
137
|
redacted from the supervisor's command ledger. `--markdown-file` replaces the
|
|
94
138
|
Markdown current at the time of the call; use `fleet_task_review_set` when a
|
|
95
|
-
concurrent change must be refused.
|
|
139
|
+
concurrent change must be refused. The text output shows the package revision.
|
|
140
|
+
`--clear` is fenced by `--expected-revision`; with `--markdown` or
|
|
141
|
+
`--markdown-file` the same locked change writes the next request, so no other
|
|
142
|
+
writer can land in between. `--clear` cannot be combined with `--attach`: attach
|
|
143
|
+
the next request's files afterwards.
|
|
96
144
|
|
|
97
145
|
## HTTP API
|
|
98
146
|
|
|
@@ -104,7 +152,7 @@ workspace-device bearer. `GET /api/v1/meta` reports API minor version 1.
|
|
|
104
152
|
| `GET /tasks`, `GET /tasks/:id` and other task payloads | `progress_count` and `review: {has_markdown, artifact_count, revision, updated_at?}`; `revision` grows with every package change (0 without a package). Bodies are not inlined. |
|
|
105
153
|
| `GET /tasks/:id/progress` | `{progress: [{id, at, author, text, unread}]}`, oldest first. |
|
|
106
154
|
| `POST /tasks/:id/progress` | `{text, requestKey?}` (or an `Idempotency-Key` header): 201 new, 200 replay, 409 conflicting replay. |
|
|
107
|
-
| `GET /tasks/:id/review` | `{review: {markdown, updated_at, updated_by, artifacts: [{id, name, mime, size, sha256, added_at, added_by}]}}` or `{review: null}`. |
|
|
155
|
+
| `GET /tasks/:id/review` | `{review: {revision, markdown, updated_at, updated_by, artifacts: [{id, name, mime, size, sha256, added_at, added_by}]}}` or `{review: null}`. |
|
|
108
156
|
| `PUT /tasks/:id/review` | `{markdown, expectedMarkdown}`; 409 when the Markdown changed meanwhile. |
|
|
109
157
|
| `GET /tasks/:id/review/artifacts/:artifactId` | The retained bytes as `application/octet-stream` attachment with `Content-Security-Policy: default-src 'none'; sandbox`, `nosniff` and `no-store`. Another task's artifact ID is 404. |
|
|
110
158
|
|
|
@@ -112,8 +160,14 @@ The HTTP API has no upload route: the browser never submits a file path.
|
|
|
112
160
|
|
|
113
161
|
## Limits, retries and concurrency
|
|
114
162
|
|
|
115
|
-
- Progress text: 1-
|
|
116
|
-
is rejected.
|
|
163
|
+
- Progress text: 1-400 characters, kept exactly as written; whitespace-only text
|
|
164
|
+
is rejected. Characters are Unicode code points of the exact submitted text,
|
|
165
|
+
without normalization or trimming: `😀` counts as 1 (although it is two UTF-16
|
|
166
|
+
units), while `é` written as `e` plus a combining accent counts as 2, and a
|
|
167
|
+
joined emoji such as `👩💻` counts as 3. Longer text is refused with
|
|
168
|
+
`invalid_request` on every surface (HTTP 400): nothing is saved, cut or split.
|
|
169
|
+
Entries written before this limit keep their text and still display. At most
|
|
170
|
+
200 entries per task.
|
|
117
171
|
- Review Markdown: up to 50,000 characters. At most 20 files of 20 MiB each per
|
|
118
172
|
task, and 200 MiB stored per task including unreferenced copies.
|
|
119
173
|
- Every change is a read-modify-write under the task's cross-process lock, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
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",
|
|
@@ -54,8 +54,9 @@ persona: |
|
|
|
54
54
|
`ours-fleet task start <task-id>` after addressing the reported blocker.
|
|
55
55
|
|
|
56
56
|
When executors report completion, check that the task's To review package exists and is not empty
|
|
57
|
-
with `ours-fleet task review-package <task-id>`: it must
|
|
58
|
-
|
|
57
|
+
with `ours-fleet task review-package <task-id>`: it must be one current acceptance request, not an
|
|
58
|
+
archive of earlier requests, let the Owner review without the room or task description, and
|
|
59
|
+
attach the original task description. If either is missing, ask the
|
|
59
60
|
room to complete it; do not write or judge it yourself. Then replace the task description with only
|
|
60
61
|
the clickable Markdown PR links, one per line, with no status or evidence (those stay in To review);
|
|
61
62
|
a task without PRs links its actual deliverables instead, never an invented PR. Then move the task
|
|
@@ -13,7 +13,9 @@ persona: |
|
|
|
13
13
|
Withhold sign-off while it is missing, stale or misleading, and record your verdict in it (with
|
|
14
14
|
`fleet_task_review_set`) or ask its author to. Research and document tasks need their documents,
|
|
15
15
|
not a PR. Checking the package grants no lifecycle authority: hand the Review move to whoever your
|
|
16
|
-
instructions authorize.
|
|
16
|
+
instructions authorize. To review holds only the current request for the Owner, not your ongoing
|
|
17
|
+
review: keep findings and verdict rounds in the room. You do not write Progress; post findings
|
|
18
|
+
the Owner should know in the room for its Progress writer.
|
|
17
19
|
|
|
18
20
|
Task state, progress, checkpoints, findings, review verdicts, completion, handoffs, and ordinary
|
|
19
21
|
errors are routine task communication: report them only in the assigned Cowork room. When a
|