@dharma-ai-labs/agent-fabric 0.2.126 → 0.2.128
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 +27 -21
- package/README.md +7 -4
- package/dist/demoEnrollment.test.js +19 -1
- package/dist/demoEnrollment.test.js.map +1 -1
- package/dist/demoPackage.d.ts +28 -0
- package/dist/demoPackage.d.ts.map +1 -1
- package/dist/demoPackage.js +16 -0
- package/dist/demoPackage.js.map +1 -1
- package/dist/demoPackage.test.js +21 -0
- package/dist/demoPackage.test.js.map +1 -1
- package/dist/demoWatchControl.d.ts.map +1 -1
- package/dist/demoWatchControl.js +3 -2
- package/dist/demoWatchControl.js.map +1 -1
- package/dist/demoWatchControl.test.js +14 -0
- package/dist/demoWatchControl.test.js.map +1 -1
- package/dist/demoWatchRegistry.d.ts +1 -0
- package/dist/demoWatchRegistry.d.ts.map +1 -1
- package/dist/demoWatchRegistry.js +5 -3
- package/dist/demoWatchRegistry.js.map +1 -1
- package/dist/demoWatchRegistry.test.js +15 -1
- package/dist/demoWatchRegistry.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +210 -40
- 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/dist/skillPreparationTransaction.test.js +4 -2
- package/dist/skillPreparationTransaction.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.128';
|
|
65
65
|
const USAGE = CLI_USAGE;
|
|
66
66
|
const execFileAsync = promisify(execFile);
|
|
67
67
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -1768,9 +1768,15 @@ async function supervisedDemoCycle(registration, signal) {
|
|
|
1768
1768
|
if (workspace !== registration.workspace)
|
|
1769
1769
|
throw new Error('Demo watch checkout is no longer canonical.');
|
|
1770
1770
|
const root = await gitValue(workspace, ['rev-parse', '--show-toplevel']);
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1771
|
+
if (registration.knowledgeOnly) {
|
|
1772
|
+
if (root)
|
|
1773
|
+
throw Object.assign(new Error('Demo knowledge-only workspace became a Git checkout.'), { code: 'demo_watch_workspace_changed' });
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
const remote = await gitValue(workspace, ['config', '--get', 'remote.origin.url']);
|
|
1777
|
+
if (!root || !remote || normalizeGitRemoteIdentity(remote) !== registration.normalizedRepository) {
|
|
1778
|
+
throw Object.assign(new Error('Demo watch repository binding changed.'), { code: 'demo_watch_repository_changed' });
|
|
1779
|
+
}
|
|
1774
1780
|
}
|
|
1775
1781
|
const scope = { hqUrl: registration.hqUrl, organizationId: registration.organizationId,
|
|
1776
1782
|
repositoryId: registration.repositoryId, normalizedRepository: registration.normalizedRepository,
|
|
@@ -1786,6 +1792,7 @@ async function supervisedDemoCycle(registration, signal) {
|
|
|
1786
1792
|
signal: options?.signal ? AbortSignal.any([options.signal, signal]) : signal });
|
|
1787
1793
|
} });
|
|
1788
1794
|
return demoRepositoryPackage({ scope, workspace, provider: registration.provider,
|
|
1795
|
+
knowledgeOnly: registration.knowledgeOnly,
|
|
1789
1796
|
nativeSkillDirectory: nativeDirectory }, deps);
|
|
1790
1797
|
});
|
|
1791
1798
|
}
|
|
@@ -2047,6 +2054,16 @@ async function bootstrap(flags) {
|
|
|
2047
2054
|
const resuming = flags.has('resume');
|
|
2048
2055
|
const bootstrapToken = resuming ? null : required(flags, 'grant');
|
|
2049
2056
|
const selectedRemote = flags.get('repository-url-base64url');
|
|
2057
|
+
const joinedBindingId = typeof flags.get('join-repository-binding-id') === 'string'
|
|
2058
|
+
? String(flags.get('join-repository-binding-id')) : null;
|
|
2059
|
+
const joinedFingerprint = typeof flags.get('join-source-fingerprint') === 'string'
|
|
2060
|
+
? String(flags.get('join-source-fingerprint')) : null;
|
|
2061
|
+
if (joinedBindingId && (!UUID_PATTERN.test(joinedBindingId) || !/^sha256:[a-f0-9]{64}$/.test(joinedFingerprint || '')
|
|
2062
|
+
|| selectedRemote || flags.has('repository-key'))) {
|
|
2063
|
+
throw new Error('repository_join_selection_invalid: joining requires an exact existing binding and fingerprint, without a source selection.');
|
|
2064
|
+
}
|
|
2065
|
+
if (!joinedBindingId && joinedFingerprint)
|
|
2066
|
+
throw new Error('repository_join_selection_invalid');
|
|
2050
2067
|
let repositorySelection;
|
|
2051
2068
|
try {
|
|
2052
2069
|
if (flags.has('repository-url-base64url') && typeof selectedRemote !== 'string') {
|
|
@@ -2055,16 +2072,18 @@ async function bootstrap(flags) {
|
|
|
2055
2072
|
if (selectedRemote && flags.has('repository-key')) {
|
|
2056
2073
|
throw new Error('repository_selection_invalid_url: selected remote cannot be combined with a repository key.');
|
|
2057
2074
|
}
|
|
2058
|
-
repositorySelection =
|
|
2059
|
-
?
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2075
|
+
repositorySelection = joinedBindingId
|
|
2076
|
+
? { workspace: resolve(dharmaHome(), 'joined-repositories', joinedBindingId), selection: 'existing_organization_binding' }
|
|
2077
|
+
: typeof selectedRemote === 'string'
|
|
2078
|
+
? await resolveBootstrapRepositoryWorkspace({
|
|
2079
|
+
workspace: String(flags.get('workspace') || '.'),
|
|
2080
|
+
selectedRemoteBase64url: selectedRemote,
|
|
2081
|
+
organizationId,
|
|
2082
|
+
home: dharmaHome(),
|
|
2083
|
+
normalizeRemote: normalizeGitRemoteIdentity,
|
|
2084
|
+
withLock: (path) => acquirePidLock(path, 30_000, 'repository_checkout_busy: another setup is selecting this repository; retry after it finishes.'),
|
|
2085
|
+
})
|
|
2086
|
+
: { workspace: await realpath(String(flags.get('workspace') || '.')), selection: 'provided' };
|
|
2068
2087
|
}
|
|
2069
2088
|
catch (error) {
|
|
2070
2089
|
const message = error && typeof error === 'object' && 'message' in error && typeof error.message === 'string'
|
|
@@ -2079,10 +2098,12 @@ async function bootstrap(flags) {
|
|
|
2079
2098
|
if (selectedRemote)
|
|
2080
2099
|
process.stderr.write(`Agent Fabric repository checkout: ${workspace}\n`);
|
|
2081
2100
|
const policyRevision = required(flags, 'policy-revision');
|
|
2082
|
-
const repositoryIdentity =
|
|
2101
|
+
const repositoryIdentity = joinedBindingId
|
|
2102
|
+
? { fingerprint: joinedFingerprint }
|
|
2103
|
+
: await preflightBootstrapWorkspaceIdentity(workspace, typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null);
|
|
2083
2104
|
const requestedProvider = String(flags.get('provider') || 'auto').trim().toLowerCase();
|
|
2084
2105
|
const provider = requestedProvider === 'auto'
|
|
2085
|
-
? await detectBootstrapProvider(workspace)
|
|
2106
|
+
? await detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace)
|
|
2086
2107
|
: requestedProvider;
|
|
2087
2108
|
if (!isLocalProviderId(provider)) {
|
|
2088
2109
|
throw new Error('Bootstrap provider must be auto, codex, claude, agy, or hermes.');
|
|
@@ -2121,7 +2142,7 @@ async function bootstrap(flags) {
|
|
|
2121
2142
|
name,
|
|
2122
2143
|
platform: devicePlatform,
|
|
2123
2144
|
publicKeyEd25519: identity.publicKeyEd25519,
|
|
2124
|
-
repositoryFingerprint: repositoryIdentity.fingerprint,
|
|
2145
|
+
repositoryFingerprint: joinedBindingId ? '' : repositoryIdentity.fingerprint,
|
|
2125
2146
|
onRecipientApprovalRequired: async (approval) => {
|
|
2126
2147
|
recipientApproval.required = true;
|
|
2127
2148
|
recipientApproval.browserOpened = flags.has('no-browser')
|
|
@@ -2204,7 +2225,9 @@ async function bootstrap(flags) {
|
|
|
2204
2225
|
onboardFlags.set('workspace', workspace);
|
|
2205
2226
|
onboardFlags.set('policy-revision', policyRevision);
|
|
2206
2227
|
onboardFlags.set('provider', provider);
|
|
2207
|
-
const onboarded = await retryBootstrapOnboarding(async () =>
|
|
2228
|
+
const onboarded = await retryBootstrapOnboarding(async () => joinedBindingId
|
|
2229
|
+
? await joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint)
|
|
2230
|
+
: await onboard(onboardFlags));
|
|
2208
2231
|
if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
|
|
2209
2232
|
return {
|
|
2210
2233
|
ok: onboarded.ok === true,
|
|
@@ -2227,7 +2250,8 @@ async function bootstrap(flags) {
|
|
|
2227
2250
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
2228
2251
|
const autostart = flags.has('no-relay-daemon')
|
|
2229
2252
|
? { 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}
|
|
2253
|
+
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`
|
|
2254
|
+
+ (joinedBindingId ? ` --join-repository-binding-id ${joinedBindingId} --join-source-fingerprint ${joinedFingerprint}` : ''), () => withRelayStartupMutation(async () => {
|
|
2231
2255
|
const home = dharmaHome();
|
|
2232
2256
|
const existing = await relayAutostartStatus({ home });
|
|
2233
2257
|
if (existing.backend !== null || existing.state !== 'disabled') {
|
|
@@ -2285,7 +2309,8 @@ async function bootstrap(flags) {
|
|
|
2285
2309
|
throw new Error(`Provider skill did not become ready: ${JSON.stringify(skill)}`);
|
|
2286
2310
|
const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
|
|
2287
2311
|
const firstLearning = onboarded.firstLearningEvidence;
|
|
2288
|
-
const firstLearningReady = firstLearning?.state === 'synchronized' || firstLearning?.state === 'denied_disclosure'
|
|
2312
|
+
const firstLearningReady = firstLearning?.state === 'synchronized' || firstLearning?.state === 'denied_disclosure'
|
|
2313
|
+
|| joinedBindingId !== null && firstLearning?.state === 'shared_package_inherited';
|
|
2289
2314
|
const relay = flags.has('no-relay-daemon')
|
|
2290
2315
|
? { started: false, ...(await waitForRelayReadiness()) }
|
|
2291
2316
|
: await startRelayDaemon(policyPath);
|
|
@@ -2329,7 +2354,7 @@ async function bootstrap(flags) {
|
|
|
2329
2354
|
usage,
|
|
2330
2355
|
});
|
|
2331
2356
|
const operatingContract = await loadAgentFabricOnboardingContract();
|
|
2332
|
-
const namedSession = provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
|
|
2357
|
+
const namedSession = !joinedBindingId && provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
|
|
2333
2358
|
? 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
2359
|
['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
|
|
2335
2360
|
['workspace-id', String(onboarded.workspaceId)], ['apply', true],
|
|
@@ -2338,13 +2363,23 @@ async function bootstrap(flags) {
|
|
|
2338
2363
|
])))
|
|
2339
2364
|
: null;
|
|
2340
2365
|
const repositoryReceipt = onboarded;
|
|
2341
|
-
const namedSessionReady = provider !== 'codex' || process.platform !== 'linux'
|
|
2366
|
+
const namedSessionReady = Boolean(joinedBindingId) || provider !== 'codex' || process.platform !== 'linux'
|
|
2342
2367
|
|| Boolean(namedSession?.ok === true && ['running', 'executing'].includes(String(namedSession.state)));
|
|
2343
2368
|
const role = repositoryReceipt.repositoryRole;
|
|
2369
|
+
const roleReady = Boolean(role);
|
|
2370
|
+
const relayReady = relay.state === 'running';
|
|
2371
|
+
const startupReady = !['win32', 'linux'].includes(process.platform) || autostart.state === 'enabled';
|
|
2372
|
+
const complete = sharedRepositoryReady && namedSessionReady && firstLearningReady
|
|
2373
|
+
&& roleReady && relayReady && startupReady;
|
|
2344
2374
|
return {
|
|
2345
|
-
ok:
|
|
2346
|
-
stage: sharedRepositoryReady
|
|
2347
|
-
|
|
2375
|
+
ok: complete,
|
|
2376
|
+
stage: !sharedRepositoryReady
|
|
2377
|
+
? repositoryReadiness?.outcome === 'blocked' ? 'shared_repository_blocked' : 'shared_repository_pending'
|
|
2378
|
+
: !namedSessionReady ? 'named_session_pending'
|
|
2379
|
+
: !firstLearningReady ? 'first_learning_pending'
|
|
2380
|
+
: !roleReady ? 'role_registration_pending'
|
|
2381
|
+
: !relayReady ? 'synchronization_pending'
|
|
2382
|
+
: !startupReady ? 'autostart_pending' : 'complete',
|
|
2348
2383
|
repositorySelection,
|
|
2349
2384
|
localStage: 'complete',
|
|
2350
2385
|
sharedRepositoryReady,
|
|
@@ -2379,13 +2414,13 @@ async function bootstrap(flags) {
|
|
|
2379
2414
|
},
|
|
2380
2415
|
workflowReadiness: {
|
|
2381
2416
|
sharedPackage: sharedRepositoryReady ? 'ready' : 'pending',
|
|
2382
|
-
roleRegistration:
|
|
2417
|
+
roleRegistration: roleReady ? 'ready' : 'pending',
|
|
2383
2418
|
firstLearning: firstLearning?.state === 'synchronized'
|
|
2384
2419
|
? Number(firstLearning.discovered || 0) > 0 ? 'synchronized' : 'no_eligible_history'
|
|
2385
2420
|
: String(firstLearning?.state || 'pending'),
|
|
2386
2421
|
synchronization: relay.state === 'running' ? 'running' : 'pending',
|
|
2387
2422
|
namedSession: namedSession && namedSessionReady ? 'ready'
|
|
2388
|
-
: provider === 'codex' && process.platform === 'linux' ? 'pending' : 'not_supported',
|
|
2423
|
+
: joinedBindingId ? 'not_requested' : provider === 'codex' && process.platform === 'linux' ? 'pending' : 'not_supported',
|
|
2389
2424
|
},
|
|
2390
2425
|
};
|
|
2391
2426
|
}
|
|
@@ -3106,6 +3141,9 @@ async function evidencePreview(flags) {
|
|
|
3106
3141
|
}
|
|
3107
3142
|
async function workspaceAdd(flags, positional) {
|
|
3108
3143
|
const path = await realpath(positional[0] || required(flags, 'path'));
|
|
3144
|
+
if ((await registry()).some(record => record.path === path && record.accessMode === 'knowledge_only')) {
|
|
3145
|
+
throw new Error('repository_join_source_operation_denied');
|
|
3146
|
+
}
|
|
3109
3147
|
const device = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
3110
3148
|
const organizationId = String(flags.get('organization-id') || device.organizationId);
|
|
3111
3149
|
if (organizationId !== device.organizationId)
|
|
@@ -3477,7 +3515,7 @@ export function canonicalEndpointRole(value) {
|
|
|
3477
3515
|
}
|
|
3478
3516
|
return { revision: Number(row.revision), profileHash: row.profileHash };
|
|
3479
3517
|
}
|
|
3480
|
-
async function bindRepositoryAgent(fabric, item) {
|
|
3518
|
+
async function bindRepositoryAgent(fabric, item, joinExistingBindingId) {
|
|
3481
3519
|
const remote = await gitValue(item.path, ['config', '--get', 'remote.origin.url']);
|
|
3482
3520
|
const currentIdentity = item.repositoryIdentityVersion === 'normalized-v1'
|
|
3483
3521
|
? item.repositoryRemoteHash
|
|
@@ -3491,6 +3529,7 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
3491
3529
|
defaultSourceRef: item.defaultBranch,
|
|
3492
3530
|
workspaceId: item.workspaceId,
|
|
3493
3531
|
legacySourceFingerprint: item.repositoryIdentityVersion === 'normalized-v1' ? null : item.repositoryRemoteHash,
|
|
3532
|
+
...(joinExistingBindingId ? { joinExistingBindingId } : {}),
|
|
3494
3533
|
});
|
|
3495
3534
|
const repositoryAgent = response.repositoryAgent && typeof response.repositoryAgent === 'object'
|
|
3496
3535
|
? response.repositoryAgent
|
|
@@ -3532,6 +3571,9 @@ async function bindRepositoryAgent(fabric, item) {
|
|
|
3532
3571
|
|| !/^agents\/[a-z0-9][a-z0-9._-]*-[a-f0-9]{8}$/.test(updated.controlBranch || '')) {
|
|
3533
3572
|
throw new Error('Dharma HQ returned an invalid repository-agent binding.');
|
|
3534
3573
|
}
|
|
3574
|
+
if (joinExistingBindingId && updated.repositoryBindingId !== joinExistingBindingId) {
|
|
3575
|
+
throw new Error('repository_join_binding_mismatch');
|
|
3576
|
+
}
|
|
3535
3577
|
await saveWorkspaceRecord(updated);
|
|
3536
3578
|
return updated;
|
|
3537
3579
|
}
|
|
@@ -3863,6 +3905,102 @@ async function registerWorkspaceBeforeRepositoryBind(fabric, item, policyRevisio
|
|
|
3863
3905
|
const registered = await bindRepositoryAgent(fabric, canonical);
|
|
3864
3906
|
return { registered, synchronized };
|
|
3865
3907
|
}
|
|
3908
|
+
async function joinExistingRepository(flags, bindingId, sourceFingerprint) {
|
|
3909
|
+
const config = await readDeviceConfig();
|
|
3910
|
+
if (!config || config.organizationId !== required(flags, 'organization-id')) {
|
|
3911
|
+
throw new Error('repository_join_enrollment_required');
|
|
3912
|
+
}
|
|
3913
|
+
const workspace = resolve(dharmaHome(), 'joined-repositories', bindingId);
|
|
3914
|
+
await mkdir(workspace, { recursive: true, mode: 0o700 });
|
|
3915
|
+
if (await realpath(workspace) !== workspace)
|
|
3916
|
+
throw new Error('repository_join_workspace_symlink_denied');
|
|
3917
|
+
const markerPath = resolve(workspace, '.dharma', 'join-identity.json');
|
|
3918
|
+
const expectedMarker = { organizationId: config.organizationId, bindingId, sourceFingerprint };
|
|
3919
|
+
const existingMarker = await readFile(markerPath, 'utf8').then(value => JSON.parse(value))
|
|
3920
|
+
.catch(error => { if (error?.code === 'ENOENT')
|
|
3921
|
+
return null; throw error; });
|
|
3922
|
+
if (existingMarker && canonicalize(existingMarker) !== canonicalize(expectedMarker)) {
|
|
3923
|
+
throw new Error('repository_join_workspace_identity_conflict');
|
|
3924
|
+
}
|
|
3925
|
+
if (!existingMarker) {
|
|
3926
|
+
const entries = await readdir(workspace);
|
|
3927
|
+
if (entries.length)
|
|
3928
|
+
throw new Error('repository_join_workspace_not_empty');
|
|
3929
|
+
await mkdir(resolve(workspace, '.dharma'), { recursive: true, mode: 0o700 });
|
|
3930
|
+
await writeJsonAtomic(markerPath, expectedMarker);
|
|
3931
|
+
}
|
|
3932
|
+
if (!await gitValue(workspace, ['rev-parse', '--verify', 'HEAD'])) {
|
|
3933
|
+
const template = resolve(workspace, '.dharma', 'empty-git-template');
|
|
3934
|
+
await mkdir(template, { recursive: true, mode: 0o700 });
|
|
3935
|
+
await execFileAsync('git', ['-c', `init.templateDir=${template}`, 'init', '--initial-branch=main', workspace]);
|
|
3936
|
+
await execFileAsync('git', ['-C', workspace, '-c', 'user.name=Dharma Agent Fabric',
|
|
3937
|
+
'-c', 'user.email=agent-fabric@localhost', '-c', 'commit.gpgsign=false', '-c', `core.hooksPath=${template}`,
|
|
3938
|
+
'commit', '--allow-empty', '-m', 'Initialize isolated knowledge workspace']);
|
|
3939
|
+
}
|
|
3940
|
+
const workspaceId = workspaceIdForDevice({ organizationId: config.organizationId,
|
|
3941
|
+
deviceId: config.deviceId, path: workspace });
|
|
3942
|
+
let item = selectDeviceWorkspace(await registry(), { organizationId: config.organizationId,
|
|
3943
|
+
deviceId: config.deviceId, path: workspace, repositoryRemoteHash: sourceFingerprint });
|
|
3944
|
+
if (item && item.accessMode !== 'knowledge_only')
|
|
3945
|
+
throw new Error('repository_join_workspace_mode_conflict');
|
|
3946
|
+
if (!item) {
|
|
3947
|
+
item = {
|
|
3948
|
+
workspaceId, organizationId: config.organizationId, name: `joined-${bindingId.slice(0, 8)}`,
|
|
3949
|
+
path: workspace, routeHash: `sha256:${createHash('sha256').update(workspace).digest('hex')}`,
|
|
3950
|
+
repositoryRemoteHash: sourceFingerprint, repositoryIdentityVersion: 'normalized-v1',
|
|
3951
|
+
repositoryAgentId: null, repositoryBindingId: null, repositoryAgentKey: null,
|
|
3952
|
+
controlBranch: null, defaultBranch: null, status: 'active', accessMode: 'knowledge_only',
|
|
3953
|
+
};
|
|
3954
|
+
await saveWorkspaceRecord(item);
|
|
3955
|
+
}
|
|
3956
|
+
const provider = String(flags.get('provider') || 'codex');
|
|
3957
|
+
if (!isLocalProviderId(provider))
|
|
3958
|
+
throw new Error('repository_join_provider_invalid');
|
|
3959
|
+
const fabric = await client();
|
|
3960
|
+
const synchronized = await syncWorkspacePolicy(fabric, item, required(flags, 'policy-revision'), true, [provider]);
|
|
3961
|
+
item = reconcileWorkspaceRegistration(await registry(), item, synchronized);
|
|
3962
|
+
item = await bindRepositoryAgent(fabric, item, bindingId);
|
|
3963
|
+
if (item.repositoryPackage?.state !== 'published' || !item.repositoryAgentId || !item.endpointId) {
|
|
3964
|
+
return { ok: false, stage: 'shared_repository_pending', code: 'repository_join_package_unavailable',
|
|
3965
|
+
workspaceId: item.workspaceId, sharedRepositoryReady: false };
|
|
3966
|
+
}
|
|
3967
|
+
const roleInput = {
|
|
3968
|
+
roleName: String(flags.get('role-name') || 'Knowledge collaborator'),
|
|
3969
|
+
questionCategories: (typeof flags.get('question-categories') === 'string'
|
|
3970
|
+
? String(flags.get('question-categories')).split(',').map(value => value.trim()).filter(Boolean)
|
|
3971
|
+
: ['repository-knowledge', 'lexicon', 'skills']).sort(),
|
|
3972
|
+
description: String(flags.get('role-description') || 'Uses the signed repository package and answers bounded knowledge questions.'),
|
|
3973
|
+
};
|
|
3974
|
+
const profileHash = sha256(canonicalize(roleInput));
|
|
3975
|
+
const role = item.repositoryRole?.profileHash === profileHash
|
|
3976
|
+
? { stage: 'repository_role_observed', reused: true }
|
|
3977
|
+
: await registerRepositoryRoleMetadata(fabric, repositoryRoleScope(item), {
|
|
3978
|
+
expectedRevision: item.repositoryRole?.revision || 0, ...roleInput,
|
|
3979
|
+
});
|
|
3980
|
+
if (item.repositoryRole?.profileHash !== profileHash) {
|
|
3981
|
+
const revision = Number(role.role?.revision);
|
|
3982
|
+
if (!Number.isSafeInteger(revision) || revision < 1)
|
|
3983
|
+
throw new Error('repository_join_role_registration_failed');
|
|
3984
|
+
item = { ...item, repositoryRole: { revision, profileHash } };
|
|
3985
|
+
await saveWorkspaceRecord(item);
|
|
3986
|
+
}
|
|
3987
|
+
const providers = await receiptAwareProviderCapabilities(await Promise.all(selectedProviderAdapters([provider]).map(adapter => adapter.capability())));
|
|
3988
|
+
const nativeSkills = await installAvailableNativeAgentFabricBootstraps({ providers,
|
|
3989
|
+
workspace, workspaceId: item.workspaceId, organizationId: config.organizationId, hqUrl: config.hqUrl });
|
|
3990
|
+
const relay = flags.has('no-relay-daemon') ? { started: false, state: 'not_started' }
|
|
3991
|
+
: await startRelayDaemon(resolve(workspace, '.dharma', 'approved-policy.json'));
|
|
3992
|
+
const sharedRepositoryReady = await repositorySharedReady(item);
|
|
3993
|
+
return { ok: true, stage: sharedRepositoryReady ? 'ready' : 'shared_repository_pending',
|
|
3994
|
+
localStage: 'ready', sharedRepositoryReady, organizationId: config.organizationId,
|
|
3995
|
+
workspaceId: item.workspaceId, deviceId: config.deviceId, accessMode: 'knowledge_only',
|
|
3996
|
+
repositoryAgent: { id: item.repositoryAgentId, key: item.repositoryAgentKey,
|
|
3997
|
+
bindingId: item.repositoryBindingId, controlBranch: item.controlBranch },
|
|
3998
|
+
repositoryPackage: item.repositoryPackage, repositoryRole: role, providers,
|
|
3999
|
+
firstLearningEvidence: { state: 'shared_package_inherited', discovered: 0,
|
|
4000
|
+
disclosureReady: false, note: 'No local source or trajectory history was read.' },
|
|
4001
|
+
nativeSkills: nativeSkills.installed, nativeSkillFailures: nativeSkills.failures,
|
|
4002
|
+
workspaceSync: synchronized, relay };
|
|
4003
|
+
}
|
|
3866
4004
|
async function readDeviceConfig() {
|
|
3867
4005
|
try {
|
|
3868
4006
|
return JSON.parse(await readFile(configPath(), 'utf8'));
|
|
@@ -4119,6 +4257,9 @@ async function repositorySnapshotCommand(flags, outputs) {
|
|
|
4119
4257
|
}
|
|
4120
4258
|
const registeredWorkspace = (await registry()).find(record => record.path === resolve(workspace)
|
|
4121
4259
|
&& record.organizationId === flags.get('organization-id') && record.workspaceId === flags.get('workspace-id'));
|
|
4260
|
+
if (registeredWorkspace?.accessMode === 'knowledge_only') {
|
|
4261
|
+
throw new Error('repository_join_source_operation_denied');
|
|
4262
|
+
}
|
|
4122
4263
|
const sourceAuthorization = registeredWorkspace?.repositoryAgentId && registeredWorkspace.repositoryBindingId
|
|
4123
4264
|
? await fetchRepositorySourceAuthorization(await client(), {
|
|
4124
4265
|
organizationId: registeredWorkspace.organizationId,
|
|
@@ -4175,7 +4316,6 @@ async function onboard(flags) {
|
|
|
4175
4316
|
? [String(flags.get('providers'))]
|
|
4176
4317
|
: typeof flags.get('provider') === 'string' ? [String(flags.get('provider'))] : []);
|
|
4177
4318
|
const repositoryKey = typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null;
|
|
4178
|
-
const repositoryIdentity = await preflightBootstrapWorkspaceIdentity(workspace, repositoryKey);
|
|
4179
4319
|
const registryState = await inspectRegistryRecoveryFile(workspaceRegistryPath());
|
|
4180
4320
|
if (registryState.kind === 'corrupt_zero') {
|
|
4181
4321
|
const anchor = await inspectOwnedRelayAutostart({ home: dharmaHome() });
|
|
@@ -4185,6 +4325,10 @@ async function onboard(flags) {
|
|
|
4185
4325
|
['workspace', anchor.workspace], ['apply', true],
|
|
4186
4326
|
]));
|
|
4187
4327
|
}
|
|
4328
|
+
if ((await registry()).some(record => record.path === workspace && record.accessMode === 'knowledge_only')) {
|
|
4329
|
+
throw new Error('repository_join_source_operation_denied');
|
|
4330
|
+
}
|
|
4331
|
+
const repositoryIdentity = await preflightBootstrapWorkspaceIdentity(workspace, repositoryKey);
|
|
4188
4332
|
let registered = selectDeviceWorkspace(await registry(), {
|
|
4189
4333
|
organizationId,
|
|
4190
4334
|
deviceId: config.deviceId,
|
|
@@ -4959,6 +5103,16 @@ async function executeOneTask(fabric, leaseSeconds, exactStudyTask) {
|
|
|
4959
5103
|
const workspace = (await registry()).find((item) => item.workspaceId === task.workspaceId);
|
|
4960
5104
|
if (!workspace)
|
|
4961
5105
|
throw new Error('Task workspace is not registered on this device.');
|
|
5106
|
+
if (workspace.accessMode === 'knowledge_only' && !(task.taskType === 'external_request'
|
|
5107
|
+
&& task.authority.readPaths.length === 1 && task.authority.readPaths[0] === '.'
|
|
5108
|
+
&& task.authority.writePaths.length === 0 && task.authority.commands.length === 0
|
|
5109
|
+
&& task.authority.network === 'deny' && task.acceptance.commands.length === 0
|
|
5110
|
+
&& task.acceptance.requiredArtifacts.length === 0)) {
|
|
5111
|
+
await fabric.postTaskEvent(task.taskId, 'failed', {
|
|
5112
|
+
phase: 'preflight', code: 'knowledge_only_task_denied',
|
|
5113
|
+
});
|
|
5114
|
+
return { ok: false, taskId: task.taskId, code: 'knowledge_only_task_denied' };
|
|
5115
|
+
}
|
|
4962
5116
|
const taskPolicy = await refreshVerifiedWorkspacePolicyForTransmission(resolve(workspace.path, '.dharma', 'approved-policy.json'), workspace.workspaceId, fabric);
|
|
4963
5117
|
assertTaskWorkspacePolicy({ task, workspace, policy: taskPolicy });
|
|
4964
5118
|
const config = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
@@ -6294,7 +6448,8 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6294
6448
|
|| (canonicalWorkspace.repositoryPackage?.state === 'published'
|
|
6295
6449
|
&& canonicalWorkspace.repositoryPackage.snapshotHash === canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash
|
|
6296
6450
|
? canonicalWorkspace.repositoryPackage.snapshotHash : null);
|
|
6297
|
-
if (canonicalWorkspace.
|
|
6451
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && canonicalWorkspace.repositoryPackage
|
|
6452
|
+
&& canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6298
6453
|
try {
|
|
6299
6454
|
const recovered = await recoverPublishedLocalSourceBaseline({
|
|
6300
6455
|
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
@@ -6509,7 +6664,8 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6509
6664
|
}
|
|
6510
6665
|
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6511
6666
|
}
|
|
6512
|
-
if (
|
|
6667
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && !sourceScanFlight
|
|
6668
|
+
&& canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6513
6669
|
try {
|
|
6514
6670
|
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
6515
6671
|
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
@@ -6538,7 +6694,7 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6538
6694
|
}
|
|
6539
6695
|
}
|
|
6540
6696
|
let evidenceRequestId;
|
|
6541
|
-
if (evidencePolicyFresh && !stopping) {
|
|
6697
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && evidencePolicyFresh && !stopping) {
|
|
6542
6698
|
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6543
6699
|
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6544
6700
|
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
@@ -6610,7 +6766,8 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6610
6766
|
}
|
|
6611
6767
|
nextSkillActivationAt = performance.now() + 60_000;
|
|
6612
6768
|
}
|
|
6613
|
-
if (!stopping && !sourceScanFlight
|
|
6769
|
+
if (canonicalWorkspace.accessMode !== 'knowledge_only' && !stopping && !sourceScanFlight
|
|
6770
|
+
&& performance.now() >= nextRepositorySourceScanAt) {
|
|
6614
6771
|
const sourceWorkspace = canonicalWorkspace;
|
|
6615
6772
|
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6616
6773
|
sourceScanFlight = (async () => {
|
|
@@ -6693,20 +6850,30 @@ export async function run(argv) {
|
|
|
6693
6850
|
const organizationId = required(flags, 'organization-id');
|
|
6694
6851
|
const repositoryId = required(flags, 'repository-id');
|
|
6695
6852
|
const normalizedRepository = required(flags, 'normalized-repository');
|
|
6853
|
+
const knowledgeOnly = flags.has('knowledge-only');
|
|
6854
|
+
if (knowledgeOnly && !flags.has('workspace')) {
|
|
6855
|
+
throw new Error('Demo knowledge-only setup requires an explicit private --workspace outside Git.');
|
|
6856
|
+
}
|
|
6696
6857
|
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
6697
6858
|
const root = await gitValue(workspace, ['rev-parse', '--show-toplevel']);
|
|
6698
|
-
if (
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6859
|
+
if (knowledgeOnly) {
|
|
6860
|
+
if (root)
|
|
6861
|
+
throw new Error('Demo knowledge-only workspace must be outside every Git checkout.');
|
|
6862
|
+
}
|
|
6863
|
+
else {
|
|
6864
|
+
if (!root)
|
|
6865
|
+
throw new Error('Demo device setup must run inside the exact Git repository.');
|
|
6866
|
+
const remote = await gitValue(workspace, ['config', '--get', 'remote.origin.url']);
|
|
6867
|
+
if (!remote || normalizeGitRemoteIdentity(remote) !== normalizedRepository) {
|
|
6868
|
+
throw new Error('Local Git remote does not match the private Demo repository binding.');
|
|
6869
|
+
}
|
|
6703
6870
|
}
|
|
6704
6871
|
const watchControl = ['watch-enable', 'watch-status', 'watch-disable'].includes(String(subcommand));
|
|
6705
6872
|
const signingProof = ['signing-client-proof', 'signing-owner-proof'].includes(String(subcommand));
|
|
6706
6873
|
if (signingProof && subcommand === 'signing-owner-proof' && flags.has('submit')) {
|
|
6707
6874
|
throw new Error('Owner signing approval requires explicit browser confirmation.');
|
|
6708
6875
|
}
|
|
6709
|
-
if (watchControl && await realpath(root) !== workspace) {
|
|
6876
|
+
if (watchControl && !knowledgeOnly && await realpath(root) !== workspace) {
|
|
6710
6877
|
throw new Error('Demo watch setup must select the exact Git repository root.');
|
|
6711
6878
|
}
|
|
6712
6879
|
let transportOrigin = null;
|
|
@@ -6725,6 +6892,7 @@ export async function run(argv) {
|
|
|
6725
6892
|
return { ok: true, stage: transportOrigin ? 'demo_transport_plan' : signingProof ? 'demo_signing_proof_plan'
|
|
6726
6893
|
: watchControl ? 'demo_watch_plan' : 'demo_device_plan',
|
|
6727
6894
|
organizationId, repositoryId, normalizedRepository, workspaceVerified: true,
|
|
6895
|
+
...(knowledgeOnly ? { workspaceMode: 'knowledge_only' } : {}),
|
|
6728
6896
|
repositoryPackageState: 'not_connected', ...(transportOrigin ? { transportOrigin } : {}) };
|
|
6729
6897
|
const grant = subcommand === 'connect' ? required(flags, 'grant') : null;
|
|
6730
6898
|
const scope = {
|
|
@@ -6746,7 +6914,8 @@ export async function run(argv) {
|
|
|
6746
6914
|
if (!isLocalProviderId(provider))
|
|
6747
6915
|
throw new Error('Demo watch provider must be auto, codex, claude, agy, or hermes.');
|
|
6748
6916
|
const registration = { schema: 'dharma.demo-watch/v1',
|
|
6749
|
-
hqUrl, organizationId, repositoryId, normalizedRepository, provider, workspace
|
|
6917
|
+
hqUrl, organizationId, repositoryId, normalizedRepository, provider, workspace,
|
|
6918
|
+
...(knowledgeOnly ? { knowledgeOnly: true } : {}) };
|
|
6750
6919
|
const input = { home: dharmaHome(), registration, version: VERSION };
|
|
6751
6920
|
const deps = {
|
|
6752
6921
|
verify: async () => {
|
|
@@ -6781,6 +6950,7 @@ export async function run(argv) {
|
|
|
6781
6950
|
: nativeSkillDirectory(provider);
|
|
6782
6951
|
const cycle = () => withDemoDeviceLock(scope, async () => demoRepositoryPackage({ scope, workspace,
|
|
6783
6952
|
statusOnly: subcommand === 'package-status', provider,
|
|
6953
|
+
knowledgeOnly,
|
|
6784
6954
|
nativeSkillDirectory: demoNativeSkillDirectory }, await createDemoDeviceTransport(scope)));
|
|
6785
6955
|
if (subcommand !== 'watch')
|
|
6786
6956
|
return cycle();
|