@dharma-ai-labs/agent-fabric 0.2.99 → 0.2.101

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.
@@ -1,3 +1,3 @@
1
- export type OnboardingStage = 'local_skill_inventory' | 'first_learning_preview' | 'first_learning_sync' | 'package_snapshot' | 'package_publication' | 'role_registration' | 'native_skill_install' | 'relay_start' | 'readiness';
1
+ export type OnboardingStage = 'local_skill_inventory' | 'first_learning_preview' | 'first_learning_sync' | 'package_snapshot' | 'package_publication' | 'role_registration' | 'native_skill_install' | 'relay_start' | 'autostart' | 'readiness';
2
2
  export declare function withOnboardingStage<T>(stage: OnboardingStage, workspaceId: string, resumeCommand: string, operation: () => Promise<T>): Promise<T>;
3
3
  //# sourceMappingURL=onboardingStage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"onboardingStage.d.ts","sourceRoot":"","sources":["../src/onboardingStage.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,uBAAuB,GACvB,wBAAwB,GACxB,qBAAqB,GACrB,kBAAkB,GAClB,qBAAqB,GACrB,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,WAAW,CAAC;AAEhB,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,KAAK,EAAE,eAAe,EACtB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAUZ"}
1
+ {"version":3,"file":"onboardingStage.d.ts","sourceRoot":"","sources":["../src/onboardingStage.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,uBAAuB,GACvB,wBAAwB,GACxB,qBAAqB,GACrB,kBAAkB,GAClB,qBAAqB,GACrB,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,WAAW,GACX,WAAW,CAAC;AAEhB,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,KAAK,EAAE,eAAe,EACtB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAUZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"onboardingStage.js","sourceRoot":"","sources":["../src/onboardingStage.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAsB,EACtB,WAAmB,EACnB,aAAqB,EACrB,SAA2B;IAE3B,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,2BAA2B,KAAK,KAAK,MAAM,mBAAmB,WAAW,qBAAqB,aAAa,EAAE,EAC7G,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"onboardingStage.js","sourceRoot":"","sources":["../src/onboardingStage.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAsB,EACtB,WAAmB,EACnB,aAAqB,EACrB,SAA2B;IAE3B,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,2BAA2B,KAAK,KAAK,MAAM,mBAAmB,WAAW,qBAAqB,aAAa,EAAE,EAC7G,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,36 @@
1
+ type Runner = (file: string, args: string[]) => Promise<{
2
+ stdout: string;
3
+ }>;
4
+ export interface RelayAutostartOptions {
5
+ home: string;
6
+ userHome?: string;
7
+ platform?: NodeJS.Platform;
8
+ run?: Runner;
9
+ }
10
+ interface Registration {
11
+ schema: 'dharma.relay-autostart/v1';
12
+ backend: 'systemd-user' | 'windows-task';
13
+ launcher: string;
14
+ policy: string;
15
+ workspace: string;
16
+ version: string;
17
+ taskName: string | null;
18
+ }
19
+ export type RelayAutostartState = {
20
+ state: 'enabled' | 'disabled' | 'unavailable' | 'unsupported';
21
+ backend: Registration['backend'] | null;
22
+ version?: string;
23
+ reason?: string;
24
+ };
25
+ export declare function linuxRelayUnit(launcher: string, policy: string, workspace: string, home?: string): string;
26
+ export declare function windowsRelayStartupScript(launcher: string, policy: string, home?: string): string;
27
+ export declare function relayAutostartStatus(options: RelayAutostartOptions): Promise<RelayAutostartState>;
28
+ export declare function enableRelayAutostart(options: RelayAutostartOptions & {
29
+ workspace: string;
30
+ launcher: string;
31
+ policy: string;
32
+ version: string;
33
+ }): Promise<RelayAutostartState>;
34
+ export declare function disableRelayAutostart(options: RelayAutostartOptions): Promise<RelayAutostartState>;
35
+ export {};
36
+ //# sourceMappingURL=relayAutostart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relayAutostart.d.ts","sourceRoot":"","sources":["../src/relayAutostart.ts"],"names":[],"mappings":"AAUA,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,2BAA2B,CAAC;IACpC,OAAO,EAAE,cAAc,GAAG,cAAc,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,aAAa,CAAC;IAC9D,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAoBF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,UAOhG;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,UAKxF;AAsBD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsBvG;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG;IAC1E,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;CACtE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgE/B;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiBxG"}
@@ -0,0 +1,167 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import { promisify } from 'node:util';
7
+ const execFileAsync = promisify(execFile);
8
+ const UNIT_NAME = 'dharma-agent-fabric.service';
9
+ const defaultRunner = async (file, args) => {
10
+ const { stdout } = await execFileAsync(file, args, { timeout: 15_000, maxBuffer: 64 * 1024 });
11
+ return { stdout };
12
+ };
13
+ function safeLine(value) {
14
+ if (!value || /[\r\n\0]/.test(value))
15
+ throw new Error('Autostart paths must be nonempty single lines.');
16
+ return value;
17
+ }
18
+ function systemdValue(value) {
19
+ return `"${safeLine(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/%/g, '%%')}"`;
20
+ }
21
+ function psLiteral(value) {
22
+ return `'${safeLine(value).replace(/'/g, "''")}'`;
23
+ }
24
+ export function linuxRelayUnit(launcher, policy, workspace, home) {
25
+ return `[Unit]\nDescription=Dharma Agent Fabric relay\nAfter=network-online.target\nWants=network-online.target\n\n`
26
+ + `[Service]\nType=simple\nWorkingDirectory=${systemdValue(workspace)}\n`
27
+ + (home ? `Environment=${systemdValue(`DHARMA_HOME=${home}`)}\n` : '')
28
+ + `ExecStart=${systemdValue(launcher)} relay supervise --policy ${systemdValue(policy)}\n`
29
+ + `Restart=on-failure\nRestartSec=10s\nTimeoutStopSec=30s\n\n`
30
+ + `[Install]\nWantedBy=default.target\n`;
31
+ }
32
+ export function windowsRelayStartupScript(launcher, policy, home) {
33
+ return `$ErrorActionPreference = 'Stop'\r\n`
34
+ + (home ? `$env:DHARMA_HOME = ${psLiteral(home)}\r\n` : '')
35
+ + `& ${psLiteral(launcher)} relay supervise --policy ${psLiteral(policy)}\r\n`
36
+ + `exit $LASTEXITCODE\r\n`;
37
+ }
38
+ function encodedPowerShell(command) {
39
+ return ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(command, 'utf16le').toString('base64')];
40
+ }
41
+ function registrationPath(home) { return join(home, 'relay', 'autostart.json'); }
42
+ function unitPath(userHome) { return join(userHome, '.config', 'systemd', 'user', UNIT_NAME); }
43
+ function scriptPath(home) { return join(home, 'relay', 'autostart.ps1'); }
44
+ function taskName(home) {
45
+ return `DharmaAgentFabric-${createHash('sha256').update(home.toLowerCase()).digest('hex').slice(0, 12)}`;
46
+ }
47
+ async function readRegistration(home) {
48
+ try {
49
+ const value = JSON.parse(await readFile(registrationPath(home), 'utf8'));
50
+ if (value.schema !== 'dharma.relay-autostart/v1'
51
+ || !['systemd-user', 'windows-task'].includes(value.backend))
52
+ return null;
53
+ return value;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ export async function relayAutostartStatus(options) {
60
+ const registration = await readRegistration(options.home);
61
+ if (!registration)
62
+ return { state: 'disabled', backend: null };
63
+ const platform = options.platform || process.platform;
64
+ const run = options.run || defaultRunner;
65
+ if ((platform === 'linux' && registration.backend !== 'systemd-user')
66
+ || (platform === 'win32' && registration.backend !== 'windows-task')) {
67
+ return { state: 'unavailable', backend: registration.backend, version: registration.version, reason: 'platform_mismatch' };
68
+ }
69
+ try {
70
+ const result = registration.backend === 'systemd-user'
71
+ ? await run('systemctl', ['--user', 'is-enabled', UNIT_NAME])
72
+ : await run('powershell.exe', encodedPowerShell(`$task = Get-ScheduledTask -TaskName ${psLiteral(registration.taskName || '')} -ErrorAction SilentlyContinue; `
73
+ + `if ($task -and $task.State -ne 'Disabled') { 'enabled' } else { 'disabled' }`));
74
+ return { state: result.stdout.trim() === 'enabled' ? 'enabled' : 'disabled',
75
+ backend: registration.backend, version: registration.version };
76
+ }
77
+ catch {
78
+ return { state: 'unavailable', backend: registration.backend, version: registration.version,
79
+ reason: registration.backend === 'systemd-user' ? 'systemd_user_unavailable' : 'task_scheduler_unavailable' };
80
+ }
81
+ }
82
+ export async function enableRelayAutostart(options) {
83
+ const platform = options.platform || process.platform;
84
+ if (platform !== 'linux' && platform !== 'win32') {
85
+ throw new Error(`Relay autostart is unsupported on ${platform}.`);
86
+ }
87
+ const userHome = options.userHome || homedir();
88
+ const run = options.run || defaultRunner;
89
+ const previous = await readRegistration(options.home);
90
+ if (previous && previous.backend !== (platform === 'linux' ? 'systemd-user' : 'windows-task')) {
91
+ throw new Error('Existing relay autostart belongs to a different operating system.');
92
+ }
93
+ const registration = {
94
+ schema: 'dharma.relay-autostart/v1',
95
+ backend: platform === 'linux' ? 'systemd-user' : 'windows-task',
96
+ launcher: safeLine(options.launcher), policy: safeLine(options.policy),
97
+ workspace: safeLine(options.workspace), version: options.version,
98
+ taskName: platform === 'win32' ? taskName(options.home) : null,
99
+ };
100
+ if (platform === 'win32' && !previous) {
101
+ const probe = await run('powershell.exe', encodedPowerShell(`$task = Get-ScheduledTask -TaskName ${psLiteral(registration.taskName)} -ErrorAction SilentlyContinue; `
102
+ + `if ($null -ne $task) { 'exists' } else { 'absent' }`));
103
+ if (probe.stdout.trim() !== 'absent') {
104
+ throw new Error('autostart_conflict: a Windows startup task exists without this enrollment receipt.');
105
+ }
106
+ }
107
+ const destination = platform === 'linux' ? unitPath(userHome) : scriptPath(options.home);
108
+ const existingContents = await readFile(destination, 'utf8').catch((error) => {
109
+ if (error.code === 'ENOENT')
110
+ return null;
111
+ throw error;
112
+ });
113
+ const ownedContents = previous && (platform === 'linux'
114
+ ? linuxRelayUnit(previous.launcher, previous.policy, previous.workspace, options.home)
115
+ : windowsRelayStartupScript(previous.launcher, previous.policy, options.home));
116
+ if (existingContents !== null && existingContents !== ownedContents) {
117
+ throw new Error('autostart_conflict: the user startup entry is not owned by this enrollment.');
118
+ }
119
+ await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
120
+ await mkdir(dirname(registrationPath(options.home)), { recursive: true, mode: 0o700 });
121
+ await writeFile(destination, platform === 'linux'
122
+ ? linuxRelayUnit(options.launcher, options.policy, options.workspace, options.home)
123
+ : windowsRelayStartupScript(options.launcher, options.policy, options.home), { mode: 0o600 });
124
+ await writeFile(registrationPath(options.home), `${JSON.stringify(registration, null, 2)}\n`, { mode: 0o600 });
125
+ if (platform === 'linux') {
126
+ await run('systemctl', ['--user', 'daemon-reload']);
127
+ await run('systemctl', ['--user', 'enable', UNIT_NAME]);
128
+ }
129
+ else {
130
+ const script = scriptPath(options.home);
131
+ const command = `$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; `
132
+ + `$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
133
+ + `-Argument ${psLiteral(`-NoProfile -NonInteractive -File "${script}"`)} `
134
+ + `-WorkingDirectory ${psLiteral(options.workspace)}; `
135
+ + `$trigger = New-ScheduledTaskTrigger -AtLogOn -User $identity; `
136
+ + `$principal = New-ScheduledTaskPrincipal -UserId $identity -LogonType Interactive -RunLevel Limited; `
137
+ + `$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit ([TimeSpan]::Zero) `
138
+ + `-RestartCount 10 -RestartInterval (New-TimeSpan -Minutes 1); `
139
+ + `Register-ScheduledTask -TaskName ${psLiteral(registration.taskName)} -Action $action `
140
+ + `-Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null`;
141
+ await run('powershell.exe', encodedPowerShell(command));
142
+ }
143
+ const status = await relayAutostartStatus(options);
144
+ if (status.state !== 'enabled')
145
+ throw new Error(`Relay autostart registration was not verified (${status.state}).`);
146
+ return status;
147
+ }
148
+ export async function disableRelayAutostart(options) {
149
+ const registration = await readRegistration(options.home);
150
+ if (!registration)
151
+ return { state: 'disabled', backend: null };
152
+ const run = options.run || defaultRunner;
153
+ if (registration.backend === 'systemd-user') {
154
+ await run('systemctl', ['--user', 'disable', UNIT_NAME]);
155
+ await rm(unitPath(options.userHome || homedir()), { force: true });
156
+ await run('systemctl', ['--user', 'daemon-reload']);
157
+ }
158
+ else {
159
+ if (registration.taskName !== taskName(options.home))
160
+ throw new Error('Relay autostart task identity is invalid.');
161
+ await run('powershell.exe', encodedPowerShell(`Unregister-ScheduledTask -TaskName ${psLiteral(registration.taskName)} -Confirm:$false -ErrorAction SilentlyContinue`));
162
+ await rm(scriptPath(options.home), { force: true });
163
+ }
164
+ await rm(registrationPath(options.home), { force: true });
165
+ return { state: 'disabled', backend: registration.backend, version: registration.version };
166
+ }
167
+ //# sourceMappingURL=relayAutostart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relayAutostart.js","sourceRoot":"","sources":["../src/relayAutostart.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=relayAutostart.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relayAutostart.test.d.ts","sourceRoot":"","sources":["../src/relayAutostart.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+ import { disableRelayAutostart, enableRelayAutostart, linuxRelayUnit, relayAutostartStatus, windowsRelayStartupScript, } from './relayAutostart.js';
7
+ test('Linux startup invokes only the pinned launcher and canonical policy', () => {
8
+ const unit = linuxRelayUnit('/home/a b/repo/.dharma/bin/dharma', '/home/a b/repo/.dharma/approved-policy.json', '/home/a b/repo');
9
+ assert.match(unit, /ExecStart="\/home\/a b\/repo\/\.dharma\/bin\/dharma" relay supervise --policy/);
10
+ assert.match(unit, /Restart=on-failure/);
11
+ assert.doesNotMatch(unit, /grant|token|password/i);
12
+ });
13
+ test('Windows startup escapes paths as PowerShell literals without embedding credentials', () => {
14
+ const script = windowsRelayStartupScript("C:\\A's Repo\\.dharma\\bin\\dharma.cmd", "C:\\A's Repo\\.dharma\\approved-policy.json");
15
+ assert.match(script, /A''s Repo/);
16
+ assert.match(script, /relay supervise --policy/);
17
+ assert.doesNotMatch(script, /grant|token|password/i);
18
+ });
19
+ test('Linux enable, status and disable preserve an explicit OS registration receipt', async () => {
20
+ const root = await mkdtemp(join(tmpdir(), 'dharma-autostart-'));
21
+ const calls = [];
22
+ const run = async (file, args) => {
23
+ calls.push(`${file} ${args.join(' ')}`);
24
+ return { stdout: args.includes('is-enabled') ? 'enabled\n' : '' };
25
+ };
26
+ const options = {
27
+ platform: 'linux', home: join(root, 'dharma'), userHome: root,
28
+ workspace: join(root, 'repo'), launcher: join(root, 'repo', '.dharma', 'bin', 'dharma'),
29
+ policy: join(root, 'repo', '.dharma', 'approved-policy.json'), version: '0.2.101', run,
30
+ };
31
+ const installed = await enableRelayAutostart(options);
32
+ assert.equal(installed.state, 'enabled');
33
+ assert.match(await readFile(join(root, '.config', 'systemd', 'user', 'dharma-agent-fabric.service'), 'utf8'), /relay supervise/);
34
+ assert.match(calls.join('\n'), /systemctl --user enable dharma-agent-fabric.service/);
35
+ assert.equal((await relayAutostartStatus(options)).state, 'enabled');
36
+ assert.equal((await disableRelayAutostart(options)).state, 'disabled');
37
+ assert.match(calls.join('\n'), /systemctl --user disable dharma-agent-fabric.service/);
38
+ });
39
+ test('startup status never calls a missing OS registration healthy', async () => {
40
+ const root = await mkdtemp(join(tmpdir(), 'dharma-autostart-missing-'));
41
+ const result = await relayAutostartStatus({
42
+ platform: 'linux', home: join(root, 'dharma'), userHome: root,
43
+ run: async () => { throw new Error('No systemd user bus'); },
44
+ });
45
+ assert.equal(result.state, 'disabled');
46
+ });
47
+ test('Windows task registration uses the enrolled user without an embedded password', async () => {
48
+ const root = await mkdtemp(join(tmpdir(), 'dharma-windows-autostart-'));
49
+ const calls = [];
50
+ const run = async (_file, args) => {
51
+ const decoded = Buffer.from(args.at(-1) || '', 'base64').toString('utf16le');
52
+ calls.push(decoded);
53
+ return { stdout: decoded.includes("'exists'") ? 'absent\n'
54
+ : decoded.includes('Get-ScheduledTask') ? 'enabled\n' : '' };
55
+ };
56
+ const options = {
57
+ platform: 'win32', home: join(root, 'dharma'), userHome: root,
58
+ workspace: 'C:\\Work Space\\repo', launcher: 'C:\\Work Space\\repo\\.dharma\\bin\\dharma.cmd',
59
+ policy: 'C:\\Work Space\\repo\\.dharma\\approved-policy.json', version: '0.2.101', run,
60
+ };
61
+ assert.equal((await enableRelayAutostart(options)).state, 'enabled');
62
+ const script = await readFile(join(root, 'dharma', 'relay', 'autostart.ps1'), 'utf8');
63
+ assert.match(script, /DHARMA_HOME/);
64
+ assert.match(calls.join('\n'), /-LogonType Interactive\b/);
65
+ assert.doesNotMatch(calls.join('\n'), /-Password|bootstrapGrant|--grant/);
66
+ assert.equal((await disableRelayAutostart(options)).state, 'disabled');
67
+ assert.match(calls.at(-1) || '', /Unregister-ScheduledTask/);
68
+ });
69
+ test('failed OS registration remains unavailable instead of reporting completion', async () => {
70
+ const root = await mkdtemp(join(tmpdir(), 'dharma-failed-autostart-'));
71
+ const options = {
72
+ platform: 'linux', home: join(root, 'dharma'), userHome: root,
73
+ workspace: join(root, 'repo'), launcher: join(root, 'repo', '.dharma', 'bin', 'dharma'),
74
+ policy: join(root, 'repo', '.dharma', 'approved-policy.json'), version: '0.2.101',
75
+ run: async () => { throw new Error('No systemd user bus'); },
76
+ };
77
+ await assert.rejects(enableRelayAutostart(options), /No systemd user bus/);
78
+ assert.equal((await relayAutostartStatus(options)).state, 'unavailable');
79
+ });
80
+ test('registration refuses to replace an unmanaged user startup entry', async () => {
81
+ const root = await mkdtemp(join(tmpdir(), 'dharma-autostart-conflict-'));
82
+ const unit = join(root, '.config', 'systemd', 'user', 'dharma-agent-fabric.service');
83
+ await mkdir(join(root, '.config', 'systemd', 'user'), { recursive: true });
84
+ await writeFile(unit, '[Service]\nExecStart=/custom/relay\n');
85
+ await assert.rejects(enableRelayAutostart({
86
+ platform: 'linux', home: join(root, 'dharma'), userHome: root,
87
+ workspace: join(root, 'repo'), launcher: join(root, 'repo', 'dharma'),
88
+ policy: join(root, 'repo', 'policy.json'), version: '0.2.101',
89
+ run: async () => ({ stdout: '' }),
90
+ }), /autostart_conflict/);
91
+ assert.equal(await readFile(unit, 'utf8'), '[Service]\nExecStart=/custom/relay\n');
92
+ });
93
+ test('Windows registration refuses an existing task without an ownership receipt', async () => {
94
+ const root = await mkdtemp(join(tmpdir(), 'dharma-task-conflict-'));
95
+ await assert.rejects(enableRelayAutostart({
96
+ platform: 'win32', home: join(root, 'dharma'), userHome: root,
97
+ workspace: 'C:\\Work', launcher: 'C:\\Work\\dharma.cmd',
98
+ policy: 'C:\\Work\\policy.json', version: '0.2.101',
99
+ run: async (_file, args) => ({ stdout: Buffer.from(args.at(-1) || '', 'base64').toString('utf16le')
100
+ .includes('Get-ScheduledTask') ? 'exists\n' : '' }),
101
+ }), /autostart_conflict/);
102
+ });
103
+ //# sourceMappingURL=relayAutostart.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relayAutostart.test.js","sourceRoot":"","sources":["../src/relayAutostart.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+ export declare function relayRestartDelayMs(consecutiveShortRuns: number): number;
3
+ export declare function superviseRelay(input: {
4
+ start: () => ChildProcess;
5
+ signal: AbortSignal;
6
+ now?: () => number;
7
+ wait?: (delayMs: number, signal: AbortSignal) => Promise<void>;
8
+ }): Promise<{
9
+ restarts: number;
10
+ }>;
11
+ //# sourceMappingURL=relaySupervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relaySupervisor.d.ts","sourceRoot":"","sources":["../src/relaySupervisor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,wBAAgB,mBAAmB,CAAC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAExE;AAeD,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,KAAK,EAAE,MAAM,YAAY,CAAC;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuChC"}
@@ -0,0 +1,56 @@
1
+ export function relayRestartDelayMs(consecutiveShortRuns) {
2
+ return Math.min(30_000, 1_000 * 2 ** Math.min(Math.max(0, consecutiveShortRuns - 1), 5));
3
+ }
4
+ async function waitForRestart(delayMs, signal) {
5
+ if (signal.aborted)
6
+ return;
7
+ await new Promise((resolveWait) => {
8
+ const finish = () => {
9
+ clearTimeout(timer);
10
+ signal.removeEventListener('abort', finish);
11
+ resolveWait();
12
+ };
13
+ const timer = setTimeout(finish, delayMs);
14
+ signal.addEventListener('abort', finish, { once: true });
15
+ });
16
+ }
17
+ export async function superviseRelay(input) {
18
+ const now = input.now || Date.now;
19
+ const wait = input.wait || waitForRestart;
20
+ let consecutiveShortRuns = 0;
21
+ let restarts = 0;
22
+ while (!input.signal.aborted) {
23
+ const startedAt = now();
24
+ let child;
25
+ try {
26
+ child = input.start();
27
+ }
28
+ catch {
29
+ consecutiveShortRuns += 1;
30
+ restarts += 1;
31
+ await wait(relayRestartDelayMs(consecutiveShortRuns), input.signal);
32
+ continue;
33
+ }
34
+ await new Promise((resolveExit) => {
35
+ const onAbort = () => { child.kill('SIGTERM'); };
36
+ const onExit = () => {
37
+ input.signal.removeEventListener('abort', onAbort);
38
+ child.removeListener('error', onExit);
39
+ child.removeListener('exit', onExit);
40
+ resolveExit();
41
+ };
42
+ child.once('error', onExit);
43
+ child.once('exit', onExit);
44
+ input.signal.addEventListener('abort', onAbort, { once: true });
45
+ if (input.signal.aborted)
46
+ onAbort();
47
+ });
48
+ if (input.signal.aborted)
49
+ break;
50
+ consecutiveShortRuns = now() - startedAt >= 60_000 ? 1 : consecutiveShortRuns + 1;
51
+ restarts += 1;
52
+ await wait(relayRestartDelayMs(consecutiveShortRuns), input.signal);
53
+ }
54
+ return { restarts };
55
+ }
56
+ //# sourceMappingURL=relaySupervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relaySupervisor.js","sourceRoot":"","sources":["../src/relaySupervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=relaySupervisor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relaySupervisor.test.d.ts","sourceRoot":"","sources":["../src/relaySupervisor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,127 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFile, spawn } from 'node:child_process';
3
+ import { EventEmitter, once } from 'node:events';
4
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import test from 'node:test';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { promisify } from 'node:util';
10
+ import { relayRestartDelayMs, superviseRelay } from './relaySupervisor.js';
11
+ const execFileAsync = promisify(execFile);
12
+ test('relay restart backoff is bounded', () => {
13
+ assert.equal(relayRestartDelayMs(1), 1_000);
14
+ assert.equal(relayRestartDelayMs(2), 2_000);
15
+ assert.equal(relayRestartDelayMs(6), 30_000);
16
+ assert.equal(relayRestartDelayMs(100), 30_000);
17
+ });
18
+ test('supervisor restarts unexpected exits and stops its child on shutdown', async () => {
19
+ const controller = new AbortController();
20
+ const waits = [];
21
+ let starts = 0;
22
+ const result = await superviseRelay({
23
+ signal: controller.signal,
24
+ wait: async (delayMs) => { waits.push(delayMs); },
25
+ start: () => {
26
+ starts += 1;
27
+ const child = new EventEmitter();
28
+ child.kill = () => {
29
+ queueMicrotask(() => child.emit('exit', 0, null));
30
+ return true;
31
+ };
32
+ queueMicrotask(() => {
33
+ if (starts === 3)
34
+ controller.abort();
35
+ else
36
+ child.emit('exit', 1, null);
37
+ });
38
+ return child;
39
+ },
40
+ });
41
+ assert.equal(starts, 3);
42
+ assert.deepEqual(waits, [1_000, 2_000]);
43
+ assert.equal(result.restarts, 2);
44
+ });
45
+ test('supervisor does not start a relay after shutdown', async () => {
46
+ const controller = new AbortController();
47
+ controller.abort();
48
+ let starts = 0;
49
+ const result = await superviseRelay({
50
+ signal: controller.signal,
51
+ start: () => { starts += 1; throw new Error('must not start'); },
52
+ });
53
+ assert.equal(starts, 0);
54
+ assert.equal(result.restarts, 0);
55
+ });
56
+ test('a detached supervisor records its workspace and relay stop shuts it down', {
57
+ skip: process.platform === 'win32',
58
+ }, async () => {
59
+ const root = await mkdtemp(join(tmpdir(), 'dharma-relay-supervisor-process-'));
60
+ const home = join(root, 'home');
61
+ const workspace = join(root, 'repo');
62
+ const policyPath = join(workspace, '.dharma', 'approved-policy.json');
63
+ await mkdir(join(workspace, '.dharma'), { recursive: true });
64
+ await mkdir(join(home, 'registry'), { recursive: true });
65
+ await writeFile(policyPath, '{}\n');
66
+ await writeFile(join(home, 'device.json'), JSON.stringify({ organizationId: 'org_test', deviceId: 'device_test' }));
67
+ await writeFile(join(home, 'registry', 'workspaces.json'), JSON.stringify([{ path: workspace, workspaceId: 'workspace_test' }]));
68
+ const env = { ...process.env, DHARMA_HOME: home };
69
+ const bin = fileURLToPath(new URL('./bin.js', import.meta.url));
70
+ const child = spawn(process.execPath, [bin, 'relay', 'supervise', '--policy', policyPath], {
71
+ env, stdio: 'ignore', detached: true,
72
+ });
73
+ try {
74
+ let binding = null;
75
+ for (let attempt = 0; attempt < 50; attempt += 1) {
76
+ binding = await readFile(join(home, 'relay', 'supervisor-workspace.json'), 'utf8')
77
+ .then(value => JSON.parse(value)).catch(() => null);
78
+ if (binding)
79
+ break;
80
+ await new Promise(resolveWait => setTimeout(resolveWait, 100));
81
+ }
82
+ assert.deepEqual(binding && { workspaceId: binding.workspaceId, policyPath: binding.policyPath }, {
83
+ workspaceId: 'workspace_test', policyPath,
84
+ });
85
+ const { stdout } = await execFileAsync(process.execPath, [bin, 'relay', 'stop'], { env, timeout: 10_000 });
86
+ assert.deepEqual(JSON.parse(stdout), { ok: true, stopped: true, vaultPreserved: true });
87
+ const status = await execFileAsync(process.execPath, [bin, 'status'], { env, timeout: 10_000 });
88
+ assert.equal(JSON.parse(status.stdout).supervisor, 'stopped');
89
+ }
90
+ finally {
91
+ if (child.exitCode === null)
92
+ child.kill('SIGTERM');
93
+ await rm(root, { recursive: true, force: true });
94
+ }
95
+ });
96
+ test('relay stop terminates an orphaned receiver after the supervisor exits', async () => {
97
+ const root = await mkdtemp(join(tmpdir(), 'dharma-relay-orphan-stop-'));
98
+ const home = join(root, 'home');
99
+ const relayDir = join(home, 'relay');
100
+ await mkdir(relayDir, { recursive: true });
101
+ const supervisor = spawn(process.execPath, ['-e', 'console.log("ready"); setInterval(() => {}, 1000)'], {
102
+ stdio: ['ignore', 'pipe', 'ignore'],
103
+ });
104
+ const receiver = spawn(process.execPath, ['-e', 'console.log("ready"); setInterval(() => {}, 1000)'], {
105
+ stdio: ['ignore', 'pipe', 'ignore'],
106
+ });
107
+ try {
108
+ assert.ok(supervisor.pid);
109
+ assert.ok(receiver.pid);
110
+ await Promise.all([once(supervisor.stdout, 'data'), once(receiver.stdout, 'data')]);
111
+ await writeFile(join(relayDir, 'supervisor.pid'), `${supervisor.pid}\n`);
112
+ await writeFile(join(relayDir, 'relay.pid'), `${receiver.pid}\n`);
113
+ const bin = fileURLToPath(new URL('./bin.js', import.meta.url));
114
+ const { stdout } = await execFileAsync(process.execPath, [bin, 'relay', 'stop'], {
115
+ env: { ...process.env, DHARMA_HOME: home }, timeout: 10_000,
116
+ });
117
+ assert.deepEqual(JSON.parse(stdout), { ok: true, stopped: true, vaultPreserved: true });
118
+ assert.equal(supervisor.exitCode !== null || supervisor.signalCode !== null, true);
119
+ assert.equal(receiver.exitCode !== null || receiver.signalCode !== null, true);
120
+ }
121
+ finally {
122
+ supervisor.kill('SIGTERM');
123
+ receiver.kill('SIGTERM');
124
+ await rm(root, { recursive: true, force: true });
125
+ }
126
+ });
127
+ //# sourceMappingURL=relaySupervisor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relaySupervisor.test.js","sourceRoot":"","sources":["../src/relaySupervisor.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3E,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAC5C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;IACtF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,IAAI,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/C,KAAK,EAAE,GAAG,EAAE;YACV,MAAM,IAAI,CAAC,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,YAAY,EAAkB,CAAC;YACjD,KAAK,CAAC,IAAI,GAAG,GAAG,EAAE;gBAChB,cAAc,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;gBAClD,OAAO,IAAI,CAAC;YACd,CAAC,CAAC;YACF,cAAc,CAAC,GAAG,EAAE;gBAClB,IAAI,MAAM,KAAK,CAAC;oBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;;oBAChC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACxC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;IAClE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,UAAU,CAAC,KAAK,EAAE,CAAC;IACnB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,KAAK,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;KACjE,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0EAA0E,EAAE;IAC/E,IAAI,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;CACnC,EAAE,KAAK,IAAI,EAAE;IACZ,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kCAAkC,CAAC,CAAC,CAAC;IAC/E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,sBAAsB,CAAC,CAAC;IACtE,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;IACpH,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,iBAAiB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;IACjI,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAClD,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE;QACzF,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI;KACrC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,IAAI,OAAO,GAAuD,IAAI,CAAC;QACvE,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,EAAE,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;YACjD,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,2BAA2B,CAAC,EAAE,MAAM,CAAC;iBAC/E,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAgD,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACrG,IAAI,OAAO;gBAAE,MAAM;YACnB,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,EAAE;YAChG,WAAW,EAAE,gBAAgB,EAAE,UAAU;SAC1C,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3G,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;QACxF,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAChG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAChE,CAAC;YAAS,CAAC;QACT,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;IACvF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,2BAA2B,CAAC,CAAC,CAAC;IACxE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,mDAAmD,CAAC,EAAE;QACtG,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;KACpC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,mDAAmD,CAAC,EAAE;QACpG,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;KACpC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC1B,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,MAAO,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACtF,MAAM,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;QACzE,MAAM,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE;YAC/E,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM;SAC5D,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;QACxF,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,KAAK,IAAI,EAAE,IAAI,CAAC,CAAC;QACnF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,KAAK,IAAI,EAAE,IAAI,CAAC,CAAC;IACjF,CAAC;YAAS,CAAC;QACT,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,CAAC,CAAC"}