@ours.network/fleet 1.2.0-nightly.12 → 1.2.0-nightly.14

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.
Files changed (108) hide show
  1. package/README.md +2 -0
  2. package/dist/application/model-catalog.d.ts +1 -1
  3. package/dist/application/model-catalog.js +5 -2
  4. package/dist/application/task-room-service.d.ts +3 -0
  5. package/dist/application/task-room-service.js +1 -1
  6. package/dist/atomic-file.d.ts +5 -0
  7. package/dist/atomic-file.js +62 -1
  8. package/dist/build-info.json +4 -4
  9. package/dist/erased-resources.d.ts +9 -0
  10. package/dist/erased-resources.js +87 -0
  11. package/dist/fleet-command-audit.d.ts +1 -0
  12. package/dist/fleet-command-audit.js +9 -5
  13. package/dist/init-wizard.js +10 -3
  14. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  15. package/dist/rooms-tasks/close.d.ts +2 -2
  16. package/dist/rooms-tasks/close.js +128 -22
  17. package/dist/rooms-tasks/deletion.d.ts +1 -1
  18. package/dist/rooms-tasks/deletion.js +41 -9
  19. package/dist/rooms-tasks/erasure.d.ts +6 -0
  20. package/dist/rooms-tasks/erasure.js +169 -0
  21. package/dist/rooms-tasks/room-state.d.ts +3 -1
  22. package/dist/rooms-tasks/room-state.js +37 -7
  23. package/dist/rooms-tasks/task-state.d.ts +10 -4
  24. package/dist/rooms-tasks/task-state.js +54 -12
  25. package/dist/rooms-tasks/types.d.ts +2 -0
  26. package/dist/temp-lifecycle.d.ts +5 -0
  27. package/dist/temp-lifecycle.js +28 -12
  28. package/docs/task-workspaces.md +34 -2
  29. package/package.json +1 -1
  30. package/presets/README.md +66 -0
  31. package/presets/brain-catalog.json +7 -2
  32. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  33. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  34. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  35. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  36. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  37. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  38. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  39. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  40. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  41. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  42. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  43. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  44. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  45. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  46. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  47. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  48. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  49. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  50. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  51. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  52. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  53. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  54. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  55. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  56. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  57. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  58. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  59. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  60. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  61. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  62. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  63. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  64. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  65. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  66. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  67. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  68. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  69. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  70. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  71. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  72. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  73. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  74. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  75. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  76. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  77. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  78. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  79. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  80. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  81. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  82. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  83. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  84. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  85. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  86. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  87. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  88. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  89. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  90. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  91. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  92. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  93. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  94. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  95. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  96. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  97. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  98. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  99. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  100. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  101. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  102. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  103. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  104. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  105. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  106. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  107. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  108. package/presets/manifest.json +2 -2
package/README.md CHANGED
@@ -1767,3 +1767,5 @@ A `task_not_ready` result distinguishes confirmed degradation from unknown healt
1767
1767
  and member status and choose supported recovery. These checks do not restart agents,
1768
1768
  recreate identities, redeem invites, or rewrite lifecycle state. Temporary room agents
1769
1769
  remain transient across host reboot; retained launch records do not restore them.
1770
+
1771
+ See [predefined Brain models and runtime requirements](presets/README.md) for the verified model/effort matrix and regeneration instructions.
@@ -14,7 +14,7 @@ export interface HarnessModelCatalog {
14
14
  export declare function codexModelCatalog(path?: string): HarnessModelCatalog;
15
15
  /**
16
16
  * Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
17
- * versioned adapter contract shipped by the installed 2.1 CLI, not aliases and
17
+ * versioned adapter contract verified with CLI 2.1.281, not aliases and
18
18
  * not a claim about account entitlement (which Claude validates at launch).
19
19
  */
20
20
  export declare function claudeModelCatalog(): HarnessModelCatalog;
@@ -41,17 +41,20 @@ export function codexModelCatalog(path = join(homedir(), '.codex', 'models_cache
41
41
  }
42
42
  /**
43
43
  * Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
44
- * versioned adapter contract shipped by the installed 2.1 CLI, not aliases and
44
+ * versioned adapter contract verified with CLI 2.1.281, not aliases and
45
45
  * not a claim about account entitlement (which Claude validates at launch).
46
46
  */
47
47
  export function claudeModelCatalog() {
48
48
  const reasoningEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
49
49
  return {
50
50
  models: [
51
+ { id: 'claude-fable-5-1', label: 'Claude Fable 5.1', reasoningEfforts, source: 'claude-adapter-2.1' },
52
+ { id: 'claude-opus-5-5', label: 'Claude Opus 5.5', reasoningEfforts, source: 'claude-adapter-2.1' },
53
+ { id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', reasoningEfforts: [], source: 'claude-adapter-2.1' },
51
54
  { id: 'claude-fable-5', label: 'Claude Fable 5', reasoningEfforts, source: 'claude-adapter-2.1' },
52
55
  { id: 'claude-opus-5', label: 'Claude Opus 5', reasoningEfforts, source: 'claude-adapter-2.1' },
53
56
  { id: 'claude-sonnet-5', label: 'Claude Sonnet 5', reasoningEfforts, source: 'claude-adapter-2.1' },
54
57
  ],
55
- warnings: ['Claude Code exposes no typed model-list endpoint; choices are exact 2.1 adapter IDs and entitlement is validated at launch.'],
58
+ warnings: ['Claude Code exposes no typed model-list endpoint; choices require a current Claude runtime (verified with 2.1.281); older bundled SDKs need CLAUDE_CODE_EXECUTABLE. Entitlement is validated at launch.'],
56
59
  };
57
60
  }
@@ -282,6 +282,9 @@ export declare class TaskRoomApplicationService {
282
282
  actor: TaskRoomActor;
283
283
  roomId: string;
284
284
  }): Promise<{
285
+ room: undefined;
286
+ settlementRequired: true;
287
+ } | {
285
288
  room: RoomOrchestrationRecord;
286
289
  settlementRequired: true;
287
290
  }>;
@@ -638,7 +638,7 @@ export class TaskRoomApplicationService {
638
638
  createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
639
639
  const room = getRoomRecord(input.roomId);
640
640
  if (!room) {
641
- throw new TaskRoomApplicationError('room_record_not_found', 'room record not found', { room: input.roomId });
641
+ return { room: undefined, settlementRequired: true };
642
642
  }
643
643
  return { room: await acceptManagedRoomClose(input.roomId), settlementRequired: true };
644
644
  }
@@ -36,3 +36,8 @@ export declare function withFileLock<T>(lockPath: string, fn: () => T | Promise<
36
36
  * intact.
37
37
  */
38
38
  export declare function replaceFileAtomically(path: string, contents: string, mode?: number, deps?: WriteDeps): void;
39
+ /** Synchronous Lamport bakery lock: unique claims are never reused, so stale
40
+ * reclamation cannot remove a successor's lock. Atomic claim replacement keeps
41
+ * choosing/ticket reads complete. Dead claims can be removed by any contender.
42
+ */
43
+ export declare function withSynchronousFileLock<T>(path: string, work: () => T): T;
@@ -1,4 +1,4 @@
1
- import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, writeSync, } from 'node:fs';
1
+ import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, writeSync, } from 'node:fs';
2
2
  import { basename, dirname, join } from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  const DEFAULT_STALE_MS = 10_000;
@@ -136,3 +136,64 @@ export function replaceFileAtomically(path, contents, mode = 0o600, deps = {}) {
136
136
  }
137
137
  catch { /* platform does not allow fsync on a directory */ }
138
138
  }
139
+ /** Synchronous Lamport bakery lock: unique claims are never reused, so stale
140
+ * reclamation cannot remove a successor's lock. Atomic claim replacement keeps
141
+ * choosing/ticket reads complete. Dead claims can be removed by any contender.
142
+ */
143
+ export function withSynchronousFileLock(path, work) {
144
+ mkdirSync(path, { recursive: true, mode: 0o700 });
145
+ const token = `${process.pid}-${randomUUID()}.json`;
146
+ const own = join(path, token);
147
+ const deadline = Date.now() + 10_000;
148
+ const claims = () => {
149
+ const result = [];
150
+ for (const name of readdirSync(path).filter(name => /^[0-9]+-[a-f0-9-]{36}\.json$/.test(name))) {
151
+ const file = join(path, name);
152
+ let value;
153
+ try {
154
+ value = JSON.parse(readFileSync(file, 'utf8'));
155
+ }
156
+ catch (error) {
157
+ if (error.code === 'ENOENT')
158
+ continue;
159
+ throw error;
160
+ }
161
+ if (!Number.isSafeInteger(value.pid) || value.pid <= 0 || !Number.isSafeInteger(value.ticket) || value.ticket < 0)
162
+ throw new Error('Invalid synchronous lock claim');
163
+ if (name !== token) {
164
+ try {
165
+ process.kill(value.pid, 0);
166
+ }
167
+ catch (error) {
168
+ if (error.code === 'ESRCH') {
169
+ // This dead contender alone owned this UUID path. Never remove
170
+ // the namespace or a successor's differently named claim.
171
+ rmSync(file, { force: true });
172
+ continue;
173
+ }
174
+ }
175
+ }
176
+ result.push({ name, value });
177
+ }
178
+ return result;
179
+ };
180
+ replaceFileAtomically(own, JSON.stringify({ pid: process.pid, choosing: true, ticket: 0 }));
181
+ try {
182
+ const ticket = Math.max(0, ...claims().map(claim => claim.value.ticket)) + 1;
183
+ if (!Number.isSafeInteger(ticket))
184
+ throw new Error('Synchronous lock ticket overflow');
185
+ replaceFileAtomically(own, JSON.stringify({ pid: process.pid, choosing: false, ticket }));
186
+ for (;;) {
187
+ const preceding = claims().some(claim => claim.name !== token && (claim.value.choosing
188
+ || claim.value.ticket < ticket || (claim.value.ticket === ticket && claim.name < token)));
189
+ if (!preceding)
190
+ return work();
191
+ if (Date.now() >= deadline)
192
+ throw new Error('Timed out acquiring synchronous journal lock');
193
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
194
+ }
195
+ }
196
+ finally {
197
+ rmSync(own, { force: true });
198
+ }
199
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.2.0-nightly.12",
3
- "buildId": "6f07096c9a0c",
4
- "commit": "7e323845714c5b638db197673b84f28fe00a2f75",
2
+ "version": "1.2.0-nightly.14",
3
+ "buildId": "b88d0d158cc7",
4
+ "commit": "717f6d8a09e6173b9b8a017fc5efb08d71927524",
5
5
  "dirty": true,
6
- "builtAt": "2026-09-24T13:14:00.219Z",
6
+ "builtAt": "2026-09-24T14:15:57.206Z",
7
7
  "capabilities": [
8
8
  "cowork.http-management-v1",
9
9
  "monitor.interrupt.after_tool"
@@ -0,0 +1,9 @@
1
+ export declare const erasedArg: (value: string) => string;
2
+ export declare function redactErasedContent<T>(value: T): T;
3
+ /** Every cooperative writer filters stale in-memory snapshots under the same lock as erasure. */
4
+ export declare function writePrivacyFilteredLedger(path: string, value: unknown): void;
5
+ /** Content-free resource IDs persist to prevent active writers resurrecting erased labels. */
6
+ export declare function eraseResourcePresentations(resources: ReadonlyArray<{
7
+ kind: 'task' | 'room' | 'agent';
8
+ id: string;
9
+ }>): void;
@@ -0,0 +1,87 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { replaceFileAtomically, withSynchronousFileLock } from './atomic-file.js';
5
+ import { stateRoot } from './paths.js';
6
+ const registryPath = () => join(stateRoot(), 'erased-resources.json');
7
+ const lockPath = () => join(stateRoot(), 'locks', 'audit-erasure');
8
+ const ledgerNames = ['.fleet-command-audit.json', '.owner-channel-command-audit.json', '.owner-channel-lifecycle-outbox.json'];
9
+ export const erasedArg = (value) => `[erased:${createHash('sha256').update(value).digest('hex')}]`;
10
+ function registry() {
11
+ if (!existsSync(registryPath()))
12
+ return new Set();
13
+ const value = JSON.parse(readFileSync(registryPath(), 'utf8'));
14
+ if (!Array.isArray(value) || value.some(v => typeof v !== 'string'))
15
+ throw new Error('Invalid erasure registry');
16
+ return new Set(value);
17
+ }
18
+ /** Preserve delivery/deduplication metadata while removing owned presentation content. */
19
+ function redact(value, erased) {
20
+ if (Array.isArray(value))
21
+ return value.map(item => redact(item, erased));
22
+ if (!value || typeof value !== 'object')
23
+ return value;
24
+ const next = { ...value };
25
+ const kind = value.kind === 'lifecycle_failure' ? value.resource?.toLowerCase() : value.kind === 'agent_started' ? 'agent' : value.kind;
26
+ if (typeof value.id === 'string' && erased.has(`${kind}:${value.id}`)) {
27
+ for (const key of ['title', 'reason', 'roomName', 'label', 'template', 'configuration', 'model', 'permissions'])
28
+ delete next[key];
29
+ if (kind === 'task')
30
+ next.agents = [];
31
+ if (kind === 'room') {
32
+ delete next.name;
33
+ next.participants = [];
34
+ }
35
+ if (kind === 'agent') {
36
+ next.name = 'erased';
37
+ next.brain = '[erased]';
38
+ next.role = '[erased]';
39
+ next.inherited = [];
40
+ }
41
+ }
42
+ if (typeof value.roomId === 'string' && erased.has(`room:${value.roomId}`)) {
43
+ delete next.roomName;
44
+ if (kind === 'task')
45
+ next.agents = [];
46
+ }
47
+ const resources = value.outcome?.resourceIds;
48
+ const ownsAttempt = resources && Object.entries(resources).some(([key, id]) => erased.has(`${key}:${id}`));
49
+ const ownsPresentation = value.outcome?.presentations?.some((p) => erased.has(`${p.kind === 'agent_started' ? 'agent' : p.kind}:${p.id}`));
50
+ if ((ownsAttempt || ownsPresentation) && Array.isArray(value.argv) && !value.erased) {
51
+ next.argv = value.argv.map(erasedArg);
52
+ next.erased = true;
53
+ }
54
+ for (const key of Object.keys(next))
55
+ if (typeof next[key] === 'object')
56
+ next[key] = redact(next[key], erased);
57
+ return next;
58
+ }
59
+ export function redactErasedContent(value) { return redact(value, registry()); }
60
+ /** Every cooperative writer filters stale in-memory snapshots under the same lock as erasure. */
61
+ export function writePrivacyFilteredLedger(path, value) {
62
+ withSynchronousFileLock(lockPath(), () => replaceFileAtomically(path, JSON.stringify(redact(value, registry())) + '\n'));
63
+ }
64
+ /** Content-free resource IDs persist to prevent active writers resurrecting erased labels. */
65
+ export function eraseResourcePresentations(resources) {
66
+ withSynchronousFileLock(lockPath(), () => {
67
+ const erased = registry();
68
+ for (const resource of resources)
69
+ erased.add(`${resource.kind}:${resource.id}`);
70
+ replaceFileAtomically(registryPath(), JSON.stringify([...erased]));
71
+ for (const root of [join(stateRoot(), 'agents'), join(stateRoot(), 'tmp'), join(stateRoot(), 'recovery', 'temporary')]) {
72
+ if (!existsSync(root))
73
+ continue;
74
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
75
+ if (!entry.isDirectory() || entry.isSymbolicLink())
76
+ continue;
77
+ for (const name of ledgerNames) {
78
+ const path = join(root, entry.name, name);
79
+ if (!existsSync(path) || !lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
80
+ continue;
81
+ const old = JSON.parse(readFileSync(path, 'utf8'));
82
+ replaceFileAtomically(path, JSON.stringify(redact(old, erased)) + '\n');
83
+ }
84
+ }
85
+ }
86
+ });
87
+ }
@@ -113,6 +113,7 @@ export interface FleetCommandClassification {
113
113
  decision: FleetCommandDecision;
114
114
  }
115
115
  export interface FleetAuditAttempt {
116
+ erased?: boolean;
116
117
  version: 1;
117
118
  correlationId: string;
118
119
  requestId: string;
@@ -1,7 +1,7 @@
1
+ import { erasedArg, redactErasedContent, writePrivacyFilteredLedger } from './erased-resources.js';
1
2
  import { existsSync, readFileSync } from 'node:fs';
2
3
  import { randomUUID } from 'node:crypto';
3
4
  import { Buffer } from 'node:buffer';
4
- import { replaceFileAtomically } from './atomic-file.js';
5
5
  import { isSensitiveConfigKey } from './sensitive-config.js';
6
6
  import { mandatoryConfigurationFits } from './lifecycle-summary.js';
7
7
  import { MARKDOWN_MAX_BYTES, MARKDOWN_MAX_CODE_POINTS, markdownCode, markdownProse, } from './rooms-tasks/markdown.js';
@@ -91,7 +91,7 @@ export function recordFleetAuditPresentation(value) {
91
91
  : value.kind === 'lifecycle_failure'
92
92
  ? { label: fleetPresentationLabel(value.label) }
93
93
  : {};
94
- const presentation = structuredClone({ ...value, ...labels, eventId: basis });
94
+ const presentation = redactErasedContent(structuredClone({ ...value, ...labels, eventId: basis }));
95
95
  const digest = lifecycleEventDigestBasis(presentation);
96
96
  if (!collection.presentations.some(existing => lifecycleEventDigestBasis(existing) === digest))
97
97
  collection.presentations.push(presentation);
@@ -305,11 +305,12 @@ export class FleetCommandAuditStore {
305
305
  if (recovered)
306
306
  this.persist();
307
307
  }
308
- list() { return this.attempts.map(item => structuredClone(item)); }
308
+ list() { return redactErasedContent(this.attempts).map(item => structuredClone(item)); }
309
309
  begin(requestId, caller, argv) {
310
+ this.attempts = redactErasedContent(this.attempts);
310
311
  const existing = this.attempts.find(item => item.caller === caller && item.requestId === requestId);
311
312
  if (existing) {
312
- if (JSON.stringify(existing.argv) !== JSON.stringify(redactFleetArgv(argv)))
313
+ if (JSON.stringify(existing.argv) !== JSON.stringify(existing.erased ? redactFleetArgv(argv).map(erasedArg) : redactFleetArgv(argv)))
313
314
  throw new Error('fleet command request ID was reused with different argv');
314
315
  return structuredClone(existing);
315
316
  }
@@ -329,6 +330,8 @@ export class FleetCommandAuditStore {
329
330
  return structuredClone(attempt);
330
331
  }
331
332
  finish(correlationId, caller, outcome) {
333
+ this.attempts = redactErasedContent(this.attempts);
334
+ outcome = redactErasedContent({ outcome }).outcome;
332
335
  const attempt = this.owned(correlationId, caller);
333
336
  if (!attempt.outcome)
334
337
  attempt.outcome = { ...outcome, completedAt: this.deps.now().toISOString(), delivery: 'sending' };
@@ -360,7 +363,8 @@ export class FleetCommandAuditStore {
360
363
  return attempt;
361
364
  }
362
365
  persist() {
363
- replaceFileAtomically(this.path, `${JSON.stringify({ version: 1, attempts: this.attempts })}\n`, 0o600);
366
+ this.attempts = redactErasedContent(this.attempts);
367
+ writePrivacyFilteredLedger(this.path, { version: 1, attempts: this.attempts });
364
368
  }
365
369
  }
366
370
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
@@ -14,6 +14,11 @@ const REASONING_EFFORT = {
14
14
  quick: 'low', balanced: 'medium', thorough: 'high',
15
15
  };
16
16
  const MODEL_LABELS = {
17
+ 'gpt-6-sol': 'GPT-6 Sol',
18
+ 'gpt-6-luna': 'GPT-6 Luna',
19
+ 'claude-fable-5-1': 'Claude Fable 5.1',
20
+ 'claude-opus-5-5': 'Claude Opus 5.5',
21
+ 'claude-haiku-4-5-20251001': 'Claude Haiku 4.5',
17
22
  'gpt-6-astra': 'GPT-6 Astra',
18
23
  'gpt-5.6-sol': 'GPT-5.6 Sol',
19
24
  'gpt-5.6-terra': 'GPT-5.6 Terra',
@@ -33,10 +38,10 @@ export function validateCatalog(value, path = 'supported model catalog') {
33
38
  for (const model of value.models) {
34
39
  if (!['codex', 'claude-code'].includes(model.harness) || model.session !== 'acp'
35
40
  || typeof model.model !== 'string' || !MODEL_LABELS[model.model]
36
- || !Array.isArray(model.efforts) || model.efforts.length === 0
41
+ || !Array.isArray(model.efforts)
37
42
  || new Set(model.efforts).size !== model.efforts.length
38
43
  || model.efforts.some(effort => !['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(effort))
39
- || !['low', 'medium', 'high'].every(effort => model.efforts.includes(effort)))
44
+ || (model.efforts.length > 0 && !['low', 'medium', 'high'].every(effort => model.efforts.includes(effort))))
40
45
  throw new Error(`unsupported model state in ${path}`);
41
46
  const tuple = `${model.harness}\0${model.session}\0${model.model}`;
42
47
  if (tuples.has(tuple))
@@ -90,7 +95,9 @@ export async function askInitQuestions(prompter, configuration) {
90
95
  return undefined;
91
96
  if (subscriptions.length === 0)
92
97
  throw new Error('Select at least one subscription.');
93
- const available = catalog().models.filter(model => subscriptions.includes(subscriptionFor(model)));
98
+ // The wizard assigns low/medium/high; no-effort models remain available as named presets.
99
+ const available = catalog().models.filter(model => subscriptions.includes(subscriptionFor(model))
100
+ && model.efforts.length > 0);
94
101
  if (!available.length)
95
102
  throw new Error('None of the selected subscriptions has a supported model.');
96
103
  prompter.note([
@@ -1,6 +1,6 @@
1
+ import { redactErasedContent, writePrivacyFilteredLedger } from '../erased-resources.js';
1
2
  import { createHash } from 'node:crypto';
2
3
  import { chmodSync, existsSync, readFileSync } from 'node:fs';
3
- import { replaceFileAtomically } from '../atomic-file.js';
4
4
  import { lifecycleEventDigestBasis, validateFleetAuditPresentations, } from '../fleet-command-audit.js';
5
5
  const DIGEST = /^[a-f0-9]{64}$/u;
6
6
  const LIMIT = 5_000;
@@ -63,7 +63,7 @@ export class FleetLifecycleOutbox {
63
63
  }
64
64
  pending() {
65
65
  this.assertHealthy();
66
- return this.entries.filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
66
+ return redactErasedContent(this.entries).filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
67
67
  }
68
68
  finish(digest, delivery) {
69
69
  this.assertHealthy();
@@ -82,6 +82,7 @@ export class FleetLifecycleOutbox {
82
82
  throw new Error(this.corruptReason);
83
83
  }
84
84
  persist() {
85
- replaceFileAtomically(this.path, `${JSON.stringify({ version: 1, entries: this.entries })}\n`, 0o600);
85
+ this.entries = redactErasedContent(this.entries);
86
+ writePrivacyFilteredLedger(this.path, { version: 1, entries: this.entries });
86
87
  }
87
88
  }
@@ -27,7 +27,7 @@ export declare function acceptManagedRoomClose(roomId: string): Promise<RoomOrch
27
27
  export declare function recordManagedRoomCloseError(roomId: string, error: string, recoveryHint: string): Promise<RoomOrchestrationRecord>;
28
28
  export declare function closeManagedRoom(input: {
29
29
  roomId: string;
30
- cowork: Pick<CoworkAdapter, 'closeRoom'>;
30
+ cowork: Pick<CoworkAdapter, 'closeRoom'> & Partial<Pick<CoworkAdapter, 'getRoom'>>;
31
31
  deps?: RoomCloseDeps;
32
32
  }): Promise<RoomOrchestrationRecord>;
33
33
  export interface ManagedRoomDeleteResult {
@@ -41,6 +41,6 @@ export declare function deleteLegacyClosedRooms(input: {
41
41
  /** Retire live resources through the existing cursor, then delete retained state. */
42
42
  export declare function deleteManagedRoom(input: {
43
43
  roomId: string;
44
- cowork: Pick<CoworkAdapter, 'closeRoom' | 'deleteRoom'>;
44
+ cowork: Pick<CoworkAdapter, 'closeRoom' | 'deleteRoom'> & Partial<Pick<CoworkAdapter, 'getRoom'>>;
45
45
  deps?: RoomCloseDeps;
46
46
  }): Promise<ManagedRoomDeleteResult>;