@fitlab-ai/agent-infra 0.9.6 → 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 (178) hide show
  1. package/dist/bin/internal-cli.js +10 -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 +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -1,14 +1,16 @@
1
1
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
2
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
2
3
  import { applyTaskLifecycle, lifecycleIntentCatalog } from '../task/lifecycle.ts';
3
4
  import type { TaskLifecycleRequest } from '../task/lifecycle.ts';
4
- import { resolveTaskRef } from '../task/resolve-ref.ts';
5
+ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
5
6
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
6
7
 
7
- const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\n`;
8
+ const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
8
9
  const FLAGS: Record<string, string> = {
9
10
  '--agent': 'agent', '--reason': 'reason', '--unblock-condition': 'unblockCondition',
10
11
  '--note': 'note', '--alert-number': 'alertNumber', '--staging-dir': 'stagingDir',
11
- '--issue-number': 'issueNumber'
12
+ '--issue-number': 'issueNumber', '--override-ticket': 'overrideTicket',
13
+ '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
12
14
  };
13
15
 
14
16
  function usageFailure(message: string): void {
@@ -39,19 +41,61 @@ function taskLifecycle(args: string[] = []): void {
39
41
  const agent = normalizeAgentToken(String(values.agent ?? ''));
40
42
  if (!agent) { usageFailure(`invalid --agent '${values.agent}': ${AGENT_USAGE_HINT}`); return; }
41
43
  values.agent = agent;
42
- const resolved = resolveTaskRef(String(values.taskRef));
43
- if (!resolved.ok) {
44
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
45
- process.exitCode = 1;
46
- return;
44
+ const dryRunConflict = overrideDryRunConflict(values);
45
+ if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
46
+ let repoRoot: string;
47
+ let taskId: string;
48
+ if (values.intent === 'restore') {
49
+ // restore's precondition is that the task does NOT exist locally yet (it only lives
50
+ // in a staging dir), so resolveTaskRef's existence check is inapplicable here; identity
51
+ // is guaranteed by applyTaskLifecycle's own full-TASK-id check plus staging validation.
52
+ try {
53
+ repoRoot = detectRepoRoot();
54
+ } catch (error) {
55
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'REPO_ROOT_NOT_FOUND', message: error instanceof Error ? error.message : String(error) } })}\n`);
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+ taskId = String(values.taskRef);
60
+ } else {
61
+ const resolved = resolveTaskRef(String(values.taskRef));
62
+ if (!resolved.ok) {
63
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ repoRoot = resolved.repoRoot;
68
+ taskId = resolved.taskId;
47
69
  }
48
- let result: ReturnType<typeof applyTaskLifecycle>;
70
+ let result: ReturnType<typeof applyTaskLifecycle> & { humanOverride?: unknown };
49
71
  try {
50
72
  result = withTaskExecutionLock(
51
- resolved.repoRoot,
52
- resolved.taskId,
73
+ repoRoot,
74
+ taskId,
53
75
  `task-lifecycle.${String(values.intent)}`,
54
- () => applyTaskLifecycle(values as TaskLifecycleRequest)
76
+ () => {
77
+ const lifecycleResult = applyTaskLifecycle(values as TaskLifecycleRequest);
78
+ if (lifecycleResult.status !== 'failed' || !values.overrideTicket) return lifecycleResult;
79
+ if (!values.overrideTarget || !values.overrideScope) {
80
+ return {
81
+ ...lifecycleResult,
82
+ error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
83
+ };
84
+ }
85
+ const override = consumeHumanOverride({
86
+ taskRef: String(values.taskRef),
87
+ ticketId: String(values.overrideTicket),
88
+ failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
89
+ target: String(values.overrideTarget),
90
+ scope: String(values.overrideScope),
91
+ intent: String(values.intent) as TaskLifecycleRequest['intent'],
92
+ ...(values.alertNumber ? { alertNumber: Number(values.alertNumber) } : {}),
93
+ ...(values.issueNumber ? { issueNumber: Number(values.issueNumber) } : {}),
94
+ ...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {})
95
+ });
96
+ if (override.status === 'failed') return { ...lifecycleResult, humanOverride: override };
97
+ return { ...lifecycleResult, ...override, humanOverride: override, error: null };
98
+ }
55
99
  );
56
100
  } catch (error) {
57
101
  if (!(error instanceof TaskExecutionLockError)) throw error;
@@ -4,12 +4,15 @@ import {
4
4
  activateMatchingOrchestrationDelegation,
5
5
  activateOrchestrationDelegation,
6
6
  advanceOrchestration,
7
+ awaitOrchestrationDelegationActivation,
7
8
  beginCommitIntent,
8
9
  beginOrResumeOrchestration,
9
10
  checkpointCommitIntent,
10
11
  completeCommitIntent,
12
+ dispatchOrchestrationDelegation,
11
13
  pauseOrchestration,
12
14
  recoverCommitIntent,
15
+ recoverPreparedOrchestrationDelegation,
13
16
  routeOrchestration,
14
17
  sealMatchingOrchestrationDelegation,
15
18
  sealOrchestrationDelegation,
@@ -22,7 +25,7 @@ import { prepareCodexOrchestrationDelegation } from '../task/codex-orchestration
22
25
  import { isAgentClientId } from '../agent-clients/types.ts';
23
26
  import type { AgentClientId } from '../agent-clients/types.ts';
24
27
 
25
- const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|hook-stop|advance|pause|status|commit-start|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-terminate|commit-status> [options]\n';
28
+ const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|dispatch|await-activation|recover-prepared|hook-start|hook-stop|advance|pause|status|commit-start|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-terminate|commit-status> [options]\n';
26
29
 
27
30
  function usageFailure(message: string): void {
28
31
  process.stdout.write(`${JSON.stringify({
@@ -44,7 +47,8 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
44
47
  }
45
48
  const [taskRef, intent] = args;
46
49
  if (![
47
- 'begin-or-resume', 'route', 'prepare', 'hook-start', 'hook-stop', 'advance', 'pause', 'status',
50
+ 'begin-or-resume', 'route', 'prepare', 'dispatch', 'await-activation', 'recover-prepared',
51
+ 'hook-start', 'hook-stop', 'advance', 'pause', 'status',
48
52
  'commit-start', 'commit-begin', 'commit-checkpoint', 'commit-complete', 'commit-recover', 'commit-abort', 'commit-terminate', 'commit-status'
49
53
  ].includes(intent!)) {
50
54
  usageFailure(`unknown intent '${intent}'`);
@@ -64,7 +68,9 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
64
68
  if (![
65
69
  '--max-steps', '--executor-model', '--executor-reasoning-effort',
66
70
  '--reviewer-model', '--reviewer-reasoning-effort',
67
- '--client', '--requested-model', '--requested-reasoning-effort', '--parent-id', '--before-fingerprint',
71
+ '--client', '--requested-model', '--requested-reasoning-effort', '--capability-token',
72
+ '--parent-id', '--before-fingerprint',
73
+ '--stage', '--round', '--artifact', '--role',
68
74
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
69
75
  '--model-fallback-reason', '--reasoning-effort-fallback-reason',
70
76
  '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent',
@@ -215,17 +221,38 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
215
221
  result = await prepareCodexOrchestrationDelegation(taskRef!, {
216
222
  client: values['--client'] as AgentClientId,
217
223
  requestedModel: values['--requested-model'],
218
- requestedReasoningEffort: values['--requested-reasoning-effort']
224
+ requestedReasoningEffort: values['--requested-reasoning-effort'],
225
+ capabilityToken: values['--capability-token']
219
226
  }, { orchestrationOptions: coreOptions });
227
+ } else if (intent === 'dispatch') {
228
+ result = dispatchOrchestrationDelegation(taskRef!, coreOptions);
229
+ } else if (intent === 'await-activation') {
230
+ const missing = requireValues(['--stage', '--round', '--artifact', '--role']);
231
+ if (missing) { usageFailure(`intent 'await-activation' requires '${missing}'`); return; }
232
+ const round = Number(values['--round']);
233
+ if (!Number.isSafeInteger(round) || round < 1) {
234
+ usageFailure('--round must be a positive integer'); return;
235
+ }
236
+ if (!['executor', 'reviewer'].includes(values['--role']!)) {
237
+ usageFailure('--role must be executor or reviewer'); return;
238
+ }
239
+ result = await awaitOrchestrationDelegationActivation(taskRef!, {
240
+ stage: values['--stage'] as Parameters<typeof awaitOrchestrationDelegationActivation>[1]['stage'],
241
+ round,
242
+ artifact: values['--artifact']!,
243
+ role: values['--role'] as 'executor' | 'reviewer'
244
+ }, coreOptions);
245
+ } else if (intent === 'recover-prepared') {
246
+ result = recoverPreparedOrchestrationDelegation(taskRef!, coreOptions);
220
247
  } else if (intent === 'hook-start') {
221
248
  const missing = requireValues([
222
249
  ...(taskRef === 'auto' ? ['--client'] : []),
223
- '--native-agent', '--child-id', '--parent-id', '--spawn-mode'
250
+ '--native-agent', '--child-id', '--parent-id'
224
251
  ]);
225
252
  if (missing) { usageFailure(`intent 'hook-start' requires '${missing}'`); return; }
226
253
  const event = {
227
254
  nativeAgent: values['--native-agent']!, childId: values['--child-id']!,
228
- parentId: values['--parent-id']!, spawnMode: values['--spawn-mode']!,
255
+ parentId: values['--parent-id']!, spawnMode: values['--spawn-mode'],
229
256
  actualModel: values['--actual-model'],
230
257
  actualReasoningEffort: values['--actual-reasoning-effort'],
231
258
  modelFallbackReason: values['--model-fallback-reason'],
@@ -239,7 +266,11 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
239
266
  const missing = requireValues(['--client', '--native-agent', '--child-id']);
240
267
  if (missing) { usageFailure(`intent 'hook-stop' requires '${missing}'`); return; }
241
268
  result = sealMatchingOrchestrationDelegation(values['--client'] as AgentClientId, {
242
- nativeAgent: values['--native-agent']!, childId: values['--child-id']!
269
+ nativeAgent: values['--native-agent']!, childId: values['--child-id']!,
270
+ actualModel: values['--actual-model'],
271
+ actualReasoningEffort: values['--actual-reasoning-effort'],
272
+ modelFallbackReason: values['--model-fallback-reason'],
273
+ reasoningEffortFallbackReason: values['--reasoning-effort-fallback-reason']
243
274
  }, coreOptions);
244
275
  } else {
245
276
  const missing = requireValues(['--child-id', '--exit-code', '--after-fingerprint']);
@@ -249,7 +280,11 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
249
280
  result = sealOrchestrationDelegation(taskRef!, {
250
281
  childId: values['--child-id']!, exitCode,
251
282
  afterFingerprint: values['--after-fingerprint']!,
252
- changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : []
283
+ changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : [],
284
+ actualModel: values['--actual-model'],
285
+ actualReasoningEffort: values['--actual-reasoning-effort'],
286
+ modelFallbackReason: values['--model-fallback-reason'],
287
+ reasoningEffortFallbackReason: values['--reasoning-effort-fallback-reason']
253
288
  }, coreOptions);
254
289
  }
255
290
  } else if (intent === 'advance') {
@@ -0,0 +1,138 @@
1
+ import {
2
+ consumeHumanOverride,
3
+ diagnoseHumanOverrideForTask,
4
+ issueHumanOverride
5
+ } from '../task/human-override.ts';
6
+ import type { ConsumeHumanOverrideRequest, HumanOverrideRequest } from '../task/human-override.ts';
7
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
8
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
+
10
+ const USAGE = `Usage: agent-infra-internal task-override <task-ref> <diagnose|issue|consume> [options]
11
+
12
+ Options: --failure-id <id> --target <target> --operator <operator> --reason <reason> --scope <scope>
13
+ --intent <lifecycle-intent> --alert-number <N> --issue-number <N> --staging-dir <path>
14
+ --expires-at <timestamp> --ticket <ticket-id> --pr-number <N>
15
+ `;
16
+
17
+ const FLAGS: Record<string, string> = {
18
+ '--failure-id': 'failureId',
19
+ '--target': 'target',
20
+ '--operator': 'operator',
21
+ '--reason': 'reason',
22
+ '--scope': 'scope',
23
+ '--intent': 'intent',
24
+ '--failure-digest': 'failureDigest',
25
+ '--expires-at': 'expiresAt',
26
+ '--ticket': 'ticketId',
27
+ '--facts': 'observedFacts',
28
+ '--alert-number': 'alertNumber',
29
+ '--issue-number': 'issueNumber',
30
+ '--pr-number': 'pullRequestNumber',
31
+ '--staging-dir': 'stagingDir'
32
+ };
33
+
34
+ function usageFailure(message: string): void {
35
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'OVERRIDE_PAYLOAD_INVALID', message } })}\n`);
36
+ process.stderr.write(USAGE);
37
+ process.exitCode = 1;
38
+ }
39
+
40
+ function taskOverride(args: string[] = []): void {
41
+ if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
42
+ const [taskRef, operation] = args;
43
+ if (!taskRef || !operation || !['diagnose', 'issue', 'consume'].includes(operation)) {
44
+ usageFailure('task ref and a valid operation are required');
45
+ return;
46
+ }
47
+ const values: Record<string, unknown> = { taskRef };
48
+ const seen = new Set<string>();
49
+ for (let index = 2; index < args.length; index += 1) {
50
+ const flag = args[index]!;
51
+ const key = FLAGS[flag];
52
+ if (!key) { usageFailure(`unknown option '${flag}'`); return; }
53
+ if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
54
+ const value = args[++index];
55
+ if (value === undefined || value.startsWith('--')) { usageFailure(`option '${flag}' requires a value`); return; }
56
+ seen.add(flag);
57
+ values[key] = key === 'observedFacts' ? value.split(',').map((fact) => fact.trim()).filter(Boolean)
58
+ : key === 'alertNumber' || key === 'issueNumber' || key === 'pullRequestNumber' ? Number(value) : value;
59
+ }
60
+
61
+ const required: Record<string, string[]> = {
62
+ diagnose: [],
63
+ issue: ['failureId', 'target', 'operator', 'reason', 'scope', 'expiresAt'],
64
+ consume: ['failureId', 'target', 'scope', 'ticketId']
65
+ };
66
+ const missing = required[operation]!.find((key) => values[key] === undefined);
67
+ if (missing) { usageFailure(`${operation} requires '--${missing.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}'`); return; }
68
+
69
+ const resolved = resolveTaskRef(taskRef);
70
+ if (!resolved.ok) {
71
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
72
+ process.exitCode = 1;
73
+ return;
74
+ }
75
+
76
+ let result;
77
+ try {
78
+ if (operation === 'diagnose') {
79
+ result = diagnoseHumanOverrideForTask(
80
+ taskRef,
81
+ values.failureId ? String(values.failureId) : undefined,
82
+ values.target ? String(values.target) : undefined
83
+ );
84
+ } else if (operation === 'issue') {
85
+ result = withTaskExecutionLock(
86
+ resolved.repoRoot,
87
+ resolved.taskId,
88
+ 'task-override.issue',
89
+ () => issueHumanOverride({
90
+ taskRef,
91
+ failureId: String(values.failureId),
92
+ target: String(values.target),
93
+ operator: String(values.operator),
94
+ reason: String(values.reason),
95
+ scope: String(values.scope),
96
+ expiresAt: String(values.expiresAt),
97
+ ...(values.intent ? { intent: String(values.intent) as HumanOverrideRequest['intent'] } : {}),
98
+ ...(values.alertNumber !== undefined ? { alertNumber: Number(values.alertNumber) } : {}),
99
+ ...(values.issueNumber !== undefined ? { issueNumber: Number(values.issueNumber) } : {}),
100
+ ...(values.pullRequestNumber !== undefined ? { pullRequestNumber: Number(values.pullRequestNumber) } : {}),
101
+ ...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {}),
102
+ ...(values.failureDigest ? { failureDigest: String(values.failureDigest) } : {})
103
+ } satisfies HumanOverrideRequest)
104
+ );
105
+ } else {
106
+ result = withTaskExecutionLock(
107
+ resolved.repoRoot,
108
+ resolved.taskId,
109
+ 'task-override.consume',
110
+ () => consumeHumanOverride({
111
+ taskRef,
112
+ ticketId: String(values.ticketId),
113
+ failureId: String(values.failureId),
114
+ target: String(values.target),
115
+ scope: String(values.scope),
116
+ ...(values.intent ? { intent: String(values.intent) as ConsumeHumanOverrideRequest['intent'] } : {}),
117
+ ...(values.alertNumber !== undefined ? { alertNumber: Number(values.alertNumber) } : {}),
118
+ ...(values.issueNumber !== undefined ? { issueNumber: Number(values.issueNumber) } : {}),
119
+ ...(values.pullRequestNumber !== undefined ? { pullRequestNumber: Number(values.pullRequestNumber) } : {}),
120
+ ...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {}),
121
+ ...(values.failureDigest ? { failureDigest: String(values.failureDigest) } : {}),
122
+ ...(values.observedFacts ? { observedFacts: values.observedFacts as string[] } : {})
123
+ } satisfies ConsumeHumanOverrideRequest)
124
+ );
125
+ }
126
+ } catch (cause) {
127
+ if (cause instanceof TaskExecutionLockError) {
128
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: cause.code, message: cause.message } })}\n`);
129
+ process.exitCode = 1;
130
+ return;
131
+ }
132
+ throw cause;
133
+ }
134
+ process.stdout.write(`${JSON.stringify(result)}\n`);
135
+ if (result.status === 'failed') process.exitCode = 1;
136
+ }
137
+
138
+ export { taskOverride };
@@ -1,6 +1,9 @@
1
1
  import { finalizeReviewSummary } from '../task/review-finalization.ts';
2
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
3
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
4
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
2
5
 
3
- const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]\n';
6
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
4
7
 
5
8
  function failUsage(message: string): void {
6
9
  process.stdout.write(`${JSON.stringify({
@@ -30,10 +33,13 @@ function taskReview(args: string[] = []): void {
30
33
  let artifact = '';
31
34
  let dryRun = false;
32
35
  let orchestrated = false;
36
+ let overrideTicket = '';
37
+ let overrideTarget = '';
38
+ let overrideScope = '';
33
39
  const seen = new Set<string>();
34
40
  for (let index = 2; index < args.length; index += 1) {
35
41
  const flag = args[index]!;
36
- if (!['--stage', '--artifact', '--orchestrated', '--dry-run'].includes(flag)) {
42
+ if (!['--stage', '--artifact', '--orchestrated', '--dry-run', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
37
43
  failUsage(`unknown option '${flag}'`);
38
44
  return;
39
45
  }
@@ -56,15 +62,54 @@ function taskReview(args: string[] = []): void {
56
62
  return;
57
63
  }
58
64
  if (flag === '--stage') stage = value;
59
- else artifact = value;
65
+ else if (flag === '--artifact') artifact = value;
66
+ else if (flag === '--override-ticket') overrideTicket = value;
67
+ else if (flag === '--override-target') overrideTarget = value;
68
+ else if (flag === '--override-scope') overrideScope = value;
60
69
  }
61
70
  if (!stage || !artifact) {
62
71
  failUsage("options '--stage' and '--artifact' are required");
63
72
  return;
64
73
  }
65
- const result = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun });
66
- process.stdout.write(`${JSON.stringify(result)}\n`);
67
- if (result.status === 'failed') process.exitCode = 1;
74
+ const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
75
+ if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
76
+ const resolved = resolveTaskRef(args[0]!);
77
+ if (!resolved.ok) { failUsage(resolved.message); return; }
78
+ let result;
79
+ let humanOverride: unknown = null;
80
+ try {
81
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => {
82
+ let current = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
83
+ if (current.status !== 'failed' || !overrideTicket) return current;
84
+ if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
85
+ const consumed = consumeHumanOverride({
86
+ taskRef: args[0]!, ticketId: overrideTicket,
87
+ failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
88
+ target: overrideTarget, scope: overrideScope
89
+ }, {
90
+ effectExecutor: (capability) => {
91
+ const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
92
+ current = retried;
93
+ return retried.status === 'failed' || retried.status === 'planned'
94
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
95
+ : null;
96
+ }
97
+ });
98
+ humanOverride = consumed;
99
+ return current;
100
+ });
101
+ } catch (error) {
102
+ if (!(error instanceof TaskExecutionLockError)) throw error;
103
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
108
+ const overrideFailed = Boolean(
109
+ humanOverride && typeof humanOverride === 'object' &&
110
+ (humanOverride as { status?: unknown }).status === 'failed'
111
+ );
112
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
68
113
  }
69
114
 
70
115
  export { taskReview };
@@ -1,6 +1,9 @@
1
1
  import { renderTaskVerification, verifyTaskEvent } from '../task/verification.ts';
2
+ import { consumeHumanOverride, failureId } from '../task/human-override.ts';
3
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
4
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
2
5
 
3
- const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]\n';
6
+ const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
4
7
 
5
8
  function fail(message: string): void {
6
9
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
@@ -11,21 +14,67 @@ function fail(message: string): void {
11
14
  function taskVerify(args: string[] = []): void {
12
15
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
13
16
  if (!args[0] || !args[1] || args[0].startsWith('--') || args[1].startsWith('--')) { fail('task ref and verification event are required'); return; }
17
+ const taskRef = args[0];
18
+ const event = args[1];
14
19
  let artifact: string | undefined;
15
20
  let format: 'json' | 'text' = 'json';
21
+ let overrideTicket: string | undefined;
22
+ let overrideTarget: string | undefined;
23
+ let overrideScope: string | undefined;
16
24
  const seen = new Set<string>();
17
25
  for (let index = 2; index < args.length; index += 1) {
18
26
  const flag = args[index]!;
19
- if (flag !== '--artifact' && flag !== '--format') { fail(`unknown option '${flag}'`); return; }
27
+ if (!['--artifact', '--format', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) { fail(`unknown option '${flag}'`); return; }
20
28
  if (seen.has(flag)) { fail(`duplicate option '${flag}'`); return; }
21
29
  const value = args[++index];
22
30
  if (!value || value.startsWith('--')) { fail(`option '${flag}' requires a value`); return; }
23
31
  seen.add(flag);
24
32
  if (flag === '--artifact') artifact = value;
33
+ else if (flag === '--override-ticket') overrideTicket = value;
34
+ else if (flag === '--override-target') overrideTarget = value;
35
+ else if (flag === '--override-scope') overrideScope = value;
25
36
  else if (value === 'json' || value === 'text') format = value;
26
37
  else { fail("option '--format' requires 'json' or 'text'"); return; }
27
38
  }
28
- const result = verifyTaskEvent({ taskRef: args[0], event: args[1], ...(artifact ? { artifact } : {}) });
39
+ const resolved = resolveTaskRef(taskRef);
40
+ if (!resolved.ok) { fail(resolved.message); return; }
41
+ let result;
42
+ let humanOverride: unknown = null;
43
+ try {
44
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, () => {
45
+ let current = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
46
+ if (current.status === 'pass' || !overrideTicket) return current;
47
+ if (!overrideTarget || !overrideScope) { fail('override ticket requires target and scope'); return current; }
48
+ const failureProducer = current.status === 'blocked' || current.status === 'fail'
49
+ ? 'verification-engine'
50
+ : 'task-verify';
51
+ const failureCode = current.status === 'blocked'
52
+ ? 'CHECK_BLOCKED'
53
+ : current.status === 'fail'
54
+ ? 'CHECK_FAILED'
55
+ : current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
56
+ const consumed = consumeHumanOverride({
57
+ taskRef, ticketId: overrideTicket,
58
+ failureId: failureId(failureProducer, failureCode),
59
+ target: overrideTarget, scope: overrideScope
60
+ }, {
61
+ effectExecutor: (capability) => {
62
+ const retried = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
63
+ current = retried;
64
+ return retried.status !== 'pass'
65
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
66
+ : null;
67
+ }
68
+ });
69
+ humanOverride = consumed;
70
+ return current;
71
+ });
72
+ } catch (error) {
73
+ if (!(error instanceof TaskExecutionLockError)) throw error;
74
+ fail(`${error.code}: ${error.message}`);
75
+ return;
76
+ }
77
+ if (humanOverride) result = { ...result, humanOverride } as typeof result;
29
78
  process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
30
79
  process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
31
80
  }
@@ -1,10 +1,14 @@
1
1
  import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
2
2
  import type { WorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
3
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
4
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
5
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
3
6
 
4
7
  const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-status|list> [intent flags] [--dry-run]\n`;
5
8
  const FLAGS: Record<string, string> = {
6
9
  '--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
7
10
  '--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
11
+ , '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
8
12
  };
9
13
 
10
14
  function usageFailure(message: string): void {
@@ -38,13 +42,50 @@ function taskWarning(args: string[] = []): void {
38
42
  };
39
43
  const optional = kind === 'list' ? ['status'] : [];
40
44
  const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...optional]);
45
+ allowed.add('overrideTicket'); allowed.add('overrideTarget'); allowed.add('overrideScope');
41
46
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
42
47
  const missing = required[kind]!.find((key) => values[key] === undefined);
43
48
  if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
44
49
  if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
45
- const result = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
46
- process.stdout.write(`${JSON.stringify(result)}\n`);
47
- if (result.status === 'failed') process.exitCode = 1;
50
+ const dryRunConflict = overrideDryRunConflict(values);
51
+ if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
52
+ const resolved = resolveTaskRef(taskRef);
53
+ if (!resolved.ok) { usageFailure(resolved.message); return; }
54
+ let result;
55
+ let humanOverride: unknown = null;
56
+ try {
57
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, () => {
58
+ let current = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
59
+ if (current.status !== 'failed' || !values.overrideTicket) return current;
60
+ if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
61
+ const consumed = consumeHumanOverride({
62
+ taskRef, ticketId: String(values.overrideTicket),
63
+ failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
64
+ target: String(values.overrideTarget), scope: String(values.overrideScope)
65
+ }, {
66
+ effectExecutor: (capability) => {
67
+ const retried = applyWorkflowWarningIntent(values as WorkflowWarningIntent, { manualOverride: capability });
68
+ current = retried;
69
+ return retried.status === 'failed' || retried.status === 'planned'
70
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no workflow warning effect was committed' : `${retried.error?.code ?? 'WARNING_FAILED'}: ${retried.error?.message ?? 'manual warning effect failed'}` }
71
+ : null;
72
+ }
73
+ });
74
+ humanOverride = consumed;
75
+ return current;
76
+ });
77
+ } catch (error) {
78
+ if (!(error instanceof TaskExecutionLockError)) throw error;
79
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
84
+ const overrideFailed = Boolean(
85
+ humanOverride && typeof humanOverride === 'object' &&
86
+ (humanOverride as { status?: unknown }).status === 'failed'
87
+ );
88
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
48
89
  }
49
90
 
50
91
  export { taskWarning };
@@ -20,6 +20,8 @@ type ContextOptions = {
20
20
  platformType?: string;
21
21
  };
22
22
 
23
+ const CURRENT_USER_QUERY = 'query { viewer { login } }';
24
+
23
25
  function findRepoRoot(cwd: string): string {
24
26
  try {
25
27
  return execFileSync('git', ['rev-parse', '--show-toplevel'], {
@@ -105,16 +107,17 @@ function resolveGitHubContext(options: ContextOptions & { cwd: string }): Platfo
105
107
  error: { code: 'UPSTREAM_UNRESOLVED', message: 'Unable to resolve the upstream repository', retryable: false }
106
108
  });
107
109
  }
108
- const user = client.json(['api', 'user'], { cwd });
110
+ const user = client.json(['api', 'graphql', '-f', `query=${CURRENT_USER_QUERY}`], { cwd });
109
111
  if (!user.ok) return failure(user.error, upstream);
110
- const userValue = user.value as { login?: string } | null;
112
+ const userValue = user.value as { data?: { viewer?: { login?: string } } } | null;
113
+ const currentUser = userValue?.data?.viewer?.login || null;
111
114
  const permissions = client.json(['api', `repos/${upstream}`], { cwd });
112
115
  if (!permissions.ok) return failure(permissions.error, upstream);
113
116
  const permissionValue = permissions.value as { permissions?: Record<string, boolean> } | null;
114
117
  const values = permissionValue?.permissions || {};
115
118
  const capabilities = {
116
- authenticated: Boolean(userValue?.login),
117
- comment: Boolean(userValue?.login),
119
+ authenticated: Boolean(currentUser),
120
+ comment: Boolean(currentUser),
118
121
  triage: Boolean(values.triage || values.push || values.admin),
119
122
  push: Boolean(values.push || values.admin),
120
123
  admin: Boolean(values.admin)
@@ -122,7 +125,7 @@ function resolveGitHubContext(options: ContextOptions & { cwd: string }): Platfo
122
125
  const status = Object.values(capabilities).every(Boolean) ? 'no-op' : 'degraded';
123
126
  return platformResult(status, {
124
127
  changed: false,
125
- platform: { type: platform, repository: upstream, currentUser: userValue?.login || null },
128
+ platform: { type: platform, repository: upstream, currentUser },
126
129
  resource: { kind: 'repository', number: null },
127
130
  capabilities,
128
131
  operations: [{ name: 'resolve', status: 'no-op', reasonCode: null }]