@lyupro/codex-bridge 0.6.5 → 0.6.7

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 (54) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +5 -5
  3. package/bin/codex-bridge.mjs +181 -178
  4. package/cli/dispatcher-contract-record.mjs +42 -0
  5. package/cli/dispatcher-contract.mjs +89 -0
  6. package/cli/doctor-hooks.mjs +104 -114
  7. package/cli/doctor-runs.mjs +55 -55
  8. package/cli/doctor.mjs +39 -3
  9. package/cli/handback-witness-check.mjs +39 -0
  10. package/cli/hook-targets.mjs +14 -6
  11. package/cli/hook.mjs +32 -23
  12. package/cli/host-contract.mjs +136 -135
  13. package/cli/install.mjs +9 -14
  14. package/cli/launcher-probe.mjs +38 -0
  15. package/cli/probe-contract.mjs +31 -62
  16. package/cli/probe-rig.mjs +53 -0
  17. package/cli/run-launcher.mjs +17 -0
  18. package/cli/settings-merge.mjs +42 -35
  19. package/cli/uninstall.mjs +15 -3
  20. package/cli/update.mjs +45 -16
  21. package/package.json +4 -4
  22. package/src/agents/codex-advisor.md +3 -4
  23. package/src/agents/codex-build.md +5 -5
  24. package/src/agents/codex-review.md +3 -3
  25. package/src/agents/codex-scout.md +3 -4
  26. package/src/home/hooks/dispatcher-gate.mjs +119 -0
  27. package/src/home/hooks/guard-tries.mjs +5 -6
  28. package/src/home/hooks/live-runs.mjs +1 -0
  29. package/src/home/hooks/order-gate.mjs +20 -5
  30. package/src/home/hooks/reply-guard.mjs +75 -10
  31. package/src/home/hooks/reply-verdicts.mjs +5 -0
  32. package/src/home/hooks/worktree-lock.mjs +108 -107
  33. package/src/home/lib/atomic-json.mjs +21 -0
  34. package/src/home/lib/brand-home.mjs +6 -0
  35. package/src/home/lib/dispatcher-command.mjs +79 -0
  36. package/src/home/lib/dispatcher-gate.mjs +95 -0
  37. package/src/home/lib/dispatcher-order.mjs +30 -8
  38. package/src/home/lib/dispatcher-state.mjs +111 -0
  39. package/src/home/lib/dispatcher-stop.mjs +52 -0
  40. package/src/home/lib/handback-witness.mjs +65 -0
  41. package/src/home/lib/hook-definitions.mjs +10 -0
  42. package/src/home/lib/hook-entry.mjs +20 -0
  43. package/src/home/lib/meta/advice-status.mjs +3 -7
  44. package/src/home/lib/meta/advice-verdict.mjs +37 -21
  45. package/src/home/lib/required-inputs.mjs +1 -1
  46. package/src/home/lib/runner/advise-carry.mjs +50 -0
  47. package/src/home/lib/runner/attach.mjs +11 -7
  48. package/src/home/lib/runner/codex-cmd.mjs +3 -2
  49. package/src/home/lib/runner/continuation.mjs +3 -1
  50. package/src/home/lib/runner/git-state.mjs +1 -1
  51. package/src/home/lib/runner/launcher.mjs +14 -13
  52. package/src/home/lib/runner/preflight.mjs +6 -3
  53. package/src/home/lib/runner/prompts.mjs +9 -8
  54. package/src/home/lib/runner/schemas.mjs +33 -7
@@ -27,7 +27,7 @@ import { parseArgs, die } from './args.mjs';
27
27
  import { settleTaskInput } from './task-input.mjs';
28
28
  import { parseContinuationGrant } from '../required-inputs.mjs';
29
29
  import { continuationRefusal } from './continuation.mjs';
30
- import { parseAdvisorTask } from '../meta/advisor-task.mjs';
30
+ import { advisorTaskOrRefuse, adviseSection } from './advise-carry.mjs';
31
31
  import { schemaFor } from './schemas.mjs';
32
32
  import { INSTRUCTIONS } from './prompts.mjs';
33
33
  import { git, headSha, branchName, worktreeSnapshot, reviewScope } from './git-state.mjs';
@@ -146,12 +146,18 @@ export async function launcher(argv = process.argv.slice(2)) {
146
146
  const chain = chainRuns(projectRunsRoot, repoRoot, opts.slug, taskHash, opts.orderId);
147
147
  const startedChain = startedRuns(projectRunsRoot, chain);
148
148
  const continuationGrant = parseContinuationGrant(taskText);
149
+ const attachExistingRun = () => attach({
150
+ runsRoot: projectRunsRoot, repo: repoRoot, slug: opts.slug, taskHash, orderId: opts.orderId, chain,
151
+ grantRun: continuationGrant?.run, isContinue: opts.continue, noWait: opts.noWait,
152
+ });
153
+ let attachedExitCode = opts.continue ? await attachExistingRun() : null;
154
+ if (attachedExitCode !== null) return attachedExitCode;
149
155
 
150
156
  // The 2026-08-10_220535_plan25-2-install-table-two-roots incident exposed this ordering:
151
157
  // a grant without its flag must refuse before attach can print an older run's verdict.
152
158
  // markAbandoned() ran before this gate, so a dead runner already has meta.json with FAIL.
153
159
  // A run without a verdict can therefore only still be in flight; ordinary repeats go through
154
- // attach(), while --continue must refuse to overlap that live work.
160
+ // attach() after this gate. Repeated --continue calls attach first because their grant is spent.
155
161
  const continuationError = continuationRefusal(
156
162
  projectRunsRoot,
157
163
  startedChain,
@@ -164,20 +170,13 @@ export async function launcher(argv = process.argv.slice(2)) {
164
170
  const scopeRun = continuationGrant &&
165
171
  scopeRunRefusal({ agent: opts.agent, phase: opts.phase, runsRoot: projectRunsRoot, grantRun: continuationGrant.run });
166
172
  if (scopeRun) die(scopeRun, 1);
173
+ // Plan_59 D22: the advise snapshot is read before the folder exists, so a broken scope result refuses for free.
174
+ const advisorTask = opts.agent === 'codex-advisor' ? advisorTaskOrRefuse({ taskText, phase: opts.phase, runsRoot: projectRunsRoot, grantRun: continuationGrant?.run }) : null;
167
175
 
168
176
  // One order produced six Codex runs on 2026-08-03 because the caller's time ceiling made it
169
177
  // restart the synchronous launcher. A live same-order run is now the repeat target: attach
170
178
  // before creating a folder, probing Codex or spending another token.
171
- const attachedExitCode = await attach({
172
- runsRoot: projectRunsRoot,
173
- repo: repoRoot,
174
- slug: opts.slug,
175
- taskHash,
176
- orderId: opts.orderId,
177
- chain,
178
- isContinue: opts.continue,
179
- noWait: opts.noWait,
180
- });
179
+ if (!opts.continue) attachedExitCode = await attachExistingRun();
181
180
  if (attachedExitCode !== null) return attachedExitCode;
182
181
 
183
182
  if (startedChain.length && !opts.continue) {
@@ -274,7 +273,7 @@ export async function launcher(argv = process.argv.slice(2)) {
274
273
  if (opts.agent === 'codex-scout') {
275
274
  fs.writeFileSync(path.join(runDir, 'questions.json'), `${JSON.stringify(questions, null, 2)}\n`);
276
275
  }
277
- if (opts.agent === 'codex-advisor') fs.writeFileSync(path.join(runDir, 'advisor-task.json'), `${JSON.stringify(parseAdvisorTask(taskText), null, 2)}\n`);
276
+ if (advisorTask) fs.writeFileSync(path.join(runDir, 'advisor-task.json'), `${JSON.stringify(advisorTask, null, 2)}\n`);
278
277
 
279
278
  if (opts.agent === 'codex-build') {
280
279
  fs.writeFileSync(path.join(runDir, 'scope.txt'), `${opts.scopePatterns.join('\n')}\n`);
@@ -289,6 +288,8 @@ export async function launcher(argv = process.argv.slice(2)) {
289
288
  // and what may be edited. Both also go to disk as questions.json / scope.txt, so the verdict
290
289
  // is computed from the same list Codex was handed, not from a second reading of the wording.
291
290
  const sections = [`## Operator task (verbatim)\n\n${taskText}`];
291
+ const scopeResults = adviseSection(advisorTask);
292
+ if (scopeResults) sections.push(scopeResults);
292
293
  if (questions.length) {
293
294
  sections.push(
294
295
  [
@@ -33,8 +33,10 @@ export function taskPreflight({ agent, taskText }) {
33
33
  }
34
34
  if (path.isAbsolute(advice)) {
35
35
  try {
36
- if (fs.statSync(advice).isDirectory() &&
37
- readJson(path.join(advice, 'meta.json'))?.agent === 'codex-advisor') {
36
+ // Plan_59 D26: only a judged advice authorizes a construction. On 2026-09-24 the gate took
37
+ // any codex-advisor folder, so a scope run or a FAIL advice would have passed as a second opinion.
38
+ const meta = fs.statSync(advice).isDirectory() ? readJson(path.join(advice, 'meta.json')) : null;
39
+ if (meta?.agent === 'codex-advisor' && meta.phase === 'advise' && meta.status === 'OK') {
38
40
  return { refusal: null, advice };
39
41
  }
40
42
  } catch {
@@ -43,7 +45,8 @@ export function taskPreflight({ agent, taskText }) {
43
45
  }
44
46
  }
45
47
  return freeRefusal('codex-build requires exactly one advice: line: mechanical | revert | docs-only | test-only ' +
46
- 'or an absolute path to an existing advisor run directory with meta.json agent codex-advisor. ' +
48
+ 'or an absolute path to an existing advisor run directory whose meta.json says agent codex-advisor, ' +
49
+ 'phase advise, status OK. ' +
47
50
  'An order that invents a construction needs a second opinion first; only work with no design choice may skip it.');
48
51
  }
49
52
 
@@ -16,10 +16,11 @@ const BODIES = {
16
16
  return `Judge a DESIGN before code exists.
17
17
 
18
18
  Rules for both phases:
19
- - Read ONLY the paths listed under \`## Paths\`, plus \`git log\` / \`git show\` on those paths.
19
+ - Read ONLY the paths listed under \`## Paths\` and, in the advise phase, under \`## Scope phase results\`, plus \`git log\` / \`git show\` on those paths.
20
20
  - Write nothing. Do your own reading and checks; do not spawn or delegate to other agents.
21
- - Every claim about code must carry a \`path:line\` address inside those paths. Addresses are
22
- machine-checked for existence; do not invent addresses or treat an unchecked claim as evidence.
21
+ - Prose fields cite code as \`path:line\`; every \`evidence\` field is a list of
22
+ \`{file, line_start, line_end}\` objects, ONE location per object. Several places mean several
23
+ objects, never a list inside \`file\`. Locations are machine-checked for existence.
23
24
  - Do not guess which option the caller prefers: the task carries no preference on purpose.
24
25
  - Two failure modes are forbidden: agreeing without independent checks ("both options are good",
25
26
  "you know better"), and disagreeing to look independent. A disagreement without an address is
@@ -32,18 +33,18 @@ Scope phase:
32
33
  (facts accepted from the task without checking, never empty), together with predicted_risks.
33
34
  ` : `
34
35
  Advise phase:
35
- - Settle every predicted risk from the scope run in risk_outcomes: its risk_id, an outcome of
36
- confirmed or refuted, and an address supporting that outcome.
36
+ - Settle every predicted risk listed under \`## Scope phase results\` in risk_outcomes with its
37
+ risk_id, an outcome of confirmed or refuted, a \`note\` explaining it, and evidence.
37
38
  - Give ONE recommendation by task option_id, or none-of-these with a described unlisted_option.
38
39
  Put every rejected task option in rejected with its cost; for none-of-these this means EVERY
39
40
  task option. Set unlisted_option to "none" when recommending a listed option.
40
41
  - Each field is a distinct lens: strongest_counterargument comes from a skeptic;
41
42
  pre_mortem comes from the executor who has only this advice and the repository. Give each
42
- scenario an early_check of kind test | command | inspect with a target; inspect requires an address.
43
+ scenario an early_check of kind test | command | inspect with a target; inspect requires evidence.
43
44
  - question_defect comes from the requester who needs the problem solved, not an option picked.
44
- - Rate assumptions VERIFIED (requires an address) | REASONABLE | FRAGILE. Anything the requester
45
+ - Rate assumptions VERIFIED (requires evidence) | REASONABLE | FRAGILE. Anything the requester
45
46
  could refute with knowledge you lack belongs in open_questions, not why.
46
- - independent_checks lists what you verified yourself, with addresses. Ground why in those checks
47
+ - independent_checks lists what you verified yourself, with evidence. Ground why in those checks
47
48
  and state confidence honestly.
48
49
  `}`;
49
50
  },
@@ -150,6 +150,30 @@ const REVIEW_SCHEMA = {
150
150
  },
151
151
  };
152
152
 
153
+ // The 2026-09-24_094352_plan62-g1-dispatcher-gate run folded 3-5 citations into one advisor
154
+ // address; like the 2026-07-31 build changes[].file precedent, constrain a single location here.
155
+ export const EVIDENCE_LOCATION = {
156
+ type: 'object',
157
+ additionalProperties: false,
158
+ required: ['file', 'line_start', 'line_end'],
159
+ properties: {
160
+ file: {
161
+ type: 'string',
162
+ minLength: 1,
163
+ pattern: '^[^:;,*?{}\\r\\n]+$',
164
+ description: 'Exactly one repository-root-relative path. Spaces are allowed; lists, globs, drive letters and line suffixes are not — use a separate location object for each place.',
165
+ },
166
+ line_start: { type: 'integer', minimum: 1 },
167
+ line_end: { type: 'integer', minimum: 1 },
168
+ },
169
+ };
170
+
171
+ const EVIDENCE_LIST = (minItems) => ({
172
+ type: 'array',
173
+ ...(minItems === undefined ? {} : { minItems }),
174
+ items: EVIDENCE_LOCATION,
175
+ });
176
+
153
177
  /** Plan_59: execution schemas stay static; schemaFor selects the advisor contract by phase. */
154
178
  export const SCHEMAS = {
155
179
  'codex-scout': SCOUT_SCHEMA,
@@ -220,11 +244,11 @@ export const PHASE_SCHEMAS = {
220
244
  items: {
221
245
  type: 'object',
222
246
  additionalProperties: false,
223
- required: ['claim', 'rating', 'address'],
247
+ required: ['claim', 'rating', 'evidence'],
224
248
  properties: {
225
249
  claim: { type: 'string' },
226
250
  rating: { type: 'string', enum: ['VERIFIED', 'REASONABLE', 'FRAGILE'] },
227
- address: { type: 'string' },
251
+ evidence: EVIDENCE_LIST(),
228
252
  },
229
253
  },
230
254
  },
@@ -233,11 +257,12 @@ export const PHASE_SCHEMAS = {
233
257
  items: {
234
258
  type: 'object',
235
259
  additionalProperties: false,
236
- required: ['risk_id', 'outcome', 'address'],
260
+ required: ['risk_id', 'outcome', 'note', 'evidence'],
237
261
  properties: {
238
262
  risk_id: { type: 'string' },
239
263
  outcome: { type: 'string', enum: ['confirmed', 'refuted'] },
240
- address: { type: 'string' },
264
+ note: { type: 'string' },
265
+ evidence: EVIDENCE_LIST(1),
241
266
  },
242
267
  },
243
268
  },
@@ -254,10 +279,11 @@ export const PHASE_SCHEMAS = {
254
279
  early_check: {
255
280
  type: 'object',
256
281
  additionalProperties: false,
257
- required: ['kind', 'target'],
282
+ required: ['kind', 'target', 'evidence'],
258
283
  properties: {
259
284
  kind: { type: 'string', enum: ['test', 'command', 'inspect'] },
260
285
  target: { type: 'string', minLength: 3 },
286
+ evidence: EVIDENCE_LIST(),
261
287
  },
262
288
  },
263
289
  },
@@ -271,8 +297,8 @@ export const PHASE_SCHEMAS = {
271
297
  items: {
272
298
  type: 'object',
273
299
  additionalProperties: false,
274
- required: ['check', 'address'],
275
- properties: { check: { type: 'string' }, address: { type: 'string' } },
300
+ required: ['check', 'evidence'],
301
+ properties: { check: { type: 'string' }, evidence: EVIDENCE_LIST(1) },
276
302
  },
277
303
  },
278
304
  },