@fitlab-ai/agent-infra 0.9.4 → 0.9.6

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 (130) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +13 -3
  4. package/dist/lib/agent-clients/adapter.js +2 -2
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +492 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +175 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +255 -0
  8. package/dist/lib/agent-clients/adapters/codex.js +3 -3
  9. package/dist/lib/git/workflow.js +0 -1
  10. package/dist/lib/git/worktree-identity.js +64 -0
  11. package/dist/lib/internal/codex-lifecycle.js +300 -0
  12. package/dist/lib/internal/release-workflow.js +108 -11
  13. package/dist/lib/internal/sandbox-control.js +43 -6
  14. package/dist/lib/internal/task-create.js +56 -0
  15. package/dist/lib/internal/task-orchestration.js +71 -31
  16. package/dist/lib/platform/issue-comments.js +2 -1
  17. package/dist/lib/release/workflow.js +29 -21
  18. package/dist/lib/sandbox/commands/create.js +16 -16
  19. package/dist/lib/sandbox/commands/prune.js +40 -16
  20. package/dist/lib/sandbox/commands/rm.js +318 -89
  21. package/dist/lib/sandbox/control/client.js +152 -22
  22. package/dist/lib/sandbox/control/executor.js +136 -0
  23. package/dist/lib/sandbox/control/lifecycle.js +348 -0
  24. package/dist/lib/sandbox/control/protocol.js +73 -18
  25. package/dist/lib/sandbox/control/server.js +235 -80
  26. package/dist/lib/sandbox/control/state.js +181 -0
  27. package/dist/lib/sandbox/index.js +2 -2
  28. package/dist/lib/sandbox/managed-fs.js +14 -2
  29. package/dist/lib/sandbox/mounts.js +35 -24
  30. package/dist/lib/sandbox/recovery.js +262 -45
  31. package/dist/lib/sandbox/workspace-view.js +80 -11
  32. package/dist/lib/sandbox/worktree-safety.js +196 -0
  33. package/dist/lib/server/process-control.js +6 -0
  34. package/dist/lib/task/activity-log.js +32 -5
  35. package/dist/lib/task/artifact-lifecycle.js +5 -4
  36. package/dist/lib/task/codex-orchestration.js +161 -0
  37. package/dist/lib/task/commands/status.js +3 -0
  38. package/dist/lib/task/commands/validate.js +289 -0
  39. package/dist/lib/task/commit-finalization.js +50 -12
  40. package/dist/lib/task/create-service.js +137 -0
  41. package/dist/lib/task/create.js +333 -0
  42. package/dist/lib/task/delegation-receipts.js +25 -0
  43. package/dist/lib/task/events.js +10 -4
  44. package/dist/lib/task/finalization-artifacts.js +2 -1
  45. package/dist/lib/task/implementation-inputs.js +1 -7
  46. package/dist/lib/task/index.js +9 -0
  47. package/dist/lib/task/orchestration.js +283 -18
  48. package/dist/lib/task/review-fingerprint.js +4 -0
  49. package/dist/lib/task/verification-engine.js +38 -6
  50. package/dist/lib/task/verification.js +1 -0
  51. package/dist/lib/task/workspace-snapshot.js +78 -3
  52. package/lib/agent-clients/adapter.ts +4 -4
  53. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +569 -0
  54. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +323 -0
  55. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +298 -0
  56. package/lib/agent-clients/adapters/codex.ts +3 -3
  57. package/lib/git/workflow.ts +0 -1
  58. package/lib/git/worktree-identity.ts +86 -0
  59. package/lib/internal/codex-lifecycle.ts +308 -0
  60. package/lib/internal/release-workflow.ts +107 -11
  61. package/lib/internal/sandbox-control.ts +38 -6
  62. package/lib/internal/task-create.ts +61 -0
  63. package/lib/internal/task-orchestration.ts +57 -31
  64. package/lib/platform/issue-comments.ts +2 -1
  65. package/lib/release/workflow.ts +69 -20
  66. package/lib/sandbox/commands/create.ts +18 -21
  67. package/lib/sandbox/commands/prune.ts +53 -15
  68. package/lib/sandbox/commands/rm.ts +368 -103
  69. package/lib/sandbox/control/client.ts +173 -25
  70. package/lib/sandbox/control/executor.ts +163 -0
  71. package/lib/sandbox/control/lifecycle.ts +342 -0
  72. package/lib/sandbox/control/protocol.ts +110 -48
  73. package/lib/sandbox/control/server.ts +252 -85
  74. package/lib/sandbox/control/state.ts +196 -0
  75. package/lib/sandbox/index.ts +2 -2
  76. package/lib/sandbox/managed-fs.ts +17 -2
  77. package/lib/sandbox/mounts.ts +31 -13
  78. package/lib/sandbox/recovery.ts +301 -60
  79. package/lib/sandbox/workspace-view.ts +89 -12
  80. package/lib/sandbox/worktree-safety.ts +234 -0
  81. package/lib/server/process-control.ts +11 -0
  82. package/lib/task/activity-log.ts +46 -7
  83. package/lib/task/artifact-lifecycle.ts +5 -4
  84. package/lib/task/codex-orchestration.ts +233 -0
  85. package/lib/task/commands/status.ts +2 -0
  86. package/lib/task/commands/validate.ts +282 -0
  87. package/lib/task/commit-finalization.ts +75 -12
  88. package/lib/task/create-service.ts +171 -0
  89. package/lib/task/create.ts +372 -0
  90. package/lib/task/delegation-receipts.ts +52 -3
  91. package/lib/task/events.ts +9 -4
  92. package/lib/task/finalization-artifacts.ts +2 -1
  93. package/lib/task/implementation-inputs.ts +1 -5
  94. package/lib/task/index.ts +8 -0
  95. package/lib/task/orchestration.ts +381 -22
  96. package/lib/task/review-fingerprint.ts +9 -0
  97. package/lib/task/verification-engine.ts +42 -8
  98. package/lib/task/verification.ts +2 -1
  99. package/lib/task/workspace-snapshot.ts +98 -4
  100. package/package.json +7 -7
  101. package/templates/.agents/hooks/lifecycle-delegation.js +88 -8
  102. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  103. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  104. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
  105. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
  106. package/templates/.agents/rules/task-management.en.md +1 -0
  107. package/templates/.agents/rules/task-management.zh-CN.md +1 -0
  108. package/templates/.agents/skills/commit/SKILL.en.md +3 -9
  109. package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -10
  110. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +4 -3
  111. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +5 -4
  112. package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
  113. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
  114. package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
  115. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
  116. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
  117. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
  118. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -0
  119. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -0
  120. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +44 -0
  121. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +44 -0
  122. package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
  123. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
  124. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -3
  125. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -3
  126. package/templates/.claude/commands/run-manual-validation.en.md +9 -0
  127. package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
  128. package/templates/.codex/hooks.json +50 -0
  129. package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
  130. package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
@@ -0,0 +1,196 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { runProbe } from './shell.js';
5
+ function probeText(probe, cwd, args) {
6
+ const result = probe('git', ['-C', cwd, ...args], { encoding: 'utf8' });
7
+ if (result.error)
8
+ throw result.error;
9
+ if (result.status !== 0) {
10
+ const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : result.stderr.toString('utf8').trim();
11
+ throw new Error(stderr || `git ${args[0] ?? 'command'} failed with exit code ${result.status ?? 'unknown'}`);
12
+ }
13
+ return typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf8');
14
+ }
15
+ function parseStatus(raw) {
16
+ const records = raw.split('\0');
17
+ const changes = [];
18
+ for (let index = 0; index < records.length; index += 1) {
19
+ const record = records[index] ?? '';
20
+ if (!record || record.startsWith('# '))
21
+ continue;
22
+ if (record.startsWith('? ')) {
23
+ changes.push({ indexStatus: '?', worktreeStatus: '?', path: record.slice(2) });
24
+ continue;
25
+ }
26
+ if (record.startsWith('1 ')) {
27
+ const match = /^1 (..) \S+ \S+ \S+ \S+ \S+ \S+ (.*)$/s.exec(record);
28
+ if (!match)
29
+ throw new Error('Unable to parse git porcelain v2 ordinary record');
30
+ changes.push({ indexStatus: match[1][0], worktreeStatus: match[1][1], path: match[2] });
31
+ continue;
32
+ }
33
+ if (record.startsWith('2 ')) {
34
+ const match = /^2 (..) \S+ \S+ \S+ \S+ \S+ \S+ \S+ (.*)$/s.exec(record);
35
+ const originalPath = records[++index];
36
+ if (!match || originalPath === undefined || originalPath === '') {
37
+ throw new Error('Unable to parse git porcelain v2 rename record');
38
+ }
39
+ changes.push({
40
+ indexStatus: match[1][0],
41
+ worktreeStatus: match[1][1],
42
+ path: match[2],
43
+ originalPath
44
+ });
45
+ continue;
46
+ }
47
+ if (record.startsWith('u ')) {
48
+ const match = /^u (..) \S+ \S+ \S+ \S+ \S+ \S+ \S+ \S+ (.*)$/s.exec(record);
49
+ if (!match)
50
+ throw new Error('Unable to parse git porcelain v2 unmerged record');
51
+ changes.push({ indexStatus: match[1][0], worktreeStatus: match[1][1], path: match[2] });
52
+ continue;
53
+ }
54
+ throw new Error(`Unsupported git porcelain v2 record '${record[0] ?? ''}'`);
55
+ }
56
+ return changes;
57
+ }
58
+ function updatePart(hash, label, value) {
59
+ const data = Buffer.isBuffer(value) ? value : Buffer.from(value, 'utf8');
60
+ hash.update(`${label.length}:${label}:${data.length}:`, 'utf8');
61
+ hash.update(data);
62
+ }
63
+ function resolveSnapshotPath(worktree, relativePath) {
64
+ const resolved = path.resolve(worktree, relativePath);
65
+ const relative = path.relative(worktree, resolved);
66
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
67
+ throw new Error(`Git reported a path outside the worktree: ${JSON.stringify(relativePath)}`);
68
+ }
69
+ return resolved;
70
+ }
71
+ function hashPath(hash, worktree, relativePath, probe) {
72
+ updatePart(hash, 'path', relativePath);
73
+ const target = resolveSnapshotPath(worktree, relativePath);
74
+ let stat;
75
+ try {
76
+ stat = fs.lstatSync(target);
77
+ }
78
+ catch (error) {
79
+ if (error.code === 'ENOENT') {
80
+ updatePart(hash, 'kind', 'absent');
81
+ return;
82
+ }
83
+ throw error;
84
+ }
85
+ updatePart(hash, 'mode', String(stat.mode & 0o7777));
86
+ if (stat.isFile()) {
87
+ updatePart(hash, 'kind', 'file');
88
+ updatePart(hash, 'content', fs.readFileSync(target));
89
+ return;
90
+ }
91
+ if (stat.isSymbolicLink()) {
92
+ updatePart(hash, 'kind', 'symlink');
93
+ updatePart(hash, 'target', fs.readlinkSync(target));
94
+ return;
95
+ }
96
+ if (stat.isDirectory()) {
97
+ updatePart(hash, 'kind', 'git-directory');
98
+ const nestedHead = probeText(probe, target, ['rev-parse', '--verify', 'HEAD']).trim();
99
+ const nestedStatus = probeText(probe, target, [
100
+ 'status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--ignore-submodules=none'
101
+ ]);
102
+ const nestedIndex = probeText(probe, target, ['ls-files', '--stage', '-z']);
103
+ updatePart(hash, 'nested-head', nestedHead);
104
+ updatePart(hash, 'nested-status', nestedStatus);
105
+ updatePart(hash, 'nested-index', nestedIndex);
106
+ for (const change of parseStatus(nestedStatus)) {
107
+ hashPath(hash, target, change.path, probe);
108
+ if (change.originalPath)
109
+ hashPath(hash, target, change.originalPath, probe);
110
+ }
111
+ return;
112
+ }
113
+ throw new Error(`Unsupported file type in dirty snapshot: ${JSON.stringify(relativePath)}`);
114
+ }
115
+ function captureOnce(worktree, probe) {
116
+ const resolvedWorktree = path.resolve(worktree);
117
+ const branch = probeText(probe, resolvedWorktree, ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
118
+ const head = probeText(probe, resolvedWorktree, ['rev-parse', '--verify', 'HEAD']).trim();
119
+ const status = probeText(probe, resolvedWorktree, [
120
+ 'status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--ignore-submodules=none'
121
+ ]);
122
+ const index = probeText(probe, resolvedWorktree, ['ls-files', '--stage', '-z']);
123
+ if (!branch)
124
+ throw new Error('Unable to determine worktree branch');
125
+ if (!head)
126
+ throw new Error('Unable to determine worktree HEAD');
127
+ const changes = parseStatus(status);
128
+ const hash = createHash('sha256');
129
+ updatePart(hash, 'worktree', resolvedWorktree);
130
+ updatePart(hash, 'branch', branch);
131
+ updatePart(hash, 'head', head);
132
+ updatePart(hash, 'status', status);
133
+ updatePart(hash, 'index', index);
134
+ for (const change of changes) {
135
+ hashPath(hash, resolvedWorktree, change.path, probe);
136
+ if (change.originalPath)
137
+ hashPath(hash, resolvedWorktree, change.originalPath, probe);
138
+ }
139
+ return { worktree: resolvedWorktree, branch, head, changes, identity: hash.digest('hex') };
140
+ }
141
+ function inspectWorktree(worktree, { probe = runProbe } = {}) {
142
+ const resolvedWorktree = path.resolve(worktree);
143
+ try {
144
+ const first = captureOnce(resolvedWorktree, probe);
145
+ const second = captureOnce(resolvedWorktree, probe);
146
+ if (first.identity !== second.identity) {
147
+ return { status: 'failed', worktree: resolvedWorktree, message: 'Snapshot changed while inspecting worktree' };
148
+ }
149
+ return { status: second.changes.length === 0 ? 'clean' : 'dirty', snapshot: second };
150
+ }
151
+ catch (error) {
152
+ return {
153
+ status: 'failed',
154
+ worktree: resolvedWorktree,
155
+ message: error instanceof Error ? error.message : String(error)
156
+ };
157
+ }
158
+ }
159
+ function inspectWorktrees(worktrees) {
160
+ return [...new Set(worktrees.map((worktree) => path.resolve(worktree)))].sort()
161
+ .map((worktree) => inspectWorktree(worktree));
162
+ }
163
+ function createCleanPermit(snapshot) {
164
+ if (snapshot.changes.length > 0)
165
+ throw new Error('Cannot create a clean permit for a dirty worktree');
166
+ return { mode: 'clean', snapshot };
167
+ }
168
+ function createDiscardPermit(snapshot) {
169
+ if (snapshot.changes.length === 0)
170
+ throw new Error('Cannot create a discard permit for a clean worktree');
171
+ return { mode: 'discard', snapshot };
172
+ }
173
+ function verifyWorktreePermit(permit, options = {}) {
174
+ const current = inspectWorktree(permit.snapshot.worktree, options);
175
+ if (current.status === 'failed')
176
+ throw new Error(`Unable to verify worktree permit: ${current.message}`);
177
+ if (current.snapshot.identity !== permit.snapshot.identity || (permit.mode === 'clean' && current.status !== 'clean')) {
178
+ throw new Error(`Worktree changed after authorization: ${permit.snapshot.worktree}`);
179
+ }
180
+ return current.snapshot;
181
+ }
182
+ function formatWorktreeSnapshot(snapshot) {
183
+ const lines = snapshot.changes.map((change) => {
184
+ const rename = change.originalPath ? ` <- ${JSON.stringify(change.originalPath)}` : '';
185
+ return ` ${change.indexStatus}${change.worktreeStatus} ${JSON.stringify(change.path)}${rename}`;
186
+ });
187
+ return [
188
+ `Worktree: ${JSON.stringify(snapshot.worktree)}`,
189
+ `Branch: ${snapshot.branch}`,
190
+ `HEAD: ${snapshot.head}`,
191
+ ...lines,
192
+ `Snapshot identity: ${snapshot.identity}`
193
+ ].join('\n');
194
+ }
195
+ export { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectWorktree, inspectWorktrees, verifyWorktreePermit };
196
+ //# sourceMappingURL=worktree-safety.js.map
@@ -4,6 +4,12 @@ import { loadServerConfig } from './config.js';
4
4
  import { runDaemon } from './daemon.js';
5
5
  import { getProcessStartTime, isProcessAlive, processIdentityMatches, readProcessState, removePidFileIfMatches, writePidRecord } from './process-state.js';
6
6
  export { isProcessAlive } from './process-state.js';
7
+ export function buildProcessTreeStopCommand(pid, platform) {
8
+ if (platform === 'win32') {
9
+ return { kind: 'exec', command: 'taskkill', args: ['/PID', String(pid), '/T', '/F'] };
10
+ }
11
+ return { kind: 'group-signal', pid: -pid, signal: 'SIGTERM' };
12
+ }
7
13
  export function buildStopCommand(pid, platform) {
8
14
  if (platform === 'win32') {
9
15
  return { kind: 'exec', command: 'taskkill', args: ['/PID', String(pid), '/T', '/F'] };
@@ -2,6 +2,9 @@ const HEADING_RE = /^##\s+(活动日志|Activity Log)\s*$/;
2
2
  const NEXT_H2_RE = /^##\s/;
3
3
  const ENTRY_RE = /^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}) — \*\*(.+?)\*\* by (.+?) — (.*)$/;
4
4
  const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
5
+ const ABORTED_SUFFIX_RE = /\s*\[aborted\]\s*$/;
6
+ const ATTEMPT_RE = /(?:^|;\s*)attempt=([A-Za-z0-9-]{8,64})(?:;|$)/;
7
+ const COMMIT_STARTED_RE = /^started; attempt=([A-Za-z0-9-]{8,64}); baseline=([a-f0-9]{40,64}); agent=([a-z-]+)$/;
5
8
  function parseActivityLog(content) {
6
9
  const section = locateActivityLog(content);
7
10
  return section ? { sectionFound: true, entries: section.entries } : { sectionFound: false, entries: [] };
@@ -34,28 +37,52 @@ function appendActivityEntry(section, entry) {
34
37
  const line = `- ${entry.time} — **${entry.step}** by ${entry.agent} — ${entry.note}`;
35
38
  return section.body ? `${section.body}\n${line}` : line;
36
39
  }
40
+ function activityAttempt(note) {
41
+ return ATTEMPT_RE.exec(note)?.[1] ?? null;
42
+ }
43
+ function parseCommitAttemptStarted(note) {
44
+ const match = COMMIT_STARTED_RE.exec(note);
45
+ return match ? { attempt: match[1], baseline: match[2], agent: match[3] } : null;
46
+ }
47
+ function commitAttemptStartedNote(attempt) {
48
+ return `started; attempt=${attempt.attempt}; baseline=${attempt.baseline}; agent=${attempt.agent}`;
49
+ }
37
50
  function pairEntries(entries) {
38
51
  const rows = [];
39
52
  const open = new Map();
40
53
  for (const entry of entries) {
41
54
  const started = STARTED_SUFFIX_RE.test(entry.step);
42
- const base = entry.step.replace(STARTED_SUFFIX_RE, '');
55
+ const aborted = ABORTED_SUFFIX_RE.test(entry.step);
56
+ const base = entry.step.replace(STARTED_SUFFIX_RE, '').replace(ABORTED_SUFFIX_RE, '');
43
57
  if (started) {
44
- const row = { step: base, agent: entry.agent, started: entry.time, done: '', note: entry.note };
58
+ const attempt = activityAttempt(entry.note);
59
+ const row = {
60
+ step: base, agent: entry.agent, started: entry.time, done: '', note: entry.note,
61
+ ...(attempt ? { attempt } : {})
62
+ };
45
63
  rows.push(row);
46
64
  const queue = open.get(base) ?? [];
47
65
  queue.push(row);
48
66
  open.set(base, queue);
49
67
  }
50
68
  else {
51
- const pending = open.get(base)?.shift();
69
+ const queue = open.get(base) ?? [];
70
+ const terminalAttempt = aborted ? activityAttempt(entry.note) : null;
71
+ const pendingIndex = terminalAttempt === null
72
+ ? 0
73
+ : queue.findIndex((row) => row.attempt === terminalAttempt);
74
+ const pending = pendingIndex >= 0 ? queue.splice(pendingIndex, 1)[0] : undefined;
52
75
  if (pending)
53
76
  Object.assign(pending, { done: entry.time, agent: entry.agent, note: entry.note });
54
77
  else
55
- rows.push({ step: base, agent: entry.agent, started: '', done: entry.time, note: entry.note });
78
+ rows.push({
79
+ step: aborted ? entry.step : base,
80
+ agent: entry.agent, started: '', done: entry.time, note: entry.note,
81
+ ...(terminalAttempt ? { attempt: terminalAttempt } : {})
82
+ });
56
83
  }
57
84
  }
58
85
  return rows;
59
86
  }
60
- export { parseActivityLog, locateActivityLog, appendActivityEntry, pairEntries };
87
+ export { activityAttempt, appendActivityEntry, commitAttemptStartedNote, locateActivityLog, pairEntries, parseActivityLog, parseCommitAttemptStarted };
61
88
  //# sourceMappingURL=activity-log.js.map
@@ -13,6 +13,7 @@ const artifactFamilyCatalog = [
13
13
  { family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
14
14
  { family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
15
15
  { family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
16
+ { family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
16
17
  { family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
17
18
  ];
18
19
  const FAMILY_SET = new Set(artifactFamilyCatalog.map((item) => item.family));
@@ -191,7 +192,8 @@ const OPTIONAL_CONTEXT = {
191
192
  analysis: { family: 'review-analysis', requireReference: true },
192
193
  plan: { family: 'review-plan', requireReference: true },
193
194
  'review-code': { family: 'review-plan' },
194
- 'manual-validation': { family: 'review-code' }
195
+ 'manual-validation': { family: 'review-code' },
196
+ 'validation-run': { family: 'review-code' }
195
197
  };
196
198
  function resolveArtifactContext(taskRef, family, options = {}) {
197
199
  const inventory = inspectTaskArtifacts(taskRef, family, options);
@@ -306,12 +308,11 @@ function resolveDecisionImplementationInput(inventory, review) {
306
308
  const activity = locateActivityLog(content);
307
309
  if (!activity)
308
310
  return { error: 'task has no unique Activity Log section' };
309
- const reviewEntries = activity.entries.filter((entry) => /^Review Code \(Round \d+\)$/.test(entry.step) && entry.note.includes(`→ ${review.name}`));
310
- const completed = reviewEntries.at(-1);
311
+ const completed = activity.entries.some((entry) => /^Review Code \(Round \d+\)$/.test(entry.step) && entry.note.includes(`→ ${review.name}`));
311
312
  if (!completed)
312
313
  return { error: `cannot find completed Activity Log identity for ${review.name}` };
313
314
  try {
314
- return { input: selectPendingImplementationInput(rows, completed.time) };
315
+ return { input: selectPendingImplementationInput(rows) };
315
316
  }
316
317
  catch (error) {
317
318
  return { error: error instanceof Error ? error.message : String(error) };
@@ -0,0 +1,161 @@
1
+ import { preflightCodexLifecycleEvidence, resolveCodexSpawnedChild, resolveCodexTerminal, resolveCodexThread } from '../agent-clients/adapters/codex-lifecycle/app-server.js';
2
+ import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
3
+ import { activateMatchingOrchestrationDelegation, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
4
+ function coreOptions(options) {
5
+ return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
6
+ }
7
+ function bridgeFailure(code, message) {
8
+ return {
9
+ status: 'failed', changed: false, taskId: null, run: null, next: null,
10
+ error: { code, message }
11
+ };
12
+ }
13
+ function pauseBridge(code, message, options) {
14
+ const paused = pauseMatchingOrchestrationDelegation('codex', code, message, coreOptions(options));
15
+ return paused.error?.code === 'ORCHESTRATION_DELEGATION_MISSING'
16
+ ? bridgeFailure(code, message)
17
+ : paused;
18
+ }
19
+ function requiredStore(options) {
20
+ if (!options.store)
21
+ throw new Error('Codex lifecycle store is required for orchestration bridge events');
22
+ return options.store;
23
+ }
24
+ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {}) {
25
+ if (input.client !== 'codex')
26
+ return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
27
+ try {
28
+ await (options.preflight ?? preflightCodexLifecycleEvidence)(options.repoRoot ?? process.cwd());
29
+ }
30
+ catch (error) {
31
+ return bridgeFailure('ORCHESTRATION_CLIENT_PREFLIGHT_FAILED', error instanceof Error ? error.message : String(error));
32
+ }
33
+ return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
34
+ }
35
+ async function activateCodexOrchestrationDelegation(childThreadId, options = {}) {
36
+ try {
37
+ const store = requiredStore(options);
38
+ const resolved = await (options.resolveThread ?? resolveCodexThread)(childThreadId);
39
+ store.apply(resolved.resolution.thread);
40
+ for (const reroute of resolved.reroutes)
41
+ store.apply(reroute);
42
+ store.apply(resolved.resolution.settings);
43
+ const record = store.read(childThreadId);
44
+ const evidence = record.state.startEvidence;
45
+ if (record.state.status !== 'start-ready' || !evidence) {
46
+ return pauseBridge('ORCHESTRATION_CODEX_START_EVIDENCE_INVALID', 'Codex lifecycle start evidence is not ready', options);
47
+ }
48
+ return activateMatchingOrchestrationDelegation('codex', {
49
+ nativeAgent: evidence.nativeAgent,
50
+ childId: evidence.childThreadId,
51
+ parentId: evidence.parentThreadId,
52
+ spawnMode: evidence.spawnMode,
53
+ actualModel: evidence.actualModel.value,
54
+ actualReasoningEffort: evidence.actualReasoningEffort.value,
55
+ ...(evidence.modelFallbackReason ? { modelFallbackReason: evidence.modelFallbackReason } : {}),
56
+ ...(evidence.reasoningEffortFallbackReason
57
+ ? { reasoningEffortFallbackReason: evidence.reasoningEffortFallbackReason }
58
+ : {}),
59
+ hostEvidence: {
60
+ kind: 'codex-lifecycle-v1',
61
+ hookDefinitionHash: evidence.hookDefinitionHash,
62
+ startRevision: record.revision
63
+ }
64
+ }, coreOptions(options));
65
+ }
66
+ catch (error) {
67
+ return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
68
+ }
69
+ }
70
+ async function activateCodexSpawnDelegation(spawn, options = {}) {
71
+ try {
72
+ const store = requiredStore(options);
73
+ const childThreadId = resolveCodexSpawnedChild(spawn.transcriptPath, spawn);
74
+ const resolved = await (options.resolveThread ?? resolveCodexThread)(childThreadId);
75
+ store.applyToSpawn(spawn, {
76
+ type: 'hook-child',
77
+ sessionId: spawn.sessionId,
78
+ turnId: spawn.turnId,
79
+ childThreadId,
80
+ parentThreadId: resolved.resolution.thread.parentThreadId,
81
+ nativeAgent: spawn.nativeAgent,
82
+ source: 'parent-rollout'
83
+ });
84
+ return activateCodexOrchestrationDelegation(childThreadId, {
85
+ ...options,
86
+ store,
87
+ resolveThread: async () => resolved
88
+ });
89
+ }
90
+ catch (error) {
91
+ return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
92
+ }
93
+ }
94
+ async function sealCodexOrchestrationDelegation(childThreadId, options = {}) {
95
+ try {
96
+ const store = requiredStore(options);
97
+ const existing = store.read(childThreadId);
98
+ if (!existing.consumer) {
99
+ store.apply(await (options.resolveTerminal ?? resolveCodexTerminal)(childThreadId));
100
+ }
101
+ const record = store.read(childThreadId);
102
+ const evidence = record.state.stopEvidence;
103
+ if (record.state.status !== 'stop-ready' || !evidence || !record.state.startEvidence) {
104
+ return pauseBridge('ORCHESTRATION_CODEX_STOP_EVIDENCE_INVALID', 'Codex lifecycle stop evidence is not ready', options);
105
+ }
106
+ return sealMatchingOrchestrationDelegationWithHostEvidence('codex', { nativeAgent: record.state.startEvidence.nativeAgent, childId: childThreadId }, (receipt) => {
107
+ const consumed = store.consume(childThreadId, receipt.id, receipt.hostEvidence?.hookDefinitionHash);
108
+ return {
109
+ stopRevision: consumed.revision,
110
+ consumer: consumed.consumer,
111
+ consumedAt: consumed.consumedAt
112
+ };
113
+ }, coreOptions(options));
114
+ }
115
+ catch (error) {
116
+ return pauseBridge('ORCHESTRATION_CODEX_STOP_FAILED', error instanceof Error ? error.message : String(error), options);
117
+ }
118
+ }
119
+ async function sealCodexParentDelegation(parentThreadId, options = {}) {
120
+ try {
121
+ const store = requiredStore(options);
122
+ const candidates = store.findByParent(parentThreadId);
123
+ if (!candidates.length)
124
+ return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
125
+ for (const consumed of candidates.filter((record) => record.consumer)) {
126
+ const replayed = await sealCodexOrchestrationDelegation(consumed.state.startEvidence.childThreadId, options);
127
+ if (replayed.error?.code !== 'ORCHESTRATION_DELEGATION_MISSING')
128
+ return replayed;
129
+ }
130
+ const active = candidates.find((record) => !record.consumer);
131
+ if (!active)
132
+ return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
133
+ const start = active.state.startEvidence;
134
+ const child = active.state.child;
135
+ if (!active.state.terminal) {
136
+ store.apply(await (options.resolveTerminal ?? resolveCodexTerminal)(start.childThreadId));
137
+ }
138
+ if (!store.read(start.childThreadId).state.stop) {
139
+ store.apply({
140
+ type: 'hook-stop',
141
+ sessionId: parentThreadId,
142
+ turnId: child.turnId,
143
+ childThreadId: start.childThreadId,
144
+ nativeAgent: start.nativeAgent,
145
+ source: 'parent-rollout'
146
+ });
147
+ }
148
+ return sealCodexOrchestrationDelegation(start.childThreadId, options);
149
+ }
150
+ catch (error) {
151
+ if (error instanceof Error && error.message === 'CODEX_TURN_NOT_TERMINAL') {
152
+ return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'The matching Codex child has not completed');
153
+ }
154
+ return pauseBridge('ORCHESTRATION_CODEX_STOP_FAILED', error instanceof Error ? error.message : String(error), options);
155
+ }
156
+ }
157
+ function reconcileCodexOrchestrationDelegation(childThreadId, options = {}) {
158
+ return reconcileMatchingOrchestrationDelegation('codex', childThreadId, coreOptions(options));
159
+ }
160
+ export { activateCodexOrchestrationDelegation, activateCodexSpawnDelegation, prepareCodexOrchestrationDelegation, reconcileCodexOrchestrationDelegation, sealCodexParentDelegation, sealCodexOrchestrationDelegation };
161
+ //# sourceMappingURL=codex-orchestration.js.map
@@ -59,6 +59,7 @@ const STAGE_ORDER = [
59
59
  'review-plan',
60
60
  'code',
61
61
  'review-code',
62
+ 'validation-run',
62
63
  'task',
63
64
  'other'
64
65
  ];
@@ -72,6 +73,8 @@ function stageOf(name) {
72
73
  return 'review-plan';
73
74
  if (stem.startsWith('review-code'))
74
75
  return 'review-code';
76
+ if (stem.startsWith('validation-run'))
77
+ return 'validation-run';
75
78
  if (stem.startsWith('analysis'))
76
79
  return 'analysis';
77
80
  if (stem.startsWith('plan'))