@fitlab-ai/agent-infra 0.9.7 → 0.9.8

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 (96) hide show
  1. package/dist/bin/internal-cli.js +5 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +2 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +2 -1
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +30 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
  7. package/dist/lib/decide.js +68 -14
  8. package/dist/lib/init.js +1 -1
  9. package/dist/lib/internal/release-workflow.js +1 -3
  10. package/dist/lib/internal/task-activity.js +57 -7
  11. package/dist/lib/internal/task-event.js +57 -4
  12. package/dist/lib/internal/task-ledger.js +59 -5
  13. package/dist/lib/internal/task-lifecycle.js +59 -8
  14. package/dist/lib/internal/task-orchestration.js +11 -3
  15. package/dist/lib/internal/task-override.js +131 -0
  16. package/dist/lib/internal/task-review.js +64 -6
  17. package/dist/lib/internal/task-verify.js +65 -3
  18. package/dist/lib/internal/task-warning.js +57 -4
  19. package/dist/lib/platform/pr-summary.js +5 -3
  20. package/dist/lib/runtime/agent-runtime.js +56 -0
  21. package/dist/lib/sandbox/commands/create.js +389 -311
  22. package/dist/lib/sandbox/control/container-identity.js +1 -1
  23. package/dist/lib/sandbox/control/executor.js +10 -2
  24. package/dist/lib/sandbox/control/lifecycle.js +392 -72
  25. package/dist/lib/sandbox/control/state.js +27 -11
  26. package/dist/lib/sandbox/engine.js +14 -7
  27. package/dist/lib/sandbox/mounts.js +20 -5
  28. package/dist/lib/sandbox/recovery.js +78 -10
  29. package/dist/lib/sandbox/workspace-view.js +62 -40
  30. package/dist/lib/server/process-state.js +51 -1
  31. package/dist/lib/task/activity-intent.js +4 -1
  32. package/dist/lib/task/decision-intents.js +4 -2
  33. package/dist/lib/task/delegation-receipts.js +37 -16
  34. package/dist/lib/task/events.js +9 -5
  35. package/dist/lib/task/guard-override.js +31 -0
  36. package/dist/lib/task/human-override.js +1240 -0
  37. package/dist/lib/task/ledger-intents.js +6 -3
  38. package/dist/lib/task/lifecycle.js +69 -14
  39. package/dist/lib/task/orchestration.js +60 -6
  40. package/dist/lib/task/review-finalization.js +3 -2
  41. package/dist/lib/task/verification-engine.js +40 -14
  42. package/dist/lib/task/verification.js +13 -1
  43. package/dist/lib/task/workflow-warning-intents.js +4 -2
  44. package/dist/lib/task/write.js +2 -1
  45. package/dist/lifecycle-build-manifest.json +3 -3
  46. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  47. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +2 -1
  48. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +2 -1
  49. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +29 -2
  50. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
  51. package/lib/decide.ts +59 -14
  52. package/lib/init.ts +1 -1
  53. package/lib/internal/release-workflow.ts +1 -3
  54. package/lib/internal/task-activity.ts +46 -6
  55. package/lib/internal/task-event.ts +53 -4
  56. package/lib/internal/task-ledger.ts +48 -4
  57. package/lib/internal/task-lifecycle.ts +56 -12
  58. package/lib/internal/task-orchestration.ts +12 -4
  59. package/lib/internal/task-override.ts +138 -0
  60. package/lib/internal/task-review.ts +51 -6
  61. package/lib/internal/task-verify.ts +52 -3
  62. package/lib/internal/task-warning.ts +44 -3
  63. package/lib/platform/pr-summary.ts +10 -3
  64. package/lib/runtime/agent-runtime.ts +76 -0
  65. package/lib/sandbox/commands/create.ts +88 -4
  66. package/lib/sandbox/config.ts +2 -1
  67. package/lib/sandbox/control/container-identity.ts +3 -3
  68. package/lib/sandbox/control/executor.ts +10 -2
  69. package/lib/sandbox/control/lifecycle.ts +463 -76
  70. package/lib/sandbox/control/protocol.ts +5 -2
  71. package/lib/sandbox/control/state.ts +35 -18
  72. package/lib/sandbox/engine.ts +20 -9
  73. package/lib/sandbox/mounts.ts +23 -5
  74. package/lib/sandbox/recovery.ts +90 -9
  75. package/lib/sandbox/workspace-view.ts +70 -40
  76. package/lib/server/process-state.ts +48 -1
  77. package/lib/task/activity-intent.ts +4 -2
  78. package/lib/task/decision-intents.ts +4 -2
  79. package/lib/task/delegation-receipts.ts +46 -18
  80. package/lib/task/events.ts +10 -5
  81. package/lib/task/guard-override.ts +50 -0
  82. package/lib/task/human-override.ts +1503 -0
  83. package/lib/task/ledger-intents.ts +6 -3
  84. package/lib/task/lifecycle.ts +71 -14
  85. package/lib/task/orchestration.ts +89 -7
  86. package/lib/task/review-finalization.ts +6 -2
  87. package/lib/task/verification-engine.ts +42 -16
  88. package/lib/task/verification.ts +15 -1
  89. package/lib/task/workflow-warning-intents.ts +4 -2
  90. package/lib/task/write.ts +4 -1
  91. package/package.json +1 -1
  92. package/templates/.agents/hooks/lifecycle-delegation.js +8 -3
  93. package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -2
  94. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -2
  95. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
  96. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
@@ -10,6 +10,10 @@ function managedDelegationRole(nativeAgent) {
10
10
  function fail(code, message) {
11
11
  return { ok: false, code, message };
12
12
  }
13
+ // 空白串折叠为 null,保持"缺失即 null"的单一表示(Start/Stop 两处共用,避免任一入口把 " " 当成已观察到的值入账)
14
+ function foldBlankToNull(value) {
15
+ return value?.trim() ? value : null;
16
+ }
13
17
  function prepareDelegation(input, options = {}) {
14
18
  const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))();
15
19
  return Object.freeze({
@@ -82,30 +86,43 @@ function activateDelegation(receipt, event, options = {}) {
82
86
  if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
83
87
  return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
84
88
  }
85
- if (event.spawnMode !== 'fresh')
89
+ // spawnMode 的必需性按 client 判断:claude-code 宿主结构上不提供该字段,跳过;其余 client 维持硬校验
90
+ if (receipt.client !== 'claude-code' && event.spawnMode !== 'fresh') {
86
91
  return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
87
- const actualModel = event.actualModel;
88
- if (!actualModel || actualModel.trim() !== actualModel) {
89
- return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
90
92
  }
91
- if (actualModel !== receipt.requestedModel && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
92
- return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
93
+ // model/effort 维度:claude-code 如实记录、不做门禁;其余 client 维持现状硬校验(HDR-2)
94
+ const actualModel = foldBlankToNull(event.actualModel);
95
+ const actualReasoningEffort = foldBlankToNull(event.actualReasoningEffort);
96
+ if (receipt.client !== 'claude-code') {
97
+ if (!actualModel || actualModel.trim() !== actualModel) {
98
+ return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
99
+ }
100
+ }
101
+ if (actualModel !== null && actualModel !== receipt.requestedModel) {
102
+ if (receipt.client !== 'claude-code' && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
103
+ return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
104
+ }
93
105
  }
94
- const actualReasoningEffort = event.actualReasoningEffort;
95
- if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
96
- return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
106
+ if (receipt.client !== 'claude-code') {
107
+ if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
108
+ return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
109
+ }
97
110
  }
98
111
  if (receipt.client === 'codex'
99
112
  && event.hostEvidence?.kind !== 'codex-lifecycle-v2')
100
113
  return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex activation requires trusted lifecycle-v2 host evidence');
101
- if (actualReasoningEffort !== receipt.requestedReasoningEffort
102
- && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
103
- return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
114
+ if (actualReasoningEffort !== null && actualReasoningEffort !== receipt.requestedReasoningEffort) {
115
+ if (receipt.client !== 'claude-code' && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
116
+ return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
117
+ }
104
118
  }
105
- if (actualModel === receipt.requestedModel && event.modelFallbackReason) {
119
+ // "无关 fallback 理由"检查对所有 client 保持不变——防止编造理由的红线,不属于 HDR-2 放宽范围
120
+ // actualModel/actualReasoningEffort 为 null(未观察到)时同样没有"实际不同"这回事,理由一样是无关的
121
+ if ((actualModel === null || actualModel === receipt.requestedModel) && event.modelFallbackReason) {
106
122
  return fail('DELEGATION_MODEL_FALLBACK_INVALID', 'model fallback reason is only valid when actual model differs');
107
123
  }
108
- if (actualReasoningEffort === receipt.requestedReasoningEffort && event.reasoningEffortFallbackReason) {
124
+ if ((actualReasoningEffort === null || actualReasoningEffort === receipt.requestedReasoningEffort)
125
+ && event.reasoningEffortFallbackReason) {
109
126
  return fail('DELEGATION_REASONING_EFFORT_FALLBACK_INVALID', 'reasoning-effort fallback reason is only valid when actual effort differs');
110
127
  }
111
128
  if (event.hostEvidence
@@ -145,7 +162,7 @@ function activateDelegation(receipt, event, options = {}) {
145
162
  status: 'activated',
146
163
  parentId: event.parentId,
147
164
  childId: event.childId,
148
- spawnMode: event.spawnMode,
165
+ spawnMode: event.spawnMode ?? null,
149
166
  actualModel,
150
167
  actualReasoningEffort,
151
168
  modelFallbackReason: event.modelFallbackReason ?? null,
@@ -213,6 +230,10 @@ function sealDelegation(receipt, event, options = {}) {
213
230
  status: 'sealed',
214
231
  afterFingerprint: event.afterFingerprint,
215
232
  changedPaths: Object.freeze([...event.changedPaths]),
233
+ actualModel: receipt.actualModel ?? foldBlankToNull(event.actualModel),
234
+ actualReasoningEffort: receipt.actualReasoningEffort ?? foldBlankToNull(event.actualReasoningEffort),
235
+ modelFallbackReason: receipt.modelFallbackReason ?? event.modelFallbackReason ?? null,
236
+ reasoningEffortFallbackReason: receipt.reasoningEffortFallbackReason ?? event.reasoningEffortFallbackReason ?? null,
216
237
  hostEvidence: event.hostEvidence && receipt.hostEvidence ? Object.freeze({
217
238
  ...receipt.hostEvidence,
218
239
  ...event.hostEvidence
@@ -231,5 +252,5 @@ function consumeDelegation(receipt, options = {}) {
231
252
  consumedAt: (options.now ?? (() => new Date().toISOString()))()
232
253
  }) };
233
254
  }
234
- export { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, managedDelegationRole, prepareDelegation, sealDelegation };
255
+ export { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, managedDelegationRole, prepareDelegation, sealDelegation };
235
256
  //# sourceMappingURL=delegation-receipts.js.map
@@ -12,6 +12,7 @@ import { validateLifecycleExecution } from './lifecycle-execution.js';
12
12
  import { commitOrchestrationStageCompletion } from './orchestration.js';
13
13
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
14
14
  import { captureTaskWriteMetadata, writeTask } from './write.js';
15
+ import { allowsManualOverride } from './guard-override.js';
15
16
  const eventCatalog = [
16
17
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
17
18
  'review-analysis.started', 'review-analysis.completed',
@@ -22,7 +23,7 @@ const eventCatalog = [
22
23
  'manual-validation.started', 'manual-validation.completed',
23
24
  'validation-run.started', 'validation-run.completed'
24
25
  ];
25
- const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun']);
26
+ const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']);
26
27
  const SCHEMAS = {
27
28
  'analyze.started': { optional: ['round'] },
28
29
  'analyze.awaiting-input': { required: ['question'] },
@@ -331,7 +332,8 @@ function applyTaskEventUnlocked(request, options = {}) {
331
332
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
332
333
  if (!resolved.ok)
333
334
  return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
334
- if (resolved.state !== 'active')
335
+ const stateOverride = allowsManualOverride(options.manualOverride, 'task-event', 'TASK_STATE_MISMATCH');
336
+ if (resolved.state !== 'active' && !stateOverride)
335
337
  return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
336
338
  let content;
337
339
  try {
@@ -403,7 +405,7 @@ function applyTaskEventUnlocked(request, options = {}) {
403
405
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
404
406
  if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0)
405
407
  return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
406
- if (eventIdentity.phase === 'completed' && !row?.started)
408
+ if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
407
409
  return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
408
410
  if (eventIdentity.phase === 'completed') {
409
411
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
@@ -416,7 +418,7 @@ function applyTaskEventUnlocked(request, options = {}) {
416
418
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, existing.time, frontmatter, artifactContext);
417
419
  }
418
420
  const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
419
- if (!allowed.includes(currentStep))
421
+ if (!allowed.includes(currentStep) && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID'))
420
422
  return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
421
423
  const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
422
424
  if (findingCountError)
@@ -528,7 +530,7 @@ function applyTaskEventUnlocked(request, options = {}) {
528
530
  });
529
531
  }
530
532
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
531
- const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
533
+ const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
532
534
  if (result.status === 'failed')
533
535
  return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
534
536
  if (!normalized.dryRun && orchestrationCompletion) {
@@ -568,6 +570,8 @@ function applyTaskEvent(request, options = {}) {
568
570
  if (invalid)
569
571
  return failed(request, invalid);
570
572
  const parts = eventParts(request.event);
573
+ if (options.lockAlreadyHeld)
574
+ return applyTaskEventUnlocked(request, options);
571
575
  if (request.dryRun || parts.phase !== 'completed' || parts.family === 'manual-validation') {
572
576
  return applyTaskEventUnlocked(request, options);
573
577
  }
@@ -0,0 +1,31 @@
1
+ const guardProducerCatalog = [
2
+ { producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_NOT_FOUND' },
3
+ { producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_IDENTITY_CONFLICT' },
4
+ { producerId: 'task.write', guardId: 'G-02', code: 'TASK_STATE_MISMATCH' },
5
+ { producerId: 'task-event', guardId: 'G-04', code: 'TASK_STATE_MISMATCH' },
6
+ { producerId: 'task-event', guardId: 'G-04', code: 'EVENT_TRANSITION_INVALID' },
7
+ { producerId: 'task-event', guardId: 'G-04', code: 'EVENT_START_MISSING' },
8
+ { producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_TASK_STATE_MISMATCH' },
9
+ { producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_ARTIFACT_INVALID' },
10
+ { producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_FAILED' },
11
+ { producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_BLOCKED' },
12
+ { producerId: 'activity-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
13
+ { producerId: 'ledger-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
14
+ { producerId: 'decision-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
15
+ { producerId: 'workflow-warning', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
16
+ { producerId: 'review-finalization', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
17
+ { producerId: 'ledger-intent', guardId: 'G-08', code: 'LEDGER_TRANSITION_INVALID' },
18
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STANDALONE_BUSY' },
19
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_PROVENANCE_MISMATCH' },
20
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STATE_INVALID' },
21
+ { producerId: 'platform.issue', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' },
22
+ { producerId: 'platform.pull-request', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' }
23
+ ];
24
+ function guardFailureId(producerId, code) {
25
+ return `${producerId}:${code}`;
26
+ }
27
+ function allowsManualOverride(capability, producerId, code) {
28
+ return capability?.failureId === guardFailureId(producerId, code);
29
+ }
30
+ export { allowsManualOverride, guardFailureId, guardProducerCatalog };
31
+ //# sourceMappingURL=guard-override.js.map