@dharma-ai-labs/agent-fabric 0.2.126 → 0.2.127
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/AGENT_FABRIC_ONBOARDING.md +21 -21
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +180 -30
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +3 -5
- package/dist/index.test.js.map +1 -1
- package/dist/repositoryOnboardingCaller.test.js +129 -1
- package/dist/repositoryOnboardingCaller.test.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
|
|
|
61
61
|
import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
|
|
62
62
|
import { startNamedCodexThread } from './namedCodexThread.js';
|
|
63
63
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
64
|
-
const VERSION = '0.2.
|
|
64
|
+
const VERSION = '0.2.127';
|
|
65
65
|
const USAGE = CLI_USAGE;
|
|
66
66
|
const execFileAsync = promisify(execFile);
|
|
67
67
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -2047,6 +2047,16 @@ async function bootstrap(flags) {
|
|
|
2047
2047
|
const resuming = flags.has('resume');
|
|
2048
2048
|
const bootstrapToken = resuming ? null : required(flags, 'grant');
|
|
2049
2049
|
const selectedRemote = flags.get('repository-url-base64url');
|
|
2050
|
+
const joinedBindingId = typeof flags.get('join-repository-binding-id') === 'string'
|
|
2051
|
+
? String(flags.get('join-repository-binding-id')) : null;
|
|
2052
|
+
const joinedFingerprint = typeof flags.get('join-source-fingerprint') === 'string'
|
|
2053
|
+
? String(flags.get('join-source-fingerprint')) : null;
|
|
2054
|
+
if (joinedBindingId && (!UUID_PATTERN.test(joinedBindingId) || !/^sha256:[a-f0-9]{64}$/.test(joinedFingerprint || '')
|
|
2055
|
+
|| selectedRemote || flags.has('repository-key'))) {
|
|
2056
|
+
throw new Error('repository_join_selection_invalid: joining requires an exact existing binding and fingerprint, without a source selection.');
|
|
2057
|
+
}
|
|
2058
|
+
if (!joinedBindingId && joinedFingerprint)
|
|
2059
|
+
throw new Error('repository_join_selection_invalid');
|
|
2050
2060
|
let repositorySelection;
|
|
2051
2061
|
try {
|
|
2052
2062
|
if (flags.has('repository-url-base64url') && typeof selectedRemote !== 'string') {
|
|
@@ -2055,16 +2065,18 @@ async function bootstrap(flags) {
|
|
|
2055
2065
|
if (selectedRemote && flags.has('repository-key')) {
|
|
2056
2066
|
throw new Error('repository_selection_invalid_url: selected remote cannot be combined with a repository key.');
|
|
2057
2067
|
}
|
|
2058
|
-
repositorySelection =
|
|
2059
|
-
?
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
+
repositorySelection = joinedBindingId
|
|
2069
|
+
? { workspace: resolve(dharmaHome(), 'joined-repositories', joinedBindingId), selection: 'existing_organization_binding' }
|
|
2070
|
+
: typeof selectedRemote === 'string'
|
|
2071
|
+
? await resolveBootstrapRepositoryWorkspace({
|
|
2072
|
+
workspace: String(flags.get('workspace') || '.'),
|
|
2073
|
+
selectedRemoteBase64url: selectedRemote,
|
|
2074
|
+
organizationId,
|
|
2075
|
+
home: dharmaHome(),
|
|
2076
|
+
normalizeRemote: normalizeGitRemoteIdentity,
|
|
2077
|
+
withLock: (path) => acquirePidLock(path, 30_000, 'repository_checkout_busy: another setup is selecting this repository; retry after it finishes.'),
|
|
2078
|
+
})
|
|
2079
|
+
: { workspace: await realpath(String(flags.get('workspace') || '.')), selection: 'provided' };
|
|
2068
2080
|
}
|
|
2069
2081
|
catch (error) {
|
|
2070
2082
|
const message = error && typeof error === 'object' && 'message' in error && typeof error.message === 'string'
|
|
@@ -2079,10 +2091,12 @@ async function bootstrap(flags) {
|
|
|
2079
2091
|
if (selectedRemote)
|
|
2080
2092
|
process.stderr.write(`Agent Fabric repository checkout: ${workspace}\n`);
|
|
2081
2093
|
const policyRevision = required(flags, 'policy-revision');
|
|
2082
|
-
const repositoryIdentity =
|
|
2094
|
+
const repositoryIdentity = joinedBindingId
|
|
2095
|
+
? { fingerprint: joinedFingerprint }
|
|
2096
|
+
: await preflightBootstrapWorkspaceIdentity(workspace, typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null);
|
|
2083
2097
|
const requestedProvider = String(flags.get('provider') || 'auto').trim().toLowerCase();
|
|
2084
2098
|
const provider = requestedProvider === 'auto'
|
|
2085
|
-
? await detectBootstrapProvider(workspace)
|
|
2099
|
+
? await detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace)
|
|
2086
2100
|
: requestedProvider;
|
|
2087
2101
|
if (!isLocalProviderId(provider)) {
|
|
2088
2102
|
throw new Error('Bootstrap provider must be auto, codex, claude, agy, or hermes.');
|
|
@@ -2121,7 +2135,7 @@ async function bootstrap(flags) {
|
|
|
2121
2135
|
name,
|
|
2122
2136
|
platform: devicePlatform,
|
|
2123
2137
|
publicKeyEd25519: identity.publicKeyEd25519,
|
|
2124
|
-
repositoryFingerprint: repositoryIdentity.fingerprint,
|
|
2138
|
+
repositoryFingerprint: joinedBindingId ? '' : repositoryIdentity.fingerprint,
|
|
2125
2139
|
onRecipientApprovalRequired: async (approval) => {
|
|
2126
2140
|
recipientApproval.required = true;
|
|
2127
2141
|
recipientApproval.browserOpened = flags.has('no-browser')
|
|
@@ -2204,7 +2218,9 @@ async function bootstrap(flags) {
|
|
|
2204
2218
|
onboardFlags.set('workspace', workspace);
|
|
2205
2219
|
onboardFlags.set('policy-revision', policyRevision);
|
|
2206
2220
|
onboardFlags.set('provider', provider);
|
|
2207
|
-
const onboarded = await retryBootstrapOnboarding(async () =>
|
|
2221
|
+
const onboarded = await retryBootstrapOnboarding(async () => joinedBindingId
|
|
2222
|
+
? await joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint)
|
|
2223
|
+
: await onboard(onboardFlags));
|
|
2208
2224
|
if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
|
|
2209
2225
|
return {
|
|
2210
2226
|
ok: onboarded.ok === true,
|
|
@@ -2227,7 +2243,8 @@ async function bootstrap(flags) {
|
|
|
2227
2243
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
2228
2244
|
const autostart = flags.has('no-relay-daemon')
|
|
2229
2245
|
? { state: 'disabled', backend: null }
|
|
2230
|
-
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}
|
|
2246
|
+
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`
|
|
2247
|
+
+ (joinedBindingId ? ` --join-repository-binding-id ${joinedBindingId} --join-source-fingerprint ${joinedFingerprint}` : ''), () => withRelayStartupMutation(async () => {
|
|
2231
2248
|
const home = dharmaHome();
|
|
2232
2249
|
const existing = await relayAutostartStatus({ home });
|
|
2233
2250
|
if (existing.backend !== null || existing.state !== 'disabled') {
|
|
@@ -2285,7 +2302,8 @@ async function bootstrap(flags) {
|
|
|
2285
2302
|
throw new Error(`Provider skill did not become ready: ${JSON.stringify(skill)}`);
|
|
2286
2303
|
const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
|
|
2287
2304
|
const firstLearning = onboarded.firstLearningEvidence;
|
|
2288
|
-
const firstLearningReady = firstLearning?.state === 'synchronized' || firstLearning?.state === 'denied_disclosure'
|
|
2305
|
+
const firstLearningReady = firstLearning?.state === 'synchronized' || firstLearning?.state === 'denied_disclosure'
|
|
2306
|
+
|| joinedBindingId !== null && firstLearning?.state === 'shared_package_inherited';
|
|
2289
2307
|
const relay = flags.has('no-relay-daemon')
|
|
2290
2308
|
? { started: false, ...(await waitForRelayReadiness()) }
|
|
2291
2309
|
: await startRelayDaemon(policyPath);
|
|
@@ -2329,7 +2347,7 @@ async function bootstrap(flags) {
|
|
|
2329
2347
|
usage,
|
|
2330
2348
|
});
|
|
2331
2349
|
const operatingContract = await loadAgentFabricOnboardingContract();
|
|
2332
|
-
const namedSession = provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
|
|
2350
|
+
const namedSession = !joinedBindingId && provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
|
|
2333
2351
|
? await withOnboardingStage('named_session', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('start', new Map([
|
|
2334
2352
|
['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
|
|
2335
2353
|
['workspace-id', String(onboarded.workspaceId)], ['apply', true],
|
|
@@ -2338,13 +2356,23 @@ async function bootstrap(flags) {
|
|
|
2338
2356
|
])))
|
|
2339
2357
|
: null;
|
|
2340
2358
|
const repositoryReceipt = onboarded;
|
|
2341
|
-
const namedSessionReady = provider !== 'codex' || process.platform !== 'linux'
|
|
2359
|
+
const namedSessionReady = Boolean(joinedBindingId) || provider !== 'codex' || process.platform !== 'linux'
|
|
2342
2360
|
|| Boolean(namedSession?.ok === true && ['running', 'executing'].includes(String(namedSession.state)));
|
|
2343
2361
|
const role = repositoryReceipt.repositoryRole;
|
|
2362
|
+
const roleReady = Boolean(role);
|
|
2363
|
+
const relayReady = relay.state === 'running';
|
|
2364
|
+
const startupReady = !['win32', 'linux'].includes(process.platform) || autostart.state === 'enabled';
|
|
2365
|
+
const complete = sharedRepositoryReady && namedSessionReady && firstLearningReady
|
|
2366
|
+
&& roleReady && relayReady && startupReady;
|
|
2344
2367
|
return {
|
|
2345
|
-
ok:
|
|
2346
|
-
stage: sharedRepositoryReady
|
|
2347
|
-
|
|
2368
|
+
ok: complete,
|
|
2369
|
+
stage: !sharedRepositoryReady
|
|
2370
|
+
? repositoryReadiness?.outcome === 'blocked' ? 'shared_repository_blocked' : 'shared_repository_pending'
|
|
2371
|
+
: !namedSessionReady ? 'named_session_pending'
|
|
2372
|
+
: !firstLearningReady ? 'first_learning_pending'
|
|
2373
|
+
: !roleReady ? 'role_registration_pending'
|
|
2374
|
+
: !relayReady ? 'synchronization_pending'
|
|
2375
|
+
: !startupReady ? 'autostart_pending' : 'complete',
|
|
2348
2376
|
repositorySelection,
|
|
2349
2377
|
localStage: 'complete',
|
|
2350
2378
|
sharedRepositoryReady,
|
|
@@ -2379,13 +2407,13 @@ async function bootstrap(flags) {
|
|
|
2379
2407
|
},
|
|
2380
2408
|
workflowReadiness: {
|
|
2381
2409
|
sharedPackage: sharedRepositoryReady ? 'ready' : 'pending',
|
|
2382
|
-
roleRegistration:
|
|
2410
|
+
roleRegistration: roleReady ? 'ready' : 'pending',
|
|
2383
2411
|
firstLearning: firstLearning?.state === 'synchronized'
|
|
2384
2412
|
? Number(firstLearning.discovered || 0) > 0 ? 'synchronized' : 'no_eligible_history'
|
|
2385
2413
|
: String(firstLearning?.state || 'pending'),
|
|
2386
2414
|
synchronization: relay.state === 'running' ? 'running' : 'pending',
|
|
2387
2415
|
namedSession: namedSession && namedSessionReady ? 'ready'
|
|
2388
|
-
: provider === 'codex' && process.platform === 'linux' ? 'pending' : 'not_supported',
|
|
2416
|
+
: joinedBindingId ? 'not_requested' : provider === 'codex' && process.platform === 'linux' ? 'pending' : 'not_supported',
|
|
2389
2417
|
},
|
|
2390
2418
|
};
|
|
2391
2419
|
}
|
|
@@ -3106,6 +3134,9 @@ async function evidencePreview(flags) {
|
|
|
3106
3134
|
}
|
|
3107
3135
|
async function workspaceAdd(flags, positional) {
|
|
3108
3136
|
const path = await realpath(positional[0] || required(flags, 'path'));
|
|
3137
|
+
if ((await registry()).some(record => record.path === path && record.accessMode === 'knowledge_only')) {
|
|
3138
|
+
throw new Error('repository_join_source_operation_denied');
|
|
3139
|
+
}
|
|
3109
3140
|
const device = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
3110
3141
|
const organizationId = String(flags.get('organization-id') || device.organizationId);
|
|
3111
3142
|
if (organizationId !== device.organizationId)
|
|
@@ -3477,7 +3508,7 @@ export function canonicalEndpointRole(value) {
|
|
|
3477
3508
|
}
|
|
3478
3509
|
return { revision: Number(row.revision), profileHash: row.profileHash };
|
|
3479
3510
|
}
|
|
3480
|
-
async function bindRepositoryAgent(fabric, item) {
|
|
3511
|
+
async function bindRepositoryAgent(fabric, item, joinExistingBindingId) {
|
|
3481
3512
|
const remote = await gitValue(item.path, ['config', '--get', 'remote.origin.url']);
|
|
3482
3513
|
const currentIdentity = item.repositoryIdentityVersion === 'normalized-v1'
|
|
3483
3514
|
? item.repositoryRemoteHash
|
|
@@ -3491,6 +3522,7 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
3491
3522
|
defaultSourceRef: item.defaultBranch,
|
|
3492
3523
|
workspaceId: item.workspaceId,
|
|
3493
3524
|
legacySourceFingerprint: item.repositoryIdentityVersion === 'normalized-v1' ? null : item.repositoryRemoteHash,
|
|
3525
|
+
...(joinExistingBindingId ? { joinExistingBindingId } : {}),
|
|
3494
3526
|
});
|
|
3495
3527
|
const repositoryAgent = response.repositoryAgent && typeof response.repositoryAgent === 'object'
|
|
3496
3528
|
? response.repositoryAgent
|
|
@@ -3532,6 +3564,9 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
3532
3564
|
|| !/^agents\/[a-z0-9][a-z0-9._-]*-[a-f0-9]{8}$/.test(updated.controlBranch || '')) {
|
|
3533
3565
|
throw new Error('Dharma HQ returned an invalid repository-agent binding.');
|
|
3534
3566
|
}
|
|
3567
|
+
if (joinExistingBindingId && updated.repositoryBindingId !== joinExistingBindingId) {
|
|
3568
|
+
throw new Error('repository_join_binding_mismatch');
|
|
3569
|
+
}
|
|
3535
3570
|
await saveWorkspaceRecord(updated);
|
|
3536
3571
|
return updated;
|
|
3537
3572
|
}
|
|
@@ -3863,6 +3898,102 @@ async function registerWorkspaceBeforeRepositoryBind(fabric, item, policyRevisio
|
|
|
3863
3898
|
const registered = await bindRepositoryAgent(fabric, canonical);
|
|
3864
3899
|
return { registered, synchronized };
|
|
3865
3900
|
}
|
|
3901
|
+
async function joinExistingRepository(flags, bindingId, sourceFingerprint) {
|
|
3902
|
+
const config = await readDeviceConfig();
|
|
3903
|
+
if (!config || config.organizationId !== required(flags, 'organization-id')) {
|
|
3904
|
+
throw new Error('repository_join_enrollment_required');
|
|
3905
|
+
}
|
|
3906
|
+
const workspace = resolve(dharmaHome(), 'joined-repositories', bindingId);
|
|
3907
|
+
await mkdir(workspace, { recursive: true, mode: 0o700 });
|
|
3908
|
+
if (await realpath(workspace) !== workspace)
|
|
3909
|
+
throw new Error('repository_join_workspace_symlink_denied');
|
|
3910
|
+
const markerPath = resolve(workspace, '.dharma', 'join-identity.json');
|
|
3911
|
+
const expectedMarker = { organizationId: config.organizationId, bindingId, sourceFingerprint };
|
|
3912
|
+
const existingMarker = await readFile(markerPath, 'utf8').then(value => JSON.parse(value))
|
|
3913
|
+
.catch(error => { if (error?.code === 'ENOENT')
|
|
3914
|
+
return null; throw error; });
|
|
3915
|
+
if (existingMarker && canonicalize(existingMarker) !== canonicalize(expectedMarker)) {
|
|
3916
|
+
throw new Error('repository_join_workspace_identity_conflict');
|
|
3917
|
+
}
|
|
3918
|
+
if (!existingMarker) {
|
|
3919
|
+
const entries = await readdir(workspace);
|
|
3920
|
+
if (entries.length)
|
|
3921
|
+
throw new Error('repository_join_workspace_not_empty');
|
|
3922
|
+
await mkdir(resolve(workspace, '.dharma'), { recursive: true, mode: 0o700 });
|
|
3923
|
+
await writeJsonAtomic(markerPath, expectedMarker);
|
|
3924
|
+
}
|
|
3925
|
+
if (!await gitValue(workspace, ['rev-parse', '--verify', 'HEAD'])) {
|
|
3926
|
+
const template = resolve(workspace, '.dharma', 'empty-git-template');
|
|
3927
|
+
await mkdir(template, { recursive: true, mode: 0o700 });
|
|
3928
|
+
await execFileAsync('git', ['-c', `init.templateDir=${template}`, 'init', '--initial-branch=main', workspace]);
|
|
3929
|
+
await execFileAsync('git', ['-C', workspace, '-c', 'user.name=Dharma Agent Fabric',
|
|
3930
|
+
'-c', 'user.email=agent-fabric@localhost', '-c', 'commit.gpgsign=false', '-c', `core.hooksPath=${template}`,
|
|
3931
|
+
'commit', '--allow-empty', '-m', 'Initialize isolated knowledge workspace']);
|
|
3932
|
+
}
|
|
3933
|
+
const workspaceId = workspaceIdForDevice({ organizationId: config.organizationId,
|
|
3934
|
+
deviceId: config.deviceId, path: workspace });
|
|
3935
|
+
let item = selectDeviceWorkspace(await registry(), { organizationId: config.organizationId,
|
|
3936
|
+
deviceId: config.deviceId, path: workspace, repositoryRemoteHash: sourceFingerprint });
|
|
3937
|
+
if (item && item.accessMode !== 'knowledge_only')
|
|
3938
|
+
throw new Error('repository_join_workspace_mode_conflict');
|
|
3939
|
+
if (!item) {
|
|
3940
|
+
item = {
|
|
3941
|
+
workspaceId, organizationId: config.organizationId, name: `joined-${bindingId.slice(0, 8)}`,
|
|
3942
|
+
path: workspace, routeHash: `sha256:${createHash('sha256').update(workspace).digest('hex')}`,
|
|
3943
|
+
repositoryRemoteHash: sourceFingerprint, repositoryIdentityVersion: 'normalized-v1',
|
|
3944
|
+
repositoryAgentId: null, repositoryBindingId: null, repositoryAgentKey: null,
|
|
3945
|
+
controlBranch: null, defaultBranch: null, status: 'active', accessMode: 'knowledge_only',
|
|
3946
|
+
};
|
|
3947
|
+
await saveWorkspaceRecord(item);
|
|
3948
|
+
}
|
|
3949
|
+
const provider = String(flags.get('provider') || 'codex');
|
|
3950
|
+
if (!isLocalProviderId(provider))
|
|
3951
|
+
throw new Error('repository_join_provider_invalid');
|
|
3952
|
+
const fabric = await client();
|
|
3953
|
+
const synchronized = await syncWorkspacePolicy(fabric, item, required(flags, 'policy-revision'), true, [provider]);
|
|
3954
|
+
item = reconcileWorkspaceRegistration(await registry(), item, synchronized);
|
|
3955
|
+
item = await bindRepositoryAgent(fabric, item, bindingId);
|
|
3956
|
+
if (item.repositoryPackage?.state !== 'published' || !item.repositoryAgentId || !item.endpointId) {
|
|
3957
|
+
return { ok: false, stage: 'shared_repository_pending', code: 'repository_join_package_unavailable',
|
|
3958
|
+
workspaceId: item.workspaceId, sharedRepositoryReady: false };
|
|
3959
|
+
}
|
|
3960
|
+
const roleInput = {
|
|
3961
|
+
roleName: String(flags.get('role-name') || 'Knowledge collaborator'),
|
|
3962
|
+
questionCategories: (typeof flags.get('question-categories') === 'string'
|
|
3963
|
+
? String(flags.get('question-categories')).split(',').map(value => value.trim()).filter(Boolean)
|
|
3964
|
+
: ['repository-knowledge', 'lexicon', 'skills']).sort(),
|
|
3965
|
+
description: String(flags.get('role-description') || 'Uses the signed repository package and answers bounded knowledge questions.'),
|
|
3966
|
+
};
|
|
3967
|
+
const profileHash = sha256(canonicalize(roleInput));
|
|
3968
|
+
const role = item.repositoryRole?.profileHash === profileHash
|
|
3969
|
+
? { stage: 'repository_role_observed', reused: true }
|
|
3970
|
+
: await registerRepositoryRoleMetadata(fabric, repositoryRoleScope(item), {
|
|
3971
|
+
expectedRevision: item.repositoryRole?.revision || 0, ...roleInput,
|
|
3972
|
+
});
|
|
3973
|
+
if (item.repositoryRole?.profileHash !== profileHash) {
|
|
3974
|
+
const revision = Number(role.role?.revision);
|
|
3975
|
+
if (!Number.isSafeInteger(revision) || revision < 1)
|
|
3976
|
+
throw new Error('repository_join_role_registration_failed');
|
|
3977
|
+
item = { ...item, repositoryRole: { revision, profileHash } };
|
|
3978
|
+
await saveWorkspaceRecord(item);
|
|
3979
|
+
}
|
|
3980
|
+
const providers = await receiptAwareProviderCapabilities(await Promise.all(selectedProviderAdapters([provider]).map(adapter => adapter.capability())));
|
|
3981
|
+
const nativeSkills = await installAvailableNativeAgentFabricBootstraps({ providers,
|
|
3982
|
+
workspace, workspaceId: item.workspaceId, organizationId: config.organizationId, hqUrl: config.hqUrl });
|
|
3983
|
+
const relay = flags.has('no-relay-daemon') ? { started: false, state: 'not_started' }
|
|
3984
|
+
: await startRelayDaemon(resolve(workspace, '.dharma', 'approved-policy.json'));
|
|
3985
|
+
const sharedRepositoryReady = await repositorySharedReady(item);
|
|
3986
|
+
return { ok: true, stage: sharedRepositoryReady ? 'ready' : 'shared_repository_pending',
|
|
3987
|
+
localStage: 'ready', sharedRepositoryReady, organizationId: config.organizationId,
|
|
3988
|
+
workspaceId: item.workspaceId, deviceId: config.deviceId, accessMode: 'knowledge_only',
|
|
3989
|
+
repositoryAgent: { id: item.repositoryAgentId, key: item.repositoryAgentKey,
|
|
3990
|
+
bindingId: item.repositoryBindingId, controlBranch: item.controlBranch },
|
|
3991
|
+
repositoryPackage: item.repositoryPackage, repositoryRole: role, providers,
|
|
3992
|
+
firstLearningEvidence: { state: 'shared_package_inherited', discovered: 0,
|
|
3993
|
+
disclosureReady: false, note: 'No local source or trajectory history was read.' },
|
|
3994
|
+
nativeSkills: nativeSkills.installed, nativeSkillFailures: nativeSkills.failures,
|
|
3995
|
+
workspaceSync: synchronized, relay };
|
|
3996
|
+
}
|
|
3866
3997
|
async function readDeviceConfig() {
|
|
3867
3998
|
try {
|
|
3868
3999
|
return JSON.parse(await readFile(configPath(), 'utf8'));
|
|
@@ -4119,6 +4250,9 @@ async function repositorySnapshotCommand(flags, outputs) {
|
|
|
4119
4250
|
}
|
|
4120
4251
|
const registeredWorkspace = (await registry()).find(record => record.path === resolve(workspace)
|
|
4121
4252
|
&& record.organizationId === flags.get('organization-id') && record.workspaceId === flags.get('workspace-id'));
|
|
4253
|
+
if (registeredWorkspace?.accessMode === 'knowledge_only') {
|
|
4254
|
+
throw new Error('repository_join_source_operation_denied');
|
|
4255
|
+
}
|
|
4122
4256
|
const sourceAuthorization = registeredWorkspace?.repositoryAgentId && registeredWorkspace.repositoryBindingId
|
|
4123
4257
|
? await fetchRepositorySourceAuthorization(await client(), {
|
|
4124
4258
|
organizationId: registeredWorkspace.organizationId,
|
|
@@ -4175,7 +4309,6 @@ async function onboard(flags) {
|
|
|
4175
4309
|
? [String(flags.get('providers'))]
|
|
4176
4310
|
: typeof flags.get('provider') === 'string' ? [String(flags.get('provider'))] : []);
|
|
4177
4311
|
const repositoryKey = typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null;
|
|
4178
|
-
const repositoryIdentity = await preflightBootstrapWorkspaceIdentity(workspace, repositoryKey);
|
|
4179
4312
|
const registryState = await inspectRegistryRecoveryFile(workspaceRegistryPath());
|
|
4180
4313
|
if (registryState.kind === 'corrupt_zero') {
|
|
4181
4314
|
const anchor = await inspectOwnedRelayAutostart({ home: dharmaHome() });
|
|
@@ -4185,6 +4318,10 @@ async function onboard(flags) {
|
|
|
4185
4318
|
['workspace', anchor.workspace], ['apply', true],
|
|
4186
4319
|
]));
|
|
4187
4320
|
}
|
|
4321
|
+
if ((await registry()).some(record => record.path === workspace && record.accessMode === 'knowledge_only')) {
|
|
4322
|
+
throw new Error('repository_join_source_operation_denied');
|
|
4323
|
+
}
|
|
4324
|
+
const repositoryIdentity = await preflightBootstrapWorkspaceIdentity(workspace, repositoryKey);
|
|
4188
4325
|
let registered = selectDeviceWorkspace(await registry(), {
|
|
4189
4326
|
organizationId,
|
|
4190
4327
|
deviceId: config.deviceId,
|
|
@@ -4959,6 +5096,16 @@ async function executeOneTask(fabric, leaseSeconds, exactStudyTask) {
|
|
|
4959
5096
|
const workspace = (await registry()).find((item) => item.workspaceId === task.workspaceId);
|
|
4960
5097
|
if (!workspace)
|
|
4961
5098
|
throw new Error('Task workspace is not registered on this device.');
|
|
5099
|
+
if (workspace.accessMode === 'knowledge_only' && !(task.taskType === 'external_request'
|
|
5100
|
+
&& task.authority.readPaths.length === 1 && task.authority.readPaths[0] === '.'
|
|
5101
|
+
&& task.authority.writePaths.length === 0 && task.authority.commands.length === 0
|
|
5102
|
+
&& task.authority.network === 'deny' && task.acceptance.commands.length === 0
|
|
5103
|
+
&& task.acceptance.requiredArtifacts.length === 0)) {
|
|
5104
|
+
await fabric.postTaskEvent(task.taskId, 'failed', {
|
|
5105
|
+
phase: 'preflight', code: 'knowledge_only_task_denied',
|
|
5106
|
+
});
|
|
5107
|
+
return { ok: false, taskId: task.taskId, code: 'knowledge_only_task_denied' };
|
|
5108
|
+
}
|
|
4962
5109
|
const taskPolicy = await refreshVerifiedWorkspacePolicyForTransmission(resolve(workspace.path, '.dharma', 'approved-policy.json'), workspace.workspaceId, fabric);
|
|
4963
5110
|
assertTaskWorkspacePolicy({ task, workspace, policy: taskPolicy });
|
|
4964
5111
|
const config = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
@@ -6294,7 +6441,8 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6294
6441
|
|| (canonicalWorkspace.repositoryPackage?.state === 'published'
|
|
6295
6442
|
&& canonicalWorkspace.repositoryPackage.snapshotHash === canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash
|
|
6296
6443
|
? canonicalWorkspace.repositoryPackage.snapshotHash : null);
|
|
6297
|
-
if (canonicalWorkspace.
|
|
6444
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && canonicalWorkspace.repositoryPackage
|
|
6445
|
+
&& canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6298
6446
|
try {
|
|
6299
6447
|
const recovered = await recoverPublishedLocalSourceBaseline({
|
|
6300
6448
|
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
@@ -6509,7 +6657,8 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6509
6657
|
}
|
|
6510
6658
|
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6511
6659
|
}
|
|
6512
|
-
if (
|
|
6660
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && !sourceScanFlight
|
|
6661
|
+
&& canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6513
6662
|
try {
|
|
6514
6663
|
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
6515
6664
|
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
@@ -6538,7 +6687,7 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6538
6687
|
}
|
|
6539
6688
|
}
|
|
6540
6689
|
let evidenceRequestId;
|
|
6541
|
-
if (evidencePolicyFresh && !stopping) {
|
|
6690
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && evidencePolicyFresh && !stopping) {
|
|
6542
6691
|
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6543
6692
|
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6544
6693
|
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
@@ -6610,7 +6759,8 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6610
6759
|
}
|
|
6611
6760
|
nextSkillActivationAt = performance.now() + 60_000;
|
|
6612
6761
|
}
|
|
6613
|
-
if (!stopping && !sourceScanFlight
|
|
6762
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && !stopping && !sourceScanFlight
|
|
6763
|
+
&& performance.now() >= nextRepositorySourceScanAt) {
|
|
6614
6764
|
const sourceWorkspace = canonicalWorkspace;
|
|
6615
6765
|
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6616
6766
|
sourceScanFlight = (async () => {
|