@dharma-ai-labs/agent-fabric 0.2.209 → 0.2.211
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 +2 -1
- package/dist/containerRelayLifecycle.d.ts.map +1 -1
- package/dist/containerRelayLifecycle.js +12 -0
- package/dist/containerRelayLifecycle.js.map +1 -1
- package/dist/containerRelayLifecycle.test.js +38 -6
- package/dist/containerRelayLifecycle.test.js.map +1 -1
- package/dist/containerRelayRuntimeTrust.test.js +10 -0
- package/dist/containerRelayRuntimeTrust.test.js.map +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 +723 -470
- 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/maintenanceControlCaller.test.d.ts +2 -0
- package/dist/maintenanceControlCaller.test.d.ts.map +1 -0
- package/dist/maintenanceControlCaller.test.js +119 -0
- package/dist/maintenanceControlCaller.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/namedSessionStoppedLock.d.ts +34 -0
- package/dist/namedSessionStoppedLock.d.ts.map +1 -0
- package/dist/namedSessionStoppedLock.js +108 -0
- package/dist/namedSessionStoppedLock.js.map +1 -0
- package/dist/namedSessionStoppedLock.test.d.ts +2 -0
- package/dist/namedSessionStoppedLock.test.d.ts.map +1 -0
- package/dist/namedSessionStoppedLock.test.js +104 -0
- package/dist/namedSessionStoppedLock.test.js.map +1 -0
- package/dist/providerSessionReplyRecovery.d.ts +1 -1
- package/dist/relayAutostart.d.ts +3 -0
- package/dist/relayAutostart.d.ts.map +1 -1
- package/dist/relayAutostart.js +45 -6
- package/dist/relayAutostart.js.map +1 -1
- package/dist/relayAutostart.test.js +49 -1
- package/dist/relayAutostart.test.js.map +1 -1
- package/dist/relayRuntimeUpgrade.d.ts +2 -1
- package/dist/relayRuntimeUpgrade.d.ts.map +1 -1
- package/dist/relayRuntimeUpgrade.js +3 -3
- package/dist/relayRuntimeUpgrade.js.map +1 -1
- package/dist/relayRuntimeUpgradeCaller.test.js +76 -0
- package/dist/relayRuntimeUpgradeCaller.test.js.map +1 -1
- package/dist/relaySigningMaintenanceUpgrade.d.ts +44 -0
- package/dist/relaySigningMaintenanceUpgrade.d.ts.map +1 -0
- package/dist/relaySigningMaintenanceUpgrade.js +249 -0
- package/dist/relaySigningMaintenanceUpgrade.js.map +1 -0
- package/dist/relaySigningMaintenanceUpgrade.test.d.ts +2 -0
- package/dist/relaySigningMaintenanceUpgrade.test.d.ts.map +1 -0
- package/dist/relaySigningMaintenanceUpgrade.test.js +230 -0
- package/dist/relaySigningMaintenanceUpgrade.test.js.map +1 -0
- 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/local-signing-maintenance-upgrade-journal.schema.json +34 -0
- package/dist/schemas/local-signing-maintenance-upgrade.schema.json +33 -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 +4 -2
- package/dist/usage.js.map +1 -1
- package/dist/wslRelayHost.d.ts +2 -0
- package/dist/wslRelayHost.d.ts.map +1 -1
- package/dist/wslRelayHost.js +35 -0
- package/dist/wslRelayHost.js.map +1 -1
- package/dist/wslRelayHost.test.js +81 -4
- package/dist/wslRelayHost.test.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -12,18 +12,22 @@ 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
|
-
import { assertRelayStartupOwnership, disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
24
|
+
import { assertRelayStartupOwnership, assertRelayAutostartPaused, disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
23
25
|
import { runOwnedContainerEntrypoint, readContainerProcessIdentity } from './containerRelayLifecycle.js';
|
|
24
26
|
import { readWorkspaceRegistry } from './workspaceRegistry.js';
|
|
25
27
|
import { appendRecoveredWorkspace, applyRegistryRecoveryFile, inspectRegistryRecoveryFile, resolveRegistryRecoveryProjection } from './workspaceRegistryRecovery.js';
|
|
26
28
|
import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
|
|
29
|
+
import { reconcileStoppedNamedSessionLock } from './namedSessionStoppedLock.js';
|
|
30
|
+
import { upgradeRelayForSigningMaintenance } from './relaySigningMaintenanceUpgrade.js';
|
|
27
31
|
import { activateMaintenanceRestart, maintainNamedSession, recoverStoppedNamedSession } from './namedSessionMaintenance.js';
|
|
28
32
|
import { assertNamedSessionStartupRegistration } from './namedSessionAdmission.js';
|
|
29
33
|
import { recoverableContainerJournal } from './containerRelayRecovery.js';
|
|
@@ -100,6 +104,8 @@ import { requestCodexSetupHostPreparation } from './codexSetupPortalPreparation.
|
|
|
100
104
|
import { readCodexSetupPublicStdin } from './codexSetupPublicStdin.js';
|
|
101
105
|
import { reserveCodexSetupLaunch } from './codexSetupLaunchReservation.js';
|
|
102
106
|
export { startCodexSetupNativeHost } from './codexSetupNativeHost.js';
|
|
107
|
+
export { attachCodexSigningRecoveryNativeHost } from './codexSigningRecoveryNativeHost.js';
|
|
108
|
+
export { openCodexSigningRecoveryOwner } from './codexBoundSession.js';
|
|
103
109
|
export { openCodexSetupVaultJournal } from './codexSetupVaultJournal.js';
|
|
104
110
|
import { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
|
|
105
111
|
export { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
|
|
@@ -107,7 +113,8 @@ import { assertCodexSetupExecutionLease } from './codexSetupAdmission.js';
|
|
|
107
113
|
import { parseLocalCodexSetupReadiness } from '@dharma-ai-labs/agent-fabric-local-vault/setup-readiness';
|
|
108
114
|
import { writeBootstrapHostJson, writeBootstrapHostText } from './bootstrapHostFiles.js';
|
|
109
115
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
110
|
-
|
|
116
|
+
import { inspectNamedCodexRecoveryToolDeclaration } from './codexSigningRecoveryTool.js';
|
|
117
|
+
const VERSION = '0.2.211';
|
|
111
118
|
const USAGE = CLI_USAGE;
|
|
112
119
|
const execFileAsync = promisify(execFile);
|
|
113
120
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -161,10 +168,37 @@ export function portalUrl(flags, fallback = 'https://www.dharma-ai.io') {
|
|
|
161
168
|
return String(flags.get('portal-url') || flags.get('hq-url') || fallback);
|
|
162
169
|
}
|
|
163
170
|
function print(value) {
|
|
164
|
-
process.stdout.write(
|
|
171
|
+
process.stdout.write(formatCommandOutput(value));
|
|
172
|
+
}
|
|
173
|
+
export function formatCommandOutput(value) {
|
|
174
|
+
if (typeof value === 'string')
|
|
175
|
+
return value + '\n';
|
|
176
|
+
const streaming = value && typeof value === 'object' && !Array.isArray(value)
|
|
177
|
+
&& value.schema === 'dharma.device-signing-maintenance-controller-progress/v1';
|
|
178
|
+
return JSON.stringify(value, null, streaming ? undefined : 2) + '\n';
|
|
165
179
|
}
|
|
166
180
|
export function commandExitCode(argv, value) {
|
|
167
181
|
const { positional, flags } = parseCliOptions(argv);
|
|
182
|
+
if (positional[0] === 'relay' && positional[1] === 'upgrade'
|
|
183
|
+
&& (flags.has('stage-signing-maintenance') || flags.has('finish-signing-maintenance'))) {
|
|
184
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
185
|
+
return 2;
|
|
186
|
+
const receipt = value;
|
|
187
|
+
if (receipt.schema !== 'dharma.local-signing-maintenance-upgrade/v1' || receipt.ready !== false
|
|
188
|
+
|| receipt.nativeContinuationAuthorized !== false)
|
|
189
|
+
return 2;
|
|
190
|
+
return flags.get('apply') !== true ? receipt.state === 'planned' ? 0 : 2
|
|
191
|
+
: flags.has('finish-signing-maintenance') ? receipt.state === 'completed' && receipt.upgradeComplete === true ? 0 : 2
|
|
192
|
+
: receipt.state === 'staged' && receipt.upgradeComplete === false ? 0 : 2;
|
|
193
|
+
}
|
|
194
|
+
if (positional[0] === 'signing-recovery') {
|
|
195
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
196
|
+
return 2;
|
|
197
|
+
const receipt = value, progress = receipt.progress;
|
|
198
|
+
return receipt.schema === 'dharma.device-signing-maintenance-controller-progress/v1' && receipt.ok === true
|
|
199
|
+
&& receipt.nativeContinuationAuthorized === false && receipt.ready === false
|
|
200
|
+
&& (flags.get('apply') === true ? progress?.state === 'trust_installed' && progress.clientTrustInstalled === true : receipt.planned === true) ? 0 : 2;
|
|
201
|
+
}
|
|
168
202
|
if (positional[0] === 'sessions' && positional[1] === 'readiness' && flags.get('diagnostic') === true) {
|
|
169
203
|
const diagnosticResult = value && typeof value === 'object' && !Array.isArray(value)
|
|
170
204
|
? value : null;
|
|
@@ -1044,7 +1078,7 @@ async function pidProcessState(pidPath) {
|
|
|
1044
1078
|
const code = error.code;
|
|
1045
1079
|
if (code === 'ESRCH')
|
|
1046
1080
|
return 'stopped';
|
|
1047
|
-
return
|
|
1081
|
+
return 'unknown';
|
|
1048
1082
|
}
|
|
1049
1083
|
}
|
|
1050
1084
|
export async function relayProcessState(home = dharmaHome()) {
|
|
@@ -1053,6 +1087,42 @@ export async function relayProcessState(home = dharmaHome()) {
|
|
|
1053
1087
|
export async function relaySupervisorProcessState(home = dharmaHome()) {
|
|
1054
1088
|
return pidProcessState(resolve(home, 'relay', 'supervisor.pid'));
|
|
1055
1089
|
}
|
|
1090
|
+
/** A legacy runtime may not participate in the new signing mutex. Require all
|
|
1091
|
+
* registrations belonging to this device disabled and their known owned PID
|
|
1092
|
+
* locks stopped, plus its relay/supervisor, before any maintenance store read.
|
|
1093
|
+
* This check never stops a process or changes startup/registration state. */
|
|
1094
|
+
export async function deviceTrustMaintenanceProcessState(home, name) {
|
|
1095
|
+
const selected = await readNamedSession(home, name);
|
|
1096
|
+
if (!selected)
|
|
1097
|
+
return 'unknown';
|
|
1098
|
+
const states = await Promise.all([relayProcessState(home), relaySupervisorProcessState(home)]);
|
|
1099
|
+
let entries;
|
|
1100
|
+
try {
|
|
1101
|
+
entries = await readdir(resolve(home, 'sessions'));
|
|
1102
|
+
}
|
|
1103
|
+
catch (error) {
|
|
1104
|
+
if (error.code !== 'ENOENT')
|
|
1105
|
+
return 'unknown';
|
|
1106
|
+
entries = [];
|
|
1107
|
+
}
|
|
1108
|
+
for (const entry of entries) {
|
|
1109
|
+
if (!/^[a-z][a-z0-9-]{0,47}$/.test(String(entry)))
|
|
1110
|
+
continue;
|
|
1111
|
+
let registration;
|
|
1112
|
+
try {
|
|
1113
|
+
registration = await readNamedSession(home, String(entry));
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
return 'unknown';
|
|
1117
|
+
}
|
|
1118
|
+
if (!registration)
|
|
1119
|
+
return 'unknown';
|
|
1120
|
+
if (registration.identity.deviceId !== selected.identity.deviceId || registration.identity.organizationId !== selected.identity.organizationId)
|
|
1121
|
+
continue;
|
|
1122
|
+
states.push(registration.enabled ? 'unknown' : await pidProcessState(resolve(namedSessionPaths(home, String(entry)).root, 'service.lock')));
|
|
1123
|
+
}
|
|
1124
|
+
return states.includes('running') ? 'running' : states.includes('unknown') ? 'unknown' : 'stopped';
|
|
1125
|
+
}
|
|
1056
1126
|
export async function acquireRelayProcessLease(home = dharmaHome()) {
|
|
1057
1127
|
const pidPath = resolve(home, 'relay', 'relay.pid');
|
|
1058
1128
|
const releaseLock = await acquirePidLock(`${pidPath}.lock`, 250, 'Another relay process is already running for this device.');
|
|
@@ -1228,6 +1298,35 @@ export async function withRelayStartupMutation(operation, home = dharmaHome()) {
|
|
|
1228
1298
|
await release?.();
|
|
1229
1299
|
}
|
|
1230
1300
|
}
|
|
1301
|
+
export async function acquireNamedSessionStartupAdmission(home = dharmaHome()) {
|
|
1302
|
+
return acquirePidLock(resolve(home, 'autostart-mutation.lock'), 250, 'named_session_startup_busy');
|
|
1303
|
+
}
|
|
1304
|
+
async function assertSigningMaintenanceManagedCohort(home, workspace) {
|
|
1305
|
+
const startup = await inspectOwnedRelayAutostart({ home });
|
|
1306
|
+
const bin = resolve(workspace, '.dharma', 'bin');
|
|
1307
|
+
if (startup.version !== VERSION || startup.workspace !== workspace
|
|
1308
|
+
|| startup.launcher !== resolve(bin, process.platform === 'win32' ? 'dharma.cmd' : 'dharma')
|
|
1309
|
+
|| startup.policy !== resolve(workspace, '.dharma', 'approved-policy.json')) {
|
|
1310
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1311
|
+
}
|
|
1312
|
+
const readLauncher = async (name) => {
|
|
1313
|
+
const path = resolve(bin, name), before = await lstat(path);
|
|
1314
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size > 65536 || await realpath(path) !== path) {
|
|
1315
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1316
|
+
}
|
|
1317
|
+
const contents = await readFile(path, 'utf8'), after = await lstat(path);
|
|
1318
|
+
if (after.ino !== before.ino || after.dev !== before.dev || after.size !== before.size
|
|
1319
|
+
|| after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || after.isSymbolicLink()) {
|
|
1320
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1321
|
+
}
|
|
1322
|
+
return contents;
|
|
1323
|
+
};
|
|
1324
|
+
const launchers = { shell: await readLauncher('dharma'), windows: await readLauncher('dharma.cmd') };
|
|
1325
|
+
if (!await verifyRecordedRepositoryLaunchers(VERSION, launchers)) {
|
|
1326
|
+
throw Error('device_signing_maintenance_managed_cohort_upgrade_required');
|
|
1327
|
+
}
|
|
1328
|
+
return sha256(JSON.stringify({ schema: 'dharma.managed-signing-maintenance-cohort/v1', startup, launchers }));
|
|
1329
|
+
}
|
|
1231
1330
|
export async function withWorkspacePolicyRefreshLock(workspaceId, operation) {
|
|
1232
1331
|
if (!workspaceId)
|
|
1233
1332
|
throw new Error('Content transmission requires a registered workspace ID.');
|
|
@@ -1929,6 +2028,15 @@ async function installStableRepositoryLauncher(workspace) {
|
|
|
1929
2028
|
return { shell: '.dharma/bin/dharma', windows: '.dharma/bin/dharma.cmd' };
|
|
1930
2029
|
}
|
|
1931
2030
|
async function relayUpgrade(flags) {
|
|
2031
|
+
const stageMaintenance = flags.has('stage-signing-maintenance'), finishMaintenance = flags.has('finish-signing-maintenance');
|
|
2032
|
+
const maintenance = stageMaintenance || finishMaintenance;
|
|
2033
|
+
if (maintenance && process.platform !== 'linux')
|
|
2034
|
+
throw new Error('relay_signing_maintenance_linux_required');
|
|
2035
|
+
if (stageMaintenance && finishMaintenance || maintenance && flags.has('rollback')
|
|
2036
|
+
|| ['stage-signing-maintenance', 'finish-signing-maintenance'].some(key => flags.has(key) && flags.get(key) !== true)
|
|
2037
|
+
|| maintenance && ['apply', 'dry-run'].some(key => flags.has(key) && flags.get(key) !== true)
|
|
2038
|
+
|| flags.has('previous-upgrade-sha256') && !stageMaintenance)
|
|
2039
|
+
throw new Error('relay_signing_maintenance_options_invalid');
|
|
1932
2040
|
const config = await readDeviceConfig();
|
|
1933
2041
|
if (!config)
|
|
1934
2042
|
throw new Error('relay_upgrade_enrollment_required');
|
|
@@ -1972,10 +2080,46 @@ async function relayUpgrade(flags) {
|
|
|
1972
2080
|
const npmCliPath = process.platform === 'linux' && !flags.has('rollback') ? await resolveRepositoryNpmCli() : undefined;
|
|
1973
2081
|
const codexDirectory = !flags.has('rollback')
|
|
1974
2082
|
? await resolveRepositoryCodexDirectory({ searchPath: providerProcessEnvironment(process.env).PATH }) : undefined;
|
|
1975
|
-
const
|
|
2083
|
+
const upgrade = maintenance ? upgradeRelayForSigningMaintenance : upgradeRelayRuntime;
|
|
2084
|
+
const receipt = await upgrade({ home, workspace, version: VERSION,
|
|
1976
2085
|
organizationId: config.organizationId, deviceId: config.deviceId, workspaceId: selected.workspaceId,
|
|
1977
|
-
dryRun: !flags.has('apply'), rollback: flags.has('rollback')
|
|
2086
|
+
dryRun: !flags.has('apply'), rollback: flags.has('rollback'),
|
|
2087
|
+
trustGeneration: config.serverSigningKeyset?.generation ?? 0, finish: finishMaintenance,
|
|
2088
|
+
...(flags.has('previous-upgrade-sha256') ? { previousUpgradeSha256: String(flags.get('previous-upgrade-sha256')) } : {}) }, {
|
|
1978
2089
|
assertStopped,
|
|
2090
|
+
assertStartupPaused: () => assertRelayAutostartPaused(startupOptions),
|
|
2091
|
+
stageStartup: (version, restore) => enableRelayAutostart({ ...startupOptions, workspace,
|
|
2092
|
+
launcher: startup.launcher, policy: startup.policy, version, restoreCodexHome: restore.codexHome, pauseForSigningMaintenance: true }),
|
|
2093
|
+
assertNamedDisabled: async () => {
|
|
2094
|
+
const entries = await readdir(resolve(home, 'sessions'), { withFileTypes: true });
|
|
2095
|
+
const names = entries.filter(entry => entry.isDirectory() && /^[a-z][a-z0-9-]{0,47}$/.test(entry.name));
|
|
2096
|
+
if (names.length !== 1)
|
|
2097
|
+
throw Error('relay_signing_maintenance_session_scope_unavailable');
|
|
2098
|
+
const registration = await readNamedSession(home, names[0].name);
|
|
2099
|
+
if (!registration || registration.enabled || registration.identity.organizationId !== config.organizationId
|
|
2100
|
+
|| registration.identity.deviceId !== config.deviceId || registration.identity.workspaceId !== selected.workspaceId)
|
|
2101
|
+
throw Error('relay_signing_maintenance_session_scope_unavailable');
|
|
2102
|
+
try {
|
|
2103
|
+
await lstat(resolve(home, 'sessions', names[0].name, 'service.lock'));
|
|
2104
|
+
throw Error('relay_signing_maintenance_session_lock_requires_reconciliation');
|
|
2105
|
+
}
|
|
2106
|
+
catch (error) {
|
|
2107
|
+
if (error.code !== 'ENOENT')
|
|
2108
|
+
throw error;
|
|
2109
|
+
}
|
|
2110
|
+
},
|
|
2111
|
+
assertRecoveredTrust: async (previousGeneration) => {
|
|
2112
|
+
const current = await readDeviceConfig(), keyset = current?.serverSigningKeyset;
|
|
2113
|
+
if (!current || current.organizationId !== config.organizationId || current.deviceId !== config.deviceId
|
|
2114
|
+
|| current.hqUrl !== config.hqUrl || current.relayUrl !== config.relayUrl || current.enrolledAt !== config.enrolledAt
|
|
2115
|
+
|| current.connectionMode !== config.connectionMode
|
|
2116
|
+
|| current.installationId !== config.installationId || current.publicKeyEd25519 !== config.publicKeyEd25519
|
|
2117
|
+
|| current.setupClaimReference !== config.setupClaimReference || current.setupClaimRepositoryFingerprint !== config.setupClaimRepositoryFingerprint
|
|
2118
|
+
|| !keyset || keyset.generation <= previousGeneration || !validateTrustedServerSigningKeysetContract(keyset).ok
|
|
2119
|
+
|| !verifyInitialServerSigningKeyset(keyset, createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: current.serverPublicKeyEd25519 }, format: 'jwk' }), current.organizationId).ok)
|
|
2120
|
+
throw Error('relay_signing_maintenance_trust_not_recovered');
|
|
2121
|
+
await loadDeviceEnrollmentAnchor({ config: current, readOnly: true });
|
|
2122
|
+
},
|
|
1979
2123
|
recoverableJournal: startup.backend === 'container-entrypoint' ? recoverableContainerJournal : undefined,
|
|
1980
2124
|
inspectStartup: async () => {
|
|
1981
2125
|
const current = await inspectOwnedRelayAutostart(startupOptions);
|
|
@@ -2035,7 +2179,8 @@ async function relayUpgrade(flags) {
|
|
|
2035
2179
|
throw new Error('relay_upgrade_receiver_unconfirmed');
|
|
2036
2180
|
},
|
|
2037
2181
|
});
|
|
2038
|
-
const validation = await validateContract(resolve(fileURLToPath(new URL('.', import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/local-
|
|
2182
|
+
const validation = await validateContract(resolve(fileURLToPath(new URL('.', import.meta.url)), 'schemas'), maintenance ? 'https://schemas.dharma-ai.io/local-signing-maintenance-upgrade/v1'
|
|
2183
|
+
: 'https://schemas.dharma-ai.io/local-relay-upgrade/v1', receipt);
|
|
2039
2184
|
if (!validation.ok)
|
|
2040
2185
|
throw new Error('relay_upgrade_receipt_invalid');
|
|
2041
2186
|
return receipt;
|
|
@@ -5231,6 +5376,11 @@ async function namedSessionCommand(action, flags) {
|
|
|
5231
5376
|
const hostScope = currentBootstrapHostScope();
|
|
5232
5377
|
const setupSessionScope = currentAcceptedSetupSessionScope();
|
|
5233
5378
|
const supervisorOwner = currentNamedSessionChildOwner();
|
|
5379
|
+
if (action === 'reconcile-stopped-lock' && (hostScope || setupSessionScope || supervisorOwner
|
|
5380
|
+
|| [...flags.keys()].some(k => !['name', 'workspace-id', 'registration-sha256', 'lock-sha256', 'apply', 'dry-run', 'json'].includes(k))
|
|
5381
|
+
|| ['apply', 'dry-run', 'json'].some(k => flags.has(k) && flags.get(k) !== true)
|
|
5382
|
+
|| flags.has('apply') && flags.has('dry-run')))
|
|
5383
|
+
throw Error('named_session_stopped_lock_options_invalid');
|
|
5234
5384
|
if (hostScope && (supervisorOwner || setupSessionScope))
|
|
5235
5385
|
throw new Error('named_session_child_owner_conflict');
|
|
5236
5386
|
if (setupSessionScope && !['start', 'status', 'readiness'].includes(action))
|
|
@@ -5246,498 +5396,552 @@ async function namedSessionCommand(action, flags) {
|
|
|
5246
5396
|
if (process.platform !== 'linux')
|
|
5247
5397
|
throw new Error('codex_session_sandbox_unqualified');
|
|
5248
5398
|
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 });
|
|
5399
|
+
let startupAdmission;
|
|
5400
|
+
const releaseStartupAdmission = async () => {
|
|
5401
|
+
const release = startupAdmission;
|
|
5402
|
+
startupAdmission = undefined;
|
|
5403
|
+
await release?.();
|
|
5404
|
+
};
|
|
5405
|
+
try {
|
|
5406
|
+
if (['start', 'serve'].includes(action) && flags.has('apply') || action === 'reconcile-stopped-lock') {
|
|
5407
|
+
startupAdmission = await step(() => acquireNamedSessionStartupAdmission());
|
|
5287
5408
|
}
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5409
|
+
let childMessage;
|
|
5410
|
+
if (flags.has('setup-handoff')) {
|
|
5411
|
+
if (action !== 'serve' || flags.get('setup-handoff') !== true
|
|
5412
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'apply', 'setup-handoff'].includes(key))) {
|
|
5413
|
+
throw new Error('setup_child_unavailable');
|
|
5414
|
+
}
|
|
5415
|
+
childMessage = await receiveCodexSetupChildStart({ process, signal: new AbortController().signal });
|
|
5292
5416
|
}
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5417
|
+
const item = (await step(() => registry())).find(row => row.workspaceId === workspaceId);
|
|
5418
|
+
const config = await step(() => readDeviceConfig());
|
|
5419
|
+
if (!item || !config || item.organizationId !== config.organizationId)
|
|
5420
|
+
throw new Error('named_session_scope_mismatch');
|
|
5421
|
+
await assertBootstrapHostSource(item.path, String(item.repositoryRemoteHash || ''));
|
|
5422
|
+
const scope = repositoryRoleScope(item);
|
|
5423
|
+
const existing = await step(() => readNamedSession(dharmaHome(), name));
|
|
5424
|
+
if (setupSessionScope && (name !== setupSessionScope.request.name || workspaceId !== setupSessionScope.request.workspaceId
|
|
5425
|
+
|| existing && existing.identity.membershipId !== setupSessionScope.request.membershipId))
|
|
5426
|
+
throw new Error('setup_session_scope_changed');
|
|
5427
|
+
if (existing && (existing.identity.workspaceId !== item.workspaceId
|
|
5428
|
+
|| existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
|
|
5429
|
+
|| existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
|
|
5430
|
+
throw new Error('named_session_scope_mismatch');
|
|
5298
5431
|
}
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5432
|
+
if (action === 'status') {
|
|
5433
|
+
try {
|
|
5434
|
+
return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
|
|
5435
|
+
}
|
|
5436
|
+
catch {
|
|
5437
|
+
return { ok: true, name, state: 'stopped', registered: Boolean(existing), enabled: existing?.enabled ?? false,
|
|
5438
|
+
bindingId: existing?.bindingId ?? null };
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
if (action === 'reconcile-stopped-lock') {
|
|
5442
|
+
if (!existing)
|
|
5443
|
+
throw Error('named_session_stopped_lock_unconfirmed');
|
|
5444
|
+
return await reconcileStoppedNamedSessionLock({ home: dharmaHome(), name, registration: existing,
|
|
5445
|
+
registrationSha256: required(flags, 'registration-sha256'), lockSha256: required(flags, 'lock-sha256'), apply: flags.has('apply') }, {
|
|
5446
|
+
assertTopology: async () => {
|
|
5447
|
+
await assertRelayAutostartPaused({ home: dharmaHome() });
|
|
5448
|
+
if (await relayProcessState(dharmaHome()) !== 'stopped' || await relaySupervisorProcessState(dharmaHome()) !== 'stopped')
|
|
5449
|
+
throw Error('named_session_stopped_lock_unconfirmed');
|
|
5450
|
+
try {
|
|
5451
|
+
await namedSessionRequest(dharmaHome(), name, { action: 'status' }, { diagnostic: true });
|
|
5452
|
+
throw Error('named_session_stopped_lock_unconfirmed');
|
|
5453
|
+
}
|
|
5454
|
+
catch (error) {
|
|
5455
|
+
if (!(error instanceof NamedSessionTransportFailure) || error.diagnostic.phase !== 'connect'
|
|
5456
|
+
|| !['ENOENT', 'ECONNREFUSED'].includes(String(error.diagnostic.osCode)))
|
|
5457
|
+
throw Error('named_session_stopped_lock_unconfirmed');
|
|
5458
|
+
}
|
|
5459
|
+
},
|
|
5460
|
+
processState: async (pid) => {
|
|
5461
|
+
try {
|
|
5462
|
+
process.kill(pid, 0);
|
|
5463
|
+
return 'running';
|
|
5464
|
+
}
|
|
5465
|
+
catch (error) {
|
|
5466
|
+
return error.code === 'ESRCH' ? 'stopped' : 'unknown';
|
|
5467
|
+
}
|
|
5468
|
+
},
|
|
5335
5469
|
});
|
|
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');
|
|
5351
|
-
}
|
|
5352
|
-
return result;
|
|
5353
|
-
}
|
|
5354
|
-
if (action === 'stop') {
|
|
5355
|
-
if (!flags.has('apply'))
|
|
5356
|
-
return { ok: true, planned: true, name, action: 'stop' };
|
|
5357
|
-
const current = await namedSessionRequest(dharmaHome(), name, { action: 'status' }).catch(() => null);
|
|
5358
|
-
if (current?.admission) {
|
|
5359
|
-
if (!existing || current.bindingId !== existing.bindingId
|
|
5360
|
-
|| Object.entries(existing.identity).some(([key, value]) => current[key] !== value)) {
|
|
5361
|
-
throw new Error('named_session_scope_mismatch');
|
|
5362
|
-
}
|
|
5363
|
-
// The capable service rejects an unfenced stop without changing its registration.
|
|
5364
|
-
return namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5365
|
-
}
|
|
5366
|
-
if (existing)
|
|
5367
|
-
await saveNamedSession(dharmaHome(), { ...existing, enabled: false });
|
|
5368
|
-
try {
|
|
5369
|
-
return await namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5370
|
-
}
|
|
5371
|
-
catch {
|
|
5372
|
-
return { ok: true, name, state: 'stopped', enabled: false };
|
|
5373
5470
|
}
|
|
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) }));
|
|
5471
|
+
if (action === 'readiness') {
|
|
5472
|
+
if (!existing?.enabled)
|
|
5473
|
+
throw new Error('named_session_readiness_unavailable');
|
|
5474
|
+
let result;
|
|
5475
|
+
try {
|
|
5476
|
+
result = await namedSessionRequest(dharmaHome(), name, { action: 'readiness' }, { diagnostic });
|
|
5391
5477
|
}
|
|
5392
|
-
|
|
5393
|
-
if (
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
if (live?.admission && live.admission.state !== 'open') {
|
|
5397
|
-
throw new Error('named_session_quiesced');
|
|
5398
|
-
}
|
|
5399
|
-
await step(() => saveNamedSession(dharmaHome(), { ...existing, enabled: true }));
|
|
5478
|
+
catch (error) {
|
|
5479
|
+
if (diagnostic && error instanceof NamedSessionTransportFailure)
|
|
5480
|
+
return namedSessionReadinessDiagnostic(existing, error);
|
|
5481
|
+
throw error;
|
|
5400
5482
|
}
|
|
5483
|
+
if (Object.keys(result).sort().join(',') !== 'bindingId,deviceId,endpointId,membershipId,name,nativeSkill,ok,organizationId,provider,repositoryBindingId,schema,sessionId,workspaceId'
|
|
5484
|
+
|| result.schema !== 'dharma.named-session-readiness/v1' || result.name !== name || result.bindingId !== existing.bindingId
|
|
5485
|
+
|| typeof result.sessionId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(result.sessionId)
|
|
5486
|
+
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
5487
|
+
throw new Error('named_session_readiness_scope_mismatch');
|
|
5488
|
+
}
|
|
5489
|
+
return { ...result, nativeSkill: parseNamedCodexSkillObservation(result.nativeSkill) };
|
|
5490
|
+
}
|
|
5491
|
+
if (action === 'work')
|
|
5492
|
+
return namedSessionRequest(dharmaHome(), name, { action: 'work',
|
|
5493
|
+
workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
|
|
5494
|
+
if (['quiesce', 'resume'].includes(action) || action === 'stop' && flags.has('drained')) {
|
|
5495
|
+
const allowed = ['name', 'workspace-id', 'fence-id', 'apply', 'dry-run', 'json',
|
|
5496
|
+
...(action === 'stop' ? ['drained'] : action === 'resume' ? ['recover-stopped'] : [])];
|
|
5497
|
+
if (hostScope || supervisorOwner || setupSessionScope || !existing
|
|
5498
|
+
|| [...flags.keys()].some(key => !allowed.includes(key))
|
|
5499
|
+
|| ['apply', 'dry-run', 'drained', 'recover-stopped', 'json'].some(key => flags.has(key) && flags.get(key) !== true)
|
|
5500
|
+
|| flags.has('apply') && flags.has('dry-run'))
|
|
5501
|
+
throw new Error('named_session_maintenance_unavailable');
|
|
5502
|
+
const fenceId = required(flags, 'fence-id');
|
|
5503
|
+
if (!UUID_PATTERN.test(fenceId))
|
|
5504
|
+
throw new Error('named_session_fence_invalid');
|
|
5505
|
+
const requestAction = action === 'stop' ? 'stop-drained' : action;
|
|
5506
|
+
if (!flags.has('apply'))
|
|
5507
|
+
return { ok: true, planned: true, name, workspaceId, action: requestAction, fenceId,
|
|
5508
|
+
mode: flags.has('recover-stopped') ? 'stopped_recovery' : 'live' };
|
|
5509
|
+
if (action === 'resume' && flags.has('recover-stopped'))
|
|
5510
|
+
return withRelayStartupMutation(async () => {
|
|
5511
|
+
let vault;
|
|
5512
|
+
try {
|
|
5513
|
+
return await recoverStoppedNamedSession({ registration: existing, fenceId, workspace: item.path,
|
|
5514
|
+
processState: () => pidProcessState(resolve(paths.root, 'service.lock')),
|
|
5515
|
+
readRegistration: () => readNamedSession(dharmaHome(), name),
|
|
5516
|
+
readBinding: async () => {
|
|
5517
|
+
vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
|
|
5518
|
+
return vault.getProviderSessionBinding(existing.bindingId, existing.identity);
|
|
5519
|
+
},
|
|
5520
|
+
saveRegistration: value => saveNamedSession(dharmaHome(), value) });
|
|
5521
|
+
}
|
|
5522
|
+
finally {
|
|
5523
|
+
await vault?.close();
|
|
5524
|
+
}
|
|
5525
|
+
});
|
|
5526
|
+
return maintainNamedSession({ action: requestAction, fenceId,
|
|
5527
|
+
registration: existing, request: body => namedSessionRequest(dharmaHome(), name, body) });
|
|
5528
|
+
}
|
|
5529
|
+
if (action === 'audit') {
|
|
5530
|
+
if (!existing?.enabled || [...flags.keys()].some(key => !['name', 'workspace-id', 'work-id'].includes(key))) {
|
|
5531
|
+
throw new Error('named_session_audit_unavailable');
|
|
5532
|
+
}
|
|
5533
|
+
const workId = required(flags, 'work-id');
|
|
5534
|
+
if (!/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(workId))
|
|
5535
|
+
throw new Error('named_session_work_invalid');
|
|
5536
|
+
const result = await namedSessionRequest(dharmaHome(), name, { action: 'audit', workId });
|
|
5537
|
+
if (!(await validateContract(resolve(dirname(fileURLToPath(import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/named-session-native-audit/v1', result)).ok
|
|
5538
|
+
|| result.bindingId !== existing.bindingId || result.workId !== workId
|
|
5539
|
+
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
5540
|
+
throw new Error('named_session_audit_scope_mismatch');
|
|
5541
|
+
}
|
|
5542
|
+
return result;
|
|
5401
5543
|
}
|
|
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();
|
|
5544
|
+
if (action === 'stop') {
|
|
5545
|
+
if (!flags.has('apply'))
|
|
5546
|
+
return { ok: true, planned: true, name, action: 'stop' };
|
|
5547
|
+
const current = await namedSessionRequest(dharmaHome(), name, { action: 'status' }).catch(() => null);
|
|
5548
|
+
if (current?.admission) {
|
|
5549
|
+
if (!existing || current.bindingId !== existing.bindingId
|
|
5550
|
+
|| Object.entries(existing.identity).some(([key, value]) => current[key] !== value)) {
|
|
5551
|
+
throw new Error('named_session_scope_mismatch');
|
|
5435
5552
|
}
|
|
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';
|
|
5553
|
+
// The capable service rejects an unfenced stop without changing its registration.
|
|
5554
|
+
return namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5555
|
+
}
|
|
5556
|
+
if (existing)
|
|
5557
|
+
await saveNamedSession(dharmaHome(), { ...existing, enabled: false });
|
|
5465
5558
|
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');
|
|
5559
|
+
return await namedSessionRequest(dharmaHome(), name, { action: 'stop' });
|
|
5560
|
+
}
|
|
5561
|
+
catch {
|
|
5562
|
+
return { ok: true, name, state: 'stopped', enabled: false };
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
if (!['start', 'serve'].includes(action))
|
|
5566
|
+
throw new Error('named_session_action_invalid');
|
|
5567
|
+
if (!flags.has('apply'))
|
|
5568
|
+
return { ok: true, planned: true, name, workspaceId,
|
|
5569
|
+
provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
|
|
5570
|
+
await step(async () => verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item)));
|
|
5571
|
+
if (action === 'start') {
|
|
5572
|
+
if (existing && !existing.enabled) {
|
|
5573
|
+
if (existing.maintenanceFence) {
|
|
5574
|
+
if (hostScope || supervisorOwner || setupSessionScope || !flags.has('fence-id') || flags.get('apply') !== true
|
|
5575
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'fence-id', 'apply', 'json'].includes(key))) {
|
|
5576
|
+
throw new Error('named_session_quiesced');
|
|
5499
5577
|
}
|
|
5500
|
-
|
|
5578
|
+
await activateMaintenanceRestart({ registration: existing, fenceId: required(flags, 'fence-id'),
|
|
5579
|
+
processState: () => pidProcessState(resolve(paths.root, 'service.lock')),
|
|
5580
|
+
readRegistration: () => readNamedSession(dharmaHome(), name), saveRegistration: value => saveNamedSession(dharmaHome(), value) });
|
|
5501
5581
|
}
|
|
5502
5582
|
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');
|
|
5583
|
+
if (supervisorOwner)
|
|
5584
|
+
throw new Error('named_session_disabled');
|
|
5585
|
+
const live = await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' })).catch(() => null);
|
|
5586
|
+
if (live?.admission && live.admission.state !== 'open') {
|
|
5587
|
+
throw new Error('named_session_quiesced');
|
|
5515
5588
|
}
|
|
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 };
|
|
5589
|
+
await step(() => saveNamedSession(dharmaHome(), { ...existing, enabled: true }));
|
|
5524
5590
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5591
|
+
}
|
|
5592
|
+
try {
|
|
5593
|
+
return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
|
|
5594
|
+
}
|
|
5595
|
+
catch { /* Start an owned worker below. */ }
|
|
5596
|
+
const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
|
|
5597
|
+
'--workspace-id', workspaceId, '--apply'];
|
|
5598
|
+
if (setupSessionScope)
|
|
5599
|
+
args.push('--setup-handoff');
|
|
5600
|
+
for (const key of ['session-budget-cents', 'turn-budget-cents']) {
|
|
5601
|
+
if (flags.has(key))
|
|
5602
|
+
args.push(`--${key}`, required(flags, key));
|
|
5603
|
+
}
|
|
5604
|
+
let startupChild;
|
|
5605
|
+
try {
|
|
5606
|
+
// The child takes this same admission lock before private startup. Never
|
|
5607
|
+
// wait for it while holding its parent-side registration/enablement lock.
|
|
5608
|
+
await releaseStartupAdmission();
|
|
5609
|
+
await step(async () => {
|
|
5610
|
+
const start = () => {
|
|
5611
|
+
const child = spawn(process.execPath, args, { cwd: item.path, detached: !hostScope && !supervisorOwner,
|
|
5612
|
+
stdio: setupSessionScope ? ['ignore', 'ignore', 'pipe', 'ipc'] : ['ignore', 'ignore', 'pipe'], env: process.env });
|
|
5613
|
+
startupChild = observeNamedSessionStartupChild(child);
|
|
5614
|
+
return child;
|
|
5615
|
+
};
|
|
5616
|
+
if (supervisorOwner) {
|
|
5617
|
+
const child = await supervisorOwner.spawn(name, start);
|
|
5618
|
+
if (setupSessionScope)
|
|
5619
|
+
await sendCodexSetupChildStart({ owner: supervisorOwner, child, scope: setupSessionScope });
|
|
5620
|
+
return;
|
|
5621
|
+
}
|
|
5622
|
+
const child = start();
|
|
5623
|
+
if (hostScope)
|
|
5624
|
+
captureBootstrapHostChild(hostScope, child);
|
|
5625
|
+
else {
|
|
5626
|
+
child.on('error', () => { });
|
|
5627
|
+
child.unref();
|
|
5542
5628
|
}
|
|
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
5629
|
});
|
|
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);
|
|
5630
|
+
return await waitForNamedSessionStartup({ step,
|
|
5631
|
+
assertActive: async () => { await hostScope?.assert(); },
|
|
5632
|
+
observe: () => namedSessionRequest(dharmaHome(), name, { action: 'status' }),
|
|
5633
|
+
readFailure: () => startupChild?.readFailure(),
|
|
5634
|
+
maximumWaitMs: setupSessionScope
|
|
5635
|
+
? Math.min(120_000, Date.parse(setupSessionScope.request.expiresAt) - Date.now()) : 120_000 });
|
|
5571
5636
|
}
|
|
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' } };
|
|
5637
|
+
finally {
|
|
5638
|
+
startupChild?.dispose();
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
await mkdir(paths.root, { recursive: true, mode: 0o700 });
|
|
5642
|
+
const vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') })
|
|
5643
|
+
.catch(error => { throw namedSessionStartupPhaseError('vault', error); });
|
|
5644
|
+
let releaseLock;
|
|
5645
|
+
const controller = new AbortController(), stop = () => controller.abort();
|
|
5646
|
+
process.once('SIGTERM', stop);
|
|
5647
|
+
process.once('SIGINT', stop);
|
|
5648
|
+
if (childMessage)
|
|
5649
|
+
process.once('disconnect', stop);
|
|
5650
|
+
let transport;
|
|
5651
|
+
try {
|
|
5652
|
+
releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running')
|
|
5653
|
+
.catch(error => { throw namedSessionStartupPhaseError('lock', error); });
|
|
5654
|
+
// A startup poll may predate quiescence. Recheck durable admission after winning the owner lock.
|
|
5655
|
+
assertNamedSessionStartupRegistration(existing, await step(() => readNamedSession(dharmaHome(), name)));
|
|
5656
|
+
// Once the per-service lock is held, maintenance cannot acquire its stopped
|
|
5657
|
+
// topology. Release home-wide admission before the long-lived provider loop.
|
|
5658
|
+
await releaseStartupAdmission();
|
|
5659
|
+
const startProvider = async (childScope) => {
|
|
5660
|
+
let startupPhase = 'client';
|
|
5661
|
+
try {
|
|
5662
|
+
const startupStep = (operation) => childScope ? childScope.step(operation) : operation();
|
|
5663
|
+
if (childScope && existing && existing.identity.membershipId !== childScope.request.membershipId) {
|
|
5664
|
+
throw new Error('setup_child_unavailable');
|
|
5665
|
+
}
|
|
5666
|
+
const fabric = await startupStep(() => AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json'),
|
|
5667
|
+
hostScope: currentBootstrapHostScope() }));
|
|
5668
|
+
if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
|
|
5669
|
+
|| fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
|
|
5670
|
+
throw new Error('named_session_trust_scope_mismatch');
|
|
5671
|
+
startupPhase = 'trust';
|
|
5672
|
+
const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
|
|
5673
|
+
await startupStep(() => trust.refresh());
|
|
5674
|
+
startupPhase = 'session';
|
|
5675
|
+
await startupStep(() => fabric.openSession(VERSION));
|
|
5676
|
+
startupPhase = 'policy';
|
|
5677
|
+
const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
|
|
5678
|
+
const policy = await startupStep(() => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric));
|
|
5679
|
+
const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
|
|
5680
|
+
.map(path => path.slice(0, -3));
|
|
5681
|
+
if (!writeRoots.length)
|
|
5682
|
+
throw new Error('named_session_workspace_write_not_authorized');
|
|
5683
|
+
startupPhase = 'registration';
|
|
5684
|
+
let registration = existing, pending;
|
|
5685
|
+
let ownerReceipt;
|
|
5686
|
+
// Resolve authenticated ownership before opening any provider transport.
|
|
5687
|
+
if (registration) {
|
|
5688
|
+
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5689
|
+
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
5690
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5691
|
+
bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5692
|
+
}));
|
|
5693
|
+
if (!isNamedSessionReconnectReceipt(response, registration.bindingId, registration.identity)) {
|
|
5694
|
+
throw new Error('named_session_registration_invalid');
|
|
5669
5695
|
}
|
|
5670
|
-
|
|
5696
|
+
ownerReceipt = response;
|
|
5671
5697
|
}
|
|
5698
|
+
else {
|
|
5699
|
+
const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
|
|
5700
|
+
const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
|
|
5701
|
+
const bindingId = randomUUID();
|
|
5702
|
+
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5703
|
+
schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
|
|
5704
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5705
|
+
bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5706
|
+
}));
|
|
5707
|
+
const remote = response.registration;
|
|
5708
|
+
if (!remote || !UUID_PATTERN.test(String(remote.membershipId))
|
|
5709
|
+
|| childScope && remote.membershipId !== childScope.request.membershipId || remote.revision !== 1) {
|
|
5710
|
+
throw new Error('named_session_registration_invalid');
|
|
5711
|
+
}
|
|
5712
|
+
const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
|
|
5713
|
+
workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
|
|
5714
|
+
provider: 'codex' };
|
|
5715
|
+
if (!isNamedSessionOwnerReceipt(response, bindingId, identity))
|
|
5716
|
+
throw new Error('named_session_registration_invalid');
|
|
5717
|
+
ownerReceipt = response;
|
|
5718
|
+
pending = { schema: 'dharma.named-session/v1', name, bindingId, identity,
|
|
5719
|
+
maximumCostCents, maximumTurnCostCents, enabled: true };
|
|
5720
|
+
}
|
|
5721
|
+
startupPhase = 'environment';
|
|
5722
|
+
const environment = namedCodexEnvironment(process.env);
|
|
5723
|
+
const privateRoots = [dharmaHome(), resolve(environment.CODEX_HOME || resolve(homedir(), '.codex'))];
|
|
5724
|
+
if (environment.XDG_RUNTIME_DIR)
|
|
5725
|
+
privateRoots.push(environment.XDG_RUNTIME_DIR);
|
|
5726
|
+
if (environment.DBUS_SESSION_BUS_ADDRESS)
|
|
5727
|
+
privateRoots.push(environment.DBUS_SESSION_BUS_ADDRESS.slice('unix:path='.length));
|
|
5728
|
+
startupPhase = 'filesystem';
|
|
5729
|
+
const filesystem = await startupStep(() => namedCodexFilesystem({ environment, workspace: item.path, privateRoots, writeRoots }));
|
|
5730
|
+
startupPhase = 'transport';
|
|
5731
|
+
await startupStep(async () => {
|
|
5732
|
+
const expectedOwner = registration ?? pending;
|
|
5733
|
+
const ownerConfirmed = registration
|
|
5734
|
+
? isNamedSessionReconnectReceipt(ownerReceipt, expectedOwner.bindingId, expectedOwner.identity)
|
|
5735
|
+
: isNamedSessionOwnerReceipt(ownerReceipt, expectedOwner.bindingId, expectedOwner.identity);
|
|
5736
|
+
if (!ownerConfirmed) {
|
|
5737
|
+
throw new Error('named_session_registration_invalid');
|
|
5738
|
+
}
|
|
5739
|
+
transport = await openCodexAppServerTransport({ command: filesystem.providerExecutable, cwd: item.path,
|
|
5740
|
+
environment: filesystem.environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
|
|
5741
|
+
argv: [...namedCodexNodeOutputArguments(), '-c', 'allow_login_shell=false', '-c', 'default_permissions="dharma_bridge"',
|
|
5742
|
+
'-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
|
|
5743
|
+
'-c', 'permissions.dharma_bridge.network={enabled=false}',
|
|
5744
|
+
'-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
|
|
5745
|
+
'-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
|
|
5746
|
+
return transport;
|
|
5747
|
+
});
|
|
5748
|
+
startupPhase = 'account';
|
|
5749
|
+
const account = await startupStep(() => transport.request('account/read', { refreshToken: false }));
|
|
5750
|
+
if (!account.account)
|
|
5751
|
+
throw new Error('named_session_provider_authentication_required');
|
|
5752
|
+
if (pending) {
|
|
5753
|
+
startupPhase = 'thread';
|
|
5754
|
+
const threadId = await startupStep(() => startNamedCodexThread(transport, item.path, name));
|
|
5755
|
+
const declaredRecoveryToolHash = inspectNamedCodexRecoveryToolDeclaration(transport, threadId);
|
|
5756
|
+
if (!declaredRecoveryToolHash)
|
|
5757
|
+
throw new Error('named_session_thread_invalid');
|
|
5758
|
+
startupPhase = 'binding';
|
|
5759
|
+
await startupStep(async () => vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...pending.identity,
|
|
5760
|
+
bindingId: pending.bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
|
|
5761
|
+
createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
5762
|
+
maximumProviderCostCents: pending.maximumTurnCostCents }));
|
|
5763
|
+
await startupStep(async () => vault.saveProviderSessionRecoveryDeclaration(pending.bindingId, pending.identity, declaredRecoveryToolHash));
|
|
5764
|
+
registration = pending;
|
|
5765
|
+
await startupStep(async () => saveNamedSession(dharmaHome(), registration));
|
|
5766
|
+
}
|
|
5767
|
+
return { registration: registration, filesystem, fabric, trust, policyPath, writeRoots };
|
|
5672
5768
|
}
|
|
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');
|
|
5769
|
+
catch (error) {
|
|
5770
|
+
throw namedSessionStartupPhaseError(startupPhase, error);
|
|
5691
5771
|
}
|
|
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 () => {
|
|
5772
|
+
};
|
|
5773
|
+
const { registration, filesystem, fabric, trust, policyPath, writeRoots } = childMessage
|
|
5774
|
+
? await runCodexSetupChildStartup({ vault: vault, message: childMessage, name, workspaceId,
|
|
5775
|
+
signal: controller.signal, authorize: qualifyCodexSetupChildRequest }, startProvider)
|
|
5776
|
+
: await startProvider();
|
|
5777
|
+
const authorizeLifetime = async () => {
|
|
5704
5778
|
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
|
-
|
|
5779
|
+
if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
|
|
5780
|
+
return false;
|
|
5781
|
+
const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
5782
|
+
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
5783
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
5784
|
+
bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
5785
|
+
});
|
|
5786
|
+
return isNamedSessionOwnerReceipt(response, registration.bindingId, registration.identity);
|
|
5787
|
+
};
|
|
5788
|
+
const refreshLifetime = () => renewNamedSessionLifetime({ vault, bindingId: registration.bindingId,
|
|
5789
|
+
identity: registration.identity, trust, authorize: authorizeLifetime });
|
|
5790
|
+
await refreshLifetime().catch(error => { throw namedSessionStartupPhaseError('lifetime', error); });
|
|
5791
|
+
const channelTransport = { async signedPost(route, body) {
|
|
5792
|
+
await trust.refresh();
|
|
5793
|
+
const response = await fabric.signedPost(route, body);
|
|
5794
|
+
await trust.refresh();
|
|
5795
|
+
return response;
|
|
5796
|
+
} };
|
|
5797
|
+
// Fresh server acceptance and this live-owner claim fence peer execution;
|
|
5798
|
+
// durable reservations retain cost even when delivery or execution is uncertain.
|
|
5799
|
+
const consumed = new Set();
|
|
5800
|
+
let taskState = null;
|
|
5801
|
+
let taskStateFailure = 'source_state_unavailable';
|
|
5802
|
+
const taskSnapshot = async () => {
|
|
5803
|
+
const sourceAuthorization = await fetchRepositorySourceAuthorization(fabric, repositoryRoleScope(item));
|
|
5804
|
+
return inventoryRepositoryPackage({ workspace: item.path, organizationId: item.organizationId,
|
|
5805
|
+
workspaceId, repositoryBindingId: item.repositoryBindingId, repositoryAgentId: item.repositoryAgentId, sourceAuthorization });
|
|
5806
|
+
};
|
|
5807
|
+
const readCurrentRepositoryContext = async (contentMode = 'references', referenceText) => readNamedSessionRepositoryContext({
|
|
5808
|
+
installation: await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }),
|
|
5809
|
+
sharedRepositoryReady: await repositorySharedReady(item), scope: repositoryRoleScope(item), workspaceRoot: item.path, contentMode, referenceText,
|
|
5810
|
+
loadAuthority: async () => ({ policy: await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
5811
|
+
source: await fetchRepositorySourceAuthorization(fabric, repositoryRoleScope(item)) })
|
|
5812
|
+
});
|
|
5813
|
+
return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
|
|
5814
|
+
localWriteRoots: writeRoots, additionalFilesystemRules: filesystem.additionalFilesystemRules,
|
|
5815
|
+
readPeerRepositoryContext: question => readCurrentRepositoryContext('verified_inline', question.question),
|
|
5816
|
+
observeNativeSkill: async () => {
|
|
5817
|
+
const installation = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5818
|
+
const native = await observeNamedCodexSkill(transport, { workspace: item.path, installation,
|
|
5819
|
+
sharedRepositoryReady: await repositorySharedReady(item) });
|
|
5820
|
+
const current = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5821
|
+
if (!current.signedLifecycleReady || current.activeBundleId !== native.bundleId
|
|
5822
|
+
|| current.activeBundleHash !== native.bundleHash || current.nativeSkillPath !== installation.nativeSkillPath
|
|
5823
|
+
|| !await repositorySharedReady(item))
|
|
5824
|
+
throw new Error('named_session_native_skill_unavailable');
|
|
5825
|
+
return native;
|
|
5826
|
+
},
|
|
5827
|
+
syncTaskExports: () => syncNamedSessionTaskExports({ vault, bindingId: registration.bindingId,
|
|
5828
|
+
identity: registration.identity,
|
|
5829
|
+
loadPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
5830
|
+
send: body => fabric.signedPost('/agent-fabric/codex-task-exports', body) }),
|
|
5831
|
+
withActivationBoundary: (operation, work) => withDeviceSigningControl({ config: fabric.config }, () => withWorkspaceSkillActivationLock(workspaceId, 'codex', async () => {
|
|
5832
|
+
await refreshLifetime();
|
|
5833
|
+
const skill = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
5834
|
+
const sharedReady = await repositorySharedReady(item);
|
|
5835
|
+
const packageContent = await readNamedSessionPackageContent(skill, sharedReady);
|
|
5836
|
+
const bundleId = packageContent.bundleId;
|
|
5837
|
+
taskState = null;
|
|
5838
|
+
taskStateFailure = 'source_state_unavailable';
|
|
5839
|
+
if (work) {
|
|
5840
|
+
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5841
|
+
if (current.evidence.automaticDisclosure?.mode === 'customer_authorized_content' && skill.activeBundleHash) {
|
|
5842
|
+
try {
|
|
5843
|
+
taskStateFailure = 'public_context_unavailable';
|
|
5844
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5845
|
+
if (!binding)
|
|
5846
|
+
throw new Error('named_session_binding_unavailable');
|
|
5847
|
+
const retained = await readCodexPublicContext(transport, { threadId: binding.sessionId, workspaceRoot: binding.workspaceRoot });
|
|
5848
|
+
taskStateFailure = 'runtime_version_unavailable';
|
|
5849
|
+
const version = (await execFileAsync('codex', ['--version'], { timeout: 10_000, maxBuffer: 4096,
|
|
5850
|
+
env: providerProcessEnvironment(process.env) })).stdout.trim();
|
|
5851
|
+
if (!/^codex-cli [0-9]+\.[0-9]+\.[0-9]+(?:[-+][a-zA-Z0-9.-]+)?$/.test(version)) {
|
|
5852
|
+
throw new Error('named_session_runtime_version_unavailable');
|
|
5853
|
+
}
|
|
5854
|
+
const contextContentHash = await vault.putBlob(Buffer.from(retained.bytes), 'named-session-public-context');
|
|
5855
|
+
if (contextContentHash !== retained.contextHash)
|
|
5856
|
+
throw new Error('named_session_context_integrity_failed');
|
|
5857
|
+
taskStateFailure = 'source_state_unavailable';
|
|
5858
|
+
const before = await taskSnapshot();
|
|
5859
|
+
const sourceContentHash = await vault.putBlob(Buffer.from(serializeRepositoryPackageSnapshot(before)), 'named-session-source-state');
|
|
5860
|
+
await vault.putBlob(Buffer.from(JSON.stringify({ workId: work.workId, bindingId: registration.bindingId,
|
|
5861
|
+
sourceContentHash, sourceSnapshotHash: before.manifest.snapshotHash, bundleId, bundleHash: skill.activeBundleHash,
|
|
5862
|
+
packageContent, contextContentHash, evidenceRevision: current.revision,
|
|
5863
|
+
consentReceiptId: current.evidence.automaticDisclosure.consentReceiptId })), 'named-session-source-boundary');
|
|
5864
|
+
taskState = { workId: work.workId, before, bundleId, bundleHash: skill.activeBundleHash, packageContent,
|
|
5865
|
+
evidenceRevision: current.revision, consentReceiptId: current.evidence.automaticDisclosure.consentReceiptId,
|
|
5866
|
+
providerContext: { retainedContextHash: retained.contextHash, contextContentHash,
|
|
5867
|
+
runtimeVersion: version.slice('codex-cli '.length), requestedModel: retained.context.configuredModel,
|
|
5868
|
+
executedModel: null, replayMode: 'task_level' } };
|
|
5869
|
+
}
|
|
5870
|
+
catch { /* Source capture failure must not replay or rewrite a coding task. */ }
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
try {
|
|
5874
|
+
const context = work ? await readCurrentRepositoryContext() : undefined;
|
|
5875
|
+
return await operation(context);
|
|
5876
|
+
}
|
|
5877
|
+
finally {
|
|
5878
|
+
taskState = null;
|
|
5879
|
+
}
|
|
5880
|
+
})),
|
|
5881
|
+
retainRepositoryState: async (capture) => {
|
|
5882
|
+
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5883
|
+
if (current.evidence.automaticDisclosure?.mode !== 'customer_authorized_content') {
|
|
5884
|
+
return { state: 'not_authorized', acceptedLearningObservation: false };
|
|
5885
|
+
}
|
|
5886
|
+
if (!taskState)
|
|
5887
|
+
return { state: 'blocked', code: taskStateFailure, acceptedLearningObservation: false };
|
|
5888
|
+
if (current.revision !== taskState.evidenceRevision
|
|
5889
|
+
|| current.evidence.automaticDisclosure.consentReceiptId !== taskState.consentReceiptId) {
|
|
5890
|
+
throw new Error('named_session_repository_state_consent_changed');
|
|
5891
|
+
}
|
|
5892
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5893
|
+
if (!binding)
|
|
5894
|
+
throw new Error('named_session_binding_unavailable');
|
|
5895
|
+
const receipt = await retainNamedSessionRepositoryState({ vault, binding, capture, workId: taskState.workId,
|
|
5896
|
+
before: taskState.before, after: await taskSnapshot(), activeBundleId: taskState.bundleId, activeBundleHash: taskState.bundleHash,
|
|
5897
|
+
taskExport: { policy: current, contextBytes: await vault.getBlob(taskState.providerContext.contextContentHash),
|
|
5898
|
+
contextHash: taskState.providerContext.retainedContextHash } });
|
|
5899
|
+
return { ...receipt, providerContext: taskState.providerContext,
|
|
5900
|
+
packageContent: { manifestHash: taskState.packageContent.manifestHash,
|
|
5901
|
+
catalogHash: taskState.packageContent.catalogHash, skillsHash: taskState.packageContent.skillsHash } };
|
|
5902
|
+
},
|
|
5903
|
+
authorizeLocalWork: async () => {
|
|
5904
|
+
const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
|
|
5905
|
+
return writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`));
|
|
5906
|
+
},
|
|
5907
|
+
queueEvidence: async (capture) => {
|
|
5908
|
+
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
5909
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5910
|
+
if (!binding)
|
|
5911
|
+
throw new Error('named_session_binding_unavailable');
|
|
5912
|
+
// The existing relay rechecks current authority, disclosure limits and device binding before upload.
|
|
5913
|
+
return queueNamedSessionEvidence({ vault, capture, binding, policy: current });
|
|
5914
|
+
},
|
|
5915
|
+
openTransport: async () => transport, channelTransport,
|
|
5916
|
+
verifier: { resolvePublicKey: trust.resolvePublicKey, consume: async (id) => { if (consumed.has(id))
|
|
5917
|
+
return false; consumed.add(id); return true; } },
|
|
5918
|
+
authorizeContent: createNamedPeerContentAuthorization({ scope: repositoryRoleScope(item), diagnostics: true,
|
|
5919
|
+
loadCurrentPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
5920
|
+
loadCurrentSourceAuthorization: () => fetchRepositorySourceAuthorization(fabric, repositoryRoleScope(item)) }) })
|
|
5921
|
+
.catch(error => { throw namedSessionStartupPhaseError('service', error); });
|
|
5727
5922
|
}
|
|
5728
5923
|
finally {
|
|
5729
5924
|
try {
|
|
5730
|
-
|
|
5925
|
+
if (transport)
|
|
5926
|
+
await transport.close();
|
|
5731
5927
|
}
|
|
5732
5928
|
finally {
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5929
|
+
try {
|
|
5930
|
+
await vault.close();
|
|
5931
|
+
}
|
|
5932
|
+
finally {
|
|
5933
|
+
await releaseLock?.();
|
|
5934
|
+
process.removeListener('SIGTERM', stop);
|
|
5935
|
+
process.removeListener('SIGINT', stop);
|
|
5936
|
+
if (childMessage)
|
|
5937
|
+
process.removeListener('disconnect', stop);
|
|
5938
|
+
}
|
|
5738
5939
|
}
|
|
5739
5940
|
}
|
|
5740
5941
|
}
|
|
5942
|
+
finally {
|
|
5943
|
+
await releaseStartupAdmission();
|
|
5944
|
+
}
|
|
5741
5945
|
}
|
|
5742
5946
|
async function workspaceSync(flags, positional) {
|
|
5743
5947
|
const workspaceId = positional[0] || required(flags, 'workspace-id');
|
|
@@ -9162,6 +9366,40 @@ export async function run(argv) {
|
|
|
9162
9366
|
return USAGE;
|
|
9163
9367
|
if (flags.has('version') || command === 'version')
|
|
9164
9368
|
return { version: VERSION };
|
|
9369
|
+
if (command === 'signing-recovery') {
|
|
9370
|
+
if (positional.length !== 1 || [...repeated.values()].some(values => values.length !== 1)
|
|
9371
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'operation-id', 'apply', 'json'].includes(key))
|
|
9372
|
+
|| flags.has('apply') && flags.get('apply') !== true)
|
|
9373
|
+
throw Error('device_signing_maintenance_options_invalid');
|
|
9374
|
+
const home = dharmaHome(), name = required(flags, 'name'), workspaceId = required(flags, 'workspace-id');
|
|
9375
|
+
const controller = new AbortController(), stop = () => controller.abort();
|
|
9376
|
+
process.once('SIGTERM', stop);
|
|
9377
|
+
process.once('SIGINT', stop);
|
|
9378
|
+
const execute = (legacyBoundary) => runDeviceSigningMaintenanceController({ home, configPath: configPath(), name, workspaceId,
|
|
9379
|
+
operationId: typeof flags.get('operation-id') === 'string' ? String(flags.get('operation-id')) : undefined, apply: flags.get('apply') === true,
|
|
9380
|
+
readWorkspace: async () => {
|
|
9381
|
+
const selected = (await registry()).find(value => value.workspaceId === workspaceId);
|
|
9382
|
+
return selected && selected.repositoryBindingId && selected.repositoryRemoteHash ? { organizationId: selected.organizationId, workspaceId: selected.workspaceId,
|
|
9383
|
+
repositoryBindingId: selected.repositoryBindingId, repositoryRemoteHash: selected.repositoryRemoteHash, path: selected.path } : null;
|
|
9384
|
+
}, processState: legacyBoundary ? async () => { await legacyBoundary.assertHeld(); return 'stopped'; } : () => deviceTrustMaintenanceProcessState(home, name),
|
|
9385
|
+
legacyBoundary,
|
|
9386
|
+
openVault: () => openBootstrapVault({ root: resolve(home, 'vault') }), signal: controller.signal, frames: process.stdin,
|
|
9387
|
+
emit: value => process.stdout.write(JSON.stringify(value) + '\n') });
|
|
9388
|
+
try {
|
|
9389
|
+
return await (flags.get('apply') === true
|
|
9390
|
+
? withLegacySigningMaintenanceBoundary({ home, name, signal: controller.signal, assertManagedCohort: async () => {
|
|
9391
|
+
const selected = (await registry()).find(value => value.workspaceId === workspaceId);
|
|
9392
|
+
if (!selected)
|
|
9393
|
+
throw Error('device_signing_maintenance_scope_unavailable');
|
|
9394
|
+
return assertSigningMaintenanceManagedCohort(home, selected.path);
|
|
9395
|
+
} }, execute) : execute());
|
|
9396
|
+
}
|
|
9397
|
+
finally {
|
|
9398
|
+
controller.abort();
|
|
9399
|
+
process.removeListener('SIGTERM', stop);
|
|
9400
|
+
process.removeListener('SIGINT', stop);
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9165
9403
|
if (command === 'demo' && ['connect', 'status', 'role', 'peers', 'ask', 'reply', 'inbox', 'ack', 'resume',
|
|
9166
9404
|
'package', 'package-status', 'watch', 'watch-enable', 'watch-status', 'watch-disable',
|
|
9167
9405
|
'signing-client-proof', 'signing-owner-proof', 'transport-connect']
|
|
@@ -9506,9 +9744,24 @@ export async function run(argv) {
|
|
|
9506
9744
|
if (command === 'relay' && subcommand === 'autostart') {
|
|
9507
9745
|
if (positional[2] === 'status')
|
|
9508
9746
|
return relayAutostartStatus({ home: dharmaHome() });
|
|
9747
|
+
if (positional[2] === 'pause') {
|
|
9748
|
+
if ([...flags.keys()].some(k => !['apply', 'dry-run', 'json'].includes(k))
|
|
9749
|
+
|| [...flags.values()].some(v => v !== true) || flags.has('apply') && flags.has('dry-run'))
|
|
9750
|
+
throw Error('relay_pause_options_invalid');
|
|
9751
|
+
return withRelayStartupMutation(async () => {
|
|
9752
|
+
const registration = await inspectOwnedRelayAutostart({ home: dharmaHome() });
|
|
9753
|
+
if (registration.backend !== 'container-entrypoint')
|
|
9754
|
+
throw Error('relay_pause_container_required');
|
|
9755
|
+
if (!flags.has('apply'))
|
|
9756
|
+
return { ok: true, planned: true, state: 'pause_planned', backend: registration.backend };
|
|
9757
|
+
await stopRelayAutostart({ home: dharmaHome() });
|
|
9758
|
+
await assertRelayAutostartPaused({ home: dharmaHome() });
|
|
9759
|
+
return { ok: true, planned: false, state: 'paused', backend: registration.backend, registrationPreserved: true };
|
|
9760
|
+
});
|
|
9761
|
+
}
|
|
9509
9762
|
if (positional[2] === 'disable')
|
|
9510
9763
|
return withRelayStartupMutation(() => disableRelayAutostart({ home: dharmaHome() }));
|
|
9511
|
-
throw new Error('Use dharma relay autostart status or
|
|
9764
|
+
throw new Error('Use dharma relay autostart status, pause or disable.');
|
|
9512
9765
|
}
|
|
9513
9766
|
if (command === 'relay' && subcommand === 'supervise')
|
|
9514
9767
|
return relaySupervise(flags);
|