@pennixrv/trellis 0.7.1-beta.9 → 0.7.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/dist/cli/index.js +4 -4
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +39 -26
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/teamwork/adapters/acp.d.ts +20 -0
  7. package/dist/commands/teamwork/adapters/acp.d.ts.map +1 -0
  8. package/dist/commands/teamwork/adapters/acp.js +268 -0
  9. package/dist/commands/teamwork/adapters/acp.js.map +1 -0
  10. package/dist/commands/teamwork/adapters/claude.d.ts +1 -0
  11. package/dist/commands/teamwork/adapters/claude.d.ts.map +1 -1
  12. package/dist/commands/teamwork/adapters/claude.js +5 -0
  13. package/dist/commands/teamwork/adapters/claude.js.map +1 -1
  14. package/dist/commands/teamwork/adapters/codex.d.ts +3 -2
  15. package/dist/commands/teamwork/adapters/codex.d.ts.map +1 -1
  16. package/dist/commands/teamwork/adapters/codex.js +14 -15
  17. package/dist/commands/teamwork/adapters/codex.js.map +1 -1
  18. package/dist/commands/teamwork/adapters/index.d.ts +23 -1
  19. package/dist/commands/teamwork/adapters/index.d.ts.map +1 -1
  20. package/dist/commands/teamwork/adapters/index.js +163 -24
  21. package/dist/commands/teamwork/adapters/index.js.map +1 -1
  22. package/dist/commands/teamwork/agent-loader.d.ts +2 -1
  23. package/dist/commands/teamwork/agent-loader.d.ts.map +1 -1
  24. package/dist/commands/teamwork/agent-loader.js +2 -1
  25. package/dist/commands/teamwork/agent-loader.js.map +1 -1
  26. package/dist/commands/teamwork/artifact.d.ts +33 -10
  27. package/dist/commands/teamwork/artifact.d.ts.map +1 -1
  28. package/dist/commands/teamwork/artifact.js +182 -50
  29. package/dist/commands/teamwork/artifact.js.map +1 -1
  30. package/dist/commands/teamwork/batch-reservation.d.ts +16 -0
  31. package/dist/commands/teamwork/batch-reservation.d.ts.map +1 -0
  32. package/dist/commands/teamwork/batch-reservation.js +43 -0
  33. package/dist/commands/teamwork/batch-reservation.js.map +1 -0
  34. package/dist/commands/teamwork/batch.d.ts +22 -0
  35. package/dist/commands/teamwork/batch.d.ts.map +1 -0
  36. package/dist/commands/teamwork/batch.js +394 -0
  37. package/dist/commands/teamwork/batch.js.map +1 -0
  38. package/dist/commands/teamwork/context-loader.d.ts +1 -1
  39. package/dist/commands/teamwork/context-loader.d.ts.map +1 -1
  40. package/dist/commands/teamwork/context-loader.js +19 -6
  41. package/dist/commands/teamwork/context-loader.js.map +1 -1
  42. package/dist/commands/teamwork/coordination.d.ts +88 -0
  43. package/dist/commands/teamwork/coordination.d.ts.map +1 -0
  44. package/dist/commands/teamwork/coordination.js +153 -0
  45. package/dist/commands/teamwork/coordination.js.map +1 -0
  46. package/dist/commands/teamwork/coordinator.d.ts +17 -3
  47. package/dist/commands/teamwork/coordinator.d.ts.map +1 -1
  48. package/dist/commands/teamwork/coordinator.js +772 -289
  49. package/dist/commands/teamwork/coordinator.js.map +1 -1
  50. package/dist/commands/teamwork/create.d.ts +1 -0
  51. package/dist/commands/teamwork/create.d.ts.map +1 -1
  52. package/dist/commands/teamwork/create.js +2 -1
  53. package/dist/commands/teamwork/create.js.map +1 -1
  54. package/dist/commands/teamwork/dev-parse-trace.d.ts +2 -1
  55. package/dist/commands/teamwork/dev-parse-trace.d.ts.map +1 -1
  56. package/dist/commands/teamwork/dev-parse-trace.js +2 -0
  57. package/dist/commands/teamwork/dev-parse-trace.js.map +1 -1
  58. package/dist/commands/teamwork/foreground-wait.d.ts +6 -0
  59. package/dist/commands/teamwork/foreground-wait.d.ts.map +1 -0
  60. package/dist/commands/teamwork/foreground-wait.js +65 -0
  61. package/dist/commands/teamwork/foreground-wait.js.map +1 -0
  62. package/dist/commands/teamwork/guard.d.ts +2 -2
  63. package/dist/commands/teamwork/guard.d.ts.map +1 -1
  64. package/dist/commands/teamwork/guard.js +23 -5
  65. package/dist/commands/teamwork/guard.js.map +1 -1
  66. package/dist/commands/teamwork/index.d.ts.map +1 -1
  67. package/dist/commands/teamwork/index.js +139 -10
  68. package/dist/commands/teamwork/index.js.map +1 -1
  69. package/dist/commands/teamwork/kill.d.ts +2 -0
  70. package/dist/commands/teamwork/kill.d.ts.map +1 -1
  71. package/dist/commands/teamwork/kill.js +79 -38
  72. package/dist/commands/teamwork/kill.js.map +1 -1
  73. package/dist/commands/teamwork/messages.d.ts.map +1 -1
  74. package/dist/commands/teamwork/messages.js +3 -0
  75. package/dist/commands/teamwork/messages.js.map +1 -1
  76. package/dist/commands/teamwork/plan.d.ts +22 -0
  77. package/dist/commands/teamwork/plan.d.ts.map +1 -0
  78. package/dist/commands/teamwork/plan.js +288 -0
  79. package/dist/commands/teamwork/plan.js.map +1 -0
  80. package/dist/commands/teamwork/process-identity.d.ts +3 -0
  81. package/dist/commands/teamwork/process-identity.d.ts.map +1 -0
  82. package/dist/commands/teamwork/process-identity.js +24 -0
  83. package/dist/commands/teamwork/process-identity.js.map +1 -0
  84. package/dist/commands/teamwork/profiles.d.ts +43 -6
  85. package/dist/commands/teamwork/profiles.d.ts.map +1 -1
  86. package/dist/commands/teamwork/profiles.js +138 -110
  87. package/dist/commands/teamwork/profiles.js.map +1 -1
  88. package/dist/commands/teamwork/prompt.d.ts +4 -0
  89. package/dist/commands/teamwork/prompt.d.ts.map +1 -0
  90. package/dist/commands/teamwork/prompt.js +33 -0
  91. package/dist/commands/teamwork/prompt.js.map +1 -0
  92. package/dist/commands/teamwork/result.d.ts +54 -0
  93. package/dist/commands/teamwork/result.d.ts.map +1 -0
  94. package/dist/commands/teamwork/result.js +94 -0
  95. package/dist/commands/teamwork/result.js.map +1 -0
  96. package/dist/commands/teamwork/rm.d.ts.map +1 -1
  97. package/dist/commands/teamwork/rm.js +64 -64
  98. package/dist/commands/teamwork/rm.js.map +1 -1
  99. package/dist/commands/teamwork/run.d.ts +5 -0
  100. package/dist/commands/teamwork/run.d.ts.map +1 -1
  101. package/dist/commands/teamwork/run.js +120 -24
  102. package/dist/commands/teamwork/run.js.map +1 -1
  103. package/dist/commands/teamwork/runtime.d.ts +9 -0
  104. package/dist/commands/teamwork/runtime.d.ts.map +1 -0
  105. package/dist/commands/teamwork/runtime.js +372 -0
  106. package/dist/commands/teamwork/runtime.js.map +1 -0
  107. package/dist/commands/teamwork/send.d.ts +1 -0
  108. package/dist/commands/teamwork/send.d.ts.map +1 -1
  109. package/dist/commands/teamwork/send.js +2 -1
  110. package/dist/commands/teamwork/send.js.map +1 -1
  111. package/dist/commands/teamwork/spawn.d.ts +10 -3
  112. package/dist/commands/teamwork/spawn.d.ts.map +1 -1
  113. package/dist/commands/teamwork/spawn.js +41 -29
  114. package/dist/commands/teamwork/spawn.js.map +1 -1
  115. package/dist/commands/teamwork/store/lock.js +28 -4
  116. package/dist/commands/teamwork/store/lock.js.map +1 -1
  117. package/dist/commands/teamwork/supervisor/inbox.d.ts +1 -0
  118. package/dist/commands/teamwork/supervisor/inbox.d.ts.map +1 -1
  119. package/dist/commands/teamwork/supervisor/inbox.js +28 -9
  120. package/dist/commands/teamwork/supervisor/inbox.js.map +1 -1
  121. package/dist/commands/teamwork/supervisor/stdout.d.ts.map +1 -1
  122. package/dist/commands/teamwork/supervisor/stdout.js +2 -3
  123. package/dist/commands/teamwork/supervisor/stdout.js.map +1 -1
  124. package/dist/commands/teamwork/supervisor/warning.d.ts.map +1 -1
  125. package/dist/commands/teamwork/supervisor/warning.js +2 -3
  126. package/dist/commands/teamwork/supervisor/warning.js.map +1 -1
  127. package/dist/commands/teamwork/supervisor.d.ts +5 -0
  128. package/dist/commands/teamwork/supervisor.d.ts.map +1 -1
  129. package/dist/commands/teamwork/supervisor.js +21 -12
  130. package/dist/commands/teamwork/supervisor.js.map +1 -1
  131. package/dist/commands/teamwork/teammate-adapters.d.ts +0 -3
  132. package/dist/commands/teamwork/teammate-adapters.d.ts.map +1 -1
  133. package/dist/commands/teamwork/teammate-adapters.js +9 -54
  134. package/dist/commands/teamwork/teammate-adapters.js.map +1 -1
  135. package/dist/commands/teamwork/teammate-config.d.ts +5 -1
  136. package/dist/commands/teamwork/teammate-config.d.ts.map +1 -1
  137. package/dist/commands/teamwork/teammate-config.js +62 -19
  138. package/dist/commands/teamwork/teammate-config.js.map +1 -1
  139. package/dist/commands/teamwork/teammate-launch.d.ts +14 -4
  140. package/dist/commands/teamwork/teammate-launch.d.ts.map +1 -1
  141. package/dist/commands/teamwork/teammate-launch.js +129 -20
  142. package/dist/commands/teamwork/teammate-launch.js.map +1 -1
  143. package/dist/commands/teamwork/wait.d.ts +2 -0
  144. package/dist/commands/teamwork/wait.d.ts.map +1 -1
  145. package/dist/commands/teamwork/wait.js +44 -3
  146. package/dist/commands/teamwork/wait.js.map +1 -1
  147. package/dist/commands/update.d.ts.map +1 -1
  148. package/dist/commands/update.js +4 -3
  149. package/dist/commands/update.js.map +1 -1
  150. package/dist/configurators/workflow.d.ts.map +1 -1
  151. package/dist/configurators/workflow.js +14 -32
  152. package/dist/configurators/workflow.js.map +1 -1
  153. package/dist/migrations/manifests/0.7.1-beta.10.json +9 -0
  154. package/dist/migrations/manifests/0.7.1-beta.11.json +9 -0
  155. package/dist/migrations/manifests/0.7.1-beta.12.json +9 -0
  156. package/dist/migrations/manifests/0.7.1-beta.13.json +9 -0
  157. package/dist/migrations/manifests/0.7.1-beta.14.json +11 -0
  158. package/dist/migrations/manifests/0.7.1-beta.15.json +9 -0
  159. package/dist/migrations/manifests/0.7.1-beta.16.json +9 -0
  160. package/dist/migrations/manifests/0.7.1-beta.17.json +9 -0
  161. package/dist/migrations/manifests/0.7.1-beta.18.json +9 -0
  162. package/dist/migrations/manifests/0.7.1-beta.19.json +9 -0
  163. package/dist/migrations/manifests/0.7.1-beta.6.json +1 -1
  164. package/dist/migrations/manifests/0.7.2-beta.0.json +11 -0
  165. package/dist/templates/codex/hooks/session-start.py +14 -11
  166. package/dist/templates/codex/hooks.json +10 -0
  167. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-teamwork.md +14 -2
  168. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +1 -1
  169. package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/workflow.md +4 -0
  170. package/dist/templates/common/bundled-skills/trellis-task-implementation/workflow.md +6 -0
  171. package/dist/templates/common/bundled-skills/trellis-teamwork/SKILL.md +14 -4
  172. package/dist/templates/common/bundled-skills/trellis-teamwork/references/coordination-modes.md +31 -2
  173. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-configuration-matrix.md +37 -31
  174. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +94 -24
  175. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +112 -20
  176. package/dist/templates/copilot/hooks/session-start.py +14 -11
  177. package/dist/templates/copilot/index.d.ts +1 -6
  178. package/dist/templates/copilot/index.d.ts.map +1 -1
  179. package/dist/templates/copilot/index.js +1 -14
  180. package/dist/templates/copilot/index.js.map +1 -1
  181. package/dist/templates/markdown/spec/backend/index.md.txt +4 -35
  182. package/dist/templates/markdown/spec/frontend/index.md.txt +4 -36
  183. package/dist/templates/omp/extensions/trellis/index.ts.txt +1 -1
  184. package/dist/templates/pi/extensions/trellis/index.ts.txt +17 -1
  185. package/dist/templates/shared-hooks/guard-teamwork-wait.py +53 -0
  186. package/dist/templates/shared-hooks/index.d.ts +1 -1
  187. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  188. package/dist/templates/shared-hooks/index.js +1 -0
  189. package/dist/templates/shared-hooks/index.js.map +1 -1
  190. package/dist/templates/shared-hooks/inject-workflow-state.py +8 -3
  191. package/dist/templates/shared-hooks/session-start.py +16 -12
  192. package/dist/templates/trellis/agents/delegated-worktree-primary-agent.md +9 -0
  193. package/dist/templates/trellis/agents/profiles/architecture.md +9 -0
  194. package/dist/templates/trellis/agents/profiles/diagnosis.md +9 -0
  195. package/dist/templates/trellis/agents/profiles/exploration.md +9 -0
  196. package/dist/templates/trellis/agents/profiles/research.md +9 -0
  197. package/dist/templates/trellis/agents/profiles/review.md +9 -0
  198. package/dist/templates/trellis/agents/profiles/verification.md +9 -0
  199. package/dist/templates/trellis/agents/teammate-profiles.json +56 -4
  200. package/dist/templates/trellis/agents/teammate.md +4 -63
  201. package/dist/templates/trellis/config.yaml +2 -5
  202. package/dist/templates/trellis/index.d.ts +1 -0
  203. package/dist/templates/trellis/index.d.ts.map +1 -1
  204. package/dist/templates/trellis/index.js +16 -4
  205. package/dist/templates/trellis/index.js.map +1 -1
  206. package/dist/templates/trellis/scripts/common/task_continuity.py +54 -28
  207. package/dist/templates/trellis/scripts/common/task_store.py +7 -2
  208. package/dist/templates/trellis/scripts/common/task_workflow.py +270 -18
  209. package/dist/templates/trellis/scripts/task.py +27 -3
  210. package/dist/templates/trellis/teamwork.json +10 -0
  211. package/dist/templates/trellis/workflow.md +7 -0
  212. package/dist/utils/asset-backup.d.ts.map +1 -1
  213. package/dist/utils/asset-backup.js +10 -4
  214. package/dist/utils/asset-backup.js.map +1 -1
  215. package/dist/utils/template-fetcher.d.ts +4 -19
  216. package/dist/utils/template-fetcher.d.ts.map +1 -1
  217. package/dist/utils/template-fetcher.js +9 -45
  218. package/dist/utils/template-fetcher.js.map +1 -1
  219. package/package.json +3 -2
  220. package/dist/templates/copilot/prompts/before-dev.prompt.md +0 -39
  221. package/dist/templates/copilot/prompts/brainstorm.prompt.md +0 -13
  222. package/dist/templates/copilot/prompts/break-loop.prompt.md +0 -129
  223. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +0 -170
  224. package/dist/templates/copilot/prompts/check.prompt.md +0 -110
  225. package/dist/templates/copilot/prompts/create-command.prompt.md +0 -116
  226. package/dist/templates/copilot/prompts/finish-work.prompt.md +0 -22
  227. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +0 -223
  228. package/dist/templates/copilot/prompts/onboard.prompt.md +0 -362
  229. package/dist/templates/copilot/prompts/record-session.prompt.md +0 -58
  230. package/dist/templates/copilot/prompts/start.prompt.md +0 -65
  231. package/dist/templates/copilot/prompts/update-spec.prompt.md +0 -358
@@ -1,6 +1,10 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { parseFrozenTeammate, } from "./teammate-launch.js";
5
+ import { assertCoordinationMembers, } from "./coordination.js";
6
+ import { REASONING_EFFORTS } from "./profiles.js";
7
+ import { teammateContentSchema } from "./result.js";
4
8
  const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
5
9
  const SECRET = /(-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|gh[pousr])_[A-Za-z0-9]{20,}\b|\bBearer\s+\S+)/i;
6
10
  function id(value, field) {
@@ -45,7 +49,7 @@ function relativePathList(value, field) {
45
49
  throw new Error(`${field} must not contain duplicates`);
46
50
  return result;
47
51
  }
48
- function safePath(root, candidate) {
52
+ export function safePath(root, candidate) {
49
53
  const absoluteRoot = path.resolve(root);
50
54
  const lexical = path.resolve(root, candidate);
51
55
  const relative = path.relative(absoluteRoot, lexical);
@@ -100,9 +104,7 @@ function canonicalJson(value) {
100
104
  if (Array.isArray(value))
101
105
  return `[${value.map(canonicalJson).join(",")}]`;
102
106
  if (value && typeof value === "object") {
103
- const entries = Object.entries(value)
104
- .filter(([key]) => key !== "plan_hash")
105
- .sort(([a], [b]) => a.localeCompare(b));
107
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
106
108
  return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
107
109
  }
108
110
  return JSON.stringify(value);
@@ -111,14 +113,20 @@ function withoutFields(value, fields) {
111
113
  return Object.fromEntries(Object.entries(value).filter(([field]) => !fields.includes(field)));
112
114
  }
113
115
  export function computeTeamworkPlanHash(value) {
114
- return crypto.createHash("sha256").update(canonicalJson(value)).digest("hex");
116
+ const contents = value && typeof value === "object" && !Array.isArray(value)
117
+ ? withoutFields(value, ["plan_hash"])
118
+ : value;
119
+ return crypto
120
+ .createHash("sha256")
121
+ .update(canonicalJson(contents))
122
+ .digest("hex");
115
123
  }
116
- export function validateTeamworkPlan(input, taskRoot, expectedContext) {
124
+ export function validateTeamworkPlan(input, taskRoot, expectedContext, options = {}) {
117
125
  if (!input || typeof input !== "object" || Array.isArray(input))
118
126
  throw new Error("Teamwork plan must be an object");
119
127
  const value = input;
120
- if (value.schema_version !== 1)
121
- throw new Error("Teamwork plan schema_version must be 1");
128
+ if (value.schema_version !== 2)
129
+ throw new Error("Teamwork plan schema_version must be 2; construct a new frozen plan");
122
130
  const taskId = id(value.task_id, "task_id");
123
131
  const batchId = id(value.batch_id, "batch_id");
124
132
  const sessionId = id(value.session_id, "session_id");
@@ -126,6 +134,7 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
126
134
  const taskRouteRevision = boundedText(value.task_route_revision, "task_route_revision");
127
135
  const inputRevision = boundedText(value.input_revision, "input_revision");
128
136
  const planSummary = boundedText(value.plan_summary, "plan_summary");
137
+ const coordinationDigest = digest(value.coordination_digest, "coordination_digest");
129
138
  const coordinationMode = value.coordination_mode;
130
139
  if (coordinationMode !== "general" &&
131
140
  coordinationMode !== "advisor" &&
@@ -147,13 +156,13 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
147
156
  const deadlineAt = Date.parse(deadline);
148
157
  if (Number.isNaN(deadlineAt))
149
158
  throw new Error("deadline must be an ISO date");
150
- if (deadlineAt <= Date.now())
159
+ if (!options.allowExpired && deadlineAt <= Date.now())
151
160
  throw new Error("deadline must be in the future");
152
161
  const assignmentValue = value.assignments;
153
162
  if (!Array.isArray(assignmentValue) || assignmentValue.length === 0)
154
163
  throw new Error("Teamwork plan assignments must be non-empty");
155
- if (coordinationMode === "advisor" && assignmentValue.length !== 1)
156
- throw new Error("advisor mode requires exactly one assignment");
164
+ if (coordinationMode === "advisor" && assignmentValue.length > 3)
165
+ throw new Error("advisor mode requires one to three assignments");
157
166
  if (coordinationMode === "committee" &&
158
167
  (assignmentValue.length < 2 || assignmentValue.length > 3))
159
168
  throw new Error("committee mode requires two or three assignments");
@@ -188,8 +197,10 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
188
197
  else if (coordinationMode === "committee") {
189
198
  throw new Error(`assignments[${index}] must declare an evaluation group`);
190
199
  }
191
- if (item.role_id !== "teammate")
192
- throw new Error(`assignments[${index}].role_id must be teammate`);
200
+ if (item.role_id !== undefined)
201
+ throw new Error(`assignments[${index}].role_id is retired; teammate is an internal execution type`);
202
+ const execution = parseFrozenTeammate(item.execution);
203
+ const selectionReason = boundedText(item.selection_reason, `assignments[${index}].selection_reason`);
193
204
  const provider = item.provider;
194
205
  if (provider !== "claude" &&
195
206
  provider !== "codex" &&
@@ -205,7 +216,9 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
205
216
  throw new Error(`assignments[${index}].max_retries must be non-negative`);
206
217
  const briefPath = boundedText(item.brief_path, `assignments[${index}].brief_path`);
207
218
  const scope = boundedText(item.scope, `assignments[${index}].scope`);
208
- const lens = boundedText(item.lens, `assignments[${index}].lens`);
219
+ const lens = item.lens === undefined
220
+ ? undefined
221
+ : boundedText(item.lens, `assignments[${index}].lens`);
209
222
  const summary = boundedText(item.summary, `assignments[${index}].summary`);
210
223
  const briefDigest = digest(item.brief_digest, `assignments[${index}].brief_digest`);
211
224
  const sourceDigest = digest(item.source_digest, `assignments[${index}].source_digest`);
@@ -222,22 +235,31 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
222
235
  if (path.relative(path.resolve(taskRoot), absoluteBrief) !==
223
236
  expectedRelative)
224
237
  throw new Error(`assignments[${index}].brief_path must be ${expectedRelative}`);
225
- const briefBytes = fs.readFileSync(absoluteBrief);
238
+ const prepared = options.briefContents?.get(briefPath);
239
+ if (options.briefContents && prepared === undefined)
240
+ throw new Error(`missing prepared brief: ${briefPath}`);
241
+ const briefBytes = prepared === undefined
242
+ ? fs.readFileSync(absoluteBrief)
243
+ : Buffer.from(prepared);
226
244
  if (briefBytes.byteLength > 64 * 1024)
227
245
  throw new Error(`brief for assignment '${assignmentId}' exceeds 65536 bytes`);
228
246
  const brief = JSON.parse(briefBytes.toString("utf8"));
229
247
  if (!brief || typeof brief !== "object" || Array.isArray(brief))
230
248
  throw new Error(`brief for assignment '${assignmentId}' must be an object`);
249
+ if (typeof brief.input !== "string" ||
250
+ brief.input.trim() === "" ||
251
+ brief.input.length > 32768 ||
252
+ brief.input.includes("\0"))
253
+ throw new Error("brief.input must be nonempty task text of at most 32768 characters");
231
254
  briefs.set(assignmentId, brief);
232
255
  rejectSecrets(brief, `brief for assignment '${assignmentId}'`);
233
256
  if (computeTeamworkPlanHash(brief) !== briefDigest)
234
257
  throw new Error(`brief.${assignmentId} digest does not match the Teamwork plan`);
235
258
  for (const [field, expected] of Object.entries({
236
- schema_version: 1,
259
+ schema_version: 2,
237
260
  task_id: taskId,
238
261
  batch_id: batchId,
239
262
  assignment_id: assignmentId,
240
- role_id: "teammate",
241
263
  provider,
242
264
  summary,
243
265
  scope,
@@ -247,6 +269,16 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
247
269
  if (brief[field] !== expected)
248
270
  throw new Error(`brief.${field} does not match the Teamwork plan`);
249
271
  }
272
+ if (brief.role_id !== undefined)
273
+ throw new Error("brief.role_id is retired");
274
+ if (canonicalJson(brief.execution) !== canonicalJson(item.execution))
275
+ throw new Error("brief.execution does not match the frozen plan");
276
+ if (brief.selection_reason !== selectionReason)
277
+ throw new Error("brief.selection_reason does not match the frozen plan");
278
+ if (execution.profile.provider !== provider ||
279
+ execution.profile.profile !== profile ||
280
+ execution.profile.model !== model)
281
+ throw new Error("assignment provider, profile, or model does not match frozen execution");
250
282
  const briefSourceDigest = digest(brief.source_digest, `brief.${assignmentId}.source_digest`);
251
283
  if (briefSourceDigest !== sourceDigest)
252
284
  throw new Error(`brief.${assignmentId}.source_digest does not match the Teamwork plan`);
@@ -279,6 +311,9 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
279
311
  summary,
280
312
  scope,
281
313
  lens,
314
+ execution,
315
+ selectionReason,
316
+ taskInput: brief.input,
282
317
  briefDigest,
283
318
  sourceDigest,
284
319
  ...(contextFiles ? { contextFiles } : {}),
@@ -289,6 +324,7 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
289
324
  rejectSecrets(assignment, `assignments[${index}]`);
290
325
  return assignment;
291
326
  });
327
+ assertCoordinationMembers(coordinationMode, assignments);
292
328
  if (coordinationMode === "committee") {
293
329
  const groups = new Map();
294
330
  for (const assignment of assignments) {
@@ -315,6 +351,8 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
315
351
  "lens",
316
352
  "brief_path",
317
353
  "brief_digest",
354
+ "execution",
355
+ "selection_reason",
318
356
  ];
319
357
  const briefFields = [
320
358
  "assignment_id",
@@ -322,6 +360,8 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
322
360
  "profile",
323
361
  "model",
324
362
  "lens",
363
+ "execution",
364
+ "selection_reason",
325
365
  ];
326
366
  const first = members[0];
327
367
  if (!first)
@@ -348,12 +388,32 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
348
388
  if (new Set(protectedTargets).size !== protectedTargets.length)
349
389
  throw new Error("protected_targets must not contain duplicates");
350
390
  const requestedCapacityValue = value.requested_capacity;
391
+ let retryOf;
392
+ if (value.retry_of !== undefined) {
393
+ const retry = value.retry_of;
394
+ const assignmentIds = retry?.assignment_ids;
395
+ if (!retry ||
396
+ typeof retry !== "object" ||
397
+ !Array.isArray(assignmentIds) ||
398
+ assignmentIds.length !== assignments.length ||
399
+ new Set(assignmentIds).size !== assignments.length ||
400
+ assignments.some((entry) => !assignmentIds.includes(entry.assignmentId)))
401
+ throw new Error("Retry lineage must contain the exact selected assignment set");
402
+ retryOf = {
403
+ batchId: id(retry.batch_id, "retry_of.batch_id"),
404
+ planHash: digest(retry.plan_hash, "retry_of.plan_hash"),
405
+ assignmentIds: assignmentIds.map((entry) => id(entry, "retry_of.assignment_ids")),
406
+ approvalRef: boundedText(retry.approval_ref, "retry_of.approval_ref"),
407
+ };
408
+ if (retryOf.batchId === batchId)
409
+ throw new Error("Retry must use a new batch identity");
410
+ }
351
411
  if (requestedCapacityValue !== undefined &&
352
412
  (!Number.isInteger(requestedCapacityValue) ||
353
413
  requestedCapacityValue <= 0))
354
414
  throw new Error("requested_capacity must be a positive integer");
355
415
  const plan = {
356
- schemaVersion: 1,
416
+ schemaVersion: 2,
357
417
  taskId,
358
418
  batchId,
359
419
  sessionId,
@@ -362,6 +422,11 @@ export function validateTeamworkPlan(input, taskRoot, expectedContext) {
362
422
  inputRevision,
363
423
  planSummary,
364
424
  coordinationMode,
425
+ coordinationDigest,
426
+ ...(retryOf ? { retryOf } : {}),
427
+ ...(value.coordination_snapshot
428
+ ? { coordinationSnapshot: value.coordination_snapshot }
429
+ : {}),
365
430
  planHash: boundedText(value.plan_hash, "plan_hash"),
366
431
  ...(requestedCapacityValue !== undefined
367
432
  ? { requestedCapacity: requestedCapacityValue }
@@ -380,8 +445,31 @@ export function validateTeammateReport(input, identity, expectedSourceDigest) {
380
445
  if (!input || typeof input !== "object" || Array.isArray(input))
381
446
  throw new Error("teammate report must be an object");
382
447
  const value = input;
383
- if (value.schema_version !== 1)
384
- throw new Error("teammate report schema_version must be 1");
448
+ if (value.schema_version !== 1 && value.schema_version !== 2)
449
+ throw new Error("teammate report schema_version must be 1 or 2");
450
+ if (value.schema_version === 2) {
451
+ const allowed = new Set([
452
+ ...Object.keys(teammateContentSchema.shape),
453
+ "task_id",
454
+ "batch_id",
455
+ "assignment_id",
456
+ "attempt_id",
457
+ "teammate_id",
458
+ "plan_hash",
459
+ "phase_id",
460
+ "task_route_revision",
461
+ "input_revision",
462
+ "source_digest",
463
+ ]);
464
+ if (Object.keys(value).some((key) => !allowed.has(key)))
465
+ throw new Error("teammate report contains unknown fields");
466
+ }
467
+ const content = value.schema_version === 2
468
+ ? teammateContentSchema.parse(Object.fromEntries(Object.keys(teammateContentSchema.shape).map((key) => [
469
+ key,
470
+ value[key],
471
+ ])))
472
+ : undefined;
385
473
  for (const key of [
386
474
  "task_id",
387
475
  "batch_id",
@@ -434,11 +522,11 @@ export function validateTeammateReport(input, identity, expectedSourceDigest) {
434
522
  summary: boundedText(item.summary, `report.evidence[${index}].summary`),
435
523
  };
436
524
  });
437
- if (status !== "complete" || evidence.length === 0) {
438
- throw new Error(`report status '${status}' cannot be accepted as completed work`);
439
- }
525
+ if (evidence.length === 0)
526
+ throw new Error("report.evidence must include at least one item");
440
527
  const report = {
441
- schemaVersion: 1,
528
+ schemaVersion: value.schema_version,
529
+ ...(content ? { content } : {}),
442
530
  taskId: identity.taskId,
443
531
  batchId: identity.batchId,
444
532
  assignmentId: identity.assignmentId,
@@ -504,8 +592,8 @@ export function validateTeammateClaim(input, identity, expectedPlanHash, expecte
504
592
  if (!input || typeof input !== "object" || Array.isArray(input))
505
593
  throw new Error("teammate claim must be an object");
506
594
  const value = input;
507
- if (value.schema_version !== 1)
508
- throw new Error("teammate claim schema_version must be 1");
595
+ if (value.schema_version !== 2)
596
+ throw new Error("teammate claim schema_version must be 2");
509
597
  for (const key of [
510
598
  "task_id",
511
599
  "batch_id",
@@ -541,7 +629,7 @@ export function validateTeammateClaim(input, identity, expectedPlanHash, expecte
541
629
  if (!Number.isInteger(value.slot) || value.slot < 0)
542
630
  throw new Error("claim.slot must be non-negative");
543
631
  const snapshotKeys = [
544
- "role_id",
632
+ "configuration",
545
633
  "provider",
546
634
  "profile",
547
635
  "model",
@@ -555,8 +643,8 @@ export function validateTeammateClaim(input, identity, expectedPlanHash, expecte
555
643
  const hasSnapshot = snapshotKeys.some((key) => value[key] !== undefined);
556
644
  let execution;
557
645
  if (hasSnapshot) {
558
- if (value.role_id !== "teammate")
559
- throw new Error("claim.role_id must be teammate");
646
+ if (value.role_id !== undefined)
647
+ throw new Error("claim.role_id is retired");
560
648
  const provider = value.provider;
561
649
  if (provider !== "claude" &&
562
650
  provider !== "codex" &&
@@ -574,9 +662,7 @@ export function validateTeammateClaim(input, identity, expectedPlanHash, expecte
574
662
  throw new Error("claim.model must be bounded single-line text");
575
663
  const reasoningEffort = value.reasoning_effort;
576
664
  if (reasoningEffort !== undefined &&
577
- reasoningEffort !== "medium" &&
578
- reasoningEffort !== "high" &&
579
- reasoningEffort !== "xhigh") {
665
+ !REASONING_EFFORTS.includes(reasoningEffort)) {
580
666
  throw new Error("claim.reasoning_effort is invalid");
581
667
  }
582
668
  if (!value.options ||
@@ -605,10 +691,13 @@ export function validateTeammateClaim(input, identity, expectedPlanHash, expecte
605
691
  }
606
692
  execution = {
607
693
  roleId: "teammate",
694
+ configuration: id(value.configuration, "claim.configuration"),
608
695
  provider,
609
696
  ...(profile ? { profile } : {}),
610
697
  ...(model ? { model } : {}),
611
- ...(reasoningEffort ? { reasoningEffort } : {}),
698
+ ...(reasoningEffort
699
+ ? { reasoningEffort: reasoningEffort }
700
+ : {}),
612
701
  options,
613
702
  ...(value.profile_digest === undefined
614
703
  ? {}
@@ -621,7 +710,7 @@ export function validateTeammateClaim(input, identity, expectedPlanHash, expecte
621
710
  };
622
711
  }
623
712
  const claim = {
624
- schemaVersion: 1,
713
+ schemaVersion: 2,
625
714
  ...identity,
626
715
  planHash,
627
716
  ...(briefDigest ? { briefDigest } : {}),
@@ -680,7 +769,7 @@ export function validateTeammateDisposition(input, identity, expectedSourceDiges
680
769
  export function writeTeammateClaim(input) {
681
770
  const execution = input.launch
682
771
  ? {
683
- role_id: input.launch.roleId,
772
+ configuration: input.launch.configuration,
684
773
  provider: input.launch.provider,
685
774
  ...(input.launch.profile ? { profile: input.launch.profile } : {}),
686
775
  ...(input.launch.model ? { model: input.launch.model } : {}),
@@ -697,7 +786,7 @@ export function writeTeammateClaim(input) {
697
786
  }
698
787
  : undefined;
699
788
  const claim = validateTeammateClaim({
700
- schema_version: 1,
789
+ schema_version: 2,
701
790
  task_id: input.identity.taskId,
702
791
  batch_id: input.identity.batchId,
703
792
  assignment_id: input.identity.assignmentId,
@@ -724,7 +813,7 @@ export function writeTeammateClaim(input) {
724
813
  status: claim.status,
725
814
  ...(claim.execution
726
815
  ? {
727
- role_id: claim.execution.roleId,
816
+ configuration: claim.execution.configuration,
728
817
  provider: claim.execution.provider,
729
818
  ...(claim.execution.profile
730
819
  ? { profile: claim.execution.profile }
@@ -789,8 +878,13 @@ export function appendTeammateWorklog(input) {
789
878
  }
790
879
  }
791
880
  export function writeTeammateReport(input) {
881
+ if (input.content &&
882
+ (input.content.status !== input.status ||
883
+ JSON.stringify(input.content.evidence) !== JSON.stringify(input.evidence)))
884
+ throw new Error("report content status/evidence must match the submitted report");
792
885
  const report = validateTeammateReport({
793
- schema_version: 1,
886
+ ...input.content,
887
+ schema_version: input.content ? 2 : 1,
794
888
  task_id: input.identity.taskId,
795
889
  batch_id: input.identity.batchId,
796
890
  assignment_id: input.identity.assignmentId,
@@ -805,6 +899,7 @@ export function writeTeammateReport(input) {
805
899
  evidence: input.evidence,
806
900
  }, input.identity, input.sourceDigest);
807
901
  writeImmutableJson(attemptArtifactPath(input.taskRoot, input.briefPath, input.identity.attemptId, "report.json"), {
902
+ ...report.content,
808
903
  schema_version: report.schemaVersion,
809
904
  task_id: report.taskId,
810
905
  batch_id: report.batchId,
@@ -854,15 +949,57 @@ export function writeTeammateDisposition(input) {
854
949
  ...(disposition.reason ? { reason: disposition.reason } : {}),
855
950
  });
856
951
  }
952
+ export class TeammateReportReadError extends Error {
953
+ kind;
954
+ constructor(kind, message) {
955
+ super(message);
956
+ this.kind = kind;
957
+ this.name = "TeammateReportReadError";
958
+ }
959
+ }
960
+ function isMissingFile(error) {
961
+ return (error !== null &&
962
+ typeof error === "object" &&
963
+ "code" in error &&
964
+ error.code === "ENOENT");
965
+ }
857
966
  export function readTeammateReport(input) {
858
967
  if (!ID.test(input.attemptId))
859
968
  throw new Error("attempt id must be safe");
860
- const reportPath = safePath(input.taskRoot, path.join(path.dirname(input.briefPath), "attempts", input.attemptId, "report.json"));
861
- const stat = fs.lstatSync(reportPath);
969
+ let reportPath;
970
+ try {
971
+ reportPath = safePath(input.taskRoot, path.join(path.dirname(input.briefPath), "attempts", input.attemptId, "report.json"));
972
+ }
973
+ catch {
974
+ throw new TeammateReportReadError("storage", "teammate report path is unsafe");
975
+ }
976
+ let stat;
977
+ try {
978
+ stat = fs.lstatSync(reportPath);
979
+ }
980
+ catch (error) {
981
+ if (isMissingFile(error))
982
+ throw new TeammateReportReadError("missing", "teammate report is missing");
983
+ throw new TeammateReportReadError("storage", "could not inspect teammate report");
984
+ }
862
985
  if (!stat.isFile() || stat.size > 128 * 1024)
863
- throw new Error("teammate report must be a regular file under 131072 bytes");
864
- const raw = fs.readFileSync(reportPath, "utf8");
865
- const report = JSON.parse(raw);
986
+ throw new TeammateReportReadError("storage", "teammate report must be a regular file under 131072 bytes");
987
+ let raw;
988
+ try {
989
+ raw = fs.readFileSync(reportPath, "utf8");
990
+ }
991
+ catch (error) {
992
+ if (isMissingFile(error))
993
+ throw new TeammateReportReadError("missing", "teammate report is missing");
994
+ throw new TeammateReportReadError("storage", "could not read teammate report");
995
+ }
996
+ let report;
997
+ try {
998
+ report = JSON.parse(raw);
999
+ }
1000
+ catch {
1001
+ throw new TeammateReportReadError("incomplete", "teammate report is not complete JSON");
1002
+ }
866
1003
  rejectSecrets(report, "teammate report");
867
1004
  return validateTeammateReport(report, { ...input.identity, attemptId: input.attemptId }, input.sourceDigest);
868
1005
  }
@@ -881,13 +1018,8 @@ export function cleanupSettledTeamworkArtifacts(input) {
881
1018
  if (!ID.test(attemptId))
882
1019
  continue;
883
1020
  const attemptDir = safePath(input.taskRoot, path.join(attemptsDir, attemptId));
884
- const keepReport = input.acceptedAttempts.get(assignment.assignmentId) === attemptId;
885
- const transientFiles = [
886
- "claim.json",
887
- "disposition.json",
888
- "worklog.md",
889
- ...(keepReport ? [] : ["report.json"]),
890
- ];
1021
+ // Keep every attempt's evidence until an explicit native cleanup.
1022
+ const transientFiles = ["claim.json", "disposition.json"];
891
1023
  for (const fileName of transientFiles) {
892
1024
  const filePath = safePath(input.taskRoot, path.join(attemptDir, fileName));
893
1025
  if (fs.existsSync(filePath))