@fitlab-ai/agent-infra 0.9.7 → 0.9.9
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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +3 -4
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +161 -288
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/pr-summary.js +53 -13
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +145 -5
- package/dist/lib/sandbox/control/lifecycle.js +395 -73
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +279 -17
- package/dist/lib/task/events.js +9 -5
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +199 -719
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +138 -59
- package/dist/lib/task/verification.js +15 -2
- package/dist/lib/task/workflow-warning-intents.js +54 -3
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/git/workflow.ts +11 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +3 -4
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +32 -107
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/pr-summary.ts +65 -14
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +172 -6
- package/lib/sandbox/control/lifecycle.ts +465 -77
- package/lib/sandbox/control/protocol.ts +85 -9
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +73 -43
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +304 -19
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +719 -0
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +239 -922
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +145 -64
- package/lib/task/verification.ts +18 -3
- package/lib/task/workflow-warning-intents.ts +61 -4
- package/lib/task/write.ts +4 -1
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -9,6 +9,9 @@ export type ProcessIdentity = Readonly<{
|
|
|
9
9
|
startTime: ProcessStartTime;
|
|
10
10
|
}>;
|
|
11
11
|
|
|
12
|
+
export type ProcessIdentityState = 'alive' | 'dead' | 'unknown';
|
|
13
|
+
export type ProcessIdentityProbe = (identity: ProcessIdentity) => ProcessIdentityState;
|
|
14
|
+
|
|
12
15
|
export type PidRecord = ProcessIdentity & {
|
|
13
16
|
version: 2;
|
|
14
17
|
};
|
|
@@ -127,6 +130,50 @@ export function getProcessStartTime(
|
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
export function getProcessIdentityState(
|
|
134
|
+
identity: ProcessIdentity,
|
|
135
|
+
platform: NodeJS.Platform = process.platform
|
|
136
|
+
): ProcessIdentityState {
|
|
137
|
+
if (!Number.isInteger(identity.pid) || identity.pid <= 0 || !Number.isSafeInteger(identity.startTime)) {
|
|
138
|
+
return 'dead';
|
|
139
|
+
}
|
|
140
|
+
if (platform === 'linux') {
|
|
141
|
+
try {
|
|
142
|
+
const stat = parseLinuxProcessStat(fs.readFileSync(`/proc/${identity.pid}/stat`, 'utf8'));
|
|
143
|
+
if (stat === null) return 'unknown';
|
|
144
|
+
if (stat.state === 'Z') return 'dead';
|
|
145
|
+
return stat.startTime === identity.startTime ? 'alive' : 'dead';
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return (error as NodeJS.ErrnoException).code === 'ENOENT' ? 'dead' : 'unknown';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
process.kill(identity.pid, 0);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
154
|
+
if (code === 'ESRCH') return 'dead';
|
|
155
|
+
if (code !== 'EPERM') return 'unknown';
|
|
156
|
+
}
|
|
157
|
+
const query = buildProcessStartTimeQuery(identity.pid, platform);
|
|
158
|
+
if (query === null) return 'unknown';
|
|
159
|
+
try {
|
|
160
|
+
const output = execFileSync(query.command, query.args, {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
|
|
163
|
+
}).trim();
|
|
164
|
+
if (output.length === 0) return 'unknown';
|
|
165
|
+
const startTime = platform === 'win32'
|
|
166
|
+
? new Date(output).getTime()
|
|
167
|
+
: platform === 'darwin'
|
|
168
|
+
? parseDarwinStartTime(output)
|
|
169
|
+
: null;
|
|
170
|
+
if (startTime === null || Number.isNaN(startTime)) return 'unknown';
|
|
171
|
+
return startTime === identity.startTime ? 'alive' : 'dead';
|
|
172
|
+
} catch {
|
|
173
|
+
return 'unknown';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
130
177
|
function parsePidRecord(
|
|
131
178
|
raw: string
|
|
132
179
|
):
|
|
@@ -234,5 +281,5 @@ export function removePidRecordIfMatches(pidFile: string, expected: PidRecord):
|
|
|
234
281
|
}
|
|
235
282
|
|
|
236
283
|
export function processIdentityMatches(identity: ProcessIdentity): boolean {
|
|
237
|
-
return
|
|
284
|
+
return getProcessIdentityState(identity) === 'alive';
|
|
238
285
|
}
|
|
@@ -14,6 +14,7 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
14
14
|
import type { TaskExecutionLockOptions } from './task-execution-lock.ts';
|
|
15
15
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
16
16
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
17
|
+
import { allowsManualOverride } from './guard-override.ts';
|
|
17
18
|
|
|
18
19
|
type PrReviewVerdict = 'approved' | 'changes-requested' | 'commented';
|
|
19
20
|
type PrReviewOutcome = 'aborted' | 'superseded';
|
|
@@ -95,7 +96,7 @@ export type PrReviewInspectionResult = {
|
|
|
95
96
|
error: { code: string; message: string } | null;
|
|
96
97
|
};
|
|
97
98
|
|
|
98
|
-
type ActivityIntentOptions = TaskWriteOptions & { lockOptions?: TaskExecutionLockOptions };
|
|
99
|
+
type ActivityIntentOptions = TaskWriteOptions & { lockOptions?: TaskExecutionLockOptions; lockAlreadyHeld?: boolean };
|
|
99
100
|
type ArtifactRecord = { identity: ArtifactIdentity; head: string; status: PrReviewArtifactStatus };
|
|
100
101
|
type RoundState = ArtifactRecord & {
|
|
101
102
|
startedAgent: string | null;
|
|
@@ -397,9 +398,10 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
|
|
|
397
398
|
if ('code' in validated) return failed(intent, validated.code, validated.message);
|
|
398
399
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
399
400
|
if (!resolved.ok) return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
400
|
-
if (resolved.state !== 'active') {
|
|
401
|
+
if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'activity-intent', 'TASK_STATE_MISMATCH')) {
|
|
401
402
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
402
403
|
}
|
|
404
|
+
if (options.lockAlreadyHeld) return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
403
405
|
try {
|
|
404
406
|
return withTaskExecutionLock(
|
|
405
407
|
resolved.repoRoot,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
preflightCodexLifecycleEvidence,
|
|
5
3
|
resolveCodexSpawnedChild,
|
|
@@ -7,16 +5,22 @@ import {
|
|
|
7
5
|
resolveCodexThread
|
|
8
6
|
} from '../agent-clients/adapters/codex-lifecycle/app-server.ts';
|
|
9
7
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
10
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createCodexCapabilityStore,
|
|
10
|
+
isCodexCapabilityProvenanceDetail
|
|
11
|
+
} from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
12
|
+
import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
11
13
|
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
12
14
|
import type { LifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
13
15
|
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
16
|
+
import { verifyCodexSandboxControllerContext as verifyControllerContextFile } from '../agent-clients/adapters/codex-lifecycle/controller-context.ts';
|
|
14
17
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
15
18
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
16
19
|
import {
|
|
17
20
|
activateMatchingOrchestrationDelegation,
|
|
18
21
|
hasActivatableOrchestrationDelegation,
|
|
19
22
|
hasSealableOrchestrationDelegation,
|
|
23
|
+
OrchestrationStateError,
|
|
20
24
|
pauseMatchingOrchestrationDelegation,
|
|
21
25
|
prepareOrchestrationDelegation,
|
|
22
26
|
reconcileMatchingOrchestrationDelegation,
|
|
@@ -52,10 +56,27 @@ function coreOptions(options: CodexBridgeOptions): OrchestrationOptions {
|
|
|
52
56
|
return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
function bridgeFailure(code: string, message: string): OrchestrationResult {
|
|
59
|
+
function bridgeFailure(code: string, message: string, detail?: CodexCapabilityProvenanceDetail): OrchestrationResult {
|
|
56
60
|
return {
|
|
57
61
|
status: 'failed', changed: false, taskId: null, run: null, next: null,
|
|
58
|
-
error: { code, message }
|
|
62
|
+
error: { code, message, ...(detail ? { detail } : {}) }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function capabilityFailure(error: unknown): NonNullable<OrchestrationResult['error']> {
|
|
67
|
+
const detailValue = error instanceof Error
|
|
68
|
+
? (error as Error & { detail?: unknown }).detail
|
|
69
|
+
: undefined;
|
|
70
|
+
return {
|
|
71
|
+
code: error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
72
|
+
? error.name
|
|
73
|
+
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
|
|
74
|
+
message: error instanceof Error ? error.message : String(error),
|
|
75
|
+
...(error instanceof Error
|
|
76
|
+
&& error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
|
|
77
|
+
&& isCodexCapabilityProvenanceDetail(detailValue)
|
|
78
|
+
? { detail: detailValue }
|
|
79
|
+
: {})
|
|
59
80
|
};
|
|
60
81
|
}
|
|
61
82
|
|
|
@@ -71,11 +92,54 @@ function requiredStore(options: CodexBridgeOptions): LifecycleStore {
|
|
|
71
92
|
return options.store;
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
function controllerContext(
|
|
95
|
+
function controllerContext(
|
|
96
|
+
repoRoot: string,
|
|
97
|
+
brokerController: Readonly<{ instanceDigest: string; controlGeneration: string }> | null
|
|
98
|
+
) {
|
|
75
99
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
if (!contextPath) return null;
|
|
101
|
+
if (brokerController) {
|
|
102
|
+
return verifyControllerContextFile(contextPath, {
|
|
103
|
+
repoRoot,
|
|
104
|
+
generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return verifyCodexSandboxControllerContext(contextPath, { repoRoot });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function brokerBindingConflictsWithContext(
|
|
111
|
+
repoRoot: string,
|
|
112
|
+
brokerController: Readonly<{ instanceDigest: string; controlGeneration: string }> | null
|
|
113
|
+
): boolean {
|
|
114
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
115
|
+
if (!contextPath || !brokerController) return false;
|
|
116
|
+
const context = verifyControllerContextFile(contextPath, {
|
|
117
|
+
repoRoot,
|
|
118
|
+
generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
|
|
119
|
+
});
|
|
120
|
+
return context.controllerInstanceDigest !== brokerController.instanceDigest
|
|
121
|
+
|| context.controlGeneration !== brokerController.controlGeneration;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function brokerControllerBinding(): Readonly<{ instanceDigest: string; controlGeneration: string }> | null {
|
|
125
|
+
const raw = process.env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING;
|
|
126
|
+
if (!raw) return null;
|
|
127
|
+
let value: unknown;
|
|
128
|
+
try {
|
|
129
|
+
value = JSON.parse(raw);
|
|
130
|
+
} catch {
|
|
131
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
132
|
+
}
|
|
133
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
134
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
135
|
+
}
|
|
136
|
+
const binding = value as Record<string, unknown>;
|
|
137
|
+
if (Object.keys(binding).sort().join(',') !== 'controlGeneration,instanceDigest'
|
|
138
|
+
|| typeof binding.instanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.instanceDigest)
|
|
139
|
+
|| typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0) {
|
|
140
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
141
|
+
}
|
|
142
|
+
return binding as { instanceDigest: string; controlGeneration: string };
|
|
79
143
|
}
|
|
80
144
|
|
|
81
145
|
async function prepareCodexOrchestrationDelegation(
|
|
@@ -101,36 +165,101 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
101
165
|
);
|
|
102
166
|
}
|
|
103
167
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
104
|
-
const
|
|
168
|
+
const brokerController = brokerControllerBinding();
|
|
169
|
+
if (brokerBindingConflictsWithContext(repoRoot, brokerController)) {
|
|
170
|
+
return bridgeFailure(
|
|
171
|
+
'CODEX_SANDBOX_CONTROLLER_BINDING_MISMATCH',
|
|
172
|
+
'broker and local controller bindings do not match'
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const localController = controllerContext(repoRoot, brokerController);
|
|
176
|
+
if (localController && brokerController
|
|
177
|
+
&& (localController.controllerInstanceDigest !== brokerController.instanceDigest
|
|
178
|
+
|| localController.controlGeneration !== brokerController.controlGeneration)) {
|
|
179
|
+
return bridgeFailure(
|
|
180
|
+
'CODEX_SANDBOX_CONTROLLER_BINDING_MISMATCH',
|
|
181
|
+
'broker and local controller bindings do not match'
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const controller = brokerController ?? (localController ? {
|
|
185
|
+
instanceDigest: localController.controllerInstanceDigest,
|
|
186
|
+
controlGeneration: localController.controlGeneration
|
|
187
|
+
} : null);
|
|
105
188
|
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
106
|
-
const
|
|
189
|
+
const attested = capabilityStore.inspect(input.capabilityToken);
|
|
190
|
+
const lifecycleProvenance = {
|
|
191
|
+
...attested.buildIdentity,
|
|
192
|
+
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
193
|
+
...preflight.hookProvenance,
|
|
194
|
+
capabilitySessionId: attested.sessionId!,
|
|
195
|
+
capabilityTurnId: attested.turnId!,
|
|
196
|
+
capabilityToolUseId: attested.toolUseId!,
|
|
197
|
+
controllerInstanceDigest: controller?.instanceDigest ?? null,
|
|
198
|
+
controlGeneration: controller?.controlGeneration ?? null
|
|
199
|
+
};
|
|
200
|
+
const capabilityExpected = {
|
|
107
201
|
taskId: resolved.taskId,
|
|
108
202
|
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
109
203
|
buildIdentity,
|
|
110
204
|
...(controller ? { controller: {
|
|
111
|
-
instanceDigest: controller.
|
|
205
|
+
instanceDigest: controller.instanceDigest,
|
|
112
206
|
controlGeneration: controller.controlGeneration
|
|
113
207
|
} } : {})
|
|
114
|
-
}
|
|
208
|
+
};
|
|
115
209
|
return prepareOrchestrationDelegation(taskRef, {
|
|
116
210
|
...input,
|
|
117
|
-
lifecycleProvenance
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
211
|
+
lifecycleProvenance
|
|
212
|
+
}, {
|
|
213
|
+
...coreOptions(options),
|
|
214
|
+
validateLifecycleCapability: () => {
|
|
215
|
+
try {
|
|
216
|
+
const validated = capabilityStore.validate(input.capabilityToken!, capabilityExpected);
|
|
217
|
+
if (validated.sessionId !== attested.sessionId
|
|
218
|
+
|| validated.turnId !== attested.turnId
|
|
219
|
+
|| validated.toolUseId !== attested.toolUseId) {
|
|
220
|
+
return {
|
|
221
|
+
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
222
|
+
message: 'capability identity changed during validation'
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return capabilityFailure(error);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
consumeLifecycleCapability: () => {
|
|
231
|
+
try {
|
|
232
|
+
const consumed = capabilityStore.consume(input.capabilityToken!, capabilityExpected);
|
|
233
|
+
if (consumed.sessionId !== attested.sessionId
|
|
234
|
+
|| consumed.turnId !== attested.turnId
|
|
235
|
+
|| consumed.toolUseId !== attested.toolUseId) {
|
|
236
|
+
return {
|
|
237
|
+
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
238
|
+
message: 'capability identity changed during consumption'
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return capabilityFailure(error);
|
|
244
|
+
}
|
|
126
245
|
}
|
|
127
|
-
}
|
|
246
|
+
});
|
|
128
247
|
} catch (error) {
|
|
248
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
249
|
+
const detailValue = error instanceof Error
|
|
250
|
+
? (error as Error & { detail?: unknown }).detail
|
|
251
|
+
: undefined;
|
|
252
|
+
const detail = error instanceof Error
|
|
253
|
+
&& error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
|
|
254
|
+
&& isCodexCapabilityProvenanceDetail(detailValue)
|
|
255
|
+
? detailValue
|
|
256
|
+
: undefined;
|
|
129
257
|
return bridgeFailure(
|
|
130
258
|
error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
131
259
|
? error.name
|
|
132
260
|
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
|
|
133
|
-
error instanceof Error ? error.message : String(error)
|
|
261
|
+
error instanceof Error ? error.message : String(error),
|
|
262
|
+
detail
|
|
134
263
|
);
|
|
135
264
|
}
|
|
136
265
|
}
|
|
@@ -161,14 +290,8 @@ async function activateCodexOrchestrationDelegation(
|
|
|
161
290
|
});
|
|
162
291
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
163
292
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
164
|
-
const contextTaskId = contextPath
|
|
165
|
-
? (JSON.parse(fs.readFileSync(contextPath, 'utf8')) as { taskId?: unknown }).taskId
|
|
166
|
-
: null;
|
|
167
|
-
if (contextPath && typeof contextTaskId !== 'string') {
|
|
168
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
169
|
-
}
|
|
170
293
|
const controller = contextPath
|
|
171
|
-
? verifyCodexSandboxControllerContext(contextPath,
|
|
294
|
+
? verifyCodexSandboxControllerContext(contextPath, { repoRoot })
|
|
172
295
|
: null;
|
|
173
296
|
return activateMatchingOrchestrationDelegation('codex', {
|
|
174
297
|
nativeAgent: evidence.nativeAgent,
|
|
@@ -196,6 +319,7 @@ async function activateCodexOrchestrationDelegation(
|
|
|
196
319
|
}
|
|
197
320
|
}, coreOptions(options));
|
|
198
321
|
} catch (error) {
|
|
322
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
199
323
|
return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
|
|
200
324
|
}
|
|
201
325
|
}
|
|
@@ -223,6 +347,7 @@ async function activateCodexSpawnDelegation(
|
|
|
223
347
|
resolveThread: async () => resolved
|
|
224
348
|
});
|
|
225
349
|
} catch (error) {
|
|
350
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
226
351
|
return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
|
|
227
352
|
}
|
|
228
353
|
}
|
|
@@ -238,7 +363,11 @@ async function sealCodexOrchestrationDelegation(
|
|
|
238
363
|
const store = requiredStore(options);
|
|
239
364
|
const existing = store.read(childThreadId);
|
|
240
365
|
if (!existing.consumer) {
|
|
241
|
-
|
|
366
|
+
const stopTurnId = existing.state.stop?.turnId;
|
|
367
|
+
if (!stopTurnId) {
|
|
368
|
+
return pauseBridge('ORCHESTRATION_CODEX_STOP_EVIDENCE_INVALID', 'Codex lifecycle stop hook is not available', options);
|
|
369
|
+
}
|
|
370
|
+
store.apply(await (options.resolveTerminal ?? resolveCodexTerminal)(childThreadId, stopTurnId));
|
|
242
371
|
}
|
|
243
372
|
const record = store.read(childThreadId);
|
|
244
373
|
const evidence = record.state.stopEvidence;
|
|
@@ -259,6 +388,7 @@ async function sealCodexOrchestrationDelegation(
|
|
|
259
388
|
coreOptions(options)
|
|
260
389
|
);
|
|
261
390
|
} catch (error) {
|
|
391
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
262
392
|
return pauseBridge('ORCHESTRATION_CODEX_STOP_FAILED', error instanceof Error ? error.message : String(error), options);
|
|
263
393
|
}
|
|
264
394
|
}
|
|
@@ -280,7 +410,10 @@ async function sealCodexParentDelegation(
|
|
|
280
410
|
const start = active.state.startEvidence!;
|
|
281
411
|
const child = active.state.child!;
|
|
282
412
|
if (!active.state.terminal) {
|
|
283
|
-
|
|
413
|
+
const resolveTerminal = options.resolveTerminal ?? resolveCodexTerminal;
|
|
414
|
+
store.apply(active.state.stop
|
|
415
|
+
? await resolveTerminal(start.childThreadId, active.state.stop.turnId)
|
|
416
|
+
: await resolveTerminal(start.childThreadId));
|
|
284
417
|
}
|
|
285
418
|
if (!store.read(start.childThreadId).state.stop) {
|
|
286
419
|
store.apply({
|
|
@@ -294,6 +427,7 @@ async function sealCodexParentDelegation(
|
|
|
294
427
|
}
|
|
295
428
|
return sealCodexOrchestrationDelegation(start.childThreadId, options);
|
|
296
429
|
} catch (error) {
|
|
430
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
297
431
|
if (error instanceof Error && error.message === 'CODEX_TURN_NOT_TERMINAL') {
|
|
298
432
|
return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'The matching Codex child has not completed');
|
|
299
433
|
}
|
|
@@ -9,6 +9,8 @@ import { parseTaskFrontmatter, extractTitle, type Frontmatter } from '../frontma
|
|
|
9
9
|
import { loadShortIdByTaskId } from '../short-id.ts';
|
|
10
10
|
import { getOpenWorkflowWarnings, formatWorkflowWarningSummary, type WorkflowWarning } from '../workflow-warnings.ts';
|
|
11
11
|
import { parseActivityLog, pairEntries } from '../activity-log.ts';
|
|
12
|
+
import { readRun, type OrchestrationRun } from '../orchestration.ts';
|
|
13
|
+
import type { DelegationReceipt } from '../delegation-receipts.ts';
|
|
12
14
|
import { statusCard, type DisplayMessage } from '../../server/display.ts';
|
|
13
15
|
|
|
14
16
|
const USAGE = `Usage: ai task status [<N | TASK-id> | --task <ref> | -t <ref>]
|
|
@@ -219,6 +221,19 @@ type RuntimeInfo = {
|
|
|
219
221
|
log: string;
|
|
220
222
|
};
|
|
221
223
|
|
|
224
|
+
type PendingDelegationInfo = Readonly<Pick<
|
|
225
|
+
DelegationReceipt,
|
|
226
|
+
'id' | 'role' | 'stage' | 'round' | 'artifact' | 'client' | 'status'
|
|
227
|
+
>>;
|
|
228
|
+
|
|
229
|
+
type OrchestrationInfo = Readonly<{
|
|
230
|
+
status: 'absent' | OrchestrationRun['status'];
|
|
231
|
+
runId: string;
|
|
232
|
+
nextStage: string;
|
|
233
|
+
pause: Readonly<{ code: string; message: string; recoverable: boolean }> | null;
|
|
234
|
+
pendingDelegation: PendingDelegationInfo | null;
|
|
235
|
+
}>;
|
|
236
|
+
|
|
222
237
|
type ManagedRunRecord = {
|
|
223
238
|
run_id: string;
|
|
224
239
|
engine: string;
|
|
@@ -323,6 +338,39 @@ function collectRuntime(taskDir: string, workflow: WorkflowInfo, run: Runner): R
|
|
|
323
338
|
};
|
|
324
339
|
}
|
|
325
340
|
|
|
341
|
+
function pendingDelegationInfo(receipt: DelegationReceipt | null): PendingDelegationInfo | null {
|
|
342
|
+
if (!receipt) return null;
|
|
343
|
+
return {
|
|
344
|
+
id: receipt.id,
|
|
345
|
+
role: receipt.role,
|
|
346
|
+
stage: receipt.stage,
|
|
347
|
+
round: receipt.round,
|
|
348
|
+
artifact: receipt.artifact,
|
|
349
|
+
client: receipt.client,
|
|
350
|
+
status: receipt.status
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function collectOrchestration(taskDir: string): OrchestrationInfo {
|
|
355
|
+
const run = readRun(taskDir);
|
|
356
|
+
if (!run) {
|
|
357
|
+
return {
|
|
358
|
+
status: 'absent',
|
|
359
|
+
runId: DASH,
|
|
360
|
+
nextStage: DASH,
|
|
361
|
+
pause: null,
|
|
362
|
+
pendingDelegation: null
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
status: run.status,
|
|
367
|
+
runId: run.runId,
|
|
368
|
+
nextStage: run.nextStage ?? DASH,
|
|
369
|
+
pause: run.pause,
|
|
370
|
+
pendingDelegation: pendingDelegationInfo(run.pendingDelegation)
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
326
374
|
type StatusModel = {
|
|
327
375
|
taskId: string;
|
|
328
376
|
shortId: string;
|
|
@@ -332,6 +380,7 @@ type StatusModel = {
|
|
|
332
380
|
artifacts: { count: number; groups: { stage: string; files: string[] }[] };
|
|
333
381
|
workflow: WorkflowInfo;
|
|
334
382
|
runtime: RuntimeInfo;
|
|
383
|
+
orchestration: OrchestrationInfo;
|
|
335
384
|
git: GitInfo;
|
|
336
385
|
};
|
|
337
386
|
|
|
@@ -380,6 +429,26 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
380
429
|
lines.push(...renderPairs(model.artifacts.groups.map((group) => [group.stage, group.files.join(', ')])));
|
|
381
430
|
}
|
|
382
431
|
|
|
432
|
+
lines.push(
|
|
433
|
+
'',
|
|
434
|
+
'Orchestration',
|
|
435
|
+
...renderPairs([
|
|
436
|
+
['status', model.orchestration.status],
|
|
437
|
+
['run_id', model.orchestration.runId],
|
|
438
|
+
['next_stage', model.orchestration.nextStage],
|
|
439
|
+
['pause_code', model.orchestration.pause?.code ?? DASH],
|
|
440
|
+
['pause_message', model.orchestration.pause?.message ?? DASH],
|
|
441
|
+
['recoverable', model.orchestration.pause ? String(model.orchestration.pause.recoverable) : DASH],
|
|
442
|
+
['pending_id', model.orchestration.pendingDelegation?.id ?? DASH],
|
|
443
|
+
['pending_role', model.orchestration.pendingDelegation?.role ?? DASH],
|
|
444
|
+
['pending_stage', model.orchestration.pendingDelegation?.stage ?? DASH],
|
|
445
|
+
['pending_round', model.orchestration.pendingDelegation ? String(model.orchestration.pendingDelegation.round) : DASH],
|
|
446
|
+
['pending_artifact', model.orchestration.pendingDelegation?.artifact ?? DASH],
|
|
447
|
+
['pending_client', model.orchestration.pendingDelegation?.client ?? DASH],
|
|
448
|
+
['pending_status', model.orchestration.pendingDelegation?.status ?? DASH]
|
|
449
|
+
])
|
|
450
|
+
);
|
|
451
|
+
|
|
383
452
|
lines.push(
|
|
384
453
|
'',
|
|
385
454
|
'Workflow',
|
|
@@ -425,6 +494,9 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
425
494
|
}
|
|
426
495
|
|
|
427
496
|
function modelTone(model: StatusModel): 'info' | 'success' | 'warning' | 'danger' | 'running' {
|
|
497
|
+
if (model.orchestration.status === 'paused') return 'warning';
|
|
498
|
+
if (model.orchestration.status === 'running') return 'running';
|
|
499
|
+
if (model.orchestration.status === 'completed') return 'success';
|
|
428
500
|
if (model.workflow.state === 'in-progress') return model.workflow.stale === 'yes' ? 'warning' : 'running';
|
|
429
501
|
if (model.metadata.some(([key, value]) => key === 'status' && value === 'completed')) return 'success';
|
|
430
502
|
if (model.metadata.some(([key, value]) => key === 'status' && (value === 'blocked' || value === 'cancelled'))) {
|
|
@@ -449,6 +521,7 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
|
449
521
|
artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
|
|
450
522
|
workflow,
|
|
451
523
|
runtime: collectRuntime(input.taskDir, workflow, run),
|
|
524
|
+
orchestration: collectOrchestration(input.taskDir),
|
|
452
525
|
git: collectGit(fm.branch ?? '', run)
|
|
453
526
|
};
|
|
454
527
|
}
|
|
@@ -483,6 +556,9 @@ function statusModelToDisplay(model: StatusModel): DisplayMessage {
|
|
|
483
556
|
`Task ${model.taskId} (${model.shortId})`,
|
|
484
557
|
modelTone(model),
|
|
485
558
|
[
|
|
559
|
+
['orchestration', model.orchestration.status],
|
|
560
|
+
['run', model.orchestration.runId],
|
|
561
|
+
['next', model.orchestration.nextStage],
|
|
486
562
|
['workflow', model.workflow.state],
|
|
487
563
|
['step', model.workflow.step],
|
|
488
564
|
['runtime', model.runtime.status],
|
|
@@ -530,8 +606,9 @@ export {
|
|
|
530
606
|
collectGit,
|
|
531
607
|
collectWorkflow,
|
|
532
608
|
collectRuntime,
|
|
609
|
+
collectOrchestration,
|
|
533
610
|
renderStatus,
|
|
534
611
|
statusModelToDisplay,
|
|
535
612
|
METADATA_KEYS
|
|
536
613
|
};
|
|
537
|
-
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel, BuildStatusModelInput };
|
|
614
|
+
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, OrchestrationInfo, PendingDelegationInfo, StatusModel, BuildStatusModelInput };
|