@tea-agent/loop-agent 0.31.1 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +1 -1
  4. package/dist/shared/operator/capabilities.js +22 -1
  5. package/dist/shared/operator/command-lifecycle.js +94 -0
  6. package/dist/shared/operator/index.js +1 -0
  7. package/dist/worker/cli.js +10 -24
  8. package/dist/worker/console/doctor.js +11 -4
  9. package/dist/worker/console/observe-health-match.js +18 -15
  10. package/dist/worker/console/operator-actions.js +2 -1
  11. package/dist/worker/console/static/assets/index-Bpa2qrc-.js +29 -0
  12. package/dist/worker/console/static/assets/index-BqfFDdnG.css +1 -0
  13. package/dist/worker/console/static/index.html +2 -2
  14. package/dist/worker/console/static-src/app/console-types.js +140 -0
  15. package/dist/worker/console/static-src/app/useConsoleShell.js +101 -0
  16. package/dist/worker/console/static-src/app/useOperatorActions.js +304 -0
  17. package/dist/worker/console/static-src/app/usePrdImport.js +171 -0
  18. package/dist/worker/console/static-src/app/useRecoveryActions.js +257 -0
  19. package/dist/worker/console/static-src/app/useRecoveryConsole.js +334 -0
  20. package/dist/worker/console/static-src/app/useTaskWizard.js +229 -0
  21. package/dist/worker/console/static-src/chat-view-types.js +2 -0
  22. package/dist/worker/console/static-src/night/night-types.js +24 -0
  23. package/dist/worker/console/static-src/night/useNightBoard.js +125 -0
  24. package/dist/worker/console/static-src/night/useNightWizard.js +171 -0
  25. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +253 -0
  26. package/dist/worker/console/static-src/operator-chat/format.js +71 -0
  27. package/dist/worker/console/static-src/operator-chat/refs.js +47 -0
  28. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +77 -0
  29. package/dist/worker/console/static-src/operator-chat/types.js +1 -0
  30. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +320 -0
  31. package/dist/worker/console/static-src/operator-chat/useChatStream.js +209 -0
  32. package/dist/worker/console/static-src/operator-chat/useChatThread.js +218 -0
  33. package/dist/worker/console/static-src/operator-chat/useComposer.js +125 -0
  34. package/dist/worker/console/static-src/operator-chat/useInterview.js +108 -0
  35. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +123 -0
  36. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +207 -0
  37. package/dist/worker/feature/acceptance-policy.js +227 -0
  38. package/dist/worker/feature/decision-loader.js +1 -1
  39. package/dist/worker/feature/next-action.js +56 -6
  40. package/dist/worker/feature/profile-schema.js +3 -0
  41. package/dist/worker/feature/reducer.js +1 -0
  42. package/dist/worker/feature/review.js +72 -8
  43. package/dist/worker/feature/scaffold.js +14 -0
  44. package/dist/worker/loop-agent/controller-protocol.js +143 -0
  45. package/dist/worker/materialize/harness-task-lifecycle-probe.js +126 -0
  46. package/dist/worker/materialize/harness-task-lineage.js +220 -0
  47. package/dist/worker/materialize/harness-task-materializer.js +350 -80
  48. package/dist/worker/observability/progress-composite.js +1 -0
  49. package/dist/worker/observability/read-model.js +66 -19
  50. package/dist/worker/pool/attempt-identity.js +41 -0
  51. package/dist/worker/pool/attempt-lease.js +184 -0
  52. package/dist/worker/pool/attempt-transition.js +210 -0
  53. package/dist/worker/pool/begin-attempt-with-lease.js +26 -0
  54. package/dist/worker/pool/begin-attempt.js +35 -0
  55. package/dist/worker/pool/failure-routing.js +49 -0
  56. package/dist/worker/pool/recovery-decision.js +163 -0
  57. package/dist/worker/pool/run-owner-store.js +126 -0
  58. package/dist/worker/pool/run-store.js +32 -46
  59. package/dist/worker/pool/runtime-reconcile-inventory.js +127 -0
  60. package/dist/worker/pool/state-projection.js +57 -0
  61. package/dist/worker/run-task/run-task.js +31 -4
  62. package/dist/worker/runner/run-ready.js +64 -14
  63. package/dist/worker/runner/single-task-attempt.js +42 -13
  64. package/dist/worker/task-graph/acceptance-schema.js +3 -0
  65. package/docs/README.md +3 -1
  66. package/docs/architecture/evolution.md +1 -1
  67. package/docs/operations/README.md +1 -0
  68. package/docs/templates/README.md +1 -0
  69. package/docs/templates/agent-worker-production-readiness-checklist.md +47 -0
  70. package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
  71. package/docs/templates/evaluation/agents-map-verbose-v0.md +1 -1
  72. package/docs/templates/init-managed-agents.md +1 -1
  73. package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +3 -0
  74. package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +11 -0
  75. package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +3 -0
  76. package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +11 -0
  77. package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +3 -0
  78. package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +11 -0
  79. package/package.json +5 -4
  80. package/skills/agent-worker/SKILL.md +1 -1
  81. package/skills/agent-worker/references/agent-worker-operator.md +1 -1
  82. package/skills/loop-agent/references/command-reference.md +2 -2
  83. package/skills/loop-agent/references/harness-policy.md +1 -1
  84. package/dist/worker/console/static/assets/index-D9gnJn_l.js +0 -29
  85. package/dist/worker/console/static/assets/index-rajoXwkM.css +0 -1
@@ -6,7 +6,7 @@ import { getTaskPaths } from "../../task/runtime.js";
6
6
  import { formatDuration, noopProgressReporter, } from "../progress-reporter.js";
7
7
  import { redactForPreview, truncatePreview } from "../observability/events.js";
8
8
  import { controllerIdentityExpectationFailure, resolveControllerIdentity, } from "../loop-agent/loop-agent-client.js";
9
- import { materializeTaskSpec, } from "../materialize/harness-task-materializer.js";
9
+ import { materializeTaskSpecV2, } from "../materialize/harness-task-materializer.js";
10
10
  import { preflightTargetRepo } from "../preflight.js";
11
11
  import { getTaskPoolRoot } from "../pool/run-store.js";
12
12
  import { checkRequiredOutputs } from "../outcomes/gate.js";
@@ -26,7 +26,12 @@ export async function runTaskSpec(options) {
26
26
  const commands = [];
27
27
  const progress = options.progress ?? noopProgressReporter;
28
28
  const taskId = options.taskSpec.id;
29
- const eventCtx = { progress, workerRunId, taskId };
29
+ const eventCtx = {
30
+ progress,
31
+ workerRunId,
32
+ taskId,
33
+ featureId: options.taskSpec.feature_id,
34
+ };
30
35
  const recordingClient = new RecordingRunTaskClient(options.client, commands);
31
36
  const client = new ObservedRunTaskClient(recordingClient, eventCtx);
32
37
  if (options.preflight !== false) {
@@ -57,9 +62,9 @@ export async function runTaskSpec(options) {
57
62
  }
58
63
  const materializeStep = "materialize";
59
64
  const materializeStartedAt = Date.now();
60
- const materializeManifest = await runObservedStep(eventCtx, materializeStep, async () => {
65
+ const materializeResult = await runObservedStep(eventCtx, materializeStep, async () => {
61
66
  progress.step("materialize harness task");
62
- return materializeTaskSpec({
67
+ return materializeTaskSpecV2({
63
68
  repoRoot: options.repoRoot,
64
69
  taskSpec: options.taskSpec,
65
70
  taskSpecPath: options.taskSpecPath,
@@ -67,6 +72,14 @@ export async function runTaskSpec(options) {
67
72
  now,
68
73
  });
69
74
  });
75
+ const materializeManifest = materializeResult.manifest;
76
+ const materializeOutcome = materializeResult.outcome;
77
+ if (materializeOutcome.type === "recovery-required") {
78
+ throw new Error(`materialize recovery required (${materializeOutcome.recovery.recommendedAction}): ${materializeOutcome.recovery.reason}`);
79
+ }
80
+ if (materializeOutcome.type === "resume-existing") {
81
+ progress.note(`materialize resume-existing: revision ${materializeOutcome.revisionId}, run ${materializeOutcome.runId}`);
82
+ }
70
83
  progress.step(`materialized ${materializeManifest.harnessTaskId} in ${formatDuration(Date.now() - materializeStartedAt)}`);
71
84
  const taskArtifactsDir = path.join(options.repoRoot, ".harness", "tasks", materializeManifest.harnessTaskId, "artifacts");
72
85
  await mkdir(taskArtifactsDir, { recursive: true });
@@ -230,6 +243,9 @@ export async function runTaskSpec(options) {
230
243
  dagPath,
231
244
  runRecordPath,
232
245
  materializeManifest,
246
+ materializeOutcome,
247
+ lineageId: materializeManifest.lineageId,
248
+ revisionId: materializeManifest.revisionId,
233
249
  reportDecision,
234
250
  commands,
235
251
  ...(declaredArtifacts ? { declaredArtifacts } : {}),
@@ -317,6 +333,9 @@ export async function runTaskSpec(options) {
317
333
  dagPath,
318
334
  runRecordPath,
319
335
  materializeManifest,
336
+ materializeOutcome,
337
+ lineageId: materializeManifest.lineageId,
338
+ revisionId: materializeManifest.revisionId,
320
339
  reportDecision,
321
340
  commands,
322
341
  ...(declaredArtifacts ? { declaredArtifacts } : {}),
@@ -329,6 +348,7 @@ export async function runTaskSpec(options) {
329
348
  source: "artifact",
330
349
  label: "worker-run-record",
331
350
  workerRunId,
351
+ featureId: options.taskSpec.feature_id,
332
352
  taskId,
333
353
  artifactRefs: {
334
354
  runRecordPath,
@@ -496,6 +516,7 @@ function emitStepStarted(ctx, stepName) {
496
516
  spanId: `step:${ctx.workerRunId}:${stepName}`,
497
517
  parentSpanId: ctx.workerRunId,
498
518
  workerRunId: ctx.workerRunId,
519
+ featureId: ctx.featureId,
499
520
  taskId: ctx.taskId,
500
521
  status: "running",
501
522
  });
@@ -508,6 +529,7 @@ function emitStepFinished(ctx, stepName, status, durationMs) {
508
529
  spanId: `step:${ctx.workerRunId}:${stepName}`,
509
530
  parentSpanId: ctx.workerRunId,
510
531
  workerRunId: ctx.workerRunId,
532
+ featureId: ctx.featureId,
511
533
  taskId: ctx.taskId,
512
534
  status,
513
535
  durationMs,
@@ -760,6 +782,7 @@ class ObservedRunTaskClient {
760
782
  source: "loop-agent-command",
761
783
  label: options.artifactName,
762
784
  workerRunId: this.eventCtx.workerRunId,
785
+ featureId: this.eventCtx.featureId,
763
786
  taskId: this.eventCtx.taskId,
764
787
  pid: info.pid,
765
788
  timeoutMs: options.timeoutMs,
@@ -783,6 +806,7 @@ class ObservedRunTaskClient {
783
806
  source: "loop-agent-command",
784
807
  label: options.artifactName,
785
808
  workerRunId: this.eventCtx.workerRunId,
809
+ featureId: this.eventCtx.featureId,
786
810
  taskId: this.eventCtx.taskId,
787
811
  message: `elapsed ${info.elapsedMs}ms`,
788
812
  });
@@ -795,6 +819,7 @@ class ObservedRunTaskClient {
795
819
  source: "loop-agent-command",
796
820
  label: options.artifactName,
797
821
  workerRunId: this.eventCtx.workerRunId,
822
+ featureId: this.eventCtx.featureId,
798
823
  taskId: this.eventCtx.taskId,
799
824
  exitCode: result.exitCode,
800
825
  timedOut: result.timedOut,
@@ -810,6 +835,7 @@ class ObservedRunTaskClient {
810
835
  source: "loop-agent-command",
811
836
  label: options.artifactName,
812
837
  workerRunId: this.eventCtx.workerRunId,
838
+ featureId: this.eventCtx.featureId,
813
839
  taskId: this.eventCtx.taskId,
814
840
  status: "failed",
815
841
  message: errorMessage(error),
@@ -823,6 +849,7 @@ class ObservedRunTaskClient {
823
849
  source: "loop-agent-command",
824
850
  label,
825
851
  workerRunId: this.eventCtx.workerRunId,
852
+ featureId: this.eventCtx.featureId,
826
853
  taskId: this.eventCtx.taskId,
827
854
  outputPreview: redactForPreview(truncatePreview(chunk)),
828
855
  });
@@ -2,9 +2,13 @@ import { createHash } from "node:crypto";
2
2
  import { readFile, writeFile, mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import YAML from "yaml";
5
+ import { controllerIdentityAnchor } from "../../shared/package-metadata.js";
5
6
  import { controllerIdentitiesMatch, controllerIdentityExpectationFailure, resolveControllerIdentity, } from "../loop-agent/loop-agent-client.js";
7
+ import { buildPublishedControllerProtocolEnvelope } from "../loop-agent/controller-protocol.js";
6
8
  import { deriveFailureRoute, deriveFailureRouteFromError, } from "../pool/failure-routing.js";
7
9
  import { findRunByWorkerRunId, getTaskPoolRoot, readFeatureTaskPoolStates, recordTaskPoolRun, writeTaskPoolState, } from "../pool/run-store.js";
10
+ import { releaseAttemptLease } from "../pool/attempt-lease.js";
11
+ import { beginWorkerAttemptWithLease } from "../pool/begin-attempt-with-lease.js";
8
12
  import { readVerifiedOutcome } from "../outcomes/store.js";
9
13
  import { runTaskSpec, } from "../run-task/run-task.js";
10
14
  import { formatDuration, noopProgressReporter, } from "../progress-reporter.js";
@@ -100,6 +104,7 @@ export async function runReadyTasks(options) {
100
104
  source: "worker",
101
105
  label: `task ${taskId}: reuse existing run ${workerRunId}`,
102
106
  batchRunId,
107
+ featureId,
103
108
  taskId,
104
109
  workerRunId,
105
110
  status: "reused",
@@ -116,32 +121,44 @@ export async function runReadyTasks(options) {
116
121
  }
117
122
  const taskStartedAt = Date.now();
118
123
  progress.task(`task ${index}/${total} ${taskId} "${taskSpec.title}" (${workerRunId})`);
119
- emit(progress, {
120
- type: "task.started",
121
- source: "worker",
122
- label: `task ${index}/${total} ${taskId}`,
123
- batchRunId,
124
- taskId,
125
- workerRunId,
126
- status: "running",
127
- });
128
124
  progress.step(`materialize → task advance → report`);
129
125
  let result;
126
+ let leaseToken;
130
127
  try {
131
- await writeTaskPoolState(options.repoRoot, {
132
- schemaVersion: 2,
128
+ const begun = await beginWorkerAttemptWithLease({
129
+ repoRoot: options.repoRoot,
130
+ featureId,
131
+ taskId,
132
+ workerRunId,
133
+ batchRunId,
134
+ now,
135
+ ...(controllerIdentity
136
+ ? {
137
+ controllerAnchor: controllerIdentityAnchor(controllerIdentity),
138
+ protocolEnvelope: buildPublishedControllerProtocolEnvelope(controllerIdentity.packageVersion),
139
+ }
140
+ : {}),
141
+ stateExtras: {
142
+ ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
143
+ },
144
+ });
145
+ leaseToken = begun.leaseRegistration.lease.token;
146
+ emit(progress, {
147
+ type: "task.started",
148
+ source: "worker",
149
+ label: `task ${index}/${total} ${taskId}`,
150
+ batchRunId,
133
151
  featureId,
134
152
  taskId,
135
- status: "Running",
136
- updatedAt: new Date().toISOString(),
137
153
  workerRunId,
138
- ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
154
+ status: "running",
139
155
  });
140
156
  emit(progress, {
141
157
  type: "state.updated",
142
158
  source: "worker",
143
159
  label: `task pool state: ${taskId} → Running`,
144
160
  batchRunId,
161
+ featureId,
145
162
  taskId,
146
163
  workerRunId,
147
164
  status: "running",
@@ -178,6 +195,7 @@ export async function runReadyTasks(options) {
178
195
  source: "worker",
179
196
  label: `task ${taskId} run-error`,
180
197
  batchRunId,
198
+ featureId,
181
199
  taskId,
182
200
  workerRunId,
183
201
  status: "failed",
@@ -215,6 +233,16 @@ export async function runReadyTasks(options) {
215
233
  }
216
234
  if (recorded)
217
235
  await options.onTaskFinalized?.({ status: "run-error", taskSpec, workerRunId });
236
+ if (leaseToken) {
237
+ await releaseAttemptLease({
238
+ repoRoot: options.repoRoot,
239
+ featureId,
240
+ taskId,
241
+ token: leaseToken,
242
+ workerRunId,
243
+ now,
244
+ }).catch(() => { });
245
+ }
218
246
  tasks.push({
219
247
  taskId,
220
248
  workerRunId,
@@ -236,6 +264,7 @@ export async function runReadyTasks(options) {
236
264
  source: "worker",
237
265
  label: `task ${taskId} ${result.status}`,
238
266
  batchRunId,
267
+ featureId,
239
268
  taskId,
240
269
  workerRunId: result.workerRunId,
241
270
  status: result.status,
@@ -247,6 +276,7 @@ export async function runReadyTasks(options) {
247
276
  source: "worker",
248
277
  label: `failure routed: ${failure.category}`,
249
278
  batchRunId,
279
+ featureId,
250
280
  taskId,
251
281
  workerRunId: result.workerRunId,
252
282
  failureCategory: failure.category,
@@ -299,6 +329,16 @@ export async function runReadyTasks(options) {
299
329
  status: result.status,
300
330
  runRecordPath: result.runRecordPath,
301
331
  });
332
+ if (leaseToken) {
333
+ await releaseAttemptLease({
334
+ repoRoot: options.repoRoot,
335
+ featureId,
336
+ taskId,
337
+ token: leaseToken,
338
+ workerRunId: result.workerRunId,
339
+ now,
340
+ }).catch(() => { });
341
+ }
302
342
  }
303
343
  catch (error) {
304
344
  if (result.status !== "succeeded") {
@@ -344,6 +384,16 @@ export async function runReadyTasks(options) {
344
384
  catch {
345
385
  // Best-effort; batch still reports record-error.
346
386
  }
387
+ if (leaseToken) {
388
+ await releaseAttemptLease({
389
+ repoRoot: options.repoRoot,
390
+ featureId,
391
+ taskId,
392
+ token: leaseToken,
393
+ workerRunId: result.workerRunId,
394
+ now,
395
+ }).catch(() => { });
396
+ }
347
397
  tasks.push({
348
398
  taskId,
349
399
  workerRunId: result.workerRunId,
@@ -1,4 +1,8 @@
1
+ import { controllerIdentityAnchor } from "../../shared/package-metadata.js";
2
+ import { buildPublishedControllerProtocolEnvelope } from "../loop-agent/controller-protocol.js";
3
+ import { releaseAttemptLease } from "../pool/attempt-lease.js";
1
4
  import { deriveFailureRoute, deriveFailureRouteFromError, } from "../pool/failure-routing.js";
5
+ import { beginWorkerAttemptWithLease } from "../pool/begin-attempt-with-lease.js";
2
6
  import { recordTaskPoolRun, writeTaskPoolState } from "../pool/run-store.js";
3
7
  /**
4
8
  * Shared Worker attempt used by batch run-ready and Night Scheduler.
@@ -7,24 +11,33 @@ import { recordTaskPoolRun, writeTaskPoolState } from "../pool/run-store.js";
7
11
  export async function runSingleTaskAttempt(input) {
8
12
  const now = input.ctx.now ?? new Date();
9
13
  const { ctx } = input;
10
- await writeTaskPoolState(ctx.controlRepoRoot, {
11
- schemaVersion: 2,
14
+ const begun = await beginWorkerAttemptWithLease({
15
+ repoRoot: ctx.controlRepoRoot,
12
16
  featureId: ctx.featureId,
13
17
  taskId: ctx.taskId,
14
- status: "Running",
15
- updatedAt: now.toISOString(),
16
18
  workerRunId: ctx.workerRunId,
17
- ...(ctx.night?.nightScheduleId
18
- ? { nightScheduleId: ctx.night.nightScheduleId }
19
- : {}),
20
- ...(ctx.night?.admissionPath
21
- ? { admissionPath: ctx.night.admissionPath }
22
- : {}),
23
- ...(ctx.night?.nightWorktreePath
24
- ? { nightWorktreePath: ctx.night.nightWorktreePath }
19
+ batchRunId: ctx.batchRunId,
20
+ now,
21
+ ...(ctx.controllerIdentity
22
+ ? {
23
+ controllerAnchor: controllerIdentityAnchor(ctx.controllerIdentity),
24
+ protocolEnvelope: buildPublishedControllerProtocolEnvelope(ctx.controllerIdentity.packageVersion),
25
+ }
25
26
  : {}),
26
- ...(ctx.night?.nightBranch ? { nightBranch: ctx.night.nightBranch } : {}),
27
+ stateExtras: {
28
+ ...(ctx.night?.nightScheduleId
29
+ ? { nightScheduleId: ctx.night.nightScheduleId }
30
+ : {}),
31
+ ...(ctx.night?.admissionPath
32
+ ? { admissionPath: ctx.night.admissionPath }
33
+ : {}),
34
+ ...(ctx.night?.nightWorktreePath
35
+ ? { nightWorktreePath: ctx.night.nightWorktreePath }
36
+ : {}),
37
+ ...(ctx.night?.nightBranch ? { nightBranch: ctx.night.nightBranch } : {}),
38
+ },
27
39
  });
40
+ const leaseToken = begun.leaseRegistration.lease.token;
28
41
  let result;
29
42
  try {
30
43
  result = await input.execute(ctx);
@@ -70,6 +83,14 @@ export async function runSingleTaskAttempt(input) {
70
83
  : {}),
71
84
  ...(ctx.night?.nightBranch ? { nightBranch: ctx.night.nightBranch } : {}),
72
85
  });
86
+ await releaseAttemptLease({
87
+ repoRoot: ctx.controlRepoRoot,
88
+ featureId: ctx.featureId,
89
+ taskId: ctx.taskId,
90
+ token: leaseToken,
91
+ workerRunId: ctx.workerRunId,
92
+ now,
93
+ }).catch(() => { });
73
94
  return {
74
95
  status: "run-error",
75
96
  workerRunId: ctx.workerRunId,
@@ -137,6 +158,14 @@ export async function runSingleTaskAttempt(input) {
137
158
  ...(ctx.night?.nightBranch ? { nightBranch: ctx.night.nightBranch } : {}),
138
159
  };
139
160
  await writeTaskPoolState(ctx.controlRepoRoot, poolState);
161
+ await releaseAttemptLease({
162
+ repoRoot: ctx.controlRepoRoot,
163
+ featureId: ctx.featureId,
164
+ taskId: ctx.taskId,
165
+ token: leaseToken,
166
+ workerRunId: ctx.workerRunId,
167
+ now,
168
+ }).catch(() => { });
140
169
  return {
141
170
  status: result.status,
142
171
  workerRunId: result.workerRunId,
@@ -21,6 +21,9 @@ export const acceptanceItemSchema = z
21
21
  implementation_task_refs: z.array(z.string().min(1)).optional(),
22
22
  verification_task_refs: z.array(z.string().min(1)).optional(),
23
23
  required_evidence: z.array(z.string().min(1)).optional(),
24
+ /** Phase 6: ADR 0007 authority / evidence kinds on required ACs. */
25
+ authority: z.string().min(1).optional(),
26
+ required_evidence_kinds: z.array(z.string().min(1)).optional(),
24
27
  integration: z
25
28
  .enum(["not-applicable", "mock-allowed", "real-required"])
26
29
  .optional(),
package/docs/README.md CHANGED
@@ -29,7 +29,9 @@
29
29
  | 运行 DAG、查看报告或处理失败 | `runtime/agent-dag-runner.md`、`runtime/agent-dag-recovery-playbook.md` |
30
30
  | 按 TDD、验证和调试纪律推进修复 | `governance/harness-methodology-tdd.md`、`governance/harness-methodology-verification.md`、`governance/harness-methodology-debugging.md` |
31
31
  | 执行真实 backend-test provider 验证 | `runtime/backend-test-live-campaign.md` |
32
- | 判断 Production Readiness | `operations/production-readiness.md` |
32
+ | 判断单任务 loop-agent Production Readiness | `operations/production-readiness.md` |
33
+ | 判断 agent-worker Feature/Attempt Production Readiness v1 | `operations/agent-worker-production-readiness.md`、`templates/agent-worker-production-readiness-checklist.md` |
34
+ | 分阶段推进 agent-worker Production Readiness v1 | `exec-plans/active/2026-08-09-agent-worker-production-readiness-v1-program.md`、`exec-plans/active/README.md` |
33
35
  | 合并分支并留下 source-SHA 报告 | `operations/branch-merge-guideline.md` |
34
36
  | 使用仓库内 GitHub 协作方式 | `operations/github-collaboration.md` |
35
37
  | 排查本机开发环境问题 | `operations/local-development-environment.md` |
@@ -20,7 +20,7 @@
20
20
  | Backend-test | **Markdown-first 8 节点**;Markdown 映射限定 pytest 执行,生成 Markdown 与分层 HTML/脱敏请求响应详情 | completed `2026-07-24-backend-test-scoped-execution-report-ui.md`;`CHANGELOG.md [0.21.0]` / `[0.22.0]` |
21
21
  | Frontend-test | 默认 short-chain RAG;内容/evidence 质量进入 advisory findings,路径与安全边界仍 fail-closed | `CHANGELOG.md [0.21.0]` / `[0.22.0]` |
22
22
  | Frontend-implementation | Contract / trace / repair / Mock assess;`ai_workspace` + 知识库 + `openspec` 规范发现;lint 基线债务隔离 | `CHANGELOG.md [0.22.0]` |
23
- | Inspect(原 Observe) | 统一 Console 的 `/inspect/` 只读运营面与富时间线;兼容期仍保留独立 `observe serve` | `website/docs/guides/observe-ui.md`、`CHANGELOG.md [Unreleased]` |
23
+ | Inspect(原 Observe) | 统一 Console 的 `/inspect/` 只读运营面与富时间线;独立 `observe serve` 已硬下线 | `website/docs/guides/observe-ui.md`、`CHANGELOG.md [Unreleased]` |
24
24
  | Local Operator Console | `agent-worker console serve\|doctor`(loopback);Operate + Inspect、Happy Path / Interview / split view / recovery CTA;**0.23.x 起含 General Operator Chat**(Pi SDK services session、operator-only tool surface);**非**远端多用户 Console | unify design;ADR 0005;completed `2026-07-25-console-phase-4-general-operator-chat.md` |
25
25
  | Task Contract / operator surface | journaled `task contract *`;machine envelope;**DagSpec v4 `taskContractBinding`**(exclusive writer 强制) | `CHANGELOG.md [0.17.0]`–`[0.17.2]`;ADR 0005 |
26
26
  | DagSpec / repair | v3 `runtimeContract` + 显式 `repairNodeId`;新 writer 生成默认 v4 binding | `dag-execution.md`、`CHANGELOG.md [0.11.0]` / `[0.17.0]` |
@@ -8,6 +8,7 @@
8
8
  - [`branch-merge-guideline.md`](branch-merge-guideline.md):按风险选择合并模式并留下 source-SHA 证据。
9
9
  - [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及自动/人工发布通道。
10
10
  - [`production-readiness.md`](production-readiness.md):交付前的生产就绪判断。
11
+ - [`agent-worker-production-readiness.md`](agent-worker-production-readiness.md):agent-worker Feature/Task Pool 外层编排的 Production Readiness v1 操作合同。
11
12
  - [`backend-test-jacoco-coverage.md`](backend-test-jacoco-coverage.md):Java 被测服务挂载 JaCoCo tcpserver agent 的 Maven、`java -jar` 与 Docker 操作手册。
12
13
 
13
14
  验证命令仍以 [`../governance/verification-matrix.md`](../governance/verification-matrix.md) 为准。
@@ -14,6 +14,7 @@
14
14
  - `branch-merge-report.md` — source-SHA 分支合并报告。
15
15
  - `init-evolution-review.md` — 初始化能力演化审查。
16
16
  - `production-readiness-checklist.md` — Production Readiness 检查。
17
+ - `agent-worker-production-readiness-checklist.md` — agent-worker Feature/Task Pool Production Readiness v1 campaign 检查清单。
17
18
  - `worker-dogfood-setup.md`、`worker-dogfood-evidence.md` — 已发布控制器下的 Worker dogfood 设置与证据。
18
19
  - `interactive-ui-round2-experiment.md` — 交互式 UI 对照实验。
19
20
 
@@ -0,0 +1,47 @@
1
+ # Agent Worker Production Readiness v1 Checklist
2
+
3
+ 用于 Phase 8 campaign / 发布门禁。勾选前必须有新鲜命令输出或 report ref。
4
+
5
+ 正式 pin(2026-08-09):`@tea-agent/loop-agent@0.32.0`(tag `v0.32.0` / `b4d9e500`)。scorecard 在 burn-in / live 门禁完成前保持 **NOT_READY**。证据:`docs/reports/feature/2026-08-09-agent-worker-production-readiness-v1.md`。
6
+
7
+ ## 包络与身份
8
+
9
+ - [x] 支持包络已冻结(单机/单用户/单仓)
10
+ - [x] controller packageVersion + fingerprint 已记录(`0.32.0`;npm shasum `8932749b1583523043a9889c84f9d316e57e451d`;canary fingerprint `sha256:b08404f5…`)
11
+ - [x] published canary / 隔离 install **无** npm link / workspace bin(live burn-in 仍须每日复核)
12
+ - [x] protocol/capabilities preflight(unit)通过;live preflight 仍待
13
+
14
+ ## Ownership / Attempt
15
+
16
+ - [x] Running 投影不依赖 terminal ledger(unit / Phase 1)
17
+ - [x] Task-scoped events 含 featureId(unit)
18
+ - [x] dual Feature 同名 Task 隔离(unit C2)
19
+ - [x] ownership warning 按根因聚合(≤1 finding/根因)(unit)
20
+
21
+ ## Lifecycle / Recovery
22
+
23
+ - [x] safe run-error → Failed → `task retry` 无需 mark-failed(unit)
24
+ - [x] Blocked 保留 contract/human 语义(unit)
25
+ - [x] resume / retry / reconcile / revise / decide 决策一致(unit)
26
+
27
+ ## Lease
28
+
29
+ - [x] `{featureId, taskId}` 单 active writer(unit C3)
30
+ - [ ] begin/finalize crash injection 有 doctor finding(进程级仍缺)
31
+ - [x] 不盲目偷取 live DAG lease(unit reclaim)
32
+
33
+ ## Acceptance / Delivery
34
+
35
+ - [x] Task Done ≠ AC covered(unit)
36
+ - [x] `feature verify-final` 唯一 writer(unit)
37
+ - [ ] verify-final → delivery → closeout **live** 可收口
38
+ - [x] dirty HEAD / tamper fail-closed(unit)
39
+
40
+ ## Campaign / SLO
41
+
42
+ - [x] C0–C11 已执行或明确 blocked-external(partial:unit + published canary;live Pi blocked)
43
+ - [ ] burn-in 窗口达标(7 日或 30 Attempt)— **OPEN day 0**
44
+ - [ ] SLO 分子/分母已记录(正式样本不足)
45
+ - [x] promote + rollback drill 完成(install-level vs `0.31.1`)
46
+ - [ ] scorecard: READY 或合法 CONDITIONALLY_READY — 当前 **`NOT_READY`**
47
+ - [x] open P0 = 0
@@ -37,7 +37,7 @@
37
37
  4. 涉及测试纪律/验证声明/调试时继续读:`docs/harness-methodology-*.md`。
38
38
  5. 查看最近提交、相关 plan/progress/report;`git status --short --branch`;跑最小基线验证。
39
39
  6. 后端/接口/pytest → `taskKind: "backend-test"`(不是 `--profile`);知识回写 `knowledge-sync`;图谱开荒 `knowledge-graph-bootstrap`。`--profile` 仅 `auto|minimal|standard|reviewed|supervised`。
40
- 7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;兼容 `agent-worker console serve --repo . --port 8790`)(`/inspect/` 只读);`observe serve` 仅为兼容入口。
40
+ 7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;兼容 `agent-worker console serve --repo . --port 8790`)(`/inspect/` 只读);`observe serve` 已下线(REMOVED / exit 2);`observe snapshot` 仍可用。
41
41
  8. 分支合并 → 先读 `docs/operations/branch-merge-guideline.md`。
42
42
 
43
43
  ## 会话协议
@@ -54,7 +54,7 @@
54
54
  10. 检查 `git status --short --branch`。
55
55
  11. 运行本次任务相关的最小基线验证。
56
56
  12. 如果用户提到"后端测试"、"接口测试"、"pytest"、"自动化测试",在任务 `task.json` 中设置 `taskKind: "backend-test"` 再 `task advance`;不要用 `--profile backend-test`(CLI 不接受该值,专用模板只走 taskKind)。知识回写用 `taskKind: "knowledge-sync"`(须 `featureId`),图谱开荒用 `taskKind: "knowledge-graph-bootstrap"`。`--profile` 仅表示治理强度:`auto|minimal|standard|reviewed|supervised`。
57
- 13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console` 启动统一 Operator Console(默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790`);`/inspect/` 提供只读检视。`agent-worker observe serve --repo . --port 8787` 仅为兼容入口。
57
+ 13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console` 启动统一 Operator Console(默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790`);`/inspect/` 提供只读检视。`agent-worker observe serve` 已下线(REMOVED / exit 2)。
58
58
  14. 如果用户要求“合并 `<source>` 到 `<target>`”或“合并 origin/main 到当前分支”,先阅读 `docs/operations/branch-merge-guideline.md`,按影响自动选择快速、标准或深度模式;始终冻结 source SHA、审查双方功能、运行 merge-tree、生成 source-SHA 合并报告,并在提交前再次 fetch 防止主干前进。
59
59
 
60
60
  ## 会话协议
@@ -131,7 +131,7 @@ agent-worker console # 默认 repo=当前目录,port=879
131
131
  agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上面裸入口
132
132
  ```
133
133
 
134
- 浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。`agent-worker observe serve` 仅为兼容入口。
134
+ 浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。`agent-worker observe serve` 已下线(REMOVED / exit 2)。
135
135
 
136
136
  ### DAG 诊断与收口
137
137
 
@@ -11,3 +11,6 @@ acceptance:
11
11
  verification:
12
12
  expected_task_refs:
13
13
  - BE-IMPL-001
14
+ authority: feature-verify-final
15
+ required_evidence_kinds:
16
+ - canonical-qa-evidence
@@ -1,3 +1,14 @@
1
1
  schema_version: 1
2
2
  feature_id: F-2099-901
3
3
  profile: generic
4
+ acceptance_policy:
5
+ schema_version: 1
6
+ authority: feature-verify-final
7
+ independent_qa_required: true
8
+ coverage_writer: feature-verify-final
9
+ final_verification:
10
+ command: agent-worker feature verify-final
11
+ delivery_sequence:
12
+ - verify-final
13
+ - delivery
14
+ - closeout
@@ -13,3 +13,6 @@ acceptance:
13
13
  - CONTRACT-001
14
14
  - BE-IMPL-001
15
15
  - FE-IMPL-001
16
+ authority: feature-verify-final
17
+ required_evidence_kinds:
18
+ - canonical-qa-evidence
@@ -1,3 +1,14 @@
1
1
  schema_version: 1
2
2
  feature_id: F-2099-903
3
3
  profile: generic
4
+ acceptance_policy:
5
+ schema_version: 1
6
+ authority: feature-verify-final
7
+ independent_qa_required: true
8
+ coverage_writer: feature-verify-final
9
+ final_verification:
10
+ command: agent-worker feature verify-final
11
+ delivery_sequence:
12
+ - verify-final
13
+ - delivery
14
+ - closeout
@@ -11,3 +11,6 @@ acceptance:
11
11
  verification:
12
12
  expected_task_refs:
13
13
  - FE-IMPL-001
14
+ authority: feature-verify-final
15
+ required_evidence_kinds:
16
+ - canonical-qa-evidence
@@ -1,3 +1,14 @@
1
1
  schema_version: 1
2
2
  feature_id: F-2099-902
3
3
  profile: generic
4
+ acceptance_policy:
5
+ schema_version: 1
6
+ authority: feature-verify-final
7
+ independent_qa_required: true
8
+ coverage_writer: feature-verify-final
9
+ final_verification:
10
+ command: agent-worker feature verify-final
11
+ delivery_sequence:
12
+ - verify-final
13
+ - delivery
14
+ - closeout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.31.1",
3
+ "version": "0.32.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -46,11 +46,12 @@
46
46
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
47
47
  "brand:sync": "node scripts/sync-brand-assets.mjs",
48
48
  "build": "npm run brand:sync && npm run clean && tsc -p tsconfig.build.json && node -e \"const fs=require('node:fs');const p='dist/worker/observe/static';fs.mkdirSync(p,{recursive:true});fs.cpSync('src/worker/observe/static',p,{recursive:true});\" && npm run console:build",
49
- "console:build": "vite build --config src/worker/console/vite.config.ts",
49
+ "console:typecheck": "tsc -p src/worker/console/tsconfig.json",
50
+ "console:build": "npm run console:typecheck && vite build --config src/worker/console/vite.config.ts",
50
51
  "prepack": "npm run build",
51
52
  "prepublishOnly": "node scripts/check-npm-publish-policy.mjs && npm run typecheck && npm test && npm run build",
52
- "lint": "tsc --noEmit",
53
- "typecheck": "tsc --noEmit",
53
+ "lint": "npm run typecheck",
54
+ "typecheck": "tsc --noEmit && npm run console:typecheck",
54
55
  "test": "node scripts/run-tests.mjs",
55
56
  "test:host": "vitest run test/init-upgrade.test.ts --maxWorkers=1 -t \"records and reuses|controller-authorized semantic merge|authority-sensitive\"",
56
57
  "test:fast": "vitest run --config vitest.fast.config.ts",
@@ -16,7 +16,7 @@ references:
16
16
  - **允许**:`agent-worker` / `loop-agent` CLI;只读 `pool doctor`、`observe`、status/report;冻结 controller identity;选择 Ready 工作与 recovery 命令。
17
17
  - **禁止**:绕过 CLI 直接 Edit 业务实现;Worker/DAG 失败后主会话「救火改文件」。
18
18
  - **失败时只允许**:保留 evidence → `task retry` / `task reconcile` / `pool mark-failed` / human gate → 再经 CLI 重跑;实现写入仍只经 published `loop-agent` DAG。
19
- - **Official vs Compatibility**:`agent-worker console` 是 Official 本地控制面(裸入口直接启动;默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790` 等价)。同进程提供 Operate + Inspect,Inspect 路径 `/inspect/#/...`;openCode 等主会话仍是 Compatibility Assist,二者**不是**同等保证。原 `observe serve` 为兼容期只读入口(启动时输出 `OBSERVE_SERVE_DEPRECATED`,stdout 仍只输出 URL),功能等价于 Console Inspect 面。
19
+ - **Official vs Compatibility**:`agent-worker console` 是 Official 本地控制面(裸入口直接启动;默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790` 等价)。同进程提供 Operate + Inspect,Inspect 路径 `/inspect/#/...`;openCode 等主会话仍是 Compatibility Assist,二者**不是**同等保证。`observe serve` 已硬下线(`OBSERVE_SERVE_REMOVED` + exit 2,不监听端口);只读检视请用 Console `/inspect/`;`observe snapshot` 仍输出 GlobalSnapshot JSON。
20
20
 
21
21
  ## Route the Work
22
22
 
@@ -54,7 +54,7 @@ Leaf DAG nodes 不得递归启动 `agent-worker`。Worker 负责 DAG 之外的 s
54
54
  ```
55
55
 
56
56
  doctor 只读;migrate 默认零写入,apply 失败全回滚且不改 JSONL。
57
- 8. Inspect(原 Observe)只读:推荐 `agent-worker console serve` `/inspect/#/...`;兼容 `observe serve|snapshot`。canonical Task 路由为 `/api/features/:featureId/tasks/:taskId` 与 `#/feature/:featureId/task/:taskId`。8790 `/api/health` 为 `OperatorSurfaceHealthV1`;8787 仍为 `ObserveHealthV1`。
57
+ 8. Inspect(原 Observe)只读:仅推荐 `agent-worker console`(或 `console serve`)的 `/inspect/#/...`;`observe snapshot` 仍可用。`observe serve` 已下线(REMOVED / exit 2)。canonical Task 路由为 `/api/features/:featureId/tasks/:taskId` 与 `#/feature/:featureId/task/:taskId`。8790 `/api/health` 为 `OperatorSurfaceHealthV1`。
58
58
  9. Official Console:`agent-worker console serve|doctor`(默认 loopback;可 `--host 0.0.0.0` / `--debug`)。Recovery CTA 为 report/doctor/decision/resume/reconcile/regenerate/打开检视;无 Cancel、无主 CTA「直接改代码」。主会话 Compatibility Assist 不得替代 Console/CLI 执法。
59
59
 
60
60
  ## Feature Packet Scaffold
@@ -576,7 +576,7 @@ agent-worker scheduler transition <schedule-id> --repo <repo-root> --to <status>
576
576
  agent-worker console [--repo <repo-root>] [--port 8790] [--host 127.0.0.1] # Official 裸入口;repo 默认当前目录
577
577
  agent-worker console serve --repo <repo-root> [--port 8790] [--host 127.0.0.1] # 兼容入口,等价于裸入口
578
578
  agent-worker console doctor --repo <repo-root> [--json] [--console-url <url>]
579
- agent-worker observe serve --repo <repo-root> [--port 8787] [--host 127.0.0.1] # 兼容入口;stderr 会提示 OBSERVE_SERVE_DEPRECATED
579
+ agent-worker observe serve ... # REMOVED:stderr OBSERVE_SERVE_REMOVED,exit 2,不监听端口;请用 console + /inspect/
580
580
  agent-worker observe snapshot --repo <repo-root> # 输出 GlobalSnapshot JSON 到 stdout
581
581
  ```
582
582
 
@@ -600,7 +600,7 @@ agent-worker observe snapshot --repo <repo-root> # 输出 GlobalSnapshot JSON
600
600
  - `report metrics` 按 UTC 月去重投影 Feature/Failure/Follow-up/Delivery/AC/decision/recovery/boundary 指标,同时写 JSON 与 Markdown;每项保留 numerator、denominator、sampleSize 和 missingData。
601
601
  - `task draft-followup` 会按全部 failure category 生成 TaskDraft 或人工行动卡:ProductBug/TestBug/FlakyTest/DependencyFailure 可批准;EnvFailure 连续两次后才生成 ENV-CHECK;Spec/Contract/Risk/Human/Unknown 只给行动卡。人工以 `feature approve-followup --dry-run` 预览,再带非空 `--owner` 批准 TaskDraft;行动卡不能批准。批准在 staging validation 后写 TaskSpec、graph、Ready/approval/event,原失败事实不改写,并有 rename/state/approval/index/event 回滚门禁。
602
602
  - `task retry` 是失败 Task 的唯一重试入口。它会保留原有运行记录和 failure handoff,并让下一次 `batch run-ready` 使用新的 `workerRunId`;不要删除运行态文件或手动修改状态来重试。
603
- - **推荐**裸入口 `agent-worker console`(repo 默认当前目录,默认 `127.0.0.1:8790`)提供 Operate + Inspect;`console serve` 是等价兼容入口。Inspect 路径为 `/inspect/#/...`,API 仍为根 `/api/**`。`observe serve` 为兼容入口(默认 `8787`,启动时 stderr 输出 `OBSERVE_SERVE_DEPRECATED`);`observe snapshot` 保留。Inspect/Observe 本身不会启动、暂停或重试 Task / Worker / DAG。
603
+ - **推荐**裸入口 `agent-worker console`(repo 默认当前目录,默认 `127.0.0.1:8790`)提供 Operate + Inspect;`console serve` 是等价兼容入口。Inspect 路径为 `/inspect/#/...`,API 仍为根 `/api/**`。`observe serve` 已硬下线(`OBSERVE_SERVE_REMOVED` + exit 2,不监听);`observe snapshot` 保留。Inspect 本身不会启动、暂停或重试 Task / Worker / DAG。
604
604
  - Night Scheduler(本地夜间自治):白天 `admission prepare` 冻结 worktree + DAG writeSet gate;`scheduler add` 消费 gate 并预约 Task Pool `Queued`(不立即执行);`scheduler clock install` 安装本机 user-level OS timer(launchd / systemd --user / schtasks)周期调用 `scheduler tick`;tick 写统一 Clock receipt,doctor/morning/status 可解释 missing/stale/error/busy/drift;成功后 `pending-harvest`,早晨 `scheduler harvest`(exact-base FF)或 `scheduler discard`;`report morning --window night` 与 Inspect `#/night` / Operate 夜间面板读取同一套 facts。Clock install/status/uninstall 为 CLI-only。
605
605
  - 当前 Worker 仍是 v0(库 + CLI + dogfood);日间批处理未强制定时/CI 驱动;`report morning` 默认可从 Task Pool runs 汇总,并支持 `--window night` 投影 Scheduler facts。
606
606