@dharma-ai-labs/agent-fabric 0.2.209 → 0.2.210
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.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/bootstrapHostScope.test.js +1 -1
- package/dist/bootstrapHostScope.test.js.map +1 -1
- package/dist/bootstrapProcessCaller.test.js +1 -0
- package/dist/bootstrapProcessCaller.test.js.map +1 -1
- package/dist/bootstrapVaultScope.test.js +25 -1
- package/dist/bootstrapVaultScope.test.js.map +1 -1
- package/dist/codexBoundSession.d.ts +59 -0
- package/dist/codexBoundSession.d.ts.map +1 -1
- package/dist/codexBoundSession.js +140 -1
- package/dist/codexBoundSession.js.map +1 -1
- package/dist/codexInboxSession.d.ts +1 -1
- package/dist/codexInboxSession.d.ts.map +1 -1
- package/dist/codexRecoveryController.test.d.ts +2 -0
- package/dist/codexRecoveryController.test.d.ts.map +1 -0
- package/dist/codexRecoveryController.test.js +295 -0
- package/dist/codexRecoveryController.test.js.map +1 -0
- package/dist/codexScopedDispatch.test.js +2 -1
- package/dist/codexScopedDispatch.test.js.map +1 -1
- package/dist/codexSetupChildProvider.test.js +20 -0
- package/dist/codexSetupChildProvider.test.js.map +1 -1
- package/dist/codexSetupNativeHost.test.js +2 -1
- package/dist/codexSetupNativeHost.test.js.map +1 -1
- package/dist/codexSetupSessionCaller.test.js +1 -0
- package/dist/codexSetupSessionCaller.test.js.map +1 -1
- package/dist/codexSigningRecoveryNativeHost.d.ts +50 -0
- package/dist/codexSigningRecoveryNativeHost.d.ts.map +1 -0
- package/dist/codexSigningRecoveryNativeHost.js +186 -0
- package/dist/codexSigningRecoveryNativeHost.js.map +1 -0
- package/dist/codexSigningRecoveryNativeHost.test.d.ts +2 -0
- package/dist/codexSigningRecoveryNativeHost.test.d.ts.map +1 -0
- package/dist/codexSigningRecoveryNativeHost.test.js +475 -0
- package/dist/codexSigningRecoveryNativeHost.test.js.map +1 -0
- package/dist/codexSigningRecoveryTool.d.ts +25 -0
- package/dist/codexSigningRecoveryTool.d.ts.map +1 -0
- package/dist/codexSigningRecoveryTool.js +24 -0
- package/dist/codexSigningRecoveryTool.js.map +1 -0
- package/dist/containerRelayLifecycle.d.ts +1 -1
- package/dist/cooperativeInboxSession.d.ts +1 -1
- package/dist/demoPackage.d.ts +1 -1
- package/dist/demoWatchControl.d.ts +6 -6
- package/dist/deviceSigningMaintenanceController.d.ts +35 -0
- package/dist/deviceSigningMaintenanceController.d.ts.map +1 -0
- package/dist/deviceSigningMaintenanceController.js +197 -0
- package/dist/deviceSigningMaintenanceController.js.map +1 -0
- package/dist/deviceSigningMaintenanceController.test.d.ts +2 -0
- package/dist/deviceSigningMaintenanceController.test.d.ts.map +1 -0
- package/dist/deviceSigningMaintenanceController.test.js +67 -0
- package/dist/deviceSigningMaintenanceController.test.js.map +1 -0
- package/dist/deviceSigningMaintenanceFlow.test.d.ts +2 -0
- package/dist/deviceSigningMaintenanceFlow.test.d.ts.map +1 -0
- package/dist/deviceSigningMaintenanceFlow.test.js +196 -0
- package/dist/deviceSigningMaintenanceFlow.test.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +609 -466
- package/dist/index.js.map +1 -1
- package/dist/legacySigningMaintenanceBoundary.d.ts +14 -0
- package/dist/legacySigningMaintenanceBoundary.d.ts.map +1 -0
- package/dist/legacySigningMaintenanceBoundary.js +128 -0
- package/dist/legacySigningMaintenanceBoundary.js.map +1 -0
- package/dist/legacySigningMaintenanceBoundary.test.d.ts +2 -0
- package/dist/legacySigningMaintenanceBoundary.test.d.ts.map +1 -0
- package/dist/legacySigningMaintenanceBoundary.test.js +109 -0
- package/dist/legacySigningMaintenanceBoundary.test.js.map +1 -0
- package/dist/namedCodexThread.d.ts.map +1 -1
- package/dist/namedCodexThread.js +3 -1
- package/dist/namedCodexThread.js.map +1 -1
- package/dist/namedCodexThread.test.js +17 -1
- package/dist/namedCodexThread.test.js.map +1 -1
- package/dist/namedSessionMaintenance.d.ts +1 -1
- package/dist/namedSessionStartupFence.test.d.ts +2 -0
- package/dist/namedSessionStartupFence.test.d.ts.map +1 -0
- package/dist/namedSessionStartupFence.test.js +81 -0
- package/dist/namedSessionStartupFence.test.js.map +1 -0
- package/dist/providerSessionReplyRecovery.d.ts +1 -1
- package/dist/relayRuntimeUpgrade.d.ts +1 -1
- package/dist/schemas/codex-signing-recovery-native-progress.schema.json +16 -0
- package/dist/schemas/device-enrollment-recovery-journal.schema.json +20 -0
- package/dist/schemas/device-signing-maintenance-challenge.schema.json +185 -0
- package/dist/schemas/device-signing-maintenance-completion.schema.json +98 -0
- package/dist/schemas/device-signing-maintenance-context-request.schema.json +68 -0
- package/dist/schemas/device-signing-maintenance-context.schema.json +137 -0
- package/dist/schemas/device-signing-maintenance-control.schema.json +12 -0
- package/dist/schemas/device-signing-maintenance-controller-progress.schema.json +26 -0
- package/dist/schemas/device-signing-maintenance-delivery-request.schema.json +45 -0
- package/dist/schemas/device-signing-maintenance-host-progress.schema.json +121 -0
- package/dist/schemas/device-signing-maintenance-plan.schema.json +136 -0
- package/dist/schemas/device-signing-maintenance-preparation.schema.json +56 -0
- package/dist/schemas/device-signing-recovery-candidate.schema.json +28 -0
- package/dist/schemas/device-signing-recovery-challenge.schema.json +52 -0
- package/dist/schemas/device-signing-recovery-completion.schema.json +23 -0
- package/dist/schemas/device-signing-recovery-delivery-request.schema.json +16 -0
- package/dist/schemas/device-signing-recovery-delivery.schema.json +22 -0
- package/dist/schemas/device-signing-recovery-host-progress.schema.json +28 -0
- package/dist/schemas/device-signing-recovery-offer-progress.schema.json +32 -0
- package/dist/schemas/device-signing-recovery-offer-read-request.schema.json +16 -0
- package/dist/schemas/device-signing-recovery-offer-registration-receipt.schema.json +16 -0
- package/dist/schemas/device-signing-recovery-offer-registration-request.schema.json +18 -0
- package/dist/schemas/device-signing-recovery-offer-response.schema.json +43 -0
- package/dist/schemas/device-signing-recovery-plan.schema.json +40 -0
- package/dist/schemas/device-signing-recovery-preparation.schema.json +23 -0
- package/dist/schemas/provider-session-recovery-declaration.schema.json +21 -0
- package/dist/signingMaintenanceCohort.test.d.ts +2 -0
- package/dist/signingMaintenanceCohort.test.d.ts.map +1 -0
- package/dist/signingMaintenanceCohort.test.js +55 -0
- package/dist/signingMaintenanceCohort.test.js.map +1 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +3 -2
- package/dist/usage.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -12,11 +12,13 @@ import { canonicalize, createActionDecisionPublicKeyResolver, sha256, validateCo
|
|
|
12
12
|
import { buildTrajectoryCapsule, containsDisallowedLocalPath, redactValue, referencesExcludedPath, trajectoryCapsuleHash } from '@dharma-ai-labs/agent-fabric-evidence-reduction';
|
|
13
13
|
import { assertPolicy, loadOrganizationPolicy, verifyServerAuthorizedPolicy } from '@dharma-ai-labs/agent-fabric-policy';
|
|
14
14
|
import { agyAdapter, claudeAdapter, codexAdapter, hermesAdapter, providerAdapters, providerExecutionRecords, providerProcessEnvironment } from '@dharma-ai-labs/agent-fabric-provider-adapters';
|
|
15
|
-
import { AgentFabricClient, beginEnrollment, loadDeviceIdentity, loadOrCreateDeviceIdentity, normalizeHqUrl, pollEnrollment, assertDeviceEnrollmentAnchorAbsent, deleteActiveSkillAuthorizationAnchor, loadActiveSkillAuthorizationAnchor, loadDeviceEnrollmentAnchor, saveActiveSkillAuthorizationAnchor, isDefinitiveAgentFabricRejection, recoverDeviceEnrollmentConsistency, loadOrganizationApiToken, redeemBootstrapGrant, claimSetupReference, setupClaimSourceRegistration, saveDeviceConfig, saveDeviceEnrollmentAnchor, saveOrganizationApiToken, saveDeviceConnectionPreference, } from '@dharma-ai-labs/agent-fabric-relay-client';
|
|
15
|
+
import { AgentFabricClient, beginEnrollment, loadDeviceIdentity, loadOrCreateDeviceIdentity, normalizeHqUrl, pollEnrollment, assertDeviceEnrollmentAnchorAbsent, deleteActiveSkillAuthorizationAnchor, loadActiveSkillAuthorizationAnchor, loadDeviceEnrollmentAnchor, saveActiveSkillAuthorizationAnchor, isDefinitiveAgentFabricRejection, recoverDeviceEnrollmentConsistency, withDeviceSigningControl, loadOrganizationApiToken, redeemBootstrapGrant, claimSetupReference, setupClaimSourceRegistration, saveDeviceConfig, saveDeviceEnrollmentAnchor, saveOrganizationApiToken, saveDeviceConnectionPreference, } from '@dharma-ai-labs/agent-fabric-relay-client';
|
|
16
16
|
import { AgentFabricClient as AgentFabricApiClient, } from '@dharma-ai-labs/agent-fabric-sdk';
|
|
17
17
|
import { contentHash, getActiveSkillBundleAuthorization, getExpiredSkillBundleAuthorizationForReplacement, getLegacySkillBundleIdForUpgrade, installSkillBundle, readVerifiedRepositoryKnowledge, rollbackUnconfirmedSkillBundle, verifySkillBundle } from '@dharma-ai-labs/agent-fabric-skill-manager';
|
|
18
18
|
import { executeTask, FileActionExecutionJournal, FileTaskReceiptStore, providerInstructionsForTask, } from '@dharma-ai-labs/agent-fabric-task-runner';
|
|
19
19
|
import { CLI_USAGE } from './usage.js';
|
|
20
|
+
import { runDeviceSigningMaintenanceController } from './deviceSigningMaintenanceController.js';
|
|
21
|
+
import { withLegacySigningMaintenanceBoundary } from './legacySigningMaintenanceBoundary.js';
|
|
20
22
|
import { superviseRelay } from './relaySupervisor.js';
|
|
21
23
|
import { currentRepositoryRelayFailure, repositoryRelayObservationReady, runRegisteredRepositoryRelays, selectRepositoryRelayRegistrations, serializeRelayWork, waitForRelayRefresh, withRepositoryRelayStage } from './repositoryRelaySupervisor.js';
|
|
22
24
|
import { assertRelayStartupOwnership, disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
@@ -100,6 +102,8 @@ import { requestCodexSetupHostPreparation } from './codexSetupPortalPreparation.
|
|
|
100
102
|
import { readCodexSetupPublicStdin } from './codexSetupPublicStdin.js';
|
|
101
103
|
import { reserveCodexSetupLaunch } from './codexSetupLaunchReservation.js';
|
|
102
104
|
export { startCodexSetupNativeHost } from './codexSetupNativeHost.js';
|
|
105
|
+
export { attachCodexSigningRecoveryNativeHost } from './codexSigningRecoveryNativeHost.js';
|
|
106
|
+
export { openCodexSigningRecoveryOwner } from './codexBoundSession.js';
|
|
103
107
|
export { openCodexSetupVaultJournal } from './codexSetupVaultJournal.js';
|
|
104
108
|
import { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
|
|
105
109
|
export { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
|
|
@@ -107,7 +111,8 @@ import { assertCodexSetupExecutionLease } from './codexSetupAdmission.js';
|
|
|
107
111
|
import { parseLocalCodexSetupReadiness } from '@dharma-ai-labs/agent-fabric-local-vault/setup-readiness';
|
|
108
112
|
import { writeBootstrapHostJson, writeBootstrapHostText } from './bootstrapHostFiles.js';
|
|
109
113
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
110
|
-
|
|
114
|
+
import { inspectNamedCodexRecoveryToolDeclaration } from './codexSigningRecoveryTool.js';
|
|
115
|
+
const VERSION = '0.2.210';
|
|
111
116
|
const USAGE = CLI_USAGE;
|
|
112
117
|
const execFileAsync = promisify(execFile);
|
|
113
118
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -161,10 +166,25 @@ export function portalUrl(flags, fallback = 'https://www.dharma-ai.io') {
|
|
|
161
166
|
return String(flags.get('portal-url') || flags.get('hq-url') || fallback);
|
|
162
167
|
}
|
|
163
168
|
function print(value) {
|
|
164
|
-
process.stdout.write(
|
|
169
|
+
process.stdout.write(formatCommandOutput(value));
|
|
170
|
+
}
|
|
171
|
+
export function formatCommandOutput(value) {
|
|
172
|
+
if (typeof value === 'string')
|
|
173
|
+
return value + '\n';
|
|
174
|
+
const streaming = value && typeof value === 'object' && !Array.isArray(value)
|
|
175
|
+
&& value.schema === 'dharma.device-signing-maintenance-controller-progress/v1';
|
|
176
|
+
return JSON.stringify(value, null, streaming ? undefined : 2) + '\n';
|
|
165
177
|
}
|
|
166
178
|
export function commandExitCode(argv, value) {
|
|
167
179
|
const { positional, flags } = parseCliOptions(argv);
|
|
180
|
+
if (positional[0] === 'signing-recovery') {
|
|
181
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
182
|
+
return 2;
|
|
183
|
+
const receipt = value, progress = receipt.progress;
|
|
184
|
+
return receipt.schema === 'dharma.device-signing-maintenance-controller-progress/v1' && receipt.ok === true
|
|
185
|
+
&& receipt.nativeContinuationAuthorized === false && receipt.ready === false
|
|
186
|
+
&& (flags.get('apply') === true ? progress?.state === 'trust_installed' && progress.clientTrustInstalled === true : receipt.planned === true) ? 0 : 2;
|
|
187
|
+
}
|
|
168
188
|
if (positional[0] === 'sessions' && positional[1] === 'readiness' && flags.get('diagnostic') === true) {
|
|
169
189
|
const diagnosticResult = value && typeof value === 'object' && !Array.isArray(value)
|
|
170
190
|
? value : null;
|
|
@@ -1044,7 +1064,7 @@ async function pidProcessState(pidPath) {
|
|
|
1044
1064
|
const code = error.code;
|
|
1045
1065
|
if (code === 'ESRCH')
|
|
1046
1066
|
return 'stopped';
|
|
1047
|
-
return
|
|
1067
|
+
return 'unknown';
|
|
1048
1068
|
}
|
|
1049
1069
|
}
|
|
1050
1070
|
export async function relayProcessState(home = dharmaHome()) {
|
|
@@ -1053,6 +1073,42 @@ export async function relayProcessState(home = dharmaHome()) {
|
|
|
1053
1073
|
export async function relaySupervisorProcessState(home = dharmaHome()) {
|
|
1054
1074
|
return pidProcessState(resolve(home, 'relay', 'supervisor.pid'));
|
|
1055
1075
|
}
|
|
1076
|
+
/** A legacy runtime may not participate in the new signing mutex. Require all
|
|
1077
|
+
* registrations belonging to this device disabled and their known owned PID
|
|
1078
|
+
* locks stopped, plus its relay/supervisor, before any maintenance store read.
|
|
1079
|
+
* This check never stops a process or changes startup/registration state. */
|
|
1080
|
+
export async function deviceTrustMaintenanceProcessState(home, name) {
|
|
1081
|
+
const selected = await readNamedSession(home, name);
|
|
1082
|
+
if (!selected)
|
|
1083
|
+
return 'unknown';
|
|
1084
|
+
const states = await Promise.all([relayProcessState(home), relaySupervisorProcessState(home)]);
|
|
1085
|
+
let entries;
|
|
1086
|
+
try {
|
|
1087
|
+
entries = await readdir(resolve(home, 'sessions'));
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
if (error.code !== 'ENOENT')
|
|
1091
|
+
return 'unknown';
|
|
1092
|
+
entries = [];
|
|
1093
|
+
}
|
|
1094
|
+
for (const entry of entries) {
|
|
1095
|
+
if (!/^[a-z][a-z0-9-]{0,47}$/.test(String(entry)))
|
|
1096
|
+
continue;
|
|
1097
|
+
let registration;
|
|
1098
|
+
try {
|
|
1099
|
+
registration = await readNamedSession(home, String(entry));
|
|
1100
|
+
}
|
|
1101
|
+
catch {
|
|
1102
|
+
return 'unknown';
|
|
1103
|
+
}
|
|
1104
|
+
if (!registration)
|
|
1105
|
+
return 'unknown';
|
|
1106
|
+
if (registration.identity.deviceId !== selected.identity.deviceId || registration.identity.organizationId !== selected.identity.organizationId)
|
|
1107
|
+
continue;
|
|
1108
|
+
states.push(registration.enabled ? 'unknown' : await pidProcessState(resolve(namedSessionPaths(home, String(entry)).root, 'service.lock')));
|
|
1109
|
+
}
|
|
1110
|
+
return states.includes('running') ? 'running' : states.includes('unknown') ? 'unknown' : 'stopped';
|
|
1111
|
+
}
|
|
1056
1112
|
export async function acquireRelayProcessLease(home = dharmaHome()) {
|
|
1057
1113
|
const pidPath = resolve(home, 'relay', 'relay.pid');
|
|
1058
1114
|
const releaseLock = await acquirePidLock(`${pidPath}.lock`, 250, 'Another relay process is already running for this device.');
|
|
@@ -1228,6 +1284,35 @@ export async function withRelayStartupMutation(operation, home = dharmaHome()) {
|
|
|
1228
1284
|
await release?.();
|
|
1229
1285
|
}
|
|
1230
1286
|
}
|
|
1287
|
+
export async function acquireNamedSessionStartupAdmission(home = dharmaHome()) {
|
|
1288
|
+
return acquirePidLock(resolve(home, 'autostart-mutation.lock'), 250, 'named_session_startup_busy');
|
|
1289
|
+
}
|
|
1290
|
+
async function assertSigningMaintenanceManagedCohort(home, workspace) {
|
|
1291
|
+
const startup = await inspectOwnedRelayAutostart({ home });
|
|
1292
|
+
const bin = resolve(workspace, '.dharma', 'bin');
|
|
1293
|
+
if (startup.version !== VERSION || startup.workspace !== workspace
|
|
1294
|
+
|| startup.launcher !== resolve(bin, process.platform === 'win32' ? 'dharma.cmd' : 'dharma')
|
|
1295
|
+
|| startup.policy !== resolve(workspace, '.dharma', 'approved-policy.json')) {
|
|
1296
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1297
|
+
}
|
|
1298
|
+
const readLauncher = async (name) => {
|
|
1299
|
+
const path = resolve(bin, name), before = await lstat(path);
|
|
1300
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size > 65536 || await realpath(path) !== path) {
|
|
1301
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1302
|
+
}
|
|
1303
|
+
const contents = await readFile(path, 'utf8'), after = await lstat(path);
|
|
1304
|
+
if (after.ino !== before.ino || after.dev !== before.dev || after.size !== before.size
|
|
1305
|
+
|| after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || after.isSymbolicLink()) {
|
|
1306
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1307
|
+
}
|
|
1308
|
+
return contents;
|
|
1309
|
+
};
|
|
1310
|
+
const launchers = { shell: await readLauncher('dharma'), windows: await readLauncher('dharma.cmd') };
|
|
1311
|
+
if (!await verifyRecordedRepositoryLaunchers(VERSION, launchers)) {
|
|
1312
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1313
|
+
}
|
|
1314
|
+
return sha256(JSON.stringify({ schema: 'dharma.managed-signing-maintenance-cohort/v1', startup, launchers }));
|
|
1315
|
+
}
|
|
1231
1316
|
export async function withWorkspacePolicyRefreshLock(workspaceId, operation) {
|
|
1232
1317
|
if (!workspaceId)
|
|
1233
1318
|
throw new Error('Content transmission requires a registered workspace ID.');
|
|
@@ -5246,498 +5331,522 @@ async function namedSessionCommand(action, flags) {
|
|
|
5246
5331
|
if (process.platform !== 'linux')
|
|
5247
5332
|
throw new Error('codex_session_sandbox_unqualified');
|
|
5248
5333
|
const workspaceId = required(flags, 'workspace-id');
|
|
5249
|
-
let
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
const config = await step(() => readDeviceConfig());
|
|
5259
|
-
if (!item || !config || item.organizationId !== config.organizationId)
|
|
5260
|
-
throw new Error('named_session_scope_mismatch');
|
|
5261
|
-
await assertBootstrapHostSource(item.path, String(item.repositoryRemoteHash || ''));
|
|
5262
|
-
const scope = repositoryRoleScope(item);
|
|
5263
|
-
const existing = await step(() => readNamedSession(dharmaHome(), name));
|
|
5264
|
-
if (setupSessionScope && (name !== setupSessionScope.request.name || workspaceId !== setupSessionScope.request.workspaceId
|
|
5265
|
-
|| existing && existing.identity.membershipId !== setupSessionScope.request.membershipId))
|
|
5266
|
-
throw new Error('setup_session_scope_changed');
|
|
5267
|
-
if (existing && (existing.identity.workspaceId !== item.workspaceId
|
|
5268
|
-
|| existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
|
|
5269
|
-
|| existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
|
|
5270
|
-
throw new Error('named_session_scope_mismatch');
|
|
5271
|
-
}
|
|
5272
|
-
if (action === 'status') {
|
|
5273
|
-
try {
|
|
5274
|
-
return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
|
|
5275
|
-
}
|
|
5276
|
-
catch {
|
|
5277
|
-
return { ok: true, name, state: 'stopped', registered: Boolean(existing), enabled: existing?.enabled ?? false,
|
|
5278
|
-
bindingId: existing?.bindingId ?? null };
|
|
5279
|
-
}
|
|
5280
|
-
}
|
|
5281
|
-
if (action === 'readiness') {
|
|
5282
|
-
if (!existing?.enabled)
|
|
5283
|
-
throw new Error('named_session_readiness_unavailable');
|
|
5284
|
-
let result;
|
|
5285
|
-
try {
|
|
5286
|
-
result = await namedSessionRequest(dharmaHome(), name, { action: 'readiness' }, { diagnostic });
|
|
5287
|
-
}
|
|
5288
|
-
catch (error) {
|
|
5289
|
-
if (diagnostic && error instanceof NamedSessionTransportFailure)
|
|
5290
|
-
return namedSessionReadinessDiagnostic(existing, error);
|
|
5291
|
-
throw error;
|
|
5292
|
-
}
|
|
5293
|
-
if (Object.keys(result).sort().join(',') !== 'bindingId,deviceId,endpointId,membershipId,name,nativeSkill,ok,organizationId,provider,repositoryBindingId,schema,sessionId,workspaceId'
|
|
5294
|
-
|| result.schema !== 'dharma.named-session-readiness/v1' || result.name !== name || result.bindingId !== existing.bindingId
|
|
5295
|
-
|| typeof result.sessionId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(result.sessionId)
|
|
5296
|
-
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
5297
|
-
throw new Error('named_session_readiness_scope_mismatch');
|
|
5334
|
+
let startupAdmission;
|
|
5335
|
+
const releaseStartupAdmission = async () => {
|
|
5336
|
+
const release = startupAdmission;
|
|
5337
|
+
startupAdmission = undefined;
|
|
5338
|
+
await release?.();
|
|
5339
|
+
};
|
|
5340
|
+
try {
|
|
5341
|
+
if (['start', 'serve'].includes(action) && flags.has('apply')) {
|
|
5342
|
+
startupAdmission = await step(() => acquireNamedSessionStartupAdmission());
|
|
5298
5343
|
}
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
...(action === 'stop' ? ['drained'] : action === 'resume' ? ['recover-stopped'] : [])];
|
|
5307
|
-
if (hostScope || supervisorOwner || setupSessionScope || !existing
|
|
5308
|
-
|| [...flags.keys()].some(key => !allowed.includes(key))
|
|
5309
|
-
|| ['apply', 'dry-run', 'drained', 'recover-stopped', 'json'].some(key => flags.has(key) && flags.get(key) !== true)
|
|
5310
|
-
|| flags.has('apply') && flags.has('dry-run'))
|
|
5311
|
-
throw new Error('named_session_maintenance_unavailable');
|
|
5312
|
-
const fenceId = required(flags, 'fence-id');
|
|
5313
|
-
if (!UUID_PATTERN.test(fenceId))
|
|
5314
|
-
throw new Error('named_session_fence_invalid');
|
|
5315
|
-
const requestAction = action === 'stop' ? 'stop-drained' : action;
|
|
5316
|
-
if (!flags.has('apply'))
|
|
5317
|
-
return { ok: true, planned: true, name, workspaceId, action: requestAction, fenceId,
|
|
5318
|
-
mode: flags.has('recover-stopped') ? 'stopped_recovery' : 'live' };
|
|
5319
|
-
if (action === 'resume' && flags.has('recover-stopped'))
|
|
5320
|
-
return withRelayStartupMutation(async () => {
|
|
5321
|
-
let vault;
|
|
5322
|
-
try {
|
|
5323
|
-
return await recoverStoppedNamedSession({ registration: existing, fenceId, workspace: item.path,
|
|
5324
|
-
processState: () => pidProcessState(resolve(paths.root, 'service.lock')),
|
|
5325
|
-
readRegistration: () => readNamedSession(dharmaHome(), name),
|
|
5326
|
-
readBinding: async () => {
|
|
5327
|
-
vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
|
|
5328
|
-
return vault.getProviderSessionBinding(existing.bindingId, existing.identity);
|
|
5329
|
-
},
|
|
5330
|
-
saveRegistration: value => saveNamedSession(dharmaHome(), value) });
|
|
5331
|
-
}
|
|
5332
|
-
finally {
|
|
5333
|
-
await vault?.close();
|
|
5334
|
-
}
|
|
5335
|
-
});
|
|
5336
|
-
return maintainNamedSession({ action: requestAction, fenceId,
|
|
5337
|
-
registration: existing, request: body => namedSessionRequest(dharmaHome(), name, body) });
|
|
5338
|
-
}
|
|
5339
|
-
if (action === 'audit') {
|
|
5340
|
-
if (!existing?.enabled || [...flags.keys()].some(key => !['name', 'workspace-id', 'work-id'].includes(key))) {
|
|
5341
|
-
throw new Error('named_session_audit_unavailable');
|
|
5342
|
-
}
|
|
5343
|
-
const workId = required(flags, 'work-id');
|
|
5344
|
-
if (!/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(workId))
|
|
5345
|
-
throw new Error('named_session_work_invalid');
|
|
5346
|
-
const result = await namedSessionRequest(dharmaHome(), name, { action: 'audit', workId });
|
|
5347
|
-
if (!(await validateContract(resolve(dirname(fileURLToPath(import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/named-session-native-audit/v1', result)).ok
|
|
5348
|
-
|| result.bindingId !== existing.bindingId || result.workId !== workId
|
|
5349
|
-
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
5350
|
-
throw new Error('named_session_audit_scope_mismatch');
|
|
5344
|
+
let childMessage;
|
|
5345
|
+
if (flags.has('setup-handoff')) {
|
|
5346
|
+
if (action !== 'serve' || flags.get('setup-handoff') !== true
|
|
5347
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'apply', 'setup-handoff'].includes(key))) {
|
|
5348
|
+
throw new Error('setup_child_unavailable');
|
|
5349
|
+
}
|
|
5350
|
+
childMessage = await receiveCodexSetupChildStart({ process, signal: new AbortController().signal });
|
|
5351
5351
|
}
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
const
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
if (existing)
|
|
5367
|
-
await saveNamedSession(dharmaHome(), { ...existing, enabled: false });
|
|
5368
|
-
try {
|
|
5369
|
-
return await namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5352
|
+
const item = (await step(() => registry())).find(row => row.workspaceId === workspaceId);
|
|
5353
|
+
const config = await step(() => readDeviceConfig());
|
|
5354
|
+
if (!item || !config || item.organizationId !== config.organizationId)
|
|
5355
|
+
throw new Error('named_session_scope_mismatch');
|
|
5356
|
+
await assertBootstrapHostSource(item.path, String(item.repositoryRemoteHash || ''));
|
|
5357
|
+
const scope = repositoryRoleScope(item);
|
|
5358
|
+
const existing = await step(() => readNamedSession(dharmaHome(), name));
|
|
5359
|
+
if (setupSessionScope && (name !== setupSessionScope.request.name || workspaceId !== setupSessionScope.request.workspaceId
|
|
5360
|
+
|| existing && existing.identity.membershipId !== setupSessionScope.request.membershipId))
|
|
5361
|
+
throw new Error('setup_session_scope_changed');
|
|
5362
|
+
if (existing && (existing.identity.workspaceId !== item.workspaceId
|
|
5363
|
+
|| existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
|
|
5364
|
+
|| existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
|
|
5365
|
+
throw new Error('named_session_scope_mismatch');
|
|
5370
5366
|
}
|
|
5371
|
-
|
|
5372
|
-
|
|
5367
|
+
if (action === 'status') {
|
|
5368
|
+
try {
|
|
5369
|
+
return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
|
|
5370
|
+
}
|
|
5371
|
+
catch {
|
|
5372
|
+
return { ok: true, name, state: 'stopped', registered: Boolean(existing), enabled: existing?.enabled ?? false,
|
|
5373
|
+
bindingId: existing?.bindingId ?? null };
|
|
5374
|
+
}
|
|
5373
5375
|
}
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
await step(async () => verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item)));
|
|
5381
|
-
if (action === 'start') {
|
|
5382
|
-
if (existing && !existing.enabled) {
|
|
5383
|
-
if (existing.maintenanceFence) {
|
|
5384
|
-
if (hostScope || supervisorOwner || setupSessionScope || !flags.has('fence-id') || flags.get('apply') !== true
|
|
5385
|
-
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'fence-id', 'apply', 'json'].includes(key))) {
|
|
5386
|
-
throw new Error('named_session_quiesced');
|
|
5387
|
-
}
|
|
5388
|
-
await withRelayStartupMutation(() => activateMaintenanceRestart({ registration: existing, fenceId: required(flags, 'fence-id'),
|
|
5389
|
-
processState: () => pidProcessState(resolve(paths.root, 'service.lock')),
|
|
5390
|
-
readRegistration: () => readNamedSession(dharmaHome(), name), saveRegistration: value => saveNamedSession(dharmaHome(), value) }));
|
|
5376
|
+
if (action === 'readiness') {
|
|
5377
|
+
if (!existing?.enabled)
|
|
5378
|
+
throw new Error('named_session_readiness_unavailable');
|
|
5379
|
+
let result;
|
|
5380
|
+
try {
|
|
5381
|
+
result = await namedSessionRequest(dharmaHome(), name, { action: 'readiness' }, { diagnostic });
|
|
5391
5382
|
}
|
|
5392
|
-
|
|
5393
|
-
if (
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5383
|
+
catch (error) {
|
|
5384
|
+
if (diagnostic && error instanceof NamedSessionTransportFailure)
|
|
5385
|
+
return namedSessionReadinessDiagnostic(existing, error);
|
|
5386
|
+
throw error;
|
|
5387
|
+
}
|
|
5388
|
+
if (Object.keys(result).sort().join(',') !== 'bindingId,deviceId,endpointId,membershipId,name,nativeSkill,ok,organizationId,provider,repositoryBindingId,schema,sessionId,workspaceId'
|
|
5389
|
+
|| result.schema !== 'dharma.named-session-readiness/v1' || result.name !== name || result.bindingId !== existing.bindingId
|
|
5390
|
+
|| typeof result.sessionId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(result.sessionId)
|
|
5391
|
+
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
5392
|
+
throw new Error('named_session_readiness_scope_mismatch');
|
|
5393
|
+
}
|
|
5394
|
+
return { ...result, nativeSkill: parseNamedCodexSkillObservation(result.nativeSkill) };
|
|
5395
|
+
}
|
|
5396
|
+
if (action === 'work')
|
|
5397
|
+
return namedSessionRequest(dharmaHome(), name, { action: 'work',
|
|
5398
|
+
workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
|
|
5399
|
+
if (['quiesce', 'resume'].includes(action) || action === 'stop' && flags.has('drained')) {
|
|
5400
|
+
const allowed = ['name', 'workspace-id', 'fence-id', 'apply', 'dry-run', 'json',
|
|
5401
|
+
...(action === 'stop' ? ['drained'] : action === 'resume' ? ['recover-stopped'] : [])];
|
|
5402
|
+
if (hostScope || supervisorOwner || setupSessionScope || !existing
|
|
5403
|
+
|| [...flags.keys()].some(key => !allowed.includes(key))
|
|
5404
|
+
|| ['apply', 'dry-run', 'drained', 'recover-stopped', 'json'].some(key => flags.has(key) && flags.get(key) !== true)
|
|
5405
|
+
|| flags.has('apply') && flags.has('dry-run'))
|
|
5406
|
+
throw new Error('named_session_maintenance_unavailable');
|
|
5407
|
+
const fenceId = required(flags, 'fence-id');
|
|
5408
|
+
if (!UUID_PATTERN.test(fenceId))
|
|
5409
|
+
throw new Error('named_session_fence_invalid');
|
|
5410
|
+
const requestAction = action === 'stop' ? 'stop-drained' : action;
|
|
5411
|
+
if (!flags.has('apply'))
|
|
5412
|
+
return { ok: true, planned: true, name, workspaceId, action: requestAction, fenceId,
|
|
5413
|
+
mode: flags.has('recover-stopped') ? 'stopped_recovery' : 'live' };
|
|
5414
|
+
if (action === 'resume' && flags.has('recover-stopped'))
|
|
5415
|
+
return withRelayStartupMutation(async () => {
|
|
5416
|
+
let vault;
|
|
5417
|
+
try {
|
|
5418
|
+
return await recoverStoppedNamedSession({ registration: existing, fenceId, workspace: item.path,
|
|
5419
|
+
processState: () => pidProcessState(resolve(paths.root, 'service.lock')),
|
|
5420
|
+
readRegistration: () => readNamedSession(dharmaHome(), name),
|
|
5421
|
+
readBinding: async () => {
|
|
5422
|
+
vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
|
|
5423
|
+
return vault.getProviderSessionBinding(existing.bindingId, existing.identity);
|
|
5424
|
+
},
|
|
5425
|
+
saveRegistration: value => saveNamedSession(dharmaHome(), value) });
|
|
5426
|
+
}
|
|
5427
|
+
finally {
|
|
5428
|
+
await vault?.close();
|
|
5429
|
+
}
|
|
5430
|
+
});
|
|
5431
|
+
return maintainNamedSession({ action: requestAction, fenceId,
|
|
5432
|
+
registration: existing, request: body => namedSessionRequest(dharmaHome(), name, body) });
|
|
5433
|
+
}
|
|
5434
|
+
if (action === 'audit') {
|
|
5435
|
+
if (!existing?.enabled || [...flags.keys()].some(key => !['name', 'workspace-id', 'work-id'].includes(key))) {
|
|
5436
|
+
throw new Error('named_session_audit_unavailable');
|
|
5437
|
+
}
|
|
5438
|
+
const workId = required(flags, 'work-id');
|
|
5439
|
+
if (!/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(workId))
|
|
5440
|
+
throw new Error('named_session_work_invalid');
|
|
5441
|
+
const result = await namedSessionRequest(dharmaHome(), name, { action: 'audit', workId });
|
|
5442
|
+
if (!(await validateContract(resolve(dirname(fileURLToPath(import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/named-session-native-audit/v1', result)).ok
|
|
5443
|
+
|| result.bindingId !== existing.bindingId || result.workId !== workId
|
|
5444
|
+
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
5445
|
+
throw new Error('named_session_audit_scope_mismatch');
|
|
5400
5446
|
}
|
|
5447
|
+
return result;
|
|
5401
5448
|
}
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
for (const key of ['session-budget-cents', 'turn-budget-cents']) {
|
|
5411
|
-
if (flags.has(key))
|
|
5412
|
-
args.push(`--${key}`, required(flags, key));
|
|
5413
|
-
}
|
|
5414
|
-
let startupChild;
|
|
5415
|
-
try {
|
|
5416
|
-
await step(async () => {
|
|
5417
|
-
const start = () => {
|
|
5418
|
-
const child = spawn(process.execPath, args, { cwd: item.path, detached: !hostScope && !supervisorOwner,
|
|
5419
|
-
stdio: setupSessionScope ? ['ignore', 'ignore', 'pipe', 'ipc'] : ['ignore', 'ignore', 'pipe'], env: process.env });
|
|
5420
|
-
startupChild = observeNamedSessionStartupChild(child);
|
|
5421
|
-
return child;
|
|
5422
|
-
};
|
|
5423
|
-
if (supervisorOwner) {
|
|
5424
|
-
const child = await supervisorOwner.spawn(name, start);
|
|
5425
|
-
if (setupSessionScope)
|
|
5426
|
-
await sendCodexSetupChildStart({ owner: supervisorOwner, child, scope: setupSessionScope });
|
|
5427
|
-
return;
|
|
5428
|
-
}
|
|
5429
|
-
const child = start();
|
|
5430
|
-
if (hostScope)
|
|
5431
|
-
captureBootstrapHostChild(hostScope, child);
|
|
5432
|
-
else {
|
|
5433
|
-
child.on('error', () => { });
|
|
5434
|
-
child.unref();
|
|
5449
|
+
if (action === 'stop') {
|
|
5450
|
+
if (!flags.has('apply'))
|
|
5451
|
+
return { ok: true, planned: true, name, action: 'stop' };
|
|
5452
|
+
const current = await namedSessionRequest(dharmaHome(), name, { action: 'status' }).catch(() => null);
|
|
5453
|
+
if (current?.admission) {
|
|
5454
|
+
if (!existing || current.bindingId !== existing.bindingId
|
|
5455
|
+
|| Object.entries(existing.identity).some(([key, value]) => current[key] !== value)) {
|
|
5456
|
+
throw new Error('named_session_scope_mismatch');
|
|
5435
5457
|
}
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
maximumWaitMs: setupSessionScope
|
|
5442
|
-
? Math.min(120_000, Date.parse(setupSessionScope.request.expiresAt) - Date.now()) : 120_000 });
|
|
5443
|
-
}
|
|
5444
|
-
finally {
|
|
5445
|
-
startupChild?.dispose();
|
|
5446
|
-
}
|
|
5447
|
-
}
|
|
5448
|
-
await mkdir(paths.root, { recursive: true, mode: 0o700 });
|
|
5449
|
-
const vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') })
|
|
5450
|
-
.catch(error => { throw namedSessionStartupPhaseError('vault', error); });
|
|
5451
|
-
let releaseLock;
|
|
5452
|
-
const controller = new AbortController(), stop = () => controller.abort();
|
|
5453
|
-
process.once('SIGTERM', stop);
|
|
5454
|
-
process.once('SIGINT', stop);
|
|
5455
|
-
if (childMessage)
|
|
5456
|
-
process.once('disconnect', stop);
|
|
5457
|
-
let transport;
|
|
5458
|
-
try {
|
|
5459
|
-
releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running')
|
|
5460
|
-
.catch(error => { throw namedSessionStartupPhaseError('lock', error); });
|
|
5461
|
-
// A startup poll may predate quiescence. Recheck durable admission after winning the owner lock.
|
|
5462
|
-
assertNamedSessionStartupRegistration(existing, await step(() => readNamedSession(dharmaHome(), name)));
|
|
5463
|
-
const startProvider = async (childScope) => {
|
|
5464
|
-
let startupPhase = 'client';
|
|
5458
|
+
// The capable service rejects an unfenced stop without changing its registration.
|
|
5459
|
+
return namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5460
|
+
}
|
|
5461
|
+
if (existing)
|
|
5462
|
+
await saveNamedSession(dharmaHome(), { ...existing, enabled: false });
|
|
5465
5463
|
try {
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
}
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
.map(path => path.slice(0, -3));
|
|
5485
|
-
if (!writeRoots.length)
|
|
5486
|
-
throw new Error('named_session_workspace_write_not_authorized');
|
|
5487
|
-
startupPhase = 'registration';
|
|
5488
|
-
let registration = existing, pending;
|
|
5489
|
-
let ownerReceipt;
|
|
5490
|
-
// Resolve authenticated ownership before opening any provider transport.
|
|
5491
|
-
if (registration) {
|
|
5492
|
-
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5493
|
-
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
5494
|
-
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5495
|
-
bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5496
|
-
}));
|
|
5497
|
-
if (!isNamedSessionReconnectReceipt(response, registration.bindingId, registration.identity)) {
|
|
5498
|
-
throw new Error('named_session_registration_invalid');
|
|
5464
|
+
return await namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5465
|
+
}
|
|
5466
|
+
catch {
|
|
5467
|
+
return { ok: true, name, state: 'stopped', enabled: false };
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
if (!['start', 'serve'].includes(action))
|
|
5471
|
+
throw new Error('named_session_action_invalid');
|
|
5472
|
+
if (!flags.has('apply'))
|
|
5473
|
+
return { ok: true, planned: true, name, workspaceId,
|
|
5474
|
+
provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
|
|
5475
|
+
await step(async () => verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item)));
|
|
5476
|
+
if (action === 'start') {
|
|
5477
|
+
if (existing && !existing.enabled) {
|
|
5478
|
+
if (existing.maintenanceFence) {
|
|
5479
|
+
if (hostScope || supervisorOwner || setupSessionScope || !flags.has('fence-id') || flags.get('apply') !== true
|
|
5480
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'fence-id', 'apply', 'json'].includes(key))) {
|
|
5481
|
+
throw new Error('named_session_quiesced');
|
|
5499
5482
|
}
|
|
5500
|
-
|
|
5483
|
+
await activateMaintenanceRestart({ registration: existing, fenceId: required(flags, 'fence-id'),
|
|
5484
|
+
processState: () => pidProcessState(resolve(paths.root, 'service.lock')),
|
|
5485
|
+
readRegistration: () => readNamedSession(dharmaHome(), name), saveRegistration: value => saveNamedSession(dharmaHome(), value) });
|
|
5501
5486
|
}
|
|
5502
5487
|
else {
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
const
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5509
|
-
bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5510
|
-
}));
|
|
5511
|
-
const remote = response.registration;
|
|
5512
|
-
if (!remote || !UUID_PATTERN.test(String(remote.membershipId))
|
|
5513
|
-
|| childScope && remote.membershipId !== childScope.request.membershipId || remote.revision !== 1) {
|
|
5514
|
-
throw new Error('named_session_registration_invalid');
|
|
5488
|
+
if (supervisorOwner)
|
|
5489
|
+
throw new Error('named_session_disabled');
|
|
5490
|
+
const live = await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' })).catch(() => null);
|
|
5491
|
+
if (live?.admission && live.admission.state !== 'open') {
|
|
5492
|
+
throw new Error('named_session_quiesced');
|
|
5515
5493
|
}
|
|
5516
|
-
|
|
5517
|
-
workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
|
|
5518
|
-
provider: 'codex' };
|
|
5519
|
-
if (!isNamedSessionOwnerReceipt(response, bindingId, identity))
|
|
5520
|
-
throw new Error('named_session_registration_invalid');
|
|
5521
|
-
ownerReceipt = response;
|
|
5522
|
-
pending = { schema: 'dharma.named-session/v1', name, bindingId, identity,
|
|
5523
|
-
maximumCostCents, maximumTurnCostCents, enabled: true };
|
|
5494
|
+
await step(() => saveNamedSession(dharmaHome(), { ...existing, enabled: true }));
|
|
5524
5495
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5496
|
+
}
|
|
5497
|
+
try {
|
|
5498
|
+
return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
|
|
5499
|
+
}
|
|
5500
|
+
catch { /* Start an owned worker below. */ }
|
|
5501
|
+
const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
|
|
5502
|
+
'--workspace-id', workspaceId, '--apply'];
|
|
5503
|
+
if (setupSessionScope)
|
|
5504
|
+
args.push('--setup-handoff');
|
|
5505
|
+
for (const key of ['session-budget-cents', 'turn-budget-cents']) {
|
|
5506
|
+
if (flags.has(key))
|
|
5507
|
+
args.push(`--${key}`, required(flags, key));
|
|
5508
|
+
}
|
|
5509
|
+
let startupChild;
|
|
5510
|
+
try {
|
|
5511
|
+
// The child takes this same admission lock before private startup. Never
|
|
5512
|
+
// wait for it while holding its parent-side registration/enablement lock.
|
|
5513
|
+
await releaseStartupAdmission();
|
|
5514
|
+
await step(async () => {
|
|
5515
|
+
const start = () => {
|
|
5516
|
+
const child = spawn(process.execPath, args, { cwd: item.path, detached: !hostScope && !supervisorOwner,
|
|
5517
|
+
stdio: setupSessionScope ? ['ignore', 'ignore', 'pipe', 'ipc'] : ['ignore', 'ignore', 'pipe'], env: process.env });
|
|
5518
|
+
startupChild = observeNamedSessionStartupChild(child);
|
|
5519
|
+
return child;
|
|
5520
|
+
};
|
|
5521
|
+
if (supervisorOwner) {
|
|
5522
|
+
const child = await supervisorOwner.spawn(name, start);
|
|
5523
|
+
if (setupSessionScope)
|
|
5524
|
+
await sendCodexSetupChildStart({ owner: supervisorOwner, child, scope: setupSessionScope });
|
|
5525
|
+
return;
|
|
5526
|
+
}
|
|
5527
|
+
const child = start();
|
|
5528
|
+
if (hostScope)
|
|
5529
|
+
captureBootstrapHostChild(hostScope, child);
|
|
5530
|
+
else {
|
|
5531
|
+
child.on('error', () => { });
|
|
5532
|
+
child.unref();
|
|
5542
5533
|
}
|
|
5543
|
-
transport = await openCodexAppServerTransport({ command: filesystem.providerExecutable, cwd: item.path,
|
|
5544
|
-
environment: filesystem.environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
|
|
5545
|
-
argv: [...namedCodexNodeOutputArguments(), '-c', 'allow_login_shell=false', '-c', 'default_permissions="dharma_bridge"',
|
|
5546
|
-
'-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
|
|
5547
|
-
'-c', 'permissions.dharma_bridge.network={enabled=false}',
|
|
5548
|
-
'-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
|
|
5549
|
-
'-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
|
|
5550
|
-
return transport;
|
|
5551
5534
|
});
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
const threadId = await startupStep(() => startNamedCodexThread(transport, item.path, name));
|
|
5559
|
-
startupPhase = 'binding';
|
|
5560
|
-
await startupStep(async () => vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...pending.identity,
|
|
5561
|
-
bindingId: pending.bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
|
|
5562
|
-
createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
5563
|
-
maximumProviderCostCents: pending.maximumTurnCostCents }));
|
|
5564
|
-
registration = pending;
|
|
5565
|
-
await startupStep(async () => saveNamedSession(dharmaHome(), registration));
|
|
5566
|
-
}
|
|
5567
|
-
return { registration: registration, filesystem, fabric, trust, policyPath, writeRoots };
|
|
5568
|
-
}
|
|
5569
|
-
catch (error) {
|
|
5570
|
-
throw namedSessionStartupPhaseError(startupPhase, error);
|
|
5535
|
+
return await waitForNamedSessionStartup({ step,
|
|
5536
|
+
assertActive: async () => { await hostScope?.assert(); },
|
|
5537
|
+
observe: () => namedSessionRequest(dharmaHome(), name, { action: 'status' }),
|
|
5538
|
+
readFailure: () => startupChild?.readFailure(),
|
|
5539
|
+
maximumWaitMs: setupSessionScope
|
|
5540
|
+
? Math.min(120_000, Date.parse(setupSessionScope.request.expiresAt) - Date.now()) : 120_000 });
|
|
5571
5541
|
}
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
const
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
send: body => fabric.signedPost('/agent-fabric/codex-task-exports', body) }),
|
|
5631
|
-
withActivationBoundary: (operation, work) => withWorkspaceSkillActivationLock(workspaceId, 'codex', async () => {
|
|
5632
|
-
await refreshLifetime();
|
|
5633
|
-
const skill = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5634
|
-
const sharedReady = await repositorySharedReady(item);
|
|
5635
|
-
const packageContent = await readNamedSessionPackageContent(skill, sharedReady);
|
|
5636
|
-
const bundleId = packageContent.bundleId;
|
|
5637
|
-
taskState = null;
|
|
5638
|
-
taskStateFailure = 'source_state_unavailable';
|
|
5639
|
-
if (work) {
|
|
5640
|
-
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5641
|
-
if (current.evidence.automaticDisclosure?.mode === 'customer_authorized_content' && skill.activeBundleHash) {
|
|
5642
|
-
try {
|
|
5643
|
-
taskStateFailure = 'public_context_unavailable';
|
|
5644
|
-
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5645
|
-
if (!binding)
|
|
5646
|
-
throw new Error('named_session_binding_unavailable');
|
|
5647
|
-
const retained = await readCodexPublicContext(transport, { threadId: binding.sessionId, workspaceRoot: binding.workspaceRoot });
|
|
5648
|
-
taskStateFailure = 'runtime_version_unavailable';
|
|
5649
|
-
const version = (await execFileAsync('codex', ['--version'], { timeout: 10_000, maxBuffer: 4096,
|
|
5650
|
-
env: providerProcessEnvironment(process.env) })).stdout.trim();
|
|
5651
|
-
if (!/^codex-cli [0-9]+\.[0-9]+\.[0-9]+(?:[-+][a-zA-Z0-9.-]+)?$/.test(version)) {
|
|
5652
|
-
throw new Error('named_session_runtime_version_unavailable');
|
|
5653
|
-
}
|
|
5654
|
-
const contextContentHash = await vault.putBlob(Buffer.from(retained.bytes), 'named-session-public-context');
|
|
5655
|
-
if (contextContentHash !== retained.contextHash)
|
|
5656
|
-
throw new Error('named_session_context_integrity_failed');
|
|
5657
|
-
taskStateFailure = 'source_state_unavailable';
|
|
5658
|
-
const before = await taskSnapshot();
|
|
5659
|
-
const sourceContentHash = await vault.putBlob(Buffer.from(serializeRepositoryPackageSnapshot(before)), 'named-session-source-state');
|
|
5660
|
-
await vault.putBlob(Buffer.from(JSON.stringify({ workId: work.workId, bindingId: registration.bindingId,
|
|
5661
|
-
sourceContentHash, sourceSnapshotHash: before.manifest.snapshotHash, bundleId, bundleHash: skill.activeBundleHash,
|
|
5662
|
-
packageContent, contextContentHash, evidenceRevision: current.revision,
|
|
5663
|
-
consentReceiptId: current.evidence.automaticDisclosure.consentReceiptId })), 'named-session-source-boundary');
|
|
5664
|
-
taskState = { workId: work.workId, before, bundleId, bundleHash: skill.activeBundleHash, packageContent,
|
|
5665
|
-
evidenceRevision: current.revision, consentReceiptId: current.evidence.automaticDisclosure.consentReceiptId,
|
|
5666
|
-
providerContext: { retainedContextHash: retained.contextHash, contextContentHash,
|
|
5667
|
-
runtimeVersion: version.slice('codex-cli '.length), requestedModel: retained.context.configuredModel,
|
|
5668
|
-
executedModel: null, replayMode: 'task_level' } };
|
|
5542
|
+
finally {
|
|
5543
|
+
startupChild?.dispose();
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
await mkdir(paths.root, { recursive: true, mode: 0o700 });
|
|
5547
|
+
const vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') })
|
|
5548
|
+
.catch(error => { throw namedSessionStartupPhaseError('vault', error); });
|
|
5549
|
+
let releaseLock;
|
|
5550
|
+
const controller = new AbortController(), stop = () => controller.abort();
|
|
5551
|
+
process.once('SIGTERM', stop);
|
|
5552
|
+
process.once('SIGINT', stop);
|
|
5553
|
+
if (childMessage)
|
|
5554
|
+
process.once('disconnect', stop);
|
|
5555
|
+
let transport;
|
|
5556
|
+
try {
|
|
5557
|
+
releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running')
|
|
5558
|
+
.catch(error => { throw namedSessionStartupPhaseError('lock', error); });
|
|
5559
|
+
// A startup poll may predate quiescence. Recheck durable admission after winning the owner lock.
|
|
5560
|
+
assertNamedSessionStartupRegistration(existing, await step(() => readNamedSession(dharmaHome(), name)));
|
|
5561
|
+
// Once the per-service lock is held, maintenance cannot acquire its stopped
|
|
5562
|
+
// topology. Release home-wide admission before the long-lived provider loop.
|
|
5563
|
+
await releaseStartupAdmission();
|
|
5564
|
+
const startProvider = async (childScope) => {
|
|
5565
|
+
let startupPhase = 'client';
|
|
5566
|
+
try {
|
|
5567
|
+
const startupStep = (operation) => childScope ? childScope.step(operation) : operation();
|
|
5568
|
+
if (childScope && existing && existing.identity.membershipId !== childScope.request.membershipId) {
|
|
5569
|
+
throw new Error('setup_child_unavailable');
|
|
5570
|
+
}
|
|
5571
|
+
const fabric = await startupStep(() => AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json'),
|
|
5572
|
+
hostScope: currentBootstrapHostScope() }));
|
|
5573
|
+
if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
|
|
5574
|
+
|| fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
|
|
5575
|
+
throw new Error('named_session_trust_scope_mismatch');
|
|
5576
|
+
startupPhase = 'trust';
|
|
5577
|
+
const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
|
|
5578
|
+
await startupStep(() => trust.refresh());
|
|
5579
|
+
startupPhase = 'session';
|
|
5580
|
+
await startupStep(() => fabric.openSession(VERSION));
|
|
5581
|
+
startupPhase = 'policy';
|
|
5582
|
+
const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
|
|
5583
|
+
const policy = await startupStep(() => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric));
|
|
5584
|
+
const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
|
|
5585
|
+
.map(path => path.slice(0, -3));
|
|
5586
|
+
if (!writeRoots.length)
|
|
5587
|
+
throw new Error('named_session_workspace_write_not_authorized');
|
|
5588
|
+
startupPhase = 'registration';
|
|
5589
|
+
let registration = existing, pending;
|
|
5590
|
+
let ownerReceipt;
|
|
5591
|
+
// Resolve authenticated ownership before opening any provider transport.
|
|
5592
|
+
if (registration) {
|
|
5593
|
+
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5594
|
+
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
5595
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5596
|
+
bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5597
|
+
}));
|
|
5598
|
+
if (!isNamedSessionReconnectReceipt(response, registration.bindingId, registration.identity)) {
|
|
5599
|
+
throw new Error('named_session_registration_invalid');
|
|
5669
5600
|
}
|
|
5670
|
-
|
|
5601
|
+
ownerReceipt = response;
|
|
5671
5602
|
}
|
|
5603
|
+
else {
|
|
5604
|
+
const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
|
|
5605
|
+
const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
|
|
5606
|
+
const bindingId = randomUUID();
|
|
5607
|
+
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5608
|
+
schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
|
|
5609
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5610
|
+
bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5611
|
+
}));
|
|
5612
|
+
const remote = response.registration;
|
|
5613
|
+
if (!remote || !UUID_PATTERN.test(String(remote.membershipId))
|
|
5614
|
+
|| childScope && remote.membershipId !== childScope.request.membershipId || remote.revision !== 1) {
|
|
5615
|
+
throw new Error('named_session_registration_invalid');
|
|
5616
|
+
}
|
|
5617
|
+
const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
|
|
5618
|
+
workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
|
|
5619
|
+
provider: 'codex' };
|
|
5620
|
+
if (!isNamedSessionOwnerReceipt(response, bindingId, identity))
|
|
5621
|
+
throw new Error('named_session_registration_invalid');
|
|
5622
|
+
ownerReceipt = response;
|
|
5623
|
+
pending = { schema: 'dharma.named-session/v1', name, bindingId, identity,
|
|
5624
|
+
maximumCostCents, maximumTurnCostCents, enabled: true };
|
|
5625
|
+
}
|
|
5626
|
+
startupPhase = 'environment';
|
|
5627
|
+
const environment = namedCodexEnvironment(process.env);
|
|
5628
|
+
const privateRoots = [dharmaHome(), resolve(environment.CODEX_HOME || resolve(homedir(), '.codex'))];
|
|
5629
|
+
if (environment.XDG_RUNTIME_DIR)
|
|
5630
|
+
privateRoots.push(environment.XDG_RUNTIME_DIR);
|
|
5631
|
+
if (environment.DBUS_SESSION_BUS_ADDRESS)
|
|
5632
|
+
privateRoots.push(environment.DBUS_SESSION_BUS_ADDRESS.slice('unix:path='.length));
|
|
5633
|
+
startupPhase = 'filesystem';
|
|
5634
|
+
const filesystem = await startupStep(() => namedCodexFilesystem({ environment, workspace: item.path, privateRoots, writeRoots }));
|
|
5635
|
+
startupPhase = 'transport';
|
|
5636
|
+
await startupStep(async () => {
|
|
5637
|
+
const expectedOwner = registration ?? pending;
|
|
5638
|
+
const ownerConfirmed = registration
|
|
5639
|
+
? isNamedSessionReconnectReceipt(ownerReceipt, expectedOwner.bindingId, expectedOwner.identity)
|
|
5640
|
+
: isNamedSessionOwnerReceipt(ownerReceipt, expectedOwner.bindingId, expectedOwner.identity);
|
|
5641
|
+
if (!ownerConfirmed) {
|
|
5642
|
+
throw new Error('named_session_registration_invalid');
|
|
5643
|
+
}
|
|
5644
|
+
transport = await openCodexAppServerTransport({ command: filesystem.providerExecutable, cwd: item.path,
|
|
5645
|
+
environment: filesystem.environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
|
|
5646
|
+
argv: [...namedCodexNodeOutputArguments(), '-c', 'allow_login_shell=false', '-c', 'default_permissions="dharma_bridge"',
|
|
5647
|
+
'-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
|
|
5648
|
+
'-c', 'permissions.dharma_bridge.network={enabled=false}',
|
|
5649
|
+
'-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
|
|
5650
|
+
'-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
|
|
5651
|
+
return transport;
|
|
5652
|
+
});
|
|
5653
|
+
startupPhase = 'account';
|
|
5654
|
+
const account = await startupStep(() => transport.request('account/read', { refreshToken: false }));
|
|
5655
|
+
if (!account.account)
|
|
5656
|
+
throw new Error('named_session_provider_authentication_required');
|
|
5657
|
+
if (pending) {
|
|
5658
|
+
startupPhase = 'thread';
|
|
5659
|
+
const threadId = await startupStep(() => startNamedCodexThread(transport, item.path, name));
|
|
5660
|
+
const declaredRecoveryToolHash = inspectNamedCodexRecoveryToolDeclaration(transport, threadId);
|
|
5661
|
+
if (!declaredRecoveryToolHash)
|
|
5662
|
+
throw new Error('named_session_thread_invalid');
|
|
5663
|
+
startupPhase = 'binding';
|
|
5664
|
+
await startupStep(async () => vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...pending.identity,
|
|
5665
|
+
bindingId: pending.bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
|
|
5666
|
+
createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
5667
|
+
maximumProviderCostCents: pending.maximumTurnCostCents }));
|
|
5668
|
+
await startupStep(async () => vault.saveProviderSessionRecoveryDeclaration(pending.bindingId, pending.identity, declaredRecoveryToolHash));
|
|
5669
|
+
registration = pending;
|
|
5670
|
+
await startupStep(async () => saveNamedSession(dharmaHome(), registration));
|
|
5671
|
+
}
|
|
5672
|
+
return { registration: registration, filesystem, fabric, trust, policyPath, writeRoots };
|
|
5672
5673
|
}
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
return await operation(context);
|
|
5676
|
-
}
|
|
5677
|
-
finally {
|
|
5678
|
-
taskState = null;
|
|
5679
|
-
}
|
|
5680
|
-
}),
|
|
5681
|
-
retainRepositoryState: async (capture) => {
|
|
5682
|
-
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5683
|
-
if (current.evidence.automaticDisclosure?.mode !== 'customer_authorized_content') {
|
|
5684
|
-
return { state: 'not_authorized', acceptedLearningObservation: false };
|
|
5685
|
-
}
|
|
5686
|
-
if (!taskState)
|
|
5687
|
-
return { state: 'blocked', code: taskStateFailure, acceptedLearningObservation: false };
|
|
5688
|
-
if (current.revision !== taskState.evidenceRevision
|
|
5689
|
-
|| current.evidence.automaticDisclosure.consentReceiptId !== taskState.consentReceiptId) {
|
|
5690
|
-
throw new Error('named_session_repository_state_consent_changed');
|
|
5674
|
+
catch (error) {
|
|
5675
|
+
throw namedSessionStartupPhaseError(startupPhase, error);
|
|
5691
5676
|
}
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
contextHash: taskState.providerContext.retainedContextHash } });
|
|
5699
|
-
return { ...receipt, providerContext: taskState.providerContext,
|
|
5700
|
-
packageContent: { manifestHash: taskState.packageContent.manifestHash,
|
|
5701
|
-
catalogHash: taskState.packageContent.catalogHash, skillsHash: taskState.packageContent.skillsHash } };
|
|
5702
|
-
},
|
|
5703
|
-
authorizeLocalWork: async () => {
|
|
5677
|
+
};
|
|
5678
|
+
const { registration, filesystem, fabric, trust, policyPath, writeRoots } = childMessage
|
|
5679
|
+
? await runCodexSetupChildStartup({ vault: vault, message: childMessage, name, workspaceId,
|
|
5680
|
+
signal: controller.signal, authorize: qualifyCodexSetupChildRequest }, startProvider)
|
|
5681
|
+
: await startProvider();
|
|
5682
|
+
const authorizeLifetime = async () => {
|
|
5704
5683
|
const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5684
|
+
if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
|
|
5685
|
+
return false;
|
|
5686
|
+
const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5687
|
+
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
5688
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5689
|
+
bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5690
|
+
});
|
|
5691
|
+
return isNamedSessionOwnerReceipt(response, registration.bindingId, registration.identity);
|
|
5692
|
+
};
|
|
5693
|
+
const refreshLifetime = () => renewNamedSessionLifetime({ vault, bindingId: registration.bindingId,
|
|
5694
|
+
identity: registration.identity, trust, authorize: authorizeLifetime });
|
|
5695
|
+
await refreshLifetime().catch(error => { throw namedSessionStartupPhaseError('lifetime', error); });
|
|
5696
|
+
const channelTransport = { async signedPost(route, body) {
|
|
5697
|
+
await trust.refresh();
|
|
5698
|
+
const response = await fabric.signedPost(route, body);
|
|
5699
|
+
await trust.refresh();
|
|
5700
|
+
return response;
|
|
5701
|
+
} };
|
|
5702
|
+
// Fresh server acceptance and this live-owner claim fence peer execution;
|
|
5703
|
+
// durable reservations retain cost even when delivery or execution is uncertain.
|
|
5704
|
+
const consumed = new Set();
|
|
5705
|
+
let taskState = null;
|
|
5706
|
+
let taskStateFailure = 'source_state_unavailable';
|
|
5707
|
+
const taskSnapshot = async () => {
|
|
5708
|
+
const sourceAuthorization = await fetchRepositorySourceAuthorization(fabric, repositoryRoleScope(item));
|
|
5709
|
+
return inventoryRepositoryPackage({ workspace: item.path, organizationId: item.organizationId,
|
|
5710
|
+
workspaceId, repositoryBindingId: item.repositoryBindingId, repositoryAgentId: item.repositoryAgentId, sourceAuthorization });
|
|
5711
|
+
};
|
|
5712
|
+
const readCurrentRepositoryContext = async (contentMode = 'references', referenceText) => readNamedSessionRepositoryContext({
|
|
5713
|
+
installation: await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }),
|
|
5714
|
+
sharedRepositoryReady: await repositorySharedReady(item), scope: repositoryRoleScope(item), workspaceRoot: item.path, contentMode, referenceText,
|
|
5715
|
+
loadAuthority: async () => ({ policy: await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
5716
|
+
source: await fetchRepositorySourceAuthorization(fabric, repositoryRoleScope(item)) })
|
|
5717
|
+
});
|
|
5718
|
+
return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
|
|
5719
|
+
localWriteRoots: writeRoots, additionalFilesystemRules: filesystem.additionalFilesystemRules,
|
|
5720
|
+
readPeerRepositoryContext: question => readCurrentRepositoryContext('verified_inline', question.question),
|
|
5721
|
+
observeNativeSkill: async () => {
|
|
5722
|
+
const installation = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5723
|
+
const native = await observeNamedCodexSkill(transport, { workspace: item.path, installation,
|
|
5724
|
+
sharedRepositoryReady: await repositorySharedReady(item) });
|
|
5725
|
+
const current = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5726
|
+
if (!current.signedLifecycleReady || current.activeBundleId !== native.bundleId
|
|
5727
|
+
|| current.activeBundleHash !== native.bundleHash || current.nativeSkillPath !== installation.nativeSkillPath
|
|
5728
|
+
|| !await repositorySharedReady(item))
|
|
5729
|
+
throw new Error('named_session_native_skill_unavailable');
|
|
5730
|
+
return native;
|
|
5731
|
+
},
|
|
5732
|
+
syncTaskExports: () => syncNamedSessionTaskExports({ vault, bindingId: registration.bindingId,
|
|
5733
|
+
identity: registration.identity,
|
|
5734
|
+
loadPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
5735
|
+
send: body => fabric.signedPost('/agent-fabric/codex-task-exports', body) }),
|
|
5736
|
+
withActivationBoundary: (operation, work) => withDeviceSigningControl({ config: fabric.config }, () => withWorkspaceSkillActivationLock(workspaceId, 'codex', async () => {
|
|
5737
|
+
await refreshLifetime();
|
|
5738
|
+
const skill = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5739
|
+
const sharedReady = await repositorySharedReady(item);
|
|
5740
|
+
const packageContent = await readNamedSessionPackageContent(skill, sharedReady);
|
|
5741
|
+
const bundleId = packageContent.bundleId;
|
|
5742
|
+
taskState = null;
|
|
5743
|
+
taskStateFailure = 'source_state_unavailable';
|
|
5744
|
+
if (work) {
|
|
5745
|
+
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5746
|
+
if (current.evidence.automaticDisclosure?.mode === 'customer_authorized_content' && skill.activeBundleHash) {
|
|
5747
|
+
try {
|
|
5748
|
+
taskStateFailure = 'public_context_unavailable';
|
|
5749
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5750
|
+
if (!binding)
|
|
5751
|
+
throw new Error('named_session_binding_unavailable');
|
|
5752
|
+
const retained = await readCodexPublicContext(transport, { threadId: binding.sessionId, workspaceRoot: binding.workspaceRoot });
|
|
5753
|
+
taskStateFailure = 'runtime_version_unavailable';
|
|
5754
|
+
const version = (await execFileAsync('codex', ['--version'], { timeout: 10_000, maxBuffer: 4096,
|
|
5755
|
+
env: providerProcessEnvironment(process.env) })).stdout.trim();
|
|
5756
|
+
if (!/^codex-cli [0-9]+\.[0-9]+\.[0-9]+(?:[-+][a-zA-Z0-9.-]+)?$/.test(version)) {
|
|
5757
|
+
throw new Error('named_session_runtime_version_unavailable');
|
|
5758
|
+
}
|
|
5759
|
+
const contextContentHash = await vault.putBlob(Buffer.from(retained.bytes), 'named-session-public-context');
|
|
5760
|
+
if (contextContentHash !== retained.contextHash)
|
|
5761
|
+
throw new Error('named_session_context_integrity_failed');
|
|
5762
|
+
taskStateFailure = 'source_state_unavailable';
|
|
5763
|
+
const before = await taskSnapshot();
|
|
5764
|
+
const sourceContentHash = await vault.putBlob(Buffer.from(serializeRepositoryPackageSnapshot(before)), 'named-session-source-state');
|
|
5765
|
+
await vault.putBlob(Buffer.from(JSON.stringify({ workId: work.workId, bindingId: registration.bindingId,
|
|
5766
|
+
sourceContentHash, sourceSnapshotHash: before.manifest.snapshotHash, bundleId, bundleHash: skill.activeBundleHash,
|
|
5767
|
+
packageContent, contextContentHash, evidenceRevision: current.revision,
|
|
5768
|
+
consentReceiptId: current.evidence.automaticDisclosure.consentReceiptId })), 'named-session-source-boundary');
|
|
5769
|
+
taskState = { workId: work.workId, before, bundleId, bundleHash: skill.activeBundleHash, packageContent,
|
|
5770
|
+
evidenceRevision: current.revision, consentReceiptId: current.evidence.automaticDisclosure.consentReceiptId,
|
|
5771
|
+
providerContext: { retainedContextHash: retained.contextHash, contextContentHash,
|
|
5772
|
+
runtimeVersion: version.slice('codex-cli '.length), requestedModel: retained.context.configuredModel,
|
|
5773
|
+
executedModel: null, replayMode: 'task_level' } };
|
|
5774
|
+
}
|
|
5775
|
+
catch { /* Source capture failure must not replay or rewrite a coding task. */ }
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
try {
|
|
5779
|
+
const context = work ? await readCurrentRepositoryContext() : undefined;
|
|
5780
|
+
return await operation(context);
|
|
5781
|
+
}
|
|
5782
|
+
finally {
|
|
5783
|
+
taskState = null;
|
|
5784
|
+
}
|
|
5785
|
+
})),
|
|
5786
|
+
retainRepositoryState: async (capture) => {
|
|
5787
|
+
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5788
|
+
if (current.evidence.automaticDisclosure?.mode !== 'customer_authorized_content') {
|
|
5789
|
+
return { state: 'not_authorized', acceptedLearningObservation: false };
|
|
5790
|
+
}
|
|
5791
|
+
if (!taskState)
|
|
5792
|
+
return { state: 'blocked', code: taskStateFailure, acceptedLearningObservation: false };
|
|
5793
|
+
if (current.revision !== taskState.evidenceRevision
|
|
5794
|
+
|| current.evidence.automaticDisclosure.consentReceiptId !== taskState.consentReceiptId) {
|
|
5795
|
+
throw new Error('named_session_repository_state_consent_changed');
|
|
5796
|
+
}
|
|
5797
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5798
|
+
if (!binding)
|
|
5799
|
+
throw new Error('named_session_binding_unavailable');
|
|
5800
|
+
const receipt = await retainNamedSessionRepositoryState({ vault, binding, capture, workId: taskState.workId,
|
|
5801
|
+
before: taskState.before, after: await taskSnapshot(), activeBundleId: taskState.bundleId, activeBundleHash: taskState.bundleHash,
|
|
5802
|
+
taskExport: { policy: current, contextBytes: await vault.getBlob(taskState.providerContext.contextContentHash),
|
|
5803
|
+
contextHash: taskState.providerContext.retainedContextHash } });
|
|
5804
|
+
return { ...receipt, providerContext: taskState.providerContext,
|
|
5805
|
+
packageContent: { manifestHash: taskState.packageContent.manifestHash,
|
|
5806
|
+
catalogHash: taskState.packageContent.catalogHash, skillsHash: taskState.packageContent.skillsHash } };
|
|
5807
|
+
},
|
|
5808
|
+
authorizeLocalWork: async () => {
|
|
5809
|
+
const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
|
|
5810
|
+
return writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`));
|
|
5811
|
+
},
|
|
5812
|
+
queueEvidence: async (capture) => {
|
|
5813
|
+
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5814
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5815
|
+
if (!binding)
|
|
5816
|
+
throw new Error('named_session_binding_unavailable');
|
|
5817
|
+
// The existing relay rechecks current authority, disclosure limits and device binding before upload.
|
|
5818
|
+
return queueNamedSessionEvidence({ vault, capture, binding, policy: current });
|
|
5819
|
+
},
|
|
5820
|
+
openTransport: async () => transport, channelTransport,
|
|
5821
|
+
verifier: { resolvePublicKey: trust.resolvePublicKey, consume: async (id) => { if (consumed.has(id))
|
|
5822
|
+
return false; consumed.add(id); return true; } },
|
|
5823
|
+
authorizeContent: createNamedPeerContentAuthorization({ scope: repositoryRoleScope(item), diagnostics: true,
|
|
5824
|
+
loadCurrentPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
5825
|
+
loadCurrentSourceAuthorization: () => fetchRepositorySourceAuthorization(fabric, repositoryRoleScope(item)) }) })
|
|
5826
|
+
.catch(error => { throw namedSessionStartupPhaseError('service', error); });
|
|
5727
5827
|
}
|
|
5728
5828
|
finally {
|
|
5729
5829
|
try {
|
|
5730
|
-
|
|
5830
|
+
if (transport)
|
|
5831
|
+
await transport.close();
|
|
5731
5832
|
}
|
|
5732
5833
|
finally {
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5834
|
+
try {
|
|
5835
|
+
await vault.close();
|
|
5836
|
+
}
|
|
5837
|
+
finally {
|
|
5838
|
+
await releaseLock?.();
|
|
5839
|
+
process.removeListener('SIGTERM', stop);
|
|
5840
|
+
process.removeListener('SIGINT', stop);
|
|
5841
|
+
if (childMessage)
|
|
5842
|
+
process.removeListener('disconnect', stop);
|
|
5843
|
+
}
|
|
5738
5844
|
}
|
|
5739
5845
|
}
|
|
5740
5846
|
}
|
|
5847
|
+
finally {
|
|
5848
|
+
await releaseStartupAdmission();
|
|
5849
|
+
}
|
|
5741
5850
|
}
|
|
5742
5851
|
async function workspaceSync(flags, positional) {
|
|
5743
5852
|
const workspaceId = positional[0] || required(flags, 'workspace-id');
|
|
@@ -9162,6 +9271,40 @@ export async function run(argv) {
|
|
|
9162
9271
|
return USAGE;
|
|
9163
9272
|
if (flags.has('version') || command === 'version')
|
|
9164
9273
|
return { version: VERSION };
|
|
9274
|
+
if (command === 'signing-recovery') {
|
|
9275
|
+
if (positional.length !== 1 || [...repeated.values()].some(values => values.length !== 1)
|
|
9276
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'operation-id', 'apply', 'json'].includes(key))
|
|
9277
|
+
|| flags.has('apply') && flags.get('apply') !== true)
|
|
9278
|
+
throw Error('device_signing_maintenance_options_invalid');
|
|
9279
|
+
const home = dharmaHome(), name = required(flags, 'name'), workspaceId = required(flags, 'workspace-id');
|
|
9280
|
+
const controller = new AbortController(), stop = () => controller.abort();
|
|
9281
|
+
process.once('SIGTERM', stop);
|
|
9282
|
+
process.once('SIGINT', stop);
|
|
9283
|
+
const execute = (legacyBoundary) => runDeviceSigningMaintenanceController({ home, configPath: configPath(), name, workspaceId,
|
|
9284
|
+
operationId: typeof flags.get('operation-id') === 'string' ? String(flags.get('operation-id')) : undefined, apply: flags.get('apply') === true,
|
|
9285
|
+
readWorkspace: async () => {
|
|
9286
|
+
const selected = (await registry()).find(value => value.workspaceId === workspaceId);
|
|
9287
|
+
return selected && selected.repositoryBindingId && selected.repositoryRemoteHash ? { organizationId: selected.organizationId, workspaceId: selected.workspaceId,
|
|
9288
|
+
repositoryBindingId: selected.repositoryBindingId, repositoryRemoteHash: selected.repositoryRemoteHash, path: selected.path } : null;
|
|
9289
|
+
}, processState: legacyBoundary ? async () => { await legacyBoundary.assertHeld(); return 'stopped'; } : () => deviceTrustMaintenanceProcessState(home, name),
|
|
9290
|
+
legacyBoundary,
|
|
9291
|
+
openVault: () => openBootstrapVault({ root: resolve(home, 'vault') }), signal: controller.signal, frames: process.stdin,
|
|
9292
|
+
emit: value => process.stdout.write(JSON.stringify(value) + '\n') });
|
|
9293
|
+
try {
|
|
9294
|
+
return await (flags.get('apply') === true
|
|
9295
|
+
? withLegacySigningMaintenanceBoundary({ home, name, signal: controller.signal, assertManagedCohort: async () => {
|
|
9296
|
+
const selected = (await registry()).find(value => value.workspaceId === workspaceId);
|
|
9297
|
+
if (!selected)
|
|
9298
|
+
throw Error('device_signing_maintenance_scope_unavailable');
|
|
9299
|
+
return assertSigningMaintenanceManagedCohort(home, selected.path);
|
|
9300
|
+
} }, execute) : execute());
|
|
9301
|
+
}
|
|
9302
|
+
finally {
|
|
9303
|
+
controller.abort();
|
|
9304
|
+
process.removeListener('SIGTERM', stop);
|
|
9305
|
+
process.removeListener('SIGINT', stop);
|
|
9306
|
+
}
|
|
9307
|
+
}
|
|
9165
9308
|
if (command === 'demo' && ['connect', 'status', 'role', 'peers', 'ask', 'reply', 'inbox', 'ack', 'resume',
|
|
9166
9309
|
'package', 'package-status', 'watch', 'watch-enable', 'watch-status', 'watch-disable',
|
|
9167
9310
|
'signing-client-proof', 'signing-owner-proof', 'transport-connect']
|