codex-orchestrator 2.0.13 → 2.0.14

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 (31) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/src/v2/acceptance-proof.js +1 -4
  3. package/dist/src/v2/acceptance-proof.js.map +1 -1
  4. package/dist/src/v2/containment.d.ts +1 -0
  5. package/dist/src/v2/containment.d.ts.map +1 -1
  6. package/dist/src/v2/containment.js +7 -1
  7. package/dist/src/v2/containment.js.map +1 -1
  8. package/dist/src/v2/pending-effect-settlement.d.ts +2 -2
  9. package/dist/src/v2/pending-effect-settlement.d.ts.map +1 -1
  10. package/dist/src/v2/pending-effect-settlement.js +2 -2
  11. package/dist/src/v2/pending-effect-settlement.js.map +1 -1
  12. package/dist/src/v2/run-issue.d.ts.map +1 -1
  13. package/dist/src/v2/run-issue.js +121 -68
  14. package/dist/src/v2/run-issue.js.map +1 -1
  15. package/dist/src/v2/run-state-projections.d.ts.map +1 -1
  16. package/dist/src/v2/run-state-projections.js +1 -1
  17. package/dist/src/v2/run-state-projections.js.map +1 -1
  18. package/dist/src/v2/run-store.d.ts +22 -7
  19. package/dist/src/v2/run-store.d.ts.map +1 -1
  20. package/dist/src/v2/run-store.js +27 -13
  21. package/dist/src/v2/run-store.js.map +1 -1
  22. package/dist/src/v2/runtime.d.ts.map +1 -1
  23. package/dist/src/v2/runtime.js +2 -4
  24. package/dist/src/v2/runtime.js.map +1 -1
  25. package/internal-workflow/manifest.json +1 -1
  26. package/internal-workflow/skills/tickets-orchestrator/SKILL.md +4 -0
  27. package/internal-workflow/skills/to-spec/SKILL.md +17 -8
  28. package/internal-workflow/skills/to-spec/evals/evals.json +6 -0
  29. package/internal-workflow/skills/to-tickets/SKILL.md +29 -2
  30. package/internal-workflow/skills/to-tickets/evals/evals.json +12 -0
  31. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { checkedChangePayloadSha256, } from './checked-change.js';
3
3
  import { parseAgentAutoConfig } from './config.js';
4
- import { canonicalJson, containsCredentialEvidence, parseJsonWithoutDuplicateKeys, sha256 } from './containment.js';
4
+ import { canonicalJson, containsCredentialEvidence, containsHostIdentityEvidence, parseJsonWithoutDuplicateKeys, sha256 } from './containment.js';
5
5
  import { findDeniedPathMatch } from './adapters/path-policy.js';
6
6
  import { adoptActiveAttempt, confirmActiveAttemptCleanup, createActiveAttempt, launchActiveAttempt, observeActiveAttempt, } from './active-attempt.js';
7
7
  import { validateImplementationReport } from './implementation-report.js';
@@ -774,13 +774,13 @@ export class RunIssue {
774
774
  baseBranch: config.github.baseBranch,
775
775
  });
776
776
  if (!pullRequest?.number || !pullRequest.nodeId || !pullRequest.headSha) {
777
- return { result: await this.persistTerminal(starting, { status: 'blocked', kind: 'safety', resumable: false }, 'review-feedback-pr-identity-missing', false) };
777
+ return { result: await this.terminal(starting, { status: 'blocked', kind: 'safety', resumable: false }, 'review-feedback-pr-identity-missing') };
778
778
  }
779
779
  const expectedHeadSha = feedback.previousPublishedHeadSha === null
780
780
  ? pullRequest.headSha
781
781
  : feedback.previousPublishedHeadSha;
782
782
  if (!expectedHeadSha) {
783
- return { result: await this.persistTerminal(starting, { status: 'blocked', kind: 'safety', resumable: false }, 'review-feedback-head-missing', false) };
783
+ return { result: await this.terminal(starting, { status: 'blocked', kind: 'safety', resumable: false }, 'review-feedback-head-missing') };
784
784
  }
785
785
  const observed = await this.dependencies.reviewFeedback.observeAndFreeze({
786
786
  runId: starting.record.runId,
@@ -801,7 +801,7 @@ export class RunIssue {
801
801
  if (observed.status === 'retryable')
802
802
  return { result: await this.invokedFailure(starting, 'review-feedback-observation-retryable') };
803
803
  if (observed.status === 'blocked') {
804
- return { result: await this.persistTerminal(starting, { status: 'blocked', kind: 'safety', resumable: false }, 'review-feedback-observation-blocked', false) };
804
+ return { result: await this.terminal(starting, { status: 'blocked', kind: 'safety', resumable: false }, 'review-feedback-observation-blocked') };
805
805
  }
806
806
  if (feedback.previousPublishedHeadSha === null) {
807
807
  const sourceIds = observed.status === 'frozen'
@@ -1004,14 +1004,20 @@ export class RunIssue {
1004
1004
  const seed = parseTerminalSeedSummary(active.record.pendingEffect.summary);
1005
1005
  return await this.persistTerminal(active, seed, active.record.pendingEffect.code, false);
1006
1006
  }
1007
- if (active.record.pendingEffect?.kind === 'review-blocked-labels') {
1008
- return await this.blockReviewFeedback(active, active.record.pendingEffect.blockKind, active.record.pendingEffect.evidenceCode);
1007
+ if (active.record.pendingEffect?.kind === 'blocked-comment') {
1008
+ return await this.publishBlockedTerminal(active, {
1009
+ status: 'blocked',
1010
+ kind: active.record.pendingEffect.blockKind,
1011
+ resumable: active.record.pendingEffect.resumable,
1012
+ ...(active.record.pendingEffect.blocker ? { blocker: active.record.pendingEffect.blocker } : {}),
1013
+ }, active.record.pendingEffect.evidenceCode);
1009
1014
  }
1010
1015
  if (active.record.pendingEffect?.kind === 'blocked-labels') {
1011
1016
  return await this.publishBlockedTerminal(active, {
1012
1017
  status: 'blocked',
1013
1018
  kind: active.record.pendingEffect.blockKind,
1014
1019
  resumable: active.record.pendingEffect.resumable,
1020
+ ...(active.record.pendingEffect.blocker ? { blocker: active.record.pendingEffect.blocker } : {}),
1015
1021
  }, active.record.pendingEffect.evidenceCode);
1016
1022
  }
1017
1023
  if (existing.terminalOutcome?.status === 'blocked'
@@ -1193,7 +1199,7 @@ export class RunIssue {
1193
1199
  }
1194
1200
  attemptLoop: while (true) {
1195
1201
  if (!await this.authorized(active, config)) {
1196
- return await this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true });
1202
+ return await this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true }, 'authorization-revoked');
1197
1203
  }
1198
1204
  if (this.signal.aborted)
1199
1205
  return await this.terminal(active, { status: 'cancelled' });
@@ -1328,7 +1334,7 @@ export class RunIssue {
1328
1334
  return await this.terminal(active, implementationBlockerOutcome(report.blocker));
1329
1335
  }
1330
1336
  if (await this.dependencies.git.getHead(worktreePath) !== expectedImplementationHead(active.record)) {
1331
- return await this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true });
1337
+ return await this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true }, 'implementation-head-drift');
1332
1338
  }
1333
1339
  const changedFiles = await this.dependencies.git.listChangedFiles(worktreePath);
1334
1340
  if (changedFiles.length === 0 || !sameStrings(changedFiles, report.changedFiles)) {
@@ -2392,7 +2398,7 @@ export class RunIssue {
2392
2398
  : this.persistCandidateEvidenceSafetyTerminal(active, 'candidate-execution-missing');
2393
2399
  }
2394
2400
  if (inspection.value === 'mutated') {
2395
- return this.persistTerminal(active, { status: 'blocked', kind: 'safety', resumable: false }, 'candidate-materialization-mutated', false);
2401
+ return this.terminal(active, { status: 'blocked', kind: 'safety', resumable: false }, 'candidate-materialization-mutated');
2396
2402
  }
2397
2403
  if (active.record.activeAttempt?.stage === 'observed' && active.record.activeAttempt.result) {
2398
2404
  return { active };
@@ -2484,7 +2490,7 @@ export class RunIssue {
2484
2490
  reviewFeedback: blockReviewFeedback(active.record.reviewFeedback, 'safety', this.timestamp()),
2485
2491
  });
2486
2492
  }
2487
- return this.persistTerminal(active, { status: 'blocked', kind: 'safety', resumable: false }, evidenceCode, false);
2493
+ return this.terminal(active, { status: 'blocked', kind: 'safety', resumable: false }, evidenceCode);
2488
2494
  }
2489
2495
  mapCandidateFailure(active, code) {
2490
2496
  if (code === 'candidate-unstable' || code === 'candidate-io-failed' || code === 'candidate-materialization-io-failed') {
@@ -2860,49 +2866,7 @@ export class RunIssue {
2860
2866
  const feedback = active.record.reviewFeedback;
2861
2867
  if (!feedback?.activeBatch)
2862
2868
  return this.terminal(active, { status: 'blocked', kind, resumable: false }, evidenceCode);
2863
- {
2864
- const config = active.config;
2865
- const blockedLabels = [config.github.labels.auto.name, config.github.labels.blocked.name].sort();
2866
- const existingIntent = active.record.pendingEffect;
2867
- if (existingIntent?.kind !== 'review-blocked-labels') {
2868
- active = await this.persist(active, { pendingEffect: {
2869
- kind: 'review-blocked-labels', issueNumber: active.record.issueNumber,
2870
- batchId: feedback.activeBatch.batchId, expected: blockedLabels, blockKind: kind, evidenceCode,
2871
- } });
2872
- }
2873
- else if (existingIntent.issueNumber !== active.record.issueNumber
2874
- || existingIntent.batchId !== feedback.activeBatch.batchId
2875
- || !sameStrings(existingIntent.expected, blockedLabels)
2876
- || existingIntent.blockKind !== kind
2877
- || existingIntent.evidenceCode !== evidenceCode) {
2878
- return this.invokedFailure(active, 'review-feedback-blocked-labels-pendingEffect-diverged');
2879
- }
2880
- const ownedRunningLabels = [config.github.labels.auto.name, config.github.labels.running.name].sort();
2881
- const ownedReviewLabels = [config.github.labels.review.name];
2882
- const effect = active.record.pendingEffect;
2883
- if (effect?.kind !== 'review-blocked-labels') {
2884
- return this.invokedFailure(active, 'review-feedback-blocked-labels-pendingEffect-diverged');
2885
- }
2886
- const settlement = await settleLabelsEffect(effect, {
2887
- observe: async () => {
2888
- const issue = await this.readIssue(active.record.issueNumber);
2889
- if (!issue || issue.state !== 'OPEN')
2890
- return 'diverged';
2891
- if (sameStrings(issue.labels, blockedLabels))
2892
- return 'confirmed';
2893
- return sameStrings(issue.labels, ownedRunningLabels) || sameStrings(issue.labels, ownedReviewLabels)
2894
- ? 'absent' : 'diverged';
2895
- },
2896
- invoke: () => this.dependencies.issues.setLabels(active.record.issueNumber, blockedLabels),
2897
- });
2898
- if (settlement.status === 'unknown') {
2899
- return this.invokedFailure(active, 'review-feedback-blocked-labels-delivery-unknown');
2900
- }
2901
- if (settlement.status !== 'confirmed') {
2902
- return this.invokedFailure(active, 'review-feedback-blocked-labels-observation-diverged');
2903
- }
2904
- }
2905
- return this.persistTerminal(active, outcome, evidenceCode, false, {
2869
+ return this.publishBlockedTerminal(active, outcome, evidenceCode, {
2906
2870
  reviewFeedback: blockReviewFeedback(feedback, kind, this.timestamp()),
2907
2871
  }, true);
2908
2872
  }
@@ -2937,7 +2901,7 @@ export class RunIssue {
2937
2901
  async revoked(active) {
2938
2902
  if (active.record.pendingEffect)
2939
2903
  return this.invokedFailure(active, 'revoked-with-pending-effect');
2940
- return this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true });
2904
+ return this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true }, 'authorization-revoked');
2941
2905
  }
2942
2906
  async invokedFailure(active, code, summary = 'Publication delivery requires reconciliation.') {
2943
2907
  const evidence = await this.dependencies.writeEvidence({ runId: active.record.runId, code, summary });
@@ -2992,23 +2956,68 @@ export class RunIssue {
2992
2956
  }
2993
2957
  return this.persistTerminal(active, outcome, evidenceCode, retainIntent);
2994
2958
  }
2995
- async publishBlockedTerminal(starting, outcome, evidenceCode) {
2959
+ async publishBlockedTerminal(starting, outcome, evidenceCode, additionalChanges = {}, replaceExistingIntent = false) {
2996
2960
  let active = starting;
2961
+ const body = blockedComment(active.record.runId, outcome, evidenceCode);
2962
+ const marker = body.split('\n')[0];
2963
+ const commentIntent = {
2964
+ kind: 'blocked-comment',
2965
+ issueNumber: active.record.issueNumber,
2966
+ marker,
2967
+ bodySha256: sha256(body),
2968
+ blockKind: outcome.kind,
2969
+ resumable: outcome.resumable,
2970
+ evidenceCode,
2971
+ ...(outcome.blocker ? { blocker: outcome.blocker } : {}),
2972
+ };
2997
2973
  const autoBlocked = [active.config.github.labels.auto.name, active.config.github.labels.blocked.name].sort();
2998
2974
  const blockedOnly = [active.config.github.labels.blocked.name];
2999
2975
  let expected = autoBlocked;
3000
- const existing = active.record.pendingEffect;
3001
- if (!existing) {
3002
- active = await this.persist(active, { pendingEffect: {
3003
- kind: 'blocked-labels',
3004
- issueNumber: active.record.issueNumber,
3005
- expected,
3006
- blockKind: outcome.kind,
3007
- resumable: outcome.resumable,
3008
- evidenceCode,
3009
- } });
2976
+ let existing = active.record.pendingEffect;
2977
+ if (!existing || replaceExistingIntent) {
2978
+ active = await this.persist(active, { ...additionalChanges, pendingEffect: commentIntent });
2979
+ existing = active.record.pendingEffect;
2980
+ }
2981
+ if (existing?.kind === 'blocked-comment') {
2982
+ if (existing.issueNumber !== commentIntent.issueNumber || existing.marker !== marker
2983
+ || existing.bodySha256 !== sha256(body) || existing.blockKind !== outcome.kind
2984
+ || existing.resumable !== outcome.resumable || existing.evidenceCode !== evidenceCode) {
2985
+ return this.invokedFailure(active, 'blocked-comment-pendingEffect-diverged');
2986
+ }
2987
+ const settlement = await settleCommentEffect(existing, {
2988
+ observe: async () => {
2989
+ const observation = await this.readIssue(active.record.issueNumber);
2990
+ if (!observation || observation.state !== 'OPEN')
2991
+ return 'diverged';
2992
+ const matching = observation ? commentsWithMarker(observation, marker) : [];
2993
+ if (matching.length === 0)
2994
+ return 'absent';
2995
+ return matching.length === 1 && sha256(matching[0].body) === sha256(body)
2996
+ ? 'confirmed' : 'diverged';
2997
+ },
2998
+ invoke: () => this.dependencies.issues.postComment(active.record.issueNumber, body),
2999
+ });
3000
+ if (settlement.status === 'unknown')
3001
+ return this.invokedFailure(active, 'blocked-comment-delivery-unknown');
3002
+ if (settlement.status !== 'confirmed')
3003
+ return this.invokedFailure(active, 'blocked-comment-observation-diverged');
3004
+ try {
3005
+ active = await this.persist(active, { pendingEffect: {
3006
+ kind: 'blocked-labels',
3007
+ issueNumber: active.record.issueNumber,
3008
+ expected,
3009
+ blockKind: outcome.kind,
3010
+ resumable: outcome.resumable,
3011
+ evidenceCode,
3012
+ ...(outcome.blocker ? { blocker: outcome.blocker } : {}),
3013
+ } });
3014
+ }
3015
+ catch {
3016
+ throw new PostEffectStateError(active);
3017
+ }
3018
+ existing = active.record.pendingEffect;
3010
3019
  }
3011
- else if (existing.kind !== 'blocked-labels'
3020
+ if (existing?.kind !== 'blocked-labels'
3012
3021
  || existing.issueNumber !== active.record.issueNumber
3013
3022
  || existing.blockKind !== outcome.kind
3014
3023
  || existing.resumable !== outcome.resumable
@@ -3059,8 +3068,7 @@ export class RunIssue {
3059
3068
  }
3060
3069
  }
3061
3070
  try {
3062
- active = await this.confirmEffect(active);
3063
- return await this.persistTerminal(active, outcome, evidenceCode, false);
3071
+ return await this.persistTerminal(active, outcome, evidenceCode, false, {}, true);
3064
3072
  }
3065
3073
  catch {
3066
3074
  throw new PostEffectStateError(active);
@@ -3277,6 +3285,51 @@ function pendingCandidateBoundary(record) {
3277
3285
  function commentsWithMarker(issue, marker) {
3278
3286
  return issue.comments.filter((comment) => comment.body.split('\n')[0] === marker);
3279
3287
  }
3288
+ function blockedComment(runId, outcome, evidenceCode) {
3289
+ const lines = [
3290
+ `<!-- codex-orchestrator:run:${runId}:blocked -->`,
3291
+ 'codex-orchestrator blocked this run.',
3292
+ '',
3293
+ `Kind: ${outcome.kind}`,
3294
+ `Resumable: ${outcome.resumable ? 'yes' : 'no'}`,
3295
+ ];
3296
+ if (outcome.blocker) {
3297
+ lines.push('', `Reason: ${publicBlockedText(outcome.blocker.summary, evidenceCode, 2_000)}`);
3298
+ if (outcome.blocker.reviewerRejectionDetail) {
3299
+ lines.push(`Detail: ${publicBlockedText(outcome.blocker.reviewerRejectionDetail, 'Review detail was unsafe or unavailable.', 2_000)}`);
3300
+ }
3301
+ if (outcome.blocker.attempted.length > 0) {
3302
+ const attempted = [];
3303
+ let remaining = 8_000;
3304
+ for (const attempt of outcome.blocker.attempted.slice(0, 16)) {
3305
+ if (remaining <= 0)
3306
+ break;
3307
+ const text = publicBlockedText(attempt, 'Unsafe or unavailable attempted action.', Math.min(2_000, remaining));
3308
+ attempted.push(`- ${text}`);
3309
+ remaining -= text.length;
3310
+ }
3311
+ lines.push('', 'Attempted:', ...attempted);
3312
+ }
3313
+ else {
3314
+ lines.push('', 'Attempted:', '- No attempted actions were recorded.');
3315
+ }
3316
+ }
3317
+ else {
3318
+ lines.push('', `Reason: ${evidenceCode}`, '', 'Attempted:', '- No attempted actions were recorded.');
3319
+ }
3320
+ return lines.join('\n');
3321
+ }
3322
+ function publicBlockedText(value, fallback, maxLength) {
3323
+ if (containsCredentialEvidence(value) || containsHostIdentityEvidence(value)
3324
+ || /["']?token["']?\s*[:=]\s*["']?[^\s"']{8,}/iu.test(value))
3325
+ return fallback;
3326
+ const normalized = value.replace(/[\r\n]+/gu, ' ').trim();
3327
+ if (normalized.length === 0)
3328
+ return fallback;
3329
+ return normalized.length <= maxLength
3330
+ ? normalized
3331
+ : `${normalized.slice(0, Math.max(0, maxLength - 12))} [truncated]`;
3332
+ }
3280
3333
  function sortedUnique(values) {
3281
3334
  return [...new Set(values)].sort();
3282
3335
  }