@dharma-ai-labs/agent-fabric 0.2.117 → 0.2.119
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 +3 -0
- package/dist/demoPackage.d.ts +1 -1
- package/dist/demoPeer.test.js +8 -11
- package/dist/demoPeer.test.js.map +1 -1
- package/dist/demoTransportFetch.d.ts.map +1 -1
- package/dist/demoTransportFetch.js +3 -1
- package/dist/demoTransportFetch.js.map +1 -1
- package/dist/demoTransportFetch.test.js +62 -0
- package/dist/demoTransportFetch.test.js.map +1 -1
- package/dist/evidencePreviewDisclosureCaller.test.d.ts +2 -0
- package/dist/evidencePreviewDisclosureCaller.test.d.ts.map +1 -0
- package/dist/evidencePreviewDisclosureCaller.test.js +56 -0
- package/dist/evidencePreviewDisclosureCaller.test.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +180 -11
- package/dist/index.js.map +1 -1
- package/dist/namedSessionEvidence.d.ts.map +1 -1
- package/dist/namedSessionEvidence.js +19 -2
- package/dist/namedSessionEvidence.js.map +1 -1
- package/dist/namedSessionEvidence.test.js +47 -1
- package/dist/namedSessionEvidence.test.js.map +1 -1
- package/dist/namedSessionService.d.ts.map +1 -1
- package/dist/namedSessionService.js +4 -1
- package/dist/namedSessionService.js.map +1 -1
- package/dist/namedSessionService.test.js +14 -2
- package/dist/namedSessionService.test.js.map +1 -1
- package/dist/relayAutostart.d.ts +8 -1
- package/dist/relayAutostart.d.ts.map +1 -1
- package/dist/relayAutostart.js +27 -0
- package/dist/relayAutostart.js.map +1 -1
- package/dist/relayRuntimeUpgrade.d.ts +58 -0
- package/dist/relayRuntimeUpgrade.d.ts.map +1 -0
- package/dist/relayRuntimeUpgrade.js +193 -0
- package/dist/relayRuntimeUpgrade.js.map +1 -0
- package/dist/relayRuntimeUpgrade.test.d.ts +2 -0
- package/dist/relayRuntimeUpgrade.test.d.ts.map +1 -0
- package/dist/relayRuntimeUpgrade.test.js +162 -0
- package/dist/relayRuntimeUpgrade.test.js.map +1 -0
- package/dist/relayRuntimeUpgradeCaller.test.d.ts +2 -0
- package/dist/relayRuntimeUpgradeCaller.test.d.ts.map +1 -0
- package/dist/relayRuntimeUpgradeCaller.test.js +116 -0
- package/dist/relayRuntimeUpgradeCaller.test.js.map +1 -0
- package/dist/repositoryOnboardingCaller.test.js +174 -1
- package/dist/repositoryOnboardingCaller.test.js.map +1 -1
- package/dist/schemas/codex-local-work-capture-v2.schema.json +64 -0
- package/dist/schemas/local-relay-upgrade.schema.json +32 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/usage.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -17,7 +17,8 @@ import { contentHash, getActiveSkillBundleAuthorization, getExpiredSkillBundleAu
|
|
|
17
17
|
import { executeTask, FileActionExecutionJournal, FileTaskReceiptStore, providerInstructionsForTask, } from '@dharma-ai-labs/agent-fabric-task-runner';
|
|
18
18
|
import { CLI_USAGE } from './usage.js';
|
|
19
19
|
import { superviseRelay } from './relaySupervisor.js';
|
|
20
|
-
import { disableRelayAutostart, enableRelayAutostart, relayAutostartStatus, startRelayAutostart } from './relayAutostart.js';
|
|
20
|
+
import { disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
21
|
+
import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
|
|
21
22
|
import { assertStudyExactPoll, studyTaskIdFromFlags } from './studyExactTaskSelector.js';
|
|
22
23
|
import { initializeRepositoryKnowledge, readRepositoryKnowledgeSource } from './repositoryKnowledge.js';
|
|
23
24
|
import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readRepositorySourceBaselineSnapshot, writeRepositoryPackageSnapshot } from './repositoryPackage.js';
|
|
@@ -55,7 +56,7 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
|
|
|
55
56
|
import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
|
|
56
57
|
import { startNamedCodexThread } from './namedCodexThread.js';
|
|
57
58
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
58
|
-
const VERSION = '0.2.
|
|
59
|
+
const VERSION = '0.2.119';
|
|
59
60
|
const USAGE = CLI_USAGE;
|
|
60
61
|
const execFileAsync = promisify(execFile);
|
|
61
62
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -1525,6 +1526,103 @@ async function installStableRepositoryLauncher(workspace) {
|
|
|
1525
1526
|
await writeFile(cmdPath, contents.windows, { mode: 0o600 });
|
|
1526
1527
|
return { shell: '.dharma/bin/dharma', windows: '.dharma/bin/dharma.cmd' };
|
|
1527
1528
|
}
|
|
1529
|
+
async function relayUpgrade(flags) {
|
|
1530
|
+
const config = await readDeviceConfig();
|
|
1531
|
+
if (!config)
|
|
1532
|
+
throw new Error('relay_upgrade_enrollment_required');
|
|
1533
|
+
if (flags.has('dry-run') && flags.has('apply'))
|
|
1534
|
+
throw new Error('relay_upgrade_flags_conflict');
|
|
1535
|
+
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
1536
|
+
const selected = selectDeviceWorkspace(await registry(), {
|
|
1537
|
+
organizationId: config.organizationId, deviceId: config.deviceId, path: workspace,
|
|
1538
|
+
});
|
|
1539
|
+
if (!selected)
|
|
1540
|
+
throw new Error('relay_upgrade_workspace_unregistered');
|
|
1541
|
+
const home = dharmaHome();
|
|
1542
|
+
const startup = await inspectOwnedRelayAutostart({ home });
|
|
1543
|
+
const expectedLauncher = resolve(workspace, '.dharma', 'bin', process.platform === 'win32' ? 'dharma.cmd' : 'dharma');
|
|
1544
|
+
if (startup.workspace !== workspace || startup.launcher !== expectedLauncher
|
|
1545
|
+
|| startup.policy !== resolve(workspace, '.dharma', 'approved-policy.json')) {
|
|
1546
|
+
throw new Error('relay_upgrade_workspace_conflict');
|
|
1547
|
+
}
|
|
1548
|
+
const assertStopped = async () => {
|
|
1549
|
+
if (await relaySupervisorProcessState(home) !== 'stopped' || await relayProcessState(home) !== 'stopped') {
|
|
1550
|
+
throw new Error('relay_upgrade_runtime_busy: finish current work, then stop the owned relay before upgrading.');
|
|
1551
|
+
}
|
|
1552
|
+
const entries = await readdir(resolve(home, 'sessions'), { withFileTypes: true }).catch(error => {
|
|
1553
|
+
if (error.code === 'ENOENT')
|
|
1554
|
+
return [];
|
|
1555
|
+
throw error;
|
|
1556
|
+
});
|
|
1557
|
+
for (const entry of entries) {
|
|
1558
|
+
if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
|
|
1559
|
+
continue;
|
|
1560
|
+
if (await pidProcessState(resolve(home, 'sessions', entry.name, 'service.lock')) !== 'stopped') {
|
|
1561
|
+
throw new Error('relay_upgrade_session_busy: finish and stop the named session before upgrading.');
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
return withRelayStartupMutation(async () => {
|
|
1566
|
+
const receipt = await upgradeRelayRuntime({ home, workspace, version: VERSION,
|
|
1567
|
+
organizationId: config.organizationId, deviceId: config.deviceId, workspaceId: selected.workspaceId,
|
|
1568
|
+
dryRun: !flags.has('apply'), rollback: flags.has('rollback') }, {
|
|
1569
|
+
assertStopped, inspectStartup: async () => {
|
|
1570
|
+
const current = await inspectOwnedRelayAutostart({ home });
|
|
1571
|
+
if (current.launcher !== expectedLauncher || current.policy !== startup.policy
|
|
1572
|
+
|| current.workspace !== workspace)
|
|
1573
|
+
throw new Error('relay_upgrade_workspace_conflict');
|
|
1574
|
+
return current;
|
|
1575
|
+
},
|
|
1576
|
+
launcherContents: version => stableRepositoryLauncherContents(version, { platform: process.platform, nodeDirectory: dirname(process.execPath) }),
|
|
1577
|
+
legacyLauncherContents: version => stableRepositoryLauncherContents(version),
|
|
1578
|
+
configureStartup: version => enableRelayAutostart({ home, workspace,
|
|
1579
|
+
launcher: startup.launcher, policy: startup.policy, version }),
|
|
1580
|
+
start: () => startRelayAutostart({ home }),
|
|
1581
|
+
stop: async () => {
|
|
1582
|
+
const activationLocks = resolve(home, 'registry', 'skill-activation-locks');
|
|
1583
|
+
const locks = await readdir(activationLocks).catch(error => {
|
|
1584
|
+
if (error.code === 'ENOENT')
|
|
1585
|
+
return [];
|
|
1586
|
+
throw error;
|
|
1587
|
+
});
|
|
1588
|
+
for (const lock of locks) {
|
|
1589
|
+
if (lock.endsWith('.lock') && await pidProcessState(resolve(activationLocks, lock)) !== 'stopped') {
|
|
1590
|
+
throw new Error('relay_upgrade_work_in_progress: recovery must wait for a safe task boundary.');
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
const binding = await readFile(resolve(home, 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
1594
|
+
.then(value => JSON.parse(value)).catch(() => null);
|
|
1595
|
+
if (await relaySupervisorProcessState(home) !== 'stopped'
|
|
1596
|
+
&& binding?.workspaceId !== selected.workspaceId)
|
|
1597
|
+
throw new Error('relay_upgrade_process_unverified');
|
|
1598
|
+
await stopRelayAutostart({ home });
|
|
1599
|
+
const stopped = await relayStop();
|
|
1600
|
+
if (stopped.ok !== true)
|
|
1601
|
+
throw new Error('relay_upgrade_stop_unconfirmed');
|
|
1602
|
+
},
|
|
1603
|
+
verify: async (version, since) => {
|
|
1604
|
+
for (let attempt = 0; attempt < 90; attempt++) {
|
|
1605
|
+
const read = (name) => readFile(resolve(home, 'relay', name), 'utf8').catch(() => 'null');
|
|
1606
|
+
const supervisorPid = Number((await read('supervisor.pid')).trim());
|
|
1607
|
+
const relayPid = Number((await read('relay.pid')).trim());
|
|
1608
|
+
const supervisor = JSON.parse(await read('supervisor-workspace.json'));
|
|
1609
|
+
const lastPoll = JSON.parse(await read('last-successful-poll.json'));
|
|
1610
|
+
if (await relaySupervisorProcessState(home) === 'running' && await relayProcessState(home) === 'running'
|
|
1611
|
+
&& relayRuntimeObservationReady({ version, workspaceId: selected.workspaceId, since,
|
|
1612
|
+
supervisorPid, relayPid, supervisor, lastPoll, allowLegacyPoll: version !== VERSION }))
|
|
1613
|
+
return;
|
|
1614
|
+
if (attempt < 89)
|
|
1615
|
+
await new Promise(resolveWait => setTimeout(resolveWait, 1000));
|
|
1616
|
+
}
|
|
1617
|
+
throw new Error('relay_upgrade_receiver_unconfirmed');
|
|
1618
|
+
},
|
|
1619
|
+
});
|
|
1620
|
+
const validation = await validateContract(resolve(fileURLToPath(new URL('.', import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/local-relay-upgrade/v1', receipt);
|
|
1621
|
+
if (!validation.ok)
|
|
1622
|
+
throw new Error('relay_upgrade_receipt_invalid');
|
|
1623
|
+
return receipt;
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1528
1626
|
export async function waitForRelayReadiness(options = {}) {
|
|
1529
1627
|
const processState = options.processState || relayProcessState;
|
|
1530
1628
|
const probe = options.probe || probeRelayConnection;
|
|
@@ -1969,6 +2067,33 @@ async function bootstrap(flags) {
|
|
|
1969
2067
|
});
|
|
1970
2068
|
scopes = redeemed.organizationApiTokenScopes;
|
|
1971
2069
|
}
|
|
2070
|
+
let organizationApiTokenStored = false;
|
|
2071
|
+
let credentialFailure = null;
|
|
2072
|
+
try {
|
|
2073
|
+
organizationApiTokenStored = Boolean(await loadOrganizationApiToken({
|
|
2074
|
+
hqUrl, organizationId, installationId: config.installationId,
|
|
2075
|
+
}));
|
|
2076
|
+
if (!organizationApiTokenStored && !String(process.env.DHARMA_ORG_API_TOKEN || '').trim()) {
|
|
2077
|
+
credentialFailure = 'organization_api_credentials_required';
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
catch {
|
|
2081
|
+
// Secure-store diagnostics can contain secrets. Emit only the failed stage.
|
|
2082
|
+
credentialFailure = 'organization_api_credential_store_unavailable';
|
|
2083
|
+
}
|
|
2084
|
+
if (credentialFailure) {
|
|
2085
|
+
return {
|
|
2086
|
+
ok: false,
|
|
2087
|
+
stage: 'organization_api_credentials',
|
|
2088
|
+
code: credentialFailure,
|
|
2089
|
+
message: 'The enrolled device is preserved, but its organization API credential is unavailable. Contact Dharma support for device-bound credential recovery; do not repeat enrollment or use another member\'s token.',
|
|
2090
|
+
sharedRepositoryReady: false,
|
|
2091
|
+
enrollment: {
|
|
2092
|
+
organizationId, deviceId: config.deviceId, status: 'approved', organizationApiTokenStored,
|
|
2093
|
+
scopes, recipientApproval, rebind,
|
|
2094
|
+
},
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
1972
2097
|
const onboardFlags = new Map(flags);
|
|
1973
2098
|
onboardFlags.delete('grant');
|
|
1974
2099
|
onboardFlags.set('portal-url', hqUrl);
|
|
@@ -1987,7 +2112,7 @@ async function bootstrap(flags) {
|
|
|
1987
2112
|
organizationId,
|
|
1988
2113
|
deviceId: config.deviceId,
|
|
1989
2114
|
status: 'approved',
|
|
1990
|
-
organizationApiTokenStored
|
|
2115
|
+
organizationApiTokenStored,
|
|
1991
2116
|
scopes,
|
|
1992
2117
|
recipientApproval,
|
|
1993
2118
|
rebind,
|
|
@@ -2015,7 +2140,7 @@ async function bootstrap(flags) {
|
|
|
2015
2140
|
organizationId,
|
|
2016
2141
|
deviceId: config.deviceId,
|
|
2017
2142
|
status: 'approved',
|
|
2018
|
-
organizationApiTokenStored
|
|
2143
|
+
organizationApiTokenStored,
|
|
2019
2144
|
scopes,
|
|
2020
2145
|
recipientApproval,
|
|
2021
2146
|
rebind,
|
|
@@ -2106,7 +2231,7 @@ async function bootstrap(flags) {
|
|
|
2106
2231
|
organizationId,
|
|
2107
2232
|
deviceId: config.deviceId,
|
|
2108
2233
|
status: 'approved',
|
|
2109
|
-
organizationApiTokenStored
|
|
2234
|
+
organizationApiTokenStored,
|
|
2110
2235
|
scopes,
|
|
2111
2236
|
recipientApproval,
|
|
2112
2237
|
rebind,
|
|
@@ -2791,8 +2916,18 @@ async function evidencePreview(flags) {
|
|
|
2791
2916
|
captureProvenance: captureProvenance(session.endedAt),
|
|
2792
2917
|
});
|
|
2793
2918
|
});
|
|
2919
|
+
const disclosureFailures = capsules.map((capsule) => {
|
|
2920
|
+
try {
|
|
2921
|
+
return containsDisallowedLocalPath(capsule) ? 'local_path_disclosure_forbidden' : null;
|
|
2922
|
+
}
|
|
2923
|
+
catch {
|
|
2924
|
+
return 'local_path_inspection_depth_exceeded';
|
|
2925
|
+
}
|
|
2926
|
+
}).filter((reason) => reason !== null);
|
|
2794
2927
|
automaticDisclosure = {
|
|
2795
|
-
ready:
|
|
2928
|
+
ready: disclosureFailures.length === 0,
|
|
2929
|
+
blockedCapsuleCount: disclosureFailures.length,
|
|
2930
|
+
reasonCodes: [...new Set(disclosureFailures)].sort(),
|
|
2796
2931
|
disclosureClass: 'automatic_capsule',
|
|
2797
2932
|
disclosureMode: capsules[0]?.automaticDisclosureMode ?? policy.evidence.automaticDisclosure?.mode ?? 'local_analysis',
|
|
2798
2933
|
consentReceiptId: policy.evidence.automaticDisclosure?.consentReceiptId ?? null,
|
|
@@ -3481,13 +3616,14 @@ async function syncWorkspacePolicy(fabric, item, policyRevision, apply = true, p
|
|
|
3481
3616
|
repositoryAgentId: item.repositoryAgentId,
|
|
3482
3617
|
policyRevision, providers,
|
|
3483
3618
|
});
|
|
3619
|
+
const canonical = reconcileWorkspaceRegistration(await registry(), item, response);
|
|
3484
3620
|
const generated = await materializeWorkspacePolicy({
|
|
3485
|
-
workspace:
|
|
3486
|
-
organizationId:
|
|
3621
|
+
workspace: canonical.path,
|
|
3622
|
+
organizationId: canonical.organizationId,
|
|
3487
3623
|
revision: String(response.workspace?.policyRevision || policyRevision),
|
|
3488
3624
|
serverPolicyAuthorization: response.organizationPolicyAuthorization,
|
|
3489
3625
|
serverPublicKeyEd25519: (await readDeviceConfig())?.serverPublicKeyEd25519,
|
|
3490
|
-
workspaceId:
|
|
3626
|
+
workspaceId: canonical.workspaceId,
|
|
3491
3627
|
dryRun: !apply,
|
|
3492
3628
|
});
|
|
3493
3629
|
return {
|
|
@@ -3500,12 +3636,43 @@ async function syncWorkspacePolicy(fabric, item, policyRevision, apply = true, p
|
|
|
3500
3636
|
},
|
|
3501
3637
|
};
|
|
3502
3638
|
}
|
|
3639
|
+
function reconcileWorkspaceRegistration(records, requested, response) {
|
|
3640
|
+
const workspace = response.workspace && typeof response.workspace === 'object' && !Array.isArray(response.workspace)
|
|
3641
|
+
? response.workspace : null;
|
|
3642
|
+
if (response.ok !== true || response.organizationId !== requested.organizationId
|
|
3643
|
+
|| !workspace || typeof workspace.id !== 'string' || !UUID_PATTERN.test(workspace.id)
|
|
3644
|
+
|| workspace.status !== 'active') {
|
|
3645
|
+
throw new Error('fabric_workspace_registration_scope_mismatch');
|
|
3646
|
+
}
|
|
3647
|
+
if (workspace.id === requested.workspaceId)
|
|
3648
|
+
return requested;
|
|
3649
|
+
// The authenticated server upsert preserves the original ID for this device's
|
|
3650
|
+
// route. Local records are hints only; signed policy verification still follows.
|
|
3651
|
+
const candidates = records.filter(record => record.workspaceId === workspace.id);
|
|
3652
|
+
if (candidates.length > 1)
|
|
3653
|
+
throw new Error('fabric_workspace_registration_ambiguous');
|
|
3654
|
+
const canonical = candidates[0];
|
|
3655
|
+
if (canonical) {
|
|
3656
|
+
if (canonical.organizationId !== requested.organizationId || canonical.path !== requested.path
|
|
3657
|
+
|| canonical.routeHash !== requested.routeHash || canonical.status !== 'active'
|
|
3658
|
+
|| canonical.repositoryRemoteHash !== requested.repositoryRemoteHash) {
|
|
3659
|
+
throw new Error('fabric_workspace_registration_scope_mismatch');
|
|
3660
|
+
}
|
|
3661
|
+
return canonical;
|
|
3662
|
+
}
|
|
3663
|
+
// A restored credential store may lack the old registry row. Do not carry
|
|
3664
|
+
// workspace-specific endpoint/package state into the server-returned identity.
|
|
3665
|
+
return { ...requested, workspaceId: workspace.id, repositoryAgentId: null,
|
|
3666
|
+
repositoryBindingId: null, endpointId: null, repositoryPackage: undefined,
|
|
3667
|
+
repositoryRole: null, repositoryAgentKey: null, controlBranch: null };
|
|
3668
|
+
}
|
|
3503
3669
|
async function registerWorkspaceBeforeRepositoryBind(fabric, item, policyRevision, providerIds = null) {
|
|
3504
3670
|
// Repository connect is fenced by a server-side workspace, provider capability,
|
|
3505
3671
|
// and signed device-session record. Register those facts before requesting the
|
|
3506
3672
|
// permanent repository binding and control branch.
|
|
3507
3673
|
const synchronized = await syncWorkspacePolicy(fabric, item, policyRevision, true, providerIds);
|
|
3508
|
-
const
|
|
3674
|
+
const canonical = reconcileWorkspaceRegistration(await registry(), item, synchronized);
|
|
3675
|
+
const registered = await bindRepositoryAgent(fabric, canonical);
|
|
3509
3676
|
return { registered, synchronized };
|
|
3510
3677
|
}
|
|
3511
3678
|
async function readDeviceConfig() {
|
|
@@ -6101,7 +6268,7 @@ async function relayStart(flags) {
|
|
|
6101
6268
|
const result = await executeOneTask(fabric, leaseSeconds);
|
|
6102
6269
|
if (Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6103
6270
|
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), {
|
|
6104
|
-
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION,
|
|
6271
|
+
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
|
|
6105
6272
|
});
|
|
6106
6273
|
lastPollReceiptAt = Date.now();
|
|
6107
6274
|
}
|
|
@@ -6490,6 +6657,8 @@ export async function run(argv) {
|
|
|
6490
6657
|
return probeRelayConnection();
|
|
6491
6658
|
if (command === 'relay' && subcommand === 'stop')
|
|
6492
6659
|
return relayStop();
|
|
6660
|
+
if (command === 'relay' && subcommand === 'upgrade')
|
|
6661
|
+
return relayUpgrade(flags);
|
|
6493
6662
|
if (command === 'relay' && subcommand === 'autostart') {
|
|
6494
6663
|
if (positional[2] === 'status')
|
|
6495
6664
|
return relayAutostartStatus({ home: dharmaHome() });
|