@fitlab-ai/agent-infra 0.11.13 → 0.11.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.
- package/dist/bin/internal-cli.js +2 -7
- package/dist/lib/agent-clients/adapter.js +0 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +5 -9
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +1 -5
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -3
- package/dist/lib/agent-clients/adapters/codex.js +0 -2
- package/dist/lib/internal/cli-route-inventory.js +0 -1
- package/dist/lib/internal/task-finalization.js +6 -4
- package/dist/lib/internal/task-lifecycle.js +3 -1
- package/dist/lib/internal/task-operation-registry.js +1 -3
- package/dist/lib/internal/task-validate.js +14 -124
- package/dist/lib/platform/release-notes.js +2 -2
- package/dist/lib/sandbox/commands/control-status.js +0 -1
- package/dist/lib/sandbox/commands/create.js +5 -4
- package/dist/lib/sandbox/config.js +12 -0
- package/dist/lib/sandbox/control/audit.js +1 -2
- package/dist/lib/sandbox/control/client.js +12 -47
- package/dist/lib/sandbox/control/executor.js +6 -10
- package/dist/lib/sandbox/control/host-recovery.js +89 -27
- package/dist/lib/sandbox/control/lifecycle.js +20 -16
- package/dist/lib/sandbox/control/protocol.js +8 -36
- package/dist/lib/sandbox/control/server.js +2 -21
- package/dist/lib/sandbox/environment.js +7 -8
- package/dist/lib/sandbox/mounts.js +71 -59
- package/dist/lib/sandbox/recovery.js +2 -3
- package/dist/lib/sandbox/removal.js +19 -4
- package/dist/lib/sandbox/workspace-view.js +41 -4
- package/dist/lib/task/control-authority.js +39 -17
- package/dist/lib/task/control-recovery.js +1 -2
- package/dist/lib/task/finalization.js +13 -2
- package/dist/lib/task/lifecycle.js +92 -33
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +0 -27
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +6 -12
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -7
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +1 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -4
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -3
- package/lib/agent-clients/adapters/codex.ts +0 -2
- package/lib/internal/cli-route-inventory.ts +0 -1
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +1 -3
- package/lib/internal/task-validate.ts +13 -127
- package/lib/platform/release-notes.ts +2 -2
- package/lib/sandbox/commands/control-status.ts +0 -1
- package/lib/sandbox/commands/create.ts +5 -4
- package/lib/sandbox/config.ts +13 -0
- package/lib/sandbox/control/audit.ts +1 -2
- package/lib/sandbox/control/client.ts +13 -61
- package/lib/sandbox/control/executor.ts +7 -13
- package/lib/sandbox/control/host-recovery.ts +94 -21
- package/lib/sandbox/control/lifecycle.ts +18 -16
- package/lib/sandbox/control/protocol.ts +9 -45
- package/lib/sandbox/control/server.ts +2 -17
- package/lib/sandbox/environment.ts +7 -8
- package/lib/sandbox/mounts.ts +92 -75
- package/lib/sandbox/recovery.ts +2 -3
- package/lib/sandbox/removal.ts +16 -4
- package/lib/sandbox/workspace-view.ts +40 -5
- package/lib/task/control-authority.ts +40 -26
- package/lib/task/control-recovery.ts +2 -3
- package/lib/task/finalization.ts +15 -2
- package/lib/task/lifecycle.ts +103 -31
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -4
- package/templates/.agents/rules/task-short-id.en.md +18 -12
- package/templates/.agents/rules/task-short-id.zh-CN.md +12 -7
- package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +0 -130
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +0 -127
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +0 -131
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-registration.js +0 -275
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js +0 -106
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +0 -345
- package/dist/lib/sandbox/control/agent-operation.js +0 -34
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +0 -158
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +0 -163
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +0 -151
- package/lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts +0 -352
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts +0 -109
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +0 -437
- package/lib/sandbox/control/agent-operation.ts +0 -49
|
@@ -23,28 +23,6 @@ import type {
|
|
|
23
23
|
SandboxTool,
|
|
24
24
|
SandboxToolContext
|
|
25
25
|
} from '../sandbox/tool-types.ts';
|
|
26
|
-
import type { SandboxControlManifest } from '../sandbox/control/protocol.ts';
|
|
27
|
-
|
|
28
|
-
type AgentClientSandboxControlOperation = Readonly<{
|
|
29
|
-
validate: (params: Readonly<{
|
|
30
|
-
operation: string;
|
|
31
|
-
payload: Record<string, unknown>;
|
|
32
|
-
manifest: SandboxControlManifest;
|
|
33
|
-
}>) => void;
|
|
34
|
-
execute: (params: Readonly<{
|
|
35
|
-
operation: string;
|
|
36
|
-
payload: Record<string, unknown>;
|
|
37
|
-
manifest: SandboxControlManifest;
|
|
38
|
-
manifestPath: string;
|
|
39
|
-
}>) => Promise<Readonly<{ exitCode: number; stdout: string; stderr: string }>>;
|
|
40
|
-
recover?: (params: Readonly<{
|
|
41
|
-
operation: string;
|
|
42
|
-
manifest: SandboxControlManifest;
|
|
43
|
-
manifestPath: string;
|
|
44
|
-
stdout: string | null;
|
|
45
|
-
}>) => Readonly<Record<string, unknown>>;
|
|
46
|
-
}>;
|
|
47
|
-
|
|
48
26
|
type AgentClientCapabilities = AgentClientCapabilityMap;
|
|
49
27
|
|
|
50
28
|
type AgentClientModelSelectionContext =
|
|
@@ -147,7 +125,6 @@ type AgentClientAdapter = Readonly<{
|
|
|
147
125
|
modelSelection: AgentClientModelSelectionContext;
|
|
148
126
|
delegationEvidence: AgentClientDelegationEvidence;
|
|
149
127
|
orchestrationAdapter?: AgentClientOrchestrationAdapter;
|
|
150
|
-
sandboxControlOperation?: AgentClientSandboxControlOperation;
|
|
151
128
|
project: AgentClientProjectDescriptor;
|
|
152
129
|
sandbox: AgentClientSandboxDescriptor;
|
|
153
130
|
}>;
|
|
@@ -728,9 +705,6 @@ function defineAgentClientAdapter(
|
|
|
728
705
|
...(candidate.orchestrationAdapter === undefined
|
|
729
706
|
? {}
|
|
730
707
|
: { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
|
|
731
|
-
...(candidate.sandboxControlOperation === undefined
|
|
732
|
-
? {}
|
|
733
|
-
: { sandboxControlOperation: Object.freeze({ ...candidate.sandboxControlOperation }) }),
|
|
734
708
|
project: Object.freeze({
|
|
735
709
|
ownedPathPrefixes: Object.freeze(paths),
|
|
736
710
|
...projectAssets,
|
|
@@ -753,7 +727,6 @@ export type {
|
|
|
753
727
|
AgentClientCapabilities,
|
|
754
728
|
AgentClientCustomCommandDescriptor,
|
|
755
729
|
AgentClientDelegationEvidence,
|
|
756
|
-
AgentClientSandboxControlOperation,
|
|
757
730
|
AgentClientLifecycleRecoveryRequest,
|
|
758
731
|
AgentClientLifecycleRecoveryResult,
|
|
759
732
|
AgentClientOrchestrationAdapter,
|
|
@@ -170,8 +170,7 @@ function validateCodexLifecycleHookConfig(value: unknown): void {
|
|
|
170
170
|
|
|
171
171
|
function parseCodexHooksList(
|
|
172
172
|
value: unknown,
|
|
173
|
-
repoRoot: string
|
|
174
|
-
expectedSource: 'direct-host' | 'isolated-user' = 'direct-host'
|
|
173
|
+
repoRoot: string
|
|
175
174
|
): readonly DiscoveredHook[] {
|
|
176
175
|
const root = object(value);
|
|
177
176
|
const entries = Array.isArray(root?.data) ? root.data : [];
|
|
@@ -203,10 +202,8 @@ function parseCodexHooksList(
|
|
|
203
202
|
&& hook.currentHash.length > 0
|
|
204
203
|
&& hook.sourcePathDigest.length === 64
|
|
205
204
|
&& hook.pluginId === null
|
|
206
|
-
&& (
|
|
207
|
-
|
|
208
|
-
: (hook.source === 'project' && hook.trustStatus === 'trusted')
|
|
209
|
-
|| hook.isManaged && hook.trustStatus === 'managed')
|
|
205
|
+
&& (hook.source === 'project' && hook.trustStatus === 'trusted'
|
|
206
|
+
|| hook.isManaged && hook.trustStatus === 'managed')
|
|
210
207
|
));
|
|
211
208
|
if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
|
|
212
209
|
return Object.freeze(hooks);
|
|
@@ -628,7 +625,7 @@ function validateStaticCodexPreflight(repoRoot: string): Readonly<{ cliVersion:
|
|
|
628
625
|
async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
|
|
629
626
|
discoveredHooks: readonly DiscoveredHook[];
|
|
630
627
|
hookProvenance: Readonly<{
|
|
631
|
-
hookSource: '
|
|
628
|
+
hookSource: 'managed' | 'project';
|
|
632
629
|
hookSourcePathDigest: string;
|
|
633
630
|
hookSourceHash: string;
|
|
634
631
|
}>;
|
|
@@ -641,8 +638,7 @@ async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
|
|
|
641
638
|
await transport.initialize();
|
|
642
639
|
discoveredHooks = parseCodexHooksList(
|
|
643
640
|
await transport.request('hooks/list', { cwds: [repoRoot] }),
|
|
644
|
-
repoRoot
|
|
645
|
-
process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT ? 'isolated-user' : 'direct-host'
|
|
641
|
+
repoRoot
|
|
646
642
|
);
|
|
647
643
|
} finally { transport.close(); }
|
|
648
644
|
const lifecycleHooks = LIFECYCLE_HOOKS.map(({ event, matcher, phase }) => discoveredHooks.filter((hook) =>
|
|
@@ -660,9 +656,7 @@ async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
|
|
|
660
656
|
throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
|
|
661
657
|
}
|
|
662
658
|
const source = [...sources][0];
|
|
663
|
-
const hookSource =
|
|
664
|
-
? 'isolated-user'
|
|
665
|
-
: source === 'managed' ? 'managed' : 'project';
|
|
659
|
+
const hookSource = source === 'managed' ? 'managed' : 'project';
|
|
666
660
|
return {
|
|
667
661
|
discoveredHooks,
|
|
668
662
|
hookProvenance: Object.freeze({
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
resolveCodexThread
|
|
10
10
|
} from './app-server.ts';
|
|
11
11
|
import { createCodexLifecycleStore } from './store.ts';
|
|
12
|
-
import { verifyCodexSandboxControllerContextWithWarnings } from './sandbox-controller.ts';
|
|
13
12
|
import type { CodexLifecycleEvent } from './evidence.ts';
|
|
14
13
|
import { resolveTaskRef } from '../../../task/resolve-ref.ts';
|
|
15
14
|
import { resolveTaskContext } from '../../../task/resolve-ref.ts';
|
|
@@ -103,10 +102,7 @@ function hookDefinitionHash(): string {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
function resolveLifecycleTaskContext() {
|
|
106
|
-
const
|
|
107
|
-
const taskId = controllerPath
|
|
108
|
-
? verifyCodexSandboxControllerContextWithWarnings(controllerPath, { repoRoot: process.cwd() }).context.taskId
|
|
109
|
-
: process.env.AGENT_INFRA_TASK_ID;
|
|
105
|
+
const taskId = process.env.AGENT_INFRA_TASK_ID;
|
|
110
106
|
const context = taskId
|
|
111
107
|
? resolveTaskRef(taskId, { repoRoot: process.cwd() })
|
|
112
108
|
: resolveTaskContext(undefined, { repoRoot: process.cwd() });
|
|
@@ -2,25 +2,18 @@
|
|
|
2
2
|
"executableFiles": [
|
|
3
3
|
"bin/internal-cli.ts",
|
|
4
4
|
"lib/agent-clients/adapters/codex-lifecycle/cli.ts",
|
|
5
|
-
"lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts",
|
|
6
5
|
"lib/internal/sandbox-control.ts",
|
|
7
6
|
"lib/internal/task-orchestration.ts",
|
|
8
7
|
"lib/agent-clients/adapters/codex-orchestration.ts",
|
|
9
8
|
"lib/task/delegation-receipts.ts",
|
|
10
9
|
"lib/task/orchestration.ts",
|
|
11
10
|
"lib/agent-clients/adapters/codex-lifecycle/build-identity.ts",
|
|
12
|
-
"lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts",
|
|
13
|
-
"lib/agent-clients/adapters/codex-lifecycle/controller-client.ts",
|
|
14
|
-
"lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts",
|
|
15
|
-
"lib/agent-clients/adapters/codex-lifecycle/controller-context.ts",
|
|
16
11
|
"lib/agent-clients/adapters/codex-lifecycle/app-server.ts",
|
|
17
12
|
"lib/agent-clients/adapters/codex-lifecycle/binding.ts",
|
|
18
13
|
"lib/agent-clients/adapters/codex-lifecycle/evidence.ts",
|
|
19
14
|
"lib/agent-clients/adapters/codex-lifecycle/store.ts",
|
|
20
15
|
"lib/agent-clients/adapters/codex-lifecycle/manifest-files.json",
|
|
21
|
-
"lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts",
|
|
22
16
|
"lib/sandbox/control/protocol.ts",
|
|
23
|
-
"lib/sandbox/control/agent-operation.ts",
|
|
24
17
|
"lib/sandbox/control/client.ts",
|
|
25
18
|
"lib/sandbox/control/executor.ts",
|
|
26
19
|
"lib/server/process-state.ts",
|
|
@@ -216,15 +216,11 @@ function hostEvidenceMatches(
|
|
|
216
216
|
&& host.spawnToolUseId === start.spawnToolUseId
|
|
217
217
|
&& Boolean(host.spawnObservedAt)
|
|
218
218
|
&& host.spawnObservedAt === record.spawnObservedAt
|
|
219
|
-
&& typeof host.controllerInstanceDigest === 'string'
|
|
220
|
-
&& typeof host.controlGeneration === 'string'
|
|
221
219
|
&& hostRevisionMatches(record, receipt, host);
|
|
222
220
|
}
|
|
223
221
|
|
|
224
222
|
function hostRevisionMatches(record: StoredCodexLifecycle, receipt: DelegationReceipt, host: CodexLifecycleActivationEvidence): boolean {
|
|
225
|
-
return
|
|
226
|
-
&& typeof host.controlGeneration === 'string'
|
|
227
|
-
&& host.startRevision >= 1
|
|
223
|
+
return host.startRevision >= 1
|
|
228
224
|
&& record.revision > host.startRevision
|
|
229
225
|
&& record.state.child?.childThreadId === receipt.childId
|
|
230
226
|
&& record.state.stop?.childThreadId === receipt.childId;
|
|
@@ -35,16 +35,13 @@ type CodexLifecycleActivationEvidence = Readonly<{
|
|
|
35
35
|
packageVersion?: string;
|
|
36
36
|
internalExecutableBuildHash?: string;
|
|
37
37
|
lifecycleContractHash?: string;
|
|
38
|
-
hookSource?: 'project' | 'managed'
|
|
38
|
+
hookSource?: 'project' | 'managed';
|
|
39
39
|
hookSourcePathDigest?: string;
|
|
40
40
|
hookSourceHash?: string;
|
|
41
41
|
capabilitySessionId?: string;
|
|
42
42
|
capabilityTurnId?: string;
|
|
43
|
-
capabilityToolUseId?: string;
|
|
44
43
|
spawnToolUseId?: string;
|
|
45
44
|
spawnObservedAt?: string;
|
|
46
|
-
controllerInstanceDigest?: string | null;
|
|
47
|
-
controlGeneration?: string | null;
|
|
48
45
|
}>;
|
|
49
46
|
|
|
50
47
|
type CodexAdapterState = Readonly<{
|
|
@@ -327,9 +327,7 @@ async function activateCodexOrchestrationDelegation(
|
|
|
327
327
|
capabilitySessionId: evidence.parentThreadId,
|
|
328
328
|
capabilityTurnId: evidence.parentTurnId,
|
|
329
329
|
spawnToolUseId: evidence.spawnToolUseId,
|
|
330
|
-
spawnObservedAt: record.spawnObservedAt ?? undefined
|
|
331
|
-
controllerInstanceDigest: null,
|
|
332
|
-
controlGeneration: null
|
|
330
|
+
spawnObservedAt: record.spawnObservedAt ?? undefined
|
|
333
331
|
}
|
|
334
332
|
}
|
|
335
333
|
}, coreOptions(options));
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from './codex-sandbox.ts';
|
|
7
7
|
import { parseCodexLifecycleBinding } from './codex-lifecycle/binding.ts';
|
|
8
8
|
import { isCodexDelegationReceiptEvidence, isCodexLifecycleActivationEvidence } from './codex-lifecycle/evidence.ts';
|
|
9
|
-
import { codexControllerOperation } from './codex-lifecycle/controller-operation.ts';
|
|
10
9
|
|
|
11
10
|
function codexCarrierError(code: string): Error {
|
|
12
11
|
const error = new Error(code);
|
|
@@ -80,7 +79,6 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
80
79
|
return recoverStartedLifecycleFromAdapter(...args);
|
|
81
80
|
}
|
|
82
81
|
},
|
|
83
|
-
sandboxControlOperation: codexControllerOperation,
|
|
84
82
|
project: {
|
|
85
83
|
ownedPathPrefixes: ['.codex/'],
|
|
86
84
|
managed: ['.codex/hooks.json', '.codex/agents/'],
|
|
@@ -38,7 +38,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
38
38
|
'sandbox-control': ['serve', 'execute', 'recover', 'client'],
|
|
39
39
|
'agent-client': ['next-steps', 'model-selection', 'launch-carrier'],
|
|
40
40
|
'codex-lifecycle': ['preflight', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
|
|
41
|
-
'codex-sandbox-controller': ['verify-context', 'run'],
|
|
42
41
|
'git-workflow': ['inspect', 'preview-tree', 'snapshot', 'compare-trees', 'commit', 'push-rebased'],
|
|
43
42
|
'task-delivery': ['deliver'],
|
|
44
43
|
'release-workflow': ['inspect', 'prepare', 'publish', 'post-prepare', 'post-publish'],
|
|
@@ -8,7 +8,7 @@ import { applyTaskFinalization } from '../task/finalization.ts';
|
|
|
8
8
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
9
9
|
import { resolveSandboxControlTransport } from './task-operation-registry.ts';
|
|
10
10
|
import { quiesceSandboxControlForTaskCompletion } from '../sandbox/control/lifecycle.ts';
|
|
11
|
-
import {
|
|
11
|
+
import { loadSandboxControlLocation } from '../sandbox/config.ts';
|
|
12
12
|
import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
|
|
13
13
|
import { verifyTaskEvent } from '../task/verification.ts';
|
|
14
14
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
@@ -86,7 +86,8 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
86
86
|
const taskId = process.env.AGENT_INFRA_TASK_ID;
|
|
87
87
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
88
88
|
const controlRootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
89
|
-
|
|
89
|
+
const controlRoot = process.env.AGENT_INFRA_CONTROL_ROOT_DIR;
|
|
90
|
+
if (!statusDir || !taskId || !generation || !controlRootId || !controlRoot || taskId !== resolved.taskId) {
|
|
90
91
|
const detail = { code: 'TASK_CONTROL_TRANSPORT_INVALID', message: 'sandbox task control identity is incomplete', retryable: false };
|
|
91
92
|
process.stdout.write(envelope('failed', false, true, null, detail));
|
|
92
93
|
process.exitCode = 1;
|
|
@@ -97,7 +98,7 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
97
98
|
beforeLifecycle: async (currentTaskId) => {
|
|
98
99
|
if (currentTaskId !== taskId) throw new Error('SANDBOX_CONTROL_IDENTITY_MISMATCH');
|
|
99
100
|
await quiesceSandboxControlForTaskCompletion(
|
|
100
|
-
|
|
101
|
+
controlRoot,
|
|
101
102
|
{ taskId, generation, controlRootId, statusDir }
|
|
102
103
|
);
|
|
103
104
|
},
|
|
@@ -111,8 +112,9 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
111
112
|
process.exitCode = exitCode(status);
|
|
112
113
|
return;
|
|
113
114
|
}
|
|
115
|
+
const controlLocation = loadSandboxControlLocation(repoRoot);
|
|
114
116
|
const result = await dispatchTaskControlOperation(
|
|
115
|
-
createDirectHostExecutionContext({ repoRoot }),
|
|
117
|
+
createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }),
|
|
116
118
|
boundOperation
|
|
117
119
|
);
|
|
118
120
|
if (result.status === 'prepared') {
|
|
@@ -9,6 +9,7 @@ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
|
9
9
|
import type { TaskLifecycleResult } from '../task/lifecycle.ts';
|
|
10
10
|
import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
|
|
11
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
12
|
+
import { loadSandboxControlLocation } from '../sandbox/config.ts';
|
|
12
13
|
|
|
13
14
|
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.filter((intent) => intent !== 'complete').join(', ')}\nrecover-started: --auto\n`;
|
|
14
15
|
|
|
@@ -59,8 +60,9 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
|
|
|
59
60
|
repoRoot = resolved.repoRoot;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const controlLocation = loadSandboxControlLocation(repoRoot);
|
|
62
64
|
const result = await dispatchTaskControlOperation(
|
|
63
|
-
createDirectHostExecutionContext({ repoRoot }),
|
|
65
|
+
createDirectHostExecutionContext({ repoRoot, ...(controlLocation ?? {}) }),
|
|
64
66
|
operation
|
|
65
67
|
) as TaskLifecycleResult | LifecycleRecoveryResult;
|
|
66
68
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
@@ -60,8 +60,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
60
60
|
descriptor('internal', 'codex-lifecycle', 'resolve-start', 'conditional', 'progress'),
|
|
61
61
|
descriptor('internal', 'codex-lifecycle', 'resolve-stop', 'conditional', 'progress'),
|
|
62
62
|
descriptor('internal', 'codex-lifecycle', 'consume', 'conditional', 'progress'),
|
|
63
|
-
descriptor('internal', 'codex-sandbox-controller', 'verify-context', 'task-bound', 'diagnostic'),
|
|
64
|
-
descriptor('internal', 'codex-sandbox-controller', 'run', 'task-bound', 'progress'),
|
|
65
63
|
descriptor('internal', 'git-workflow', 'inspect', 'conditional', 'diagnostic'),
|
|
66
64
|
descriptor('internal', 'git-workflow', 'preview-tree', 'conditional', 'diagnostic'),
|
|
67
65
|
descriptor('internal', 'git-workflow', 'snapshot', 'conditional', 'diagnostic'),
|
|
@@ -213,7 +211,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
|
|
|
213
211
|
]);
|
|
214
212
|
|
|
215
213
|
export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
216
|
-
'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle',
|
|
214
|
+
'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle',
|
|
217
215
|
'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
|
|
218
216
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
219
217
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
@@ -2,27 +2,9 @@ import { execFileSync, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
5
|
import { loadConfig } from '../sandbox/config.ts';
|
|
7
|
-
import { detectEngine } from '../sandbox/engine.ts';
|
|
8
|
-
import { runEngine } from '../sandbox/shell.ts';
|
|
9
|
-
import { sandboxBranchLabel, sandboxLabel } from '../sandbox/constants.ts';
|
|
10
|
-
import { fetchSandboxRows, selectSandboxContainer } from '../sandbox/commands/list-running.ts';
|
|
11
|
-
import { containerNameCandidates } from '../sandbox/constants.ts';
|
|
12
6
|
import { resolveSandboxTarget } from '../sandbox/workspace-identity.ts';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
appendSandboxControlAudit,
|
|
16
|
-
readActiveLease,
|
|
17
|
-
readSandboxControlStatus
|
|
18
|
-
} from '../sandbox/control/state.ts';
|
|
19
|
-
import type { SandboxControlLease } from '../sandbox/control/protocol.ts';
|
|
20
|
-
import { readSandboxControlManifest } from '../sandbox/control/lifecycle.ts';
|
|
21
|
-
import {
|
|
22
|
-
SANDBOX_CONTROL_FUTURE_SKEW_MS,
|
|
23
|
-
SANDBOX_CONTROL_STATUS_STALE_MS
|
|
24
|
-
} from '../sandbox/control/protocol.ts';
|
|
25
|
-
import { getProcessStartTime } from '../server/process-state.ts';
|
|
7
|
+
import { resolveBranchWorktree } from '../git/branch-worktree.ts';
|
|
26
8
|
import { assertGitWorktreeBinding } from '../git/worktree-identity.ts';
|
|
27
9
|
import { createManualValidationEvidence, manualValidationEvidenceSummary } from '../task/manual-validation-evidence.ts';
|
|
28
10
|
import type { ManualValidationCleanup } from '../task/manual-validation-evidence.ts';
|
|
@@ -31,7 +13,6 @@ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-in
|
|
|
31
13
|
|
|
32
14
|
const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] [--evidence-file <path>] -- <command> [args...]`;
|
|
33
15
|
const MAX_TIMEOUT_MS = 60 * 60 * 1000;
|
|
34
|
-
const RECOVERY_GRACE_MS = 30_000;
|
|
35
16
|
|
|
36
17
|
type ValidateOptions = {
|
|
37
18
|
target: string;
|
|
@@ -108,20 +89,6 @@ function git(root: string, args: string[]): string {
|
|
|
108
89
|
return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
109
90
|
}
|
|
110
91
|
|
|
111
|
-
function waitForState(statusDir: string, predicate: (state: ReturnType<typeof readSandboxControlStatus>) => boolean, timeoutMs = 5_000): void {
|
|
112
|
-
const deadline = Date.now() + timeoutMs;
|
|
113
|
-
while (Date.now() < deadline) {
|
|
114
|
-
try {
|
|
115
|
-
const status = readSandboxControlStatus(statusDir);
|
|
116
|
-
if (predicate(status)) return;
|
|
117
|
-
} catch {
|
|
118
|
-
// Atomic status publication may be between renames.
|
|
119
|
-
}
|
|
120
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
121
|
-
}
|
|
122
|
-
throw new Error('SANDBOX_VALIDATION_BROKER_STATE_TIMEOUT');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
92
|
function snapshotValidation(repoRoot: string, commit: string, options: ValidateOptions) {
|
|
126
93
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-validation-'));
|
|
127
94
|
const worktree = path.join(tempRoot, 'worktree');
|
|
@@ -150,109 +117,28 @@ function snapshotValidation(repoRoot: string, commit: string, options: ValidateO
|
|
|
150
117
|
}
|
|
151
118
|
|
|
152
119
|
function inplaceValidation(config: ReturnType<typeof loadConfig>, target: ReturnType<typeof resolveSandboxTarget>, options: ValidateOptions) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
base: config.controlBase, repoRoot: config.repoRoot, project: config.project,
|
|
159
|
-
container: row.name, identity: target.workspace
|
|
160
|
-
});
|
|
161
|
-
const manifest = readSandboxControlManifest(control.manifestPath);
|
|
162
|
-
const initial = readSandboxControlStatus(control.statusDir);
|
|
163
|
-
const now = Date.now();
|
|
164
|
-
if (initial.generation !== manifest.generation
|
|
165
|
-
|| now - initial.updatedAt > SANDBOX_CONTROL_STATUS_STALE_MS
|
|
166
|
-
|| initial.updatedAt > now + SANDBOX_CONTROL_FUTURE_SKEW_MS) {
|
|
167
|
-
throw new Error('SANDBOX_VALIDATION_BROKER_STATUS_STALE');
|
|
168
|
-
}
|
|
169
|
-
if (initial.state !== 'healthy' || initial.activeRequestId !== null) {
|
|
170
|
-
throw new Error('SANDBOX_VALIDATION_BROKER_NOT_IDLE');
|
|
171
|
-
}
|
|
172
|
-
const startTime = getProcessStartTime(process.pid);
|
|
173
|
-
if (!startTime) throw new Error('SANDBOX_VALIDATION_OWNER_IDENTITY_UNAVAILABLE');
|
|
174
|
-
const leasePath = path.join(control.root, 'lease.json');
|
|
175
|
-
const nonce = randomUUID();
|
|
176
|
-
const issuedAt = Date.now();
|
|
177
|
-
const lease: SandboxControlLease = {
|
|
178
|
-
version: 2, generation: manifest.generation, nonce,
|
|
179
|
-
owner: { pid: process.pid, startTime }, issuedAt,
|
|
180
|
-
expiresAt: issuedAt + Math.min(options.timeoutMs + RECOVERY_GRACE_MS, MAX_TIMEOUT_MS + RECOVERY_GRACE_MS),
|
|
181
|
-
taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
|
|
182
|
-
branch: target.branch, reason: 'manual-validation'
|
|
183
|
-
};
|
|
184
|
-
const originalBranch = git(manifest.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
185
|
-
const originalHead = git(manifest.worktreeRoot, ['rev-parse', 'HEAD']);
|
|
186
|
-
fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
|
|
187
|
-
let stopped = false;
|
|
120
|
+
const targetRoot = resolveBranchWorktree(config.repoRoot, target.branch);
|
|
121
|
+
if (!targetRoot) throw new Error(`TASK_VALIDATION_WORKTREE_NOT_FOUND: ${target.branch}`);
|
|
122
|
+
const worktree = assertGitWorktreeBinding(config.repoRoot, targetRoot, target.branch);
|
|
123
|
+
const originalBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
124
|
+
const originalHead = git(worktree.worktreeRoot, ['rev-parse', 'HEAD']);
|
|
188
125
|
let restored = false;
|
|
189
126
|
try {
|
|
190
|
-
|
|
191
|
-
waitForState(control.statusDir, (status) => status.state === 'parked' && status.reasonCode === 'SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
192
|
-
if (row.running) {
|
|
193
|
-
runEngine(engine, 'docker', ['stop', row.name]);
|
|
194
|
-
stopped = true;
|
|
195
|
-
}
|
|
196
|
-
const refreshedRows = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
197
|
-
const refreshed = selectSandboxContainer(
|
|
198
|
-
[...refreshedRows.running, ...refreshedRows.nonRunning],
|
|
199
|
-
containerNameCandidates(config, target.branch)
|
|
200
|
-
);
|
|
201
|
-
if (!refreshed || refreshed.running) throw new Error('SANDBOX_VALIDATION_CONTAINER_STILL_RUNNING');
|
|
202
|
-
const activeLease = readActiveLease(manifest);
|
|
203
|
-
if (!activeLease || activeLease.nonce !== nonce) throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
|
|
204
|
-
assertGitWorktreeBinding(config.repoRoot, manifest.worktreeRoot, target.branch);
|
|
205
|
-
const result = runValidationCommand(options.command, manifest.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
|
|
127
|
+
const result = runValidationCommand(options.command, worktree.worktreeRoot, 'inplace', options.timeoutMs, options.format === 'json');
|
|
206
128
|
return { ...result, cleanup: () => restored ? 'completed' : 'pending' };
|
|
207
129
|
} finally {
|
|
208
|
-
const recoveryErrors: string[] = [];
|
|
209
130
|
try {
|
|
210
|
-
const currentBranch = git(
|
|
211
|
-
if (currentBranch !== originalBranch) git(
|
|
131
|
+
const currentBranch = git(worktree.worktreeRoot, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
132
|
+
if (currentBranch !== originalBranch) git(worktree.worktreeRoot, ['switch', originalBranch]);
|
|
212
133
|
} catch {
|
|
213
134
|
try {
|
|
214
|
-
git(
|
|
215
|
-
git(
|
|
216
|
-
} catch (fallbackError) {
|
|
217
|
-
recoveryErrors.push(`git binding restore failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (stopped) {
|
|
221
|
-
try {
|
|
222
|
-
runEngine(engine, 'docker', ['start', row.name]);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
recoveryErrors.push(`container start failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
try {
|
|
228
|
-
const currentLease = JSON.parse(fs.readFileSync(leasePath, 'utf8')) as { nonce?: unknown };
|
|
229
|
-
if (currentLease.nonce !== nonce) throw new Error('SANDBOX_VALIDATION_LEASE_OWNERSHIP_LOST');
|
|
230
|
-
} catch (error) {
|
|
231
|
-
recoveryErrors.push(error instanceof Error ? error.message : String(error));
|
|
232
|
-
}
|
|
233
|
-
if (recoveryErrors.length === 0) {
|
|
234
|
-
try {
|
|
235
|
-
fs.unlinkSync(leasePath);
|
|
236
|
-
appendSandboxControlAudit(manifest, 'lease-release', { ownerPid: process.pid });
|
|
237
|
-
waitForState(control.statusDir, (status) => status.state === 'healthy');
|
|
238
|
-
restored = true;
|
|
135
|
+
git(worktree.worktreeRoot, ['checkout', '--detach', originalHead]);
|
|
136
|
+
git(worktree.worktreeRoot, ['switch', originalBranch]);
|
|
239
137
|
} catch (error) {
|
|
240
|
-
|
|
241
|
-
if (!fs.existsSync(leasePath)) {
|
|
242
|
-
fs.writeFileSync(leasePath, `${JSON.stringify(lease)}\n`, { flag: 'wx', mode: 0o600 });
|
|
243
|
-
}
|
|
244
|
-
appendSandboxControlAudit(manifest, 'lease-release-failed', { ownerPid: process.pid });
|
|
245
|
-
} catch (leaseError) {
|
|
246
|
-
recoveryErrors.push(`lease preservation failed: ${leaseError instanceof Error ? leaseError.message : String(leaseError)}`);
|
|
247
|
-
}
|
|
248
|
-
recoveryErrors.push(error instanceof Error ? error.message : String(error));
|
|
138
|
+
throw new Error(`TASK_VALIDATION_WORKTREE_RESTORE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
|
|
249
139
|
}
|
|
250
140
|
}
|
|
251
|
-
|
|
252
|
-
throw new Error(
|
|
253
|
-
`SANDBOX_VALIDATION_RECOVERY_INCOMPLETE: ${recoveryErrors.join('; ')}; run 'ai sandbox start ${target.branch}'`
|
|
254
|
-
);
|
|
255
|
-
}
|
|
141
|
+
restored = true;
|
|
256
142
|
}
|
|
257
143
|
}
|
|
258
144
|
|
|
@@ -136,8 +136,8 @@ async function releaseNoteContext(
|
|
|
136
136
|
options: ReleaseNoteOptions = {}
|
|
137
137
|
) {
|
|
138
138
|
const historyLimit = input.historyLimit ?? 3;
|
|
139
|
-
if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit >
|
|
140
|
-
return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to
|
|
139
|
+
if (!input.fromTag || !input.toTag || !input.branch || !Number.isInteger(historyLimit) || historyLimit < 1 || historyLimit > 100) {
|
|
140
|
+
return { ...baseResult('failed'), error: { code: 'RELEASE_NOTES_INPUT_INVALID', message: 'from-tag, to-tag, branch, and a history limit from 1 to 100 are required', retryable: false } };
|
|
141
141
|
}
|
|
142
142
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
143
143
|
const platformType = options.platformType ?? configuredPlatform(cwd);
|
|
@@ -1108,7 +1108,6 @@ export async function create(
|
|
|
1108
1108
|
const container = containerName(effectiveConfig, branch);
|
|
1109
1109
|
const controlPaths = sandboxControlPaths({
|
|
1110
1110
|
base: effectiveConfig.controlBase,
|
|
1111
|
-
repoRoot: effectiveConfig.repoRoot,
|
|
1112
1111
|
project: effectiveConfig.project,
|
|
1113
1112
|
container,
|
|
1114
1113
|
identity: target.workspace
|
|
@@ -1493,6 +1492,7 @@ export async function create(
|
|
|
1493
1492
|
shellConfigHostDir: hostShellConfig.hostDir,
|
|
1494
1493
|
workspaceViewRoot: workspaceView.root,
|
|
1495
1494
|
controlDir: control.channelDir,
|
|
1495
|
+
...(target.workspace.mode === 'task-bound' ? { controlRootDir: control.root } : {}),
|
|
1496
1496
|
controlStatusDir: control.statusDir,
|
|
1497
1497
|
...(target.workspace.mode === 'task-bound' ? { runtimeDir: control.runtimeDir } : {}),
|
|
1498
1498
|
...(target.workspace.mode === 'task-bound'
|
|
@@ -1588,8 +1588,6 @@ export async function create(
|
|
|
1588
1588
|
]
|
|
1589
1589
|
: []),
|
|
1590
1590
|
'-e',
|
|
1591
|
-
`AGENT_INFRA_CONTROL_TOKEN=${control.token}`,
|
|
1592
|
-
'-e',
|
|
1593
1591
|
`AGENT_INFRA_CONTROL_GENERATION=${control.generation}`,
|
|
1594
1592
|
'-e',
|
|
1595
1593
|
'AGENT_INFRA_CONTROL_DIR=/run/agent-infra/control',
|
|
@@ -1598,7 +1596,10 @@ export async function create(
|
|
|
1598
1596
|
'-e',
|
|
1599
1597
|
`AGENT_INFRA_CONTROL_ROOT_ID=${control.controlRootId}`,
|
|
1600
1598
|
...(target.workspace.mode === 'task-bound'
|
|
1601
|
-
? [
|
|
1599
|
+
? [
|
|
1600
|
+
'-e', 'AGENT_INFRA_CONTROL_ROOT_DIR=/run/agent-infra/control-root',
|
|
1601
|
+
'-e', 'AGENT_INFRA_RUNTIME_DIR=/run/agent-infra/runtime'
|
|
1602
|
+
]
|
|
1602
1603
|
: []),
|
|
1603
1604
|
'--label',
|
|
1604
1605
|
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -237,3 +237,16 @@ export function loadConfig({
|
|
|
237
237
|
}
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
|
+
|
|
241
|
+
export function loadSandboxControlLocation(repoRoot = detectRepoRoot()): Readonly<{
|
|
242
|
+
controlBase: string;
|
|
243
|
+
project: string;
|
|
244
|
+
}> | null {
|
|
245
|
+
const configPath = path.join(repoRoot, '.agents', '.airc.json');
|
|
246
|
+
if (!fs.existsSync(configPath)) return null;
|
|
247
|
+
const airc = JSON.parse(fs.readFileSync(configPath, 'utf8')) as AircConfig;
|
|
248
|
+
if (typeof airc.project !== 'string' || !airc.project) return null;
|
|
249
|
+
const home = homedir();
|
|
250
|
+
if (!home) throw new Error('sandbox: home directory is required');
|
|
251
|
+
return { controlBase: hostJoin(home, '.agent-infra', 'sandbox-control'), project: airc.project };
|
|
252
|
+
}
|
|
@@ -41,8 +41,7 @@ const AUDIT_MAX_BYTES = 1024 * 1024;
|
|
|
41
41
|
|
|
42
42
|
export function requestAuditFields(manifest: SandboxControlManifest, request: SandboxControlRequest) {
|
|
43
43
|
return {
|
|
44
|
-
requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId
|
|
45
|
-
requestGeneration: request.generation, requestIssuedAt: request.issuedAt, requestExpiresAt: request.expiresAt
|
|
44
|
+
requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
47
|
|