@platless/axom-cli 0.6.36 → 0.6.52
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/README.md +7 -1
- package/dist/axom-patch/cli/README.md +31 -2
- package/dist/axom-patch/cli/account-limits/generate.py +2 -2
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
- package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +137 -41
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
- package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
- package/dist/axom-patch/cli/runtime.json +3 -1
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +7 -7
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -9
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
- package/dist/cli.js +9 -11
- package/dist/commands/auth.js +4 -1
- package/dist/commands/update.js +68 -7
- package/dist/commands/workstation.js +94 -223
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +2 -2
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +11 -4
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +4 -3
- package/dist/workstation/acp/processor/control.js +2 -2
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/session.js +3 -2
- package/dist/workstation/acp/processor/state.js +1 -0
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +25 -0
- package/dist/workstation/acp/processorSession.js +12 -1
- package/dist/workstation/acp/registry/catalog.js +6 -6
- package/dist/workstation/acp/test/keeperWorld.js +4 -3
- package/dist/workstation/agentSessions.js +76 -23
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +36 -5
- package/dist/workstation/config.js +18 -6
- package/dist/workstation/control/bindingStore.js +4 -0
- package/dist/workstation/control/commands.js +1 -0
- package/dist/workstation/control/daemonIdentity.js +90 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/locks.js +51 -4
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +60 -5
- package/dist/workstation/control/runtimeHost.js +96 -9
- package/dist/workstation/control/serverIdentity.js +2 -2
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +262 -360
- package/dist/workstation/daemonctl.js +150 -12
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationPreparation.js +1 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +2 -1
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +2 -2
- package/dist/workstation/desktopControl/helperProcess.js +250 -1
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +4 -2
- package/dist/workstation/desktopControl/macProvider.js +41 -40
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +31 -5
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +15 -6
- package/dist/workstation/desktopControl/supervisor.js +110 -17
- package/dist/workstation/desktopControl/wallpaper.js +26 -6
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +80 -4
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/management.js +23 -4
- package/dist/workstation/rd/artifact.js +4 -3
- package/dist/workstation/rd/control.js +3 -5
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +44 -10
- package/dist/workstation/rd/state.js +14 -2
- package/dist/workstation/rd/update.js +5 -8
- package/dist/workstation/rd/upkeep.js +44 -2
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +93 -1
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/systemMetrics.js +3 -0
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +80 -10
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +2 -2
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +7 -1
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +27427 -14410
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +7 -3
- package/dist/native/darwin-arm64/axom-quic +0 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { KeeperMethodError } from '../kit/protocol.js';
|
|
6
|
+
import { TERMINAL_GRID_LIMITS as limits, } from './gridProtocol.js';
|
|
7
|
+
import { TerminalFrames } from './frames.js';
|
|
8
|
+
import { TerminalScreen } from './screen.js';
|
|
9
|
+
import { createTerminalInput } from './input.js';
|
|
10
|
+
import { createTerminalMetadata } from './metadata.js';
|
|
11
|
+
export const NATIVE_PTY_OPERATIONS = new Set(['attach', 'poll', 'resize', 'input', 'scroll', 'detach', 'close']);
|
|
12
|
+
const bad = (message) => { throw new KeeperMethodError('bad_request', message); };
|
|
13
|
+
const fail = (code, message) => { throw new KeeperMethodError(code, message); };
|
|
14
|
+
const ok = { ok: true };
|
|
15
|
+
const nativeScheduler = {
|
|
16
|
+
schedule(callback, delay) { const timer = setTimeout(callback, delay); timer.unref(); return { dispose: () => clearTimeout(timer) }; },
|
|
17
|
+
};
|
|
18
|
+
const basename = (name) => path.basename(name.trim()).replace(/^-/, '');
|
|
19
|
+
/** Keeper-owned sessions: ordered within a PTY, independent between PTYs. */
|
|
20
|
+
export class NativePtySessions {
|
|
21
|
+
options;
|
|
22
|
+
sessions = new Map();
|
|
23
|
+
now;
|
|
24
|
+
scheduler;
|
|
25
|
+
module;
|
|
26
|
+
unavailable;
|
|
27
|
+
disposed = false;
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
this.now = options.now ?? Date.now;
|
|
31
|
+
this.scheduler = options.scheduler ?? nativeScheduler;
|
|
32
|
+
try {
|
|
33
|
+
this.module = (options.loadPty ?? (() => createRequire(import.meta.url)('node-pty')))();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
this.unavailable = 'Terminal native dependency unavailable; reinstall CLI with optional node-pty support';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
list() {
|
|
40
|
+
return { schema: 1, limits: { ...limits }, sessions: [...this.sessions.values()].map(s => this.descriptor(s)) };
|
|
41
|
+
}
|
|
42
|
+
async create(params, launch) {
|
|
43
|
+
this.available();
|
|
44
|
+
request(params);
|
|
45
|
+
keys(params, ['id', 'cwd', 'registrationId']);
|
|
46
|
+
const id = identifier(params, 'id');
|
|
47
|
+
if (params.registrationId !== undefined)
|
|
48
|
+
identifier(params, 'registrationId');
|
|
49
|
+
if (params.cwd !== undefined && (typeof params.cwd !== 'string' || params.cwd.includes('\0')))
|
|
50
|
+
bad('Invalid cwd');
|
|
51
|
+
const existing = this.sessions.get(id);
|
|
52
|
+
if (existing)
|
|
53
|
+
return this.descriptor(existing);
|
|
54
|
+
this.checkLimit();
|
|
55
|
+
const resolved = await launch();
|
|
56
|
+
this.available();
|
|
57
|
+
const concurrent = this.sessions.get(id);
|
|
58
|
+
if (concurrent)
|
|
59
|
+
return this.descriptor(concurrent);
|
|
60
|
+
this.checkLimit();
|
|
61
|
+
const cols = 100;
|
|
62
|
+
const rows = 30;
|
|
63
|
+
const screen = new TerminalScreen(cols, rows);
|
|
64
|
+
let pty;
|
|
65
|
+
try {
|
|
66
|
+
pty = this.module.spawn(resolved.shell, ['-l'], { name: 'xterm-256color', cwd: resolved.cwd, cols, rows, env: { ...resolved.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' } });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
screen.dispose();
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const createdAt = this.now();
|
|
73
|
+
const shell = basename(resolved.shell);
|
|
74
|
+
const s = {
|
|
75
|
+
value: { id, ...(typeof params.registrationId === 'string' ? { registrationId: params.registrationId } : {}), incarnation: randomUUID(), title: 'Terminal', cwd: resolved.cwd, cwdSource: 'launch', cols, rows, createdAt, exited: false, exitCode: null },
|
|
76
|
+
shell, shellNames: new Set([shell]), shellNameStarted: false, pty, screen, frames: new TerminalFrames(), attachments: new Map(), geometry: 1,
|
|
77
|
+
closed: false, queue: Promise.resolve(), rendered: Promise.resolve(), pendingBytes: 0, dirty: true, capturedAt: -Infinity,
|
|
78
|
+
listeners: [], input: createTerminalInput(screen), exitedAt: 0, observed: { label: shell, state: 'idle', since: createdAt },
|
|
79
|
+
};
|
|
80
|
+
const context = { pty, screen, session: () => this.descriptor(s), changed: () => this.dirty(s) };
|
|
81
|
+
try {
|
|
82
|
+
s.input = this.options.createInput?.(context) ?? s.input;
|
|
83
|
+
s.metadata = (this.options.createMetadata ?? createTerminalMetadata)(context);
|
|
84
|
+
this.capture(s, true);
|
|
85
|
+
s.listeners.push(disposable(pty.onData(data => this.output(s, data))), disposable(pty.onExit(event => {
|
|
86
|
+
if (s.closed)
|
|
87
|
+
return;
|
|
88
|
+
s.shellNameTimer?.dispose();
|
|
89
|
+
s.shellNameTimer = undefined;
|
|
90
|
+
s.value.exited = true;
|
|
91
|
+
s.value.exitCode = event.exitCode;
|
|
92
|
+
s.exitedAt = this.now();
|
|
93
|
+
this.dirty(s);
|
|
94
|
+
})), screen.onReply(data => { if (!s.closed && !s.value.exited) {
|
|
95
|
+
try {
|
|
96
|
+
pty.write(data);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.failed(s, error);
|
|
100
|
+
}
|
|
101
|
+
} }));
|
|
102
|
+
this.sessions.set(id, s);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await this.close(s);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return this.descriptor(s);
|
|
109
|
+
}
|
|
110
|
+
async operate(op, params) {
|
|
111
|
+
request(params);
|
|
112
|
+
const id = identifier(params, 'id');
|
|
113
|
+
const incarnation = identifier(params, 'incarnation');
|
|
114
|
+
const s = this.sessions.get(id);
|
|
115
|
+
if (!s) {
|
|
116
|
+
if (op === 'close' || op === 'detach')
|
|
117
|
+
return ok;
|
|
118
|
+
return fail('pty_not_found', 'Terminal session not found; keeper restart loses live PTYs');
|
|
119
|
+
}
|
|
120
|
+
if (s.value.incarnation !== incarnation)
|
|
121
|
+
return fail('pty_incarnation_mismatch', 'Terminal incarnation no longer matches');
|
|
122
|
+
return this.ordered(s, async () => {
|
|
123
|
+
if (s.closed) {
|
|
124
|
+
if (op === 'close' || op === 'detach')
|
|
125
|
+
return ok;
|
|
126
|
+
return fail('pty_not_found', 'Terminal session is closed');
|
|
127
|
+
}
|
|
128
|
+
if (op === 'close') {
|
|
129
|
+
keys(params, ['id', 'incarnation']);
|
|
130
|
+
await this.close(s);
|
|
131
|
+
return ok;
|
|
132
|
+
}
|
|
133
|
+
await this.expire(s);
|
|
134
|
+
const clientId = identifier(params, 'clientId');
|
|
135
|
+
const ref = ['id', 'incarnation', 'clientId'];
|
|
136
|
+
if (op === 'detach') {
|
|
137
|
+
keys(params, ref);
|
|
138
|
+
this.release(s, clientId);
|
|
139
|
+
await this.capacity(s);
|
|
140
|
+
return ok;
|
|
141
|
+
}
|
|
142
|
+
if (op === 'attach') {
|
|
143
|
+
keys(params, [...ref, 'cols', 'rows']);
|
|
144
|
+
const cols = integer(params, 'cols', limits.minCols, limits.maxCols);
|
|
145
|
+
const rows = integer(params, 'rows', limits.minRows, limits.maxRows);
|
|
146
|
+
if (!s.attachments.has(clientId) && s.attachments.size >= limits.maxAttachments)
|
|
147
|
+
return fail('pty_limit', 'Terminal attachment limit exceeded');
|
|
148
|
+
const previous = s.attachments.get(clientId);
|
|
149
|
+
s.attachments.set(clientId, { cols, rows, expires: this.now() + limits.leaseMs });
|
|
150
|
+
try {
|
|
151
|
+
await this.capacity(s);
|
|
152
|
+
await this.settle(s);
|
|
153
|
+
this.capture(s, true);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (previous)
|
|
157
|
+
s.attachments.set(clientId, previous);
|
|
158
|
+
else
|
|
159
|
+
this.release(s, clientId);
|
|
160
|
+
try {
|
|
161
|
+
await this.capacity(s);
|
|
162
|
+
}
|
|
163
|
+
catch (rollback) {
|
|
164
|
+
this.failed(s, rollback);
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
this.scheduleLease(s);
|
|
169
|
+
return s.frames.poll(0);
|
|
170
|
+
}
|
|
171
|
+
const attachment = s.attachments.get(clientId);
|
|
172
|
+
if (!attachment)
|
|
173
|
+
return fail('pty_attachment_expired', 'Terminal attachment expired; attach again');
|
|
174
|
+
if (op === 'poll') {
|
|
175
|
+
keys(params, [...ref, 'after']);
|
|
176
|
+
const after = integer(params, 'after', 0, Number.MAX_SAFE_INTEGER);
|
|
177
|
+
attachment.expires = this.now() + limits.leaseMs;
|
|
178
|
+
this.scheduleLease(s);
|
|
179
|
+
await this.captureDue(s);
|
|
180
|
+
return s.frames.poll(after);
|
|
181
|
+
}
|
|
182
|
+
if (op === 'resize') {
|
|
183
|
+
keys(params, [...ref, 'cols', 'rows']);
|
|
184
|
+
const cols = integer(params, 'cols', limits.minCols, limits.maxCols);
|
|
185
|
+
const rows = integer(params, 'rows', limits.minRows, limits.maxRows);
|
|
186
|
+
const previous = { ...attachment };
|
|
187
|
+
attachment.cols = cols;
|
|
188
|
+
attachment.rows = rows;
|
|
189
|
+
try {
|
|
190
|
+
await this.capacity(s);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
Object.assign(attachment, previous);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (op === 'input') {
|
|
198
|
+
const event = input(params, ref);
|
|
199
|
+
await this.settle(s);
|
|
200
|
+
this.assertRunning(s);
|
|
201
|
+
if (event.kind === 'pointer' || event.kind === 'button')
|
|
202
|
+
this.coordinates(s, event);
|
|
203
|
+
const data = s.input.input(event, clientId, this.inputContext(s));
|
|
204
|
+
if (data)
|
|
205
|
+
s.pty.write(data);
|
|
206
|
+
}
|
|
207
|
+
else if (op === 'scroll') {
|
|
208
|
+
keys(params, [...ref, 'lines', 'x', 'y', 'modifiers', 'geometry']);
|
|
209
|
+
const gesture = { lines: integer(params, 'lines', -Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER), ...position(params), modifiers: integer(params, 'modifiers', 0, 15) };
|
|
210
|
+
await this.settle(s);
|
|
211
|
+
this.coordinates(s, gesture);
|
|
212
|
+
const data = s.input.scroll(gesture, clientId, this.inputContext(s));
|
|
213
|
+
if (data) {
|
|
214
|
+
this.assertRunning(s);
|
|
215
|
+
s.pty.write(data);
|
|
216
|
+
}
|
|
217
|
+
else if (data === undefined && s.screen.state().buffer === 'normal') {
|
|
218
|
+
s.screen.scroll(gesture.lines);
|
|
219
|
+
this.dirty(s);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else
|
|
223
|
+
return bad('Unsupported terminal operation');
|
|
224
|
+
attachment.expires = this.now() + limits.leaseMs;
|
|
225
|
+
this.scheduleLease(s);
|
|
226
|
+
return ok;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
activity() {
|
|
230
|
+
const metrics = { ptyOpen: 0, ptyActive: 0, ptyExited: 0 };
|
|
231
|
+
const items = [...this.sessions.values()].map(s => {
|
|
232
|
+
if (s.value.exited) {
|
|
233
|
+
metrics.ptyExited++;
|
|
234
|
+
return { type: 'pty', id: s.value.id, label: s.shell.slice(0, 64), state: 'exited', sinceEpochMs: s.exitedAt };
|
|
235
|
+
}
|
|
236
|
+
metrics.ptyOpen++;
|
|
237
|
+
let foreground = '';
|
|
238
|
+
try {
|
|
239
|
+
foreground = basename(s.pty.process);
|
|
240
|
+
}
|
|
241
|
+
catch { /* process can disappear during exit */ }
|
|
242
|
+
const active = foreground !== '' && !s.shellNames.has(foreground);
|
|
243
|
+
const label = (active ? foreground : s.shell).slice(0, 64);
|
|
244
|
+
const state = active ? 'active' : 'idle';
|
|
245
|
+
if (active)
|
|
246
|
+
metrics.ptyActive++;
|
|
247
|
+
if (s.observed.label !== label || s.observed.state !== state)
|
|
248
|
+
s.observed = { label, state, since: this.now() };
|
|
249
|
+
return { type: 'pty', id: s.value.id, label, state, sinceEpochMs: s.observed.since };
|
|
250
|
+
});
|
|
251
|
+
return { metrics, items };
|
|
252
|
+
}
|
|
253
|
+
async dispose() {
|
|
254
|
+
if (this.disposed)
|
|
255
|
+
return;
|
|
256
|
+
this.disposed = true;
|
|
257
|
+
await Promise.all([...this.sessions.values()].map(s => this.ordered(s, () => this.close(s))));
|
|
258
|
+
}
|
|
259
|
+
descriptor(s) { return { ...s.value, ...s.metadata?.read() }; }
|
|
260
|
+
available() {
|
|
261
|
+
if (this.disposed)
|
|
262
|
+
fail('pty_unavailable', 'Terminal host is disposed');
|
|
263
|
+
if (!this.module)
|
|
264
|
+
fail('pty_unavailable', this.unavailable ?? 'Terminal native dependency unavailable');
|
|
265
|
+
}
|
|
266
|
+
checkLimit() { if (this.sessions.size >= limits.maxSessions)
|
|
267
|
+
fail('pty_limit', 'Terminal session limit exceeded'); }
|
|
268
|
+
scheduleShellName(s, attempt = 0) {
|
|
269
|
+
const live = () => !s.closed && !s.value.exited && !this.disposed;
|
|
270
|
+
if (!live())
|
|
271
|
+
return;
|
|
272
|
+
s.shellNameTimer = this.scheduler.schedule(() => {
|
|
273
|
+
s.shellNameTimer = undefined;
|
|
274
|
+
if (!live())
|
|
275
|
+
return;
|
|
276
|
+
const retry = () => { if (attempt < 2)
|
|
277
|
+
this.scheduleShellName(s, attempt + 1); };
|
|
278
|
+
// Query the original shell PID: the foreground process may be a running command.
|
|
279
|
+
void Promise.resolve().then(() => live() ? (this.options.shellProcessName ?? defaultShellProcessName)(s.pty.pid) : null).then(name => {
|
|
280
|
+
if (!live())
|
|
281
|
+
return;
|
|
282
|
+
const shell = name && basename(name);
|
|
283
|
+
if (shell && shell !== 'spawn-helper')
|
|
284
|
+
s.shellNames.add(shell);
|
|
285
|
+
else
|
|
286
|
+
retry();
|
|
287
|
+
}, retry);
|
|
288
|
+
}, this.options.shellNameDelayMs ?? 250);
|
|
289
|
+
}
|
|
290
|
+
ordered(s, action) {
|
|
291
|
+
const operation = s.queue.then(action);
|
|
292
|
+
s.queue = operation.catch(() => { });
|
|
293
|
+
return operation;
|
|
294
|
+
}
|
|
295
|
+
async settle(s) {
|
|
296
|
+
for (;;) {
|
|
297
|
+
const rendered = s.rendered;
|
|
298
|
+
await rendered;
|
|
299
|
+
if (rendered === s.rendered)
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
if (s.failure)
|
|
303
|
+
throw s.failure;
|
|
304
|
+
}
|
|
305
|
+
output(s, data) {
|
|
306
|
+
if (s.closed || !data || s.failure)
|
|
307
|
+
return;
|
|
308
|
+
// node-pty initially runs a spawn-helper trampoline at this PID. First output
|
|
309
|
+
// starts the bounded lookup after shell startup, once per session.
|
|
310
|
+
if (!s.shellNameStarted) {
|
|
311
|
+
s.shellNameStarted = true;
|
|
312
|
+
this.scheduleShellName(s);
|
|
313
|
+
}
|
|
314
|
+
const bytes = Buffer.byteLength(data);
|
|
315
|
+
s.pendingBytes += bytes;
|
|
316
|
+
try {
|
|
317
|
+
if (s.pendingBytes > 256 * 1024)
|
|
318
|
+
s.pty.pause();
|
|
319
|
+
s.metadata?.onData?.(data);
|
|
320
|
+
// Submit directly to xterm's ordered parser; chaining writes costs one timer per PTY chunk.
|
|
321
|
+
s.rendered = s.screen.write(data);
|
|
322
|
+
void s.rendered.then(() => {
|
|
323
|
+
s.pendingBytes -= bytes;
|
|
324
|
+
try {
|
|
325
|
+
if (!s.closed && !s.value.exited && s.pendingBytes < 64 * 1024)
|
|
326
|
+
s.pty.resume();
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.failed(s, error);
|
|
330
|
+
}
|
|
331
|
+
this.dirty(s);
|
|
332
|
+
}, error => this.failed(s, error));
|
|
333
|
+
this.dirty(s);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
this.failed(s, error);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
failed(s, error) { if (!s.closed) {
|
|
340
|
+
s.failure = error;
|
|
341
|
+
s.captureTimer?.dispose();
|
|
342
|
+
s.captureTimer = undefined;
|
|
343
|
+
} }
|
|
344
|
+
dirty(s) {
|
|
345
|
+
if (s.closed || s.failure)
|
|
346
|
+
return;
|
|
347
|
+
s.dirty = true;
|
|
348
|
+
if (!s.captureTimer)
|
|
349
|
+
s.captureTimer = this.scheduler.schedule(() => {
|
|
350
|
+
s.captureTimer = undefined;
|
|
351
|
+
void this.ordered(s, async () => { if (s.closed)
|
|
352
|
+
return; await this.settle(s); this.capture(s); }).catch(error => { if (!(error instanceof KeeperMethodError && error.code === 'pty_snapshot_too_large'))
|
|
353
|
+
this.failed(s, error); });
|
|
354
|
+
}, Math.max(0, 50 - (this.now() - s.capturedAt)));
|
|
355
|
+
}
|
|
356
|
+
async captureDue(s) {
|
|
357
|
+
if (s.dirty && this.now() - s.capturedAt < 50) {
|
|
358
|
+
s.captureTimer?.dispose();
|
|
359
|
+
s.captureTimer = undefined;
|
|
360
|
+
await new Promise(resolve => this.scheduler.schedule(resolve, 50 - (this.now() - s.capturedAt)));
|
|
361
|
+
}
|
|
362
|
+
await this.settle(s);
|
|
363
|
+
this.capture(s, true);
|
|
364
|
+
}
|
|
365
|
+
capture(s, force = false) {
|
|
366
|
+
if (!s.dirty || (!force && this.now() - s.capturedAt < 50))
|
|
367
|
+
return;
|
|
368
|
+
s.frames.publish(s.screen.capture(), this.descriptor(s), s.geometry);
|
|
369
|
+
s.dirty = false;
|
|
370
|
+
s.capturedAt = this.now();
|
|
371
|
+
s.captureTimer?.dispose();
|
|
372
|
+
s.captureTimer = undefined;
|
|
373
|
+
}
|
|
374
|
+
async capacity(s) {
|
|
375
|
+
if (!s.attachments.size)
|
|
376
|
+
return;
|
|
377
|
+
const cols = Math.min(...[...s.attachments.values()].map(a => a.cols));
|
|
378
|
+
const rows = Math.min(...[...s.attachments.values()].map(a => a.rows));
|
|
379
|
+
if (cols === s.value.cols && rows === s.value.rows)
|
|
380
|
+
return;
|
|
381
|
+
await this.settle(s);
|
|
382
|
+
if (!s.value.exited)
|
|
383
|
+
s.pty.resize(cols, rows);
|
|
384
|
+
try {
|
|
385
|
+
await s.screen.resize(cols, rows);
|
|
386
|
+
await this.settle(s);
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
// A native emulator failure may have changed part of the grid. Never publish or accept input against uncertain geometry.
|
|
390
|
+
this.failed(s, error);
|
|
391
|
+
try {
|
|
392
|
+
if (!s.value.exited)
|
|
393
|
+
s.pty.resize(s.value.cols, s.value.rows);
|
|
394
|
+
}
|
|
395
|
+
catch (rollback) {
|
|
396
|
+
this.failed(s, rollback);
|
|
397
|
+
}
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
s.value.cols = cols;
|
|
401
|
+
s.value.rows = rows;
|
|
402
|
+
s.geometry++;
|
|
403
|
+
this.dirty(s);
|
|
404
|
+
}
|
|
405
|
+
inputContext(s) { return { cols: s.value.cols, rows: s.value.rows, geometry: s.geometry, modes: s.screen.state().modes }; }
|
|
406
|
+
coordinates(s, event) {
|
|
407
|
+
if (event.geometry !== s.geometry)
|
|
408
|
+
fail('pty_geometry_mismatch', 'Terminal geometry changed');
|
|
409
|
+
if (event.x >= s.value.cols || event.y >= s.value.rows)
|
|
410
|
+
bad('Terminal coordinates outside geometry');
|
|
411
|
+
}
|
|
412
|
+
assertRunning(s) { if (s.value.exited)
|
|
413
|
+
fail('pty_exited', 'Terminal has exited'); }
|
|
414
|
+
release(s, clientId) {
|
|
415
|
+
if (s.attachments.delete(clientId))
|
|
416
|
+
s.input.release(clientId);
|
|
417
|
+
this.scheduleLease(s);
|
|
418
|
+
}
|
|
419
|
+
async expire(s) {
|
|
420
|
+
let changed = false;
|
|
421
|
+
for (const [id, attachment] of s.attachments)
|
|
422
|
+
if (attachment.expires <= this.now()) {
|
|
423
|
+
this.release(s, id);
|
|
424
|
+
changed = true;
|
|
425
|
+
}
|
|
426
|
+
if (changed)
|
|
427
|
+
await this.capacity(s);
|
|
428
|
+
}
|
|
429
|
+
scheduleLease(s) {
|
|
430
|
+
s.leaseTimer?.dispose();
|
|
431
|
+
s.leaseTimer = undefined;
|
|
432
|
+
if (s.closed || this.disposed || !s.attachments.size)
|
|
433
|
+
return;
|
|
434
|
+
const expires = Math.min(...[...s.attachments.values()].map(attachment => attachment.expires));
|
|
435
|
+
s.leaseTimer = this.scheduler.schedule(() => {
|
|
436
|
+
s.leaseTimer = undefined;
|
|
437
|
+
void this.ordered(s, () => this.expire(s)).catch(error => this.failed(s, error)).finally(() => this.scheduleLease(s));
|
|
438
|
+
}, Math.max(0, expires - this.now()));
|
|
439
|
+
}
|
|
440
|
+
async close(s) {
|
|
441
|
+
if (s.closed)
|
|
442
|
+
return;
|
|
443
|
+
s.closed = true;
|
|
444
|
+
if (this.sessions.get(s.value.id) === s)
|
|
445
|
+
this.sessions.delete(s.value.id);
|
|
446
|
+
s.shellNameTimer?.dispose();
|
|
447
|
+
s.shellNameTimer = undefined;
|
|
448
|
+
s.captureTimer?.dispose();
|
|
449
|
+
s.captureTimer = undefined;
|
|
450
|
+
s.leaseTimer?.dispose();
|
|
451
|
+
s.leaseTimer = undefined;
|
|
452
|
+
for (const listener of s.listeners)
|
|
453
|
+
listener.dispose();
|
|
454
|
+
for (const id of [...s.attachments.keys()])
|
|
455
|
+
this.release(s, id);
|
|
456
|
+
s.input.dispose();
|
|
457
|
+
s.metadata?.dispose?.();
|
|
458
|
+
try {
|
|
459
|
+
if (!s.value.exited)
|
|
460
|
+
s.pty.kill();
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
s.screen.dispose();
|
|
464
|
+
await s.rendered.catch(() => { });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function disposable(value) {
|
|
469
|
+
return value && typeof value.dispose === 'function' ? value : { dispose: () => { } };
|
|
470
|
+
}
|
|
471
|
+
function request(params) { if (Buffer.byteLength(JSON.stringify(params)) > limits.maxRequestBytes)
|
|
472
|
+
bad('Terminal request exceeds limit'); }
|
|
473
|
+
function keys(params, allowed) { if (Object.keys(params).some(key => !allowed.includes(key)))
|
|
474
|
+
bad('Unexpected terminal parameter'); }
|
|
475
|
+
function identifier(params, name) {
|
|
476
|
+
const value = params[name];
|
|
477
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9_.:-]{1,128}$/.test(value))
|
|
478
|
+
return bad(`Invalid ${name}`);
|
|
479
|
+
return value;
|
|
480
|
+
}
|
|
481
|
+
function integer(params, name, min, max) {
|
|
482
|
+
const value = params[name];
|
|
483
|
+
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
484
|
+
return bad(`Invalid ${name}`);
|
|
485
|
+
return value;
|
|
486
|
+
}
|
|
487
|
+
function position(params) {
|
|
488
|
+
return { x: integer(params, 'x', 0, limits.maxCols - 1), y: integer(params, 'y', 0, limits.maxRows - 1), geometry: integer(params, 'geometry', 1, Number.MAX_SAFE_INTEGER) };
|
|
489
|
+
}
|
|
490
|
+
function input(params, ref) {
|
|
491
|
+
switch (params.kind) {
|
|
492
|
+
case 'text':
|
|
493
|
+
case 'paste':
|
|
494
|
+
keys(params, [...ref, 'kind', 'text']);
|
|
495
|
+
if (typeof params.text !== 'string' || Buffer.byteLength(params.text) > limits.maxInputBytes)
|
|
496
|
+
return bad('Terminal input exceeds limit');
|
|
497
|
+
return { kind: params.kind, text: params.text };
|
|
498
|
+
case 'key':
|
|
499
|
+
keys(params, [...ref, 'kind', 'usage', 'down', 'modifiers']);
|
|
500
|
+
if (typeof params.down !== 'boolean')
|
|
501
|
+
return bad('Invalid down');
|
|
502
|
+
return { kind: 'key', usage: integer(params, 'usage', 0, 65535), down: params.down, modifiers: integer(params, 'modifiers', 0, 15) };
|
|
503
|
+
case 'pointer':
|
|
504
|
+
case 'button': {
|
|
505
|
+
keys(params, [...ref, 'kind', 'x', 'y', 'geometry', 'modifiers', ...(params.kind === 'button' ? ['button', 'down'] : [])]);
|
|
506
|
+
const point = { ...position(params), modifiers: integer(params, 'modifiers', 0, 15) };
|
|
507
|
+
if (params.kind === 'pointer')
|
|
508
|
+
return { kind: 'pointer', ...point };
|
|
509
|
+
if (typeof params.down !== 'boolean')
|
|
510
|
+
return bad('Invalid down');
|
|
511
|
+
return { kind: 'button', ...point, button: integer(params, 'button', 0, 2), down: params.down };
|
|
512
|
+
}
|
|
513
|
+
case 'release':
|
|
514
|
+
keys(params, [...ref, 'kind']);
|
|
515
|
+
return { kind: 'release' };
|
|
516
|
+
default: return bad('Invalid terminal input kind');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/** This lookup is never awaited by poll/input/activity and never exposes process arguments. */
|
|
520
|
+
function defaultShellProcessName(pid) {
|
|
521
|
+
if (process.platform !== 'darwin')
|
|
522
|
+
return Promise.resolve(null);
|
|
523
|
+
return new Promise(resolve => {
|
|
524
|
+
execFile('ps', ['-o', 'ucomm=', '-p', String(pid)], { timeout: 1000 }, (error, stdout) => resolve(error ? null : stdout.trim() || null));
|
|
525
|
+
});
|
|
526
|
+
}
|
|
@@ -42,8 +42,11 @@ function requireString(value, field, description) {
|
|
|
42
42
|
throw new DeviceAuthorizationError(`${description} missing ${field}`);
|
|
43
43
|
return value;
|
|
44
44
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
45
|
+
function deviceEndpoint(server, endpoint, identity) {
|
|
46
|
+
return identity ? new URL(server + endpoint) : new URL(endpoint, server);
|
|
47
|
+
}
|
|
48
|
+
function websocketUrl(server, identity) {
|
|
49
|
+
const url = deviceEndpoint(server, '/oauth/device/ws', identity);
|
|
47
50
|
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
48
51
|
return url.toString();
|
|
49
52
|
}
|
|
@@ -89,23 +92,33 @@ function waitForNotification(socket, sessionId, notificationTicket, deadline, no
|
|
|
89
92
|
socket.on('error', () => finish('reconnect'));
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
|
-
async function exchangeDeviceCode(server, clientId, deviceCode, fetch_, requestedScopes) {
|
|
93
|
-
const response = await requestJson(fetch_,
|
|
95
|
+
async function exchangeDeviceCode(server, clientId, deviceCode, fetch_, requestedScopes, identity) {
|
|
96
|
+
const response = await requestJson(fetch_, deviceEndpoint(server, '/oauth/token', identity), {
|
|
94
97
|
method: 'POST',
|
|
95
98
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
96
99
|
body: new URLSearchParams({
|
|
97
100
|
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
98
101
|
client_id: clientId,
|
|
99
102
|
device_code: deviceCode,
|
|
100
|
-
device_id: await loadDeviceId(),
|
|
103
|
+
device_id: identity?.deviceId ?? await loadDeviceId(),
|
|
101
104
|
}),
|
|
102
105
|
}, 200, 'Device token exchange');
|
|
103
|
-
return validateTokenResponse(server, response, requestedScopes, fetch_);
|
|
106
|
+
return validateTokenResponse(server, response, requestedScopes, fetch_, identity);
|
|
104
107
|
}
|
|
105
|
-
async function validateTokenResponse(server, response, requestedScopes, fetch_) {
|
|
106
|
-
|
|
108
|
+
async function validateTokenResponse(server, response, requestedScopes, fetch_, identity) {
|
|
109
|
+
let accessToken;
|
|
110
|
+
try {
|
|
111
|
+
accessToken = requireString(response.access_token, 'access_token', 'Device token response');
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (identity && typeof response.refresh_token === 'string' && response.refresh_token)
|
|
115
|
+
await revokeRefreshToken(server, response.refresh_token, fetch_);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
107
118
|
const refreshToken = requireString(response.refresh_token, 'refresh_token', 'Device token response');
|
|
108
119
|
if (!Number.isFinite(response.expires_in) || response.expires_in <= 0) {
|
|
120
|
+
if (identity)
|
|
121
|
+
await revokeRefreshToken(server, refreshToken, fetch_);
|
|
109
122
|
throw new DeviceAuthorizationError('Device token response has invalid expires_in');
|
|
110
123
|
}
|
|
111
124
|
let scopes;
|
|
@@ -118,15 +131,15 @@ async function validateTokenResponse(server, response, requestedScopes, fetch_)
|
|
|
118
131
|
}
|
|
119
132
|
return { accessToken, refreshToken, expiresInSeconds: response.expires_in, scopes };
|
|
120
133
|
}
|
|
121
|
-
async function pollDeviceCode(server, clientId, deviceCode, fetch_, requestedScopes) {
|
|
122
|
-
const response = await fetch_(
|
|
134
|
+
async function pollDeviceCode(server, clientId, deviceCode, fetch_, requestedScopes, identity) {
|
|
135
|
+
const response = await fetch_(deviceEndpoint(server, '/oauth/token', identity), {
|
|
123
136
|
method: 'POST',
|
|
124
137
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
125
138
|
body: new URLSearchParams({
|
|
126
139
|
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
127
140
|
client_id: clientId,
|
|
128
141
|
device_code: deviceCode,
|
|
129
|
-
device_id: await loadDeviceId(),
|
|
142
|
+
device_id: identity?.deviceId ?? await loadDeviceId(),
|
|
130
143
|
}),
|
|
131
144
|
});
|
|
132
145
|
if (response.status === 400) {
|
|
@@ -141,13 +154,13 @@ async function pollDeviceCode(server, clientId, deviceCode, fetch_, requestedSco
|
|
|
141
154
|
if (response.status !== 200)
|
|
142
155
|
throw new DeviceAuthorizationError(`Device token polling failed: ${response.status}`);
|
|
143
156
|
const token = await response.json();
|
|
144
|
-
return validateTokenResponse(server, token, requestedScopes, fetch_);
|
|
157
|
+
return validateTokenResponse(server, token, requestedScopes, fetch_, identity);
|
|
145
158
|
}
|
|
146
159
|
/**
|
|
147
160
|
* Uses OAuth Device Authorization for both desktop and headless clients. The notification socket
|
|
148
161
|
* only carries state; the tokens exist only in this call's return value.
|
|
149
162
|
*/
|
|
150
|
-
export async function authorizeDevice(server, clientName, hostname, deps = {}, requestedScopes = DEFAULT_CLI_SCOPES) {
|
|
163
|
+
export async function authorizeDevice(server, clientName, hostname, deps = {}, requestedScopes = DEFAULT_CLI_SCOPES, identity) {
|
|
151
164
|
const baseUrl = normalizedServer(server);
|
|
152
165
|
const fetch_ = deps.fetch ?? globalThis.fetch;
|
|
153
166
|
const print = deps.print ?? console.log;
|
|
@@ -155,9 +168,10 @@ export async function authorizeDevice(server, clientName, hostname, deps = {}, r
|
|
|
155
168
|
const createWebSocket = deps.createWebSocket ?? ((url) => new WebSocket(url));
|
|
156
169
|
const now = deps.now ?? Date.now;
|
|
157
170
|
const wait = deps.sleep ?? sleep;
|
|
171
|
+
const flowIdentity = identity ? Object.freeze({ deviceId: requireString(identity.deviceId, 'deviceId', 'Device authorization identity') }) : undefined;
|
|
158
172
|
void clientName;
|
|
159
173
|
const clientId = 'axom-cli';
|
|
160
|
-
const authorization = await requestJson(fetch_,
|
|
174
|
+
const authorization = await requestJson(fetch_, deviceEndpoint(baseUrl, '/oauth/device_authorization', flowIdentity), {
|
|
161
175
|
method: 'POST',
|
|
162
176
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
163
177
|
body: new URLSearchParams({ client_id: clientId, hostname, scope: requestedScopes.join(' ') }),
|
|
@@ -176,7 +190,7 @@ export async function authorizeDevice(server, clientName, hostname, deps = {}, r
|
|
|
176
190
|
while (now() < deadline) {
|
|
177
191
|
let socket;
|
|
178
192
|
try {
|
|
179
|
-
socket = createWebSocket(websocketUrl(baseUrl));
|
|
193
|
+
socket = createWebSocket(websocketUrl(baseUrl, flowIdentity));
|
|
180
194
|
}
|
|
181
195
|
catch {
|
|
182
196
|
const delay = Math.min(retry.recordFailure(), Math.max(0, deadline - now()));
|
|
@@ -187,14 +201,14 @@ export async function authorizeDevice(server, clientName, hostname, deps = {}, r
|
|
|
187
201
|
}
|
|
188
202
|
const status = await waitForNotification(socket, sessionId, notificationTicket, deadline, now, () => retry.recordSuccess());
|
|
189
203
|
if (status === 'approved') {
|
|
190
|
-
const tokens = await exchangeDeviceCode(baseUrl, clientId, deviceCode, fetch_, requestedScopes);
|
|
204
|
+
const tokens = await exchangeDeviceCode(baseUrl, clientId, deviceCode, fetch_, requestedScopes, flowIdentity);
|
|
191
205
|
return { clientId, ...tokens };
|
|
192
206
|
}
|
|
193
207
|
if (status === 'denied')
|
|
194
208
|
throw new DeviceAuthorizationError('Device authorization was denied');
|
|
195
209
|
if (status === 'expired')
|
|
196
210
|
break;
|
|
197
|
-
const polled = await pollDeviceCode(baseUrl, clientId, deviceCode, fetch_, requestedScopes);
|
|
211
|
+
const polled = await pollDeviceCode(baseUrl, clientId, deviceCode, fetch_, requestedScopes, flowIdentity);
|
|
198
212
|
if (polled)
|
|
199
213
|
return { clientId, ...polled };
|
|
200
214
|
const pollingFloor = Math.max(1, authorization.interval ?? 5) * 1_000;
|