@felan-ai/daemon 0.0.0-stage → 0.0.0
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/LICENSE +21 -0
- package/NOTICE +69 -0
- package/README.md +61 -2
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -0
- package/dist/daemon/admission.d.ts +39 -0
- package/dist/daemon/admission.d.ts.map +1 -0
- package/dist/daemon/admission.js +175 -0
- package/dist/daemon/admission.js.map +1 -0
- package/dist/daemon/agent-logs.d.ts +11 -0
- package/dist/daemon/agent-logs.d.ts.map +1 -0
- package/dist/daemon/agent-logs.js +31 -0
- package/dist/daemon/agent-logs.js.map +1 -0
- package/dist/daemon/bootstrap.d.ts +7 -0
- package/dist/daemon/bootstrap.d.ts.map +1 -0
- package/dist/daemon/bootstrap.js +4 -0
- package/dist/daemon/bootstrap.js.map +1 -0
- package/dist/daemon/broker.d.ts +94 -0
- package/dist/daemon/broker.d.ts.map +1 -0
- package/dist/daemon/broker.js +374 -0
- package/dist/daemon/broker.js.map +1 -0
- package/dist/daemon/commands.d.ts +47 -0
- package/dist/daemon/commands.d.ts.map +1 -0
- package/dist/daemon/commands.js +639 -0
- package/dist/daemon/commands.js.map +1 -0
- package/dist/daemon/control-recovery.d.ts +9 -0
- package/dist/daemon/control-recovery.d.ts.map +1 -0
- package/dist/daemon/control-recovery.js +68 -0
- package/dist/daemon/control-recovery.js.map +1 -0
- package/dist/daemon/control-state.d.ts +1915 -0
- package/dist/daemon/control-state.d.ts.map +1 -0
- package/dist/daemon/control-state.js +154 -0
- package/dist/daemon/control-state.js.map +1 -0
- package/dist/daemon/control-store.d.ts +113 -0
- package/dist/daemon/control-store.d.ts.map +1 -0
- package/dist/daemon/control-store.js +901 -0
- package/dist/daemon/control-store.js.map +1 -0
- package/dist/daemon/execution-policy.d.ts +15 -0
- package/dist/daemon/execution-policy.d.ts.map +1 -0
- package/dist/daemon/execution-policy.js +42 -0
- package/dist/daemon/execution-policy.js.map +1 -0
- package/dist/daemon/execution-services.d.ts +5 -0
- package/dist/daemon/execution-services.d.ts.map +1 -0
- package/dist/daemon/execution-services.js +73 -0
- package/dist/daemon/execution-services.js.map +1 -0
- package/dist/daemon/executions.d.ts +134 -0
- package/dist/daemon/executions.d.ts.map +1 -0
- package/dist/daemon/executions.js +486 -0
- package/dist/daemon/executions.js.map +1 -0
- package/dist/daemon/extension-ui.d.ts +24 -0
- package/dist/daemon/extension-ui.d.ts.map +1 -0
- package/dist/daemon/extension-ui.js +161 -0
- package/dist/daemon/extension-ui.js.map +1 -0
- package/dist/daemon/fanout.d.ts +40 -0
- package/dist/daemon/fanout.d.ts.map +1 -0
- package/dist/daemon/fanout.js +304 -0
- package/dist/daemon/fanout.js.map +1 -0
- package/dist/daemon/headless-json.d.ts +3 -0
- package/dist/daemon/headless-json.d.ts.map +1 -0
- package/dist/daemon/headless-json.js +20 -0
- package/dist/daemon/headless-json.js.map +1 -0
- package/dist/daemon/host-config.d.ts +61 -0
- package/dist/daemon/host-config.d.ts.map +1 -0
- package/dist/daemon/host-config.js +167 -0
- package/dist/daemon/host-config.js.map +1 -0
- package/dist/daemon/host-services.d.ts +61 -0
- package/dist/daemon/host-services.d.ts.map +1 -0
- package/dist/daemon/host-services.js +2 -0
- package/dist/daemon/host-services.js.map +1 -0
- package/dist/daemon/ipc.d.ts +14 -0
- package/dist/daemon/ipc.d.ts.map +1 -0
- package/dist/daemon/ipc.js +233 -0
- package/dist/daemon/ipc.js.map +1 -0
- package/dist/daemon/legacy-sessions.d.ts +48 -0
- package/dist/daemon/legacy-sessions.d.ts.map +1 -0
- package/dist/daemon/legacy-sessions.js +328 -0
- package/dist/daemon/legacy-sessions.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +44 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +164 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/main.d.ts +3 -0
- package/dist/daemon/main.d.ts.map +1 -0
- package/dist/daemon/main.js +83 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/message-display.d.ts +4 -0
- package/dist/daemon/message-display.d.ts.map +1 -0
- package/dist/daemon/message-display.js +20 -0
- package/dist/daemon/message-display.js.map +1 -0
- package/dist/daemon/native-ownership.d.ts +13 -0
- package/dist/daemon/native-ownership.d.ts.map +1 -0
- package/dist/daemon/native-ownership.js +173 -0
- package/dist/daemon/native-ownership.js.map +1 -0
- package/dist/daemon/operational-log.d.ts +23 -0
- package/dist/daemon/operational-log.d.ts.map +1 -0
- package/dist/daemon/operational-log.js +79 -0
- package/dist/daemon/operational-log.js.map +1 -0
- package/dist/daemon/ownership.d.ts +35 -0
- package/dist/daemon/ownership.d.ts.map +1 -0
- package/dist/daemon/ownership.js +395 -0
- package/dist/daemon/ownership.js.map +1 -0
- package/dist/daemon/persistence.d.ts +110 -0
- package/dist/daemon/persistence.d.ts.map +1 -0
- package/dist/daemon/persistence.js +540 -0
- package/dist/daemon/persistence.js.map +1 -0
- package/dist/daemon/process-adapter.d.ts +23 -0
- package/dist/daemon/process-adapter.d.ts.map +1 -0
- package/dist/daemon/process-adapter.js +2 -0
- package/dist/daemon/process-adapter.js.map +1 -0
- package/dist/daemon/processes.d.ts +102 -0
- package/dist/daemon/processes.d.ts.map +1 -0
- package/dist/daemon/processes.js +691 -0
- package/dist/daemon/processes.js.map +1 -0
- package/dist/daemon/profile-storage.d.ts +16 -0
- package/dist/daemon/profile-storage.d.ts.map +1 -0
- package/dist/daemon/profile-storage.js +148 -0
- package/dist/daemon/profile-storage.js.map +1 -0
- package/dist/daemon/projection.d.ts +110 -0
- package/dist/daemon/projection.d.ts.map +1 -0
- package/dist/daemon/projection.js +661 -0
- package/dist/daemon/projection.js.map +1 -0
- package/dist/daemon/provider-loader.d.ts +28 -0
- package/dist/daemon/provider-loader.d.ts.map +1 -0
- package/dist/daemon/provider-loader.js +376 -0
- package/dist/daemon/provider-loader.js.map +1 -0
- package/dist/daemon/provider.d.ts +92 -0
- package/dist/daemon/provider.d.ts.map +1 -0
- package/dist/daemon/provider.js +31 -0
- package/dist/daemon/provider.js.map +1 -0
- package/dist/daemon/registry.d.ts +209 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +1591 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/runtime-dependencies.d.ts +5 -0
- package/dist/daemon/runtime-dependencies.d.ts.map +1 -0
- package/dist/daemon/runtime-dependencies.js +31 -0
- package/dist/daemon/runtime-dependencies.js.map +1 -0
- package/dist/daemon/server.d.ts +110 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +556 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/service.d.ts +38 -0
- package/dist/daemon/service.d.ts.map +1 -0
- package/dist/daemon/service.js +275 -0
- package/dist/daemon/service.js.map +1 -0
- package/dist/daemon/session-cursor.d.ts +3 -0
- package/dist/daemon/session-cursor.d.ts.map +1 -0
- package/dist/daemon/session-cursor.js +4 -0
- package/dist/daemon/session-cursor.js.map +1 -0
- package/dist/daemon/session-execution.d.ts +88 -0
- package/dist/daemon/session-execution.d.ts.map +1 -0
- package/dist/daemon/session-execution.js +2 -0
- package/dist/daemon/session-execution.js.map +1 -0
- package/dist/daemon/session-executions.d.ts +5 -0
- package/dist/daemon/session-executions.d.ts.map +1 -0
- package/dist/daemon/session-executions.js +146 -0
- package/dist/daemon/session-executions.js.map +1 -0
- package/dist/daemon/session-files.d.ts +28 -0
- package/dist/daemon/session-files.d.ts.map +1 -0
- package/dist/daemon/session-files.js +189 -0
- package/dist/daemon/session-files.js.map +1 -0
- package/dist/daemon/session-lifecycle-contracts.d.ts +104 -0
- package/dist/daemon/session-lifecycle-contracts.d.ts.map +1 -0
- package/dist/daemon/session-lifecycle-contracts.js +2 -0
- package/dist/daemon/session-lifecycle-contracts.js.map +1 -0
- package/dist/daemon/session-lifecycle.d.ts +26 -0
- package/dist/daemon/session-lifecycle.d.ts.map +1 -0
- package/dist/daemon/session-lifecycle.js +246 -0
- package/dist/daemon/session-lifecycle.js.map +1 -0
- package/dist/daemon/session-runtime.d.ts +64 -0
- package/dist/daemon/session-runtime.d.ts.map +1 -0
- package/dist/daemon/session-runtime.js +3 -0
- package/dist/daemon/session-runtime.js.map +1 -0
- package/dist/daemon/session-services.d.ts +6 -0
- package/dist/daemon/session-services.d.ts.map +1 -0
- package/dist/daemon/session-services.js +144 -0
- package/dist/daemon/session-services.js.map +1 -0
- package/dist/daemon/shutdown-error.d.ts +12 -0
- package/dist/daemon/shutdown-error.d.ts.map +1 -0
- package/dist/daemon/shutdown-error.js +51 -0
- package/dist/daemon/shutdown-error.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +28 -0
- package/dist/daemon/supervisor.d.ts.map +1 -0
- package/dist/daemon/supervisor.js +159 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/daemon/target.d.ts +55 -0
- package/dist/daemon/target.d.ts.map +1 -0
- package/dist/daemon/target.js +191 -0
- package/dist/daemon/target.js.map +1 -0
- package/dist/daemon/tree.d.ts +7 -0
- package/dist/daemon/tree.d.ts.map +1 -0
- package/dist/daemon/tree.js +126 -0
- package/dist/daemon/tree.js.map +1 -0
- package/dist/daemon/ui-vocabulary.d.ts +29 -0
- package/dist/daemon/ui-vocabulary.d.ts.map +1 -0
- package/dist/daemon/ui-vocabulary.js +323 -0
- package/dist/daemon/ui-vocabulary.js.map +1 -0
- package/dist/daemon/worker.d.ts +5 -0
- package/dist/daemon/worker.d.ts.map +1 -0
- package/dist/daemon/worker.js +49 -0
- package/dist/daemon/worker.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +27 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +432 -0
- package/dist/lock.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +56 -0
- package/dist/logger.js.map +1 -0
- package/dist/remote/backoff.d.ts +17 -0
- package/dist/remote/backoff.d.ts.map +1 -0
- package/dist/remote/backoff.js +18 -0
- package/dist/remote/backoff.js.map +1 -0
- package/dist/remote/batch.d.ts +3 -0
- package/dist/remote/batch.d.ts.map +1 -0
- package/dist/remote/batch.js +27 -0
- package/dist/remote/batch.js.map +1 -0
- package/dist/remote/command-journal.d.ts +30 -0
- package/dist/remote/command-journal.d.ts.map +1 -0
- package/dist/remote/command-journal.js +72 -0
- package/dist/remote/command-journal.js.map +1 -0
- package/dist/remote/commands.d.ts +48 -0
- package/dist/remote/commands.d.ts.map +1 -0
- package/dist/remote/commands.js +145 -0
- package/dist/remote/commands.js.map +1 -0
- package/dist/remote/connector.d.ts +135 -0
- package/dist/remote/connector.d.ts.map +1 -0
- package/dist/remote/connector.js +1069 -0
- package/dist/remote/connector.js.map +1 -0
- package/dist/remote/content.d.ts +17 -0
- package/dist/remote/content.d.ts.map +1 -0
- package/dist/remote/content.js +116 -0
- package/dist/remote/content.js.map +1 -0
- package/dist/remote/outbox.d.ts +52 -0
- package/dist/remote/outbox.d.ts.map +1 -0
- package/dist/remote/outbox.js +321 -0
- package/dist/remote/outbox.js.map +1 -0
- package/dist/remote/platform.d.ts +53 -0
- package/dist/remote/platform.d.ts.map +1 -0
- package/dist/remote/platform.js +231 -0
- package/dist/remote/platform.js.map +1 -0
- package/dist/remote/realtime.d.ts +45 -0
- package/dist/remote/realtime.d.ts.map +1 -0
- package/dist/remote/realtime.js +265 -0
- package/dist/remote/realtime.js.map +1 -0
- package/dist/remote/sharing.d.ts +16 -0
- package/dist/remote/sharing.d.ts.map +1 -0
- package/dist/remote/sharing.js +24 -0
- package/dist/remote/sharing.js.map +1 -0
- package/dist/remote/state.d.ts +52 -0
- package/dist/remote/state.d.ts.map +1 -0
- package/dist/remote/state.js +156 -0
- package/dist/remote/state.js.map +1 -0
- package/dist/remote/text.d.ts +11 -0
- package/dist/remote/text.d.ts.map +1 -0
- package/dist/remote/text.js +188 -0
- package/dist/remote/text.js.map +1 -0
- package/dist/remote/token.d.ts +39 -0
- package/dist/remote/token.d.ts.map +1 -0
- package/dist/remote/token.js +238 -0
- package/dist/remote/token.js.map +1 -0
- package/dist/runtime-factory.d.ts +12 -0
- package/dist/runtime-factory.d.ts.map +1 -0
- package/dist/runtime-factory.js +12 -0
- package/dist/runtime-factory.js.map +1 -0
- package/dist/session-history.d.ts +29 -0
- package/dist/session-history.d.ts.map +1 -0
- package/dist/session-history.js +374 -0
- package/dist/session-history.js.map +1 -0
- package/dist/setup/dependencies.d.ts +15 -0
- package/dist/setup/dependencies.d.ts.map +1 -0
- package/dist/setup/dependencies.js +2 -0
- package/dist/setup/dependencies.js.map +1 -0
- package/dist/setup/open.d.ts +8 -0
- package/dist/setup/open.d.ts.map +1 -0
- package/dist/setup/open.js +16 -0
- package/dist/setup/open.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +66 -4
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { registrySessionExecutions } from './session-services.js';
|
|
2
|
+
import { SessionsServiceError } from '@felan-ai/agent-core';
|
|
3
|
+
import { truncateTail } from '@earendil-works/pi-coding-agent';
|
|
4
|
+
import { LIMITS, parseOperationResult } from '@felan-ai/session-protocol';
|
|
5
|
+
import { boundedText, digest } from './projection.js';
|
|
6
|
+
import { projectTreeEntries } from './tree.js';
|
|
7
|
+
import { prepareExport, prepareImport, publishExport, stageImport } from './session-files.js';
|
|
8
|
+
import { sha256 } from './persistence.js';
|
|
9
|
+
import { canonicalPath } from './profile-storage.js';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
export async function forkSession(registry, requestId, session, params) {
|
|
12
|
+
const binding = await registry.forkAdmitted(requestId, session, params.from);
|
|
13
|
+
return { sessionId: binding.sessionId };
|
|
14
|
+
}
|
|
15
|
+
export class SessionCommands {
|
|
16
|
+
options;
|
|
17
|
+
remote;
|
|
18
|
+
setRemote(remote) { this.remote = remote; this.operations.add('session.remote'); }
|
|
19
|
+
operations = new Set(['sessions.list', 'sessions.create', 'close', 'prompt', 'steer', 'follow_up', 'cancel', 'set_model', 'set_thinking',
|
|
20
|
+
'compact', 'fork', 'command.invoke', 'session.rename', 'session.tree', 'session.branch.select', 'session.entry.label', 'session.history', 'session.clone', 'shell.execute',
|
|
21
|
+
'config.get', 'config.set', 'processes.list', 'processes.read', 'processes.write', 'processes.stop', 'executions.list', 'executions.read', 'executions.steer', 'executions.cancel',
|
|
22
|
+
'session.delete', 'session.reload', 'session.import', 'session.export', 'ui.action']);
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.options = options;
|
|
25
|
+
if (options.setup)
|
|
26
|
+
for (const op of ['host.setup.catalog', 'host.setup.start', 'host.setup.read', 'host.setup.answer', 'host.setup.cancel', 'host.setup.open_link'])
|
|
27
|
+
this.operations.add(op);
|
|
28
|
+
if (options.prepareOpenInteraction)
|
|
29
|
+
this.operations.add('host.open_interaction');
|
|
30
|
+
}
|
|
31
|
+
async read(request, client) {
|
|
32
|
+
const { registry, configuration } = this.options;
|
|
33
|
+
switch (request.op) {
|
|
34
|
+
case 'remote.status': {
|
|
35
|
+
if (!this.remote)
|
|
36
|
+
throw commandError('unavailable');
|
|
37
|
+
return accepted(request, this.remote.status());
|
|
38
|
+
}
|
|
39
|
+
case 'remote.configure': {
|
|
40
|
+
if (!this.remote)
|
|
41
|
+
throw commandError('unavailable');
|
|
42
|
+
return accepted(request, await this.remote.configure(request.params));
|
|
43
|
+
}
|
|
44
|
+
case 'host.setup.catalog': {
|
|
45
|
+
if (!this.options.setup)
|
|
46
|
+
throw commandError('unavailable');
|
|
47
|
+
return accepted(request, await this.options.setup.catalog());
|
|
48
|
+
}
|
|
49
|
+
case 'host.setup.read': {
|
|
50
|
+
if (!this.options.setup || !client)
|
|
51
|
+
throw commandError('unavailable');
|
|
52
|
+
return accepted(request, this.options.setup.read(request.params.jobId, client));
|
|
53
|
+
}
|
|
54
|
+
case 'host.setup.answer': {
|
|
55
|
+
if (!this.options.setup || !client)
|
|
56
|
+
throw commandError('unavailable');
|
|
57
|
+
await this.options.setup.answer(request.params.jobId, request.params.promptId, request.params.text, client);
|
|
58
|
+
return accepted(request, {});
|
|
59
|
+
}
|
|
60
|
+
case 'host.setup.cancel': {
|
|
61
|
+
if (!this.options.setup || !client)
|
|
62
|
+
throw commandError('unavailable');
|
|
63
|
+
this.options.setup.cancel(request.params.jobId, client);
|
|
64
|
+
return accepted(request, {});
|
|
65
|
+
}
|
|
66
|
+
case 'sessions.list': {
|
|
67
|
+
const previews = new Map();
|
|
68
|
+
const legacy = new Map();
|
|
69
|
+
const cwd = request.params.cwd === undefined ? undefined : await canonicalPath(request.params.cwd).catch(() => request.params.cwd);
|
|
70
|
+
const entries = [...registry.listed().map(row => {
|
|
71
|
+
const entry = { session: row.id, root: row.root, ...(row.parent ? { parent: row.parent } : {}), cwd: row.cwd,
|
|
72
|
+
revision: row.revision, state: row.state, residency: row.residency, title: row.title, ...(row.model ? { model: row.model } : {}), lastActivity: row.updatedAt, run: row.run, policy: row.policy };
|
|
73
|
+
previews.set(row.id, () => registry.preview(row));
|
|
74
|
+
return entry;
|
|
75
|
+
}), ...(request.params.root ? [] : await registry.legacySessions(cwd)).map(row => {
|
|
76
|
+
legacy.set(row.id, row);
|
|
77
|
+
return { session: row.id, root: row.id, cwd: row.cwd, revision: 0, state: 'open', residency: 'evicted', title: '',
|
|
78
|
+
lastActivity: row.modifiedAt, run: { state: 'idle' }, policy: { effective: 'host-default', generation: 0 } };
|
|
79
|
+
})]
|
|
80
|
+
.filter(row => (!cwd || row.cwd === cwd) && (!request.params.root || row.root === request.params.root));
|
|
81
|
+
const result = recentPage(entries, request.params, JSON.stringify(['sessions', request.params.cwd ?? null, request.params.root ?? null]));
|
|
82
|
+
// Only the returned page pays for transcript reads.
|
|
83
|
+
const page = await Promise.all(result.entries.map(async (entry) => {
|
|
84
|
+
const row = legacy.get(entry.session);
|
|
85
|
+
if (row) {
|
|
86
|
+
const summary = await registry.legacySummary(row);
|
|
87
|
+
return { ...entry, title: summary.title, ...(summary.preview ? { preview: summary.preview } : {}) };
|
|
88
|
+
}
|
|
89
|
+
const preview = await previews.get(entry.session)();
|
|
90
|
+
return preview ? { ...entry, preview } : entry;
|
|
91
|
+
}));
|
|
92
|
+
return accepted(request, { ...result, entries: page });
|
|
93
|
+
}
|
|
94
|
+
case 'config.get': {
|
|
95
|
+
if (request.params.scope !== 'session')
|
|
96
|
+
return accepted(request, await configuration.get(request.params.scope, request.session ? registry.binding(request.session)?.runtime.session.model : undefined));
|
|
97
|
+
if (!request.session)
|
|
98
|
+
throw commandError('invalid_request');
|
|
99
|
+
return accepted(request, { ...await configuration.sessionValues(registry.options.store.session(request.session).profile?.extensionConfigOverrides ?? []), writable: true });
|
|
100
|
+
}
|
|
101
|
+
case 'executions.list': return accepted(request, await executionRequest(() => registrySessionExecutions(registry, request.session).executions(request.params)));
|
|
102
|
+
case 'executions.read': return accepted(request, await executionRequest(() => registrySessionExecutions(registry, request.session).execution(request.params.executionId)));
|
|
103
|
+
case 'session.history': {
|
|
104
|
+
const projection = registry.projection(request.session);
|
|
105
|
+
projection.refresh();
|
|
106
|
+
const revision = registry.options.store.sessionMetadata(request.session).revision;
|
|
107
|
+
const branch = registry.binding(request.session).runtime.session.sessionManager.getLeafId() ?? request.session;
|
|
108
|
+
if (revision !== request.params.revision || branch !== request.params.branch)
|
|
109
|
+
throw commandError('resync_required');
|
|
110
|
+
return accepted(request, projection.history(request.params.cursor, request.params.revision, request.params.limit));
|
|
111
|
+
}
|
|
112
|
+
case 'session.tree': {
|
|
113
|
+
const binding = registry.binding(request.session);
|
|
114
|
+
registry.projection(request.session).refresh();
|
|
115
|
+
const manager = binding.runtime.session.sessionManager;
|
|
116
|
+
const revision = registry.options.store.sessionMetadata(request.session).revision;
|
|
117
|
+
const branch = manager.getLeafId() ?? request.session;
|
|
118
|
+
const entries = projectTreeEntries(manager);
|
|
119
|
+
return accepted(request, { revision, branch,
|
|
120
|
+
...page(entries, request.params, `tree:${request.session}:${revision}:${branch}`) });
|
|
121
|
+
}
|
|
122
|
+
case 'processes.list': {
|
|
123
|
+
this.assertRoot(request.session, request.params.root);
|
|
124
|
+
return accepted(request, keysetPage(registry.processes.list(request.params.root), request.params, JSON.stringify(['processes', request.params.root]), row => row.id));
|
|
125
|
+
}
|
|
126
|
+
case 'processes.read': {
|
|
127
|
+
this.assertRoot(request.session, request.params.root);
|
|
128
|
+
const value = await registry.processes.read(request.session, request.params.resourceId, request.params.offset, request.params.limit);
|
|
129
|
+
return accepted(request, { resourceId: request.params.resourceId, ...value, data: Buffer.from(value.data).toString('base64') });
|
|
130
|
+
}
|
|
131
|
+
default: throw commandError('unavailable');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async prepare(command, context) {
|
|
135
|
+
const { request } = command;
|
|
136
|
+
const { registry, configuration } = this.options;
|
|
137
|
+
const localClient = 'attachment' in context.origin ? context.origin.attachment : undefined;
|
|
138
|
+
switch (request.op) {
|
|
139
|
+
case 'session.remote': {
|
|
140
|
+
if (!this.remote)
|
|
141
|
+
throw commandError('unavailable');
|
|
142
|
+
command.assertDispatch();
|
|
143
|
+
return { response: command.dispatch(() => { this.remote.setShared(request.session, request.params.shared); return accepted(request, {}); }) };
|
|
144
|
+
}
|
|
145
|
+
case 'ui.action': {
|
|
146
|
+
const execute = registry.prepareUiAction(request.session, request.params);
|
|
147
|
+
command.assertDispatch();
|
|
148
|
+
return { response: registry.mutateAdmitted(request.id, request.session, async ({ assertCurrent }) => {
|
|
149
|
+
assertCurrent();
|
|
150
|
+
await execute({ requestId: request.id, signal: context.signal ?? new AbortController().signal });
|
|
151
|
+
assertCurrent();
|
|
152
|
+
}).then(() => accepted(request, {})) };
|
|
153
|
+
}
|
|
154
|
+
case 'host.setup.start': {
|
|
155
|
+
if (!localClient)
|
|
156
|
+
throw commandError('forbidden');
|
|
157
|
+
if (!this.options.setup)
|
|
158
|
+
throw commandError('unavailable');
|
|
159
|
+
this.options.setup.validateStart(request.params);
|
|
160
|
+
return { response: command.dispatch(() => accepted(request, this.options.setup.start(request.params, localClient))) };
|
|
161
|
+
}
|
|
162
|
+
case 'host.setup.open_link': {
|
|
163
|
+
if (!localClient)
|
|
164
|
+
throw commandError('forbidden');
|
|
165
|
+
if (!this.options.setup)
|
|
166
|
+
throw commandError('unavailable');
|
|
167
|
+
const open = this.options.setup.prepareOpenLink(request.params.jobId, request.params.index, localClient);
|
|
168
|
+
return { response: command.dispatch(open).then(() => accepted(request, {})) };
|
|
169
|
+
}
|
|
170
|
+
case 'host.open_interaction': {
|
|
171
|
+
if (!localClient)
|
|
172
|
+
throw commandError('forbidden');
|
|
173
|
+
if (!this.options.prepareOpenInteraction || !context.signal)
|
|
174
|
+
throw commandError('unavailable');
|
|
175
|
+
const open = this.options.prepareOpenInteraction(request.params.interactionId, localClient, context.signal);
|
|
176
|
+
return { response: command.dispatch(open).then(() => accepted(request, {})) };
|
|
177
|
+
}
|
|
178
|
+
case 'sessions.create': {
|
|
179
|
+
if (request.params.extensions !== undefined && !localClient)
|
|
180
|
+
throw commandError('forbidden');
|
|
181
|
+
const overrides = request.params.config && Object.keys(request.params.config).length ? configuration.sessionOverrides(request.params.config) : [];
|
|
182
|
+
const model = request.params.model && await this.model(request.params.model);
|
|
183
|
+
if (!request.params.model && !(await this.options.models.getAvailable()).length)
|
|
184
|
+
throw commandError('authentication_required', 'No model is available. Log in with /login or configure a provider API key.');
|
|
185
|
+
const resources = await registry.options.lifecycle.resolveResources({ cwd: request.params.cwd, ...(request.params.sessionDir ? { sessionDir: request.params.sessionDir } : {}) });
|
|
186
|
+
if (context.rootPreparation && context.rootPreparation.cwd !== resources.cwd)
|
|
187
|
+
throw commandError('target_mismatch');
|
|
188
|
+
registry.assertClientSessionDirectory(resources);
|
|
189
|
+
const extensionPaths = context.rootPreparation?.extensionPaths ?? request.params.extensions;
|
|
190
|
+
const creation = { cwd: resources.cwd, target: { kind: 'new' }, clientRoot: true,
|
|
191
|
+
sessionDir: resources.sessionDir, ...(request.params.requirePolicy === 'acp-strict' ? { policy: { mode: 'acp-strict', generation: 0 } } : {}), ...(model ? { model } : {}),
|
|
192
|
+
...(request.params.thinking ? { thinkingLevel: request.params.thinking } : {}), ...(extensionPaths ? { extensionPaths } : {}),
|
|
193
|
+
...(overrides.length ? { extensionConfigOverrides: overrides } : {}) };
|
|
194
|
+
await registry.validateConfiguration(creation, resources);
|
|
195
|
+
command.assertDispatch();
|
|
196
|
+
const work = command.dispatch(() => registry.create(creation));
|
|
197
|
+
return { response: work.then(binding => accepted(request, { sessionId: binding.sessionId })) };
|
|
198
|
+
}
|
|
199
|
+
case 'config.set': {
|
|
200
|
+
if (request.params.scope === 'session') {
|
|
201
|
+
if (!request.session)
|
|
202
|
+
throw commandError('invalid_request');
|
|
203
|
+
const overrides = configuration.sessionOverrides(request.params.values, registry.options.store.session(request.session).profile?.extensionConfigOverrides ?? []);
|
|
204
|
+
const stored = registry.options.store.session(request.session);
|
|
205
|
+
const resources = registry.binding(request.session)?.resources;
|
|
206
|
+
await registry.validateConfiguration({ cwd: resources?.cwd ?? stored.cwd, sessionDir: dirname(stored.piPath), target: { kind: 'resume', sessionFile: stored.piPath },
|
|
207
|
+
policy: registry.getPolicy(request.session), extensionConfigOverrides: overrides }, resources);
|
|
208
|
+
return { response: registry.configureAdmitted(request.id, request.session, { values: request.params.values, overrides }).then(() => accepted(request, {})) };
|
|
209
|
+
}
|
|
210
|
+
if (!localClient)
|
|
211
|
+
throw commandError('forbidden');
|
|
212
|
+
if (!(await configuration.get(request.params.scope)).writable)
|
|
213
|
+
throw Object.assign(new Error('This configuration scope is read-only.'), { code: 'unavailable' });
|
|
214
|
+
configuration.prepare(request.params.scope, request.params.values);
|
|
215
|
+
return { response: command.dispatch(() => configuration.set(request.params.scope, request.params.values)).then(() => accepted(request, {})) };
|
|
216
|
+
}
|
|
217
|
+
case 'close': return { response: registry.closeAdmitted(request.id, request.session).then(() => accepted(request, {})) };
|
|
218
|
+
case 'session.delete': return { response: registry.deleteAdmitted(request.id, request.session).then(() => accepted(request, {})) };
|
|
219
|
+
case 'session.reload': return { response: registry.reloadAdmitted(request.id, request.session).then(() => accepted(request, {})) };
|
|
220
|
+
case 'session.import': {
|
|
221
|
+
const resources = await registry.options.lifecycle.resolveResources({ cwd: request.params.cwd });
|
|
222
|
+
if (context.rootPreparation && context.rootPreparation.cwd !== resources.cwd)
|
|
223
|
+
throw commandError('target_mismatch');
|
|
224
|
+
for (const root of resources.writableRoots)
|
|
225
|
+
registry.options.owner.assertOwnsPath(root);
|
|
226
|
+
registry.assertClientSessionDirectory(resources);
|
|
227
|
+
const prepared = preflight(() => prepareImport(registry.options.owner, request.params.path));
|
|
228
|
+
command.assertDispatch();
|
|
229
|
+
return { response: command.dispatch(async () => {
|
|
230
|
+
const staged = stageImport(registry.options.owner, resources.sessionDir, request.params.path, prepared);
|
|
231
|
+
try {
|
|
232
|
+
const binding = await registry.create({ cwd: resources.cwd, sessionDir: resources.sessionDir, target: { kind: 'import', sessionFile: staged.path },
|
|
233
|
+
clientRoot: true,
|
|
234
|
+
...(context.rootPreparation ? { extensionPaths: context.rootPreparation.extensionPaths } : {}),
|
|
235
|
+
...(request.params.requirePolicy === 'acp-strict' ? { policy: { mode: 'acp-strict', generation: 0 } } : {}) });
|
|
236
|
+
return accepted(request, { sessionId: binding.sessionId });
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
staged.dispose();
|
|
240
|
+
}
|
|
241
|
+
}) };
|
|
242
|
+
}
|
|
243
|
+
case 'session.export': {
|
|
244
|
+
const managed = [registry.options.store.directory, registry.options.owner.target.runtimeDirectory, ...registry.options.store.snapshot().sessions.map(row => row.piPath)];
|
|
245
|
+
const prepared = preflight(() => prepareExport(registry.options.owner, request.params.destination, managed));
|
|
246
|
+
return { response: registry.mutateAdmitted(request.id, request.session, async ({ session, assertCurrent }) => {
|
|
247
|
+
const destination = await publishExport(registry.options.owner, async (path) => { if (request.params.format === 'html')
|
|
248
|
+
await session.exportToHtml(path);
|
|
249
|
+
else
|
|
250
|
+
await session.exportToJsonl(path); assertCurrent(); }, prepared);
|
|
251
|
+
assertCurrent();
|
|
252
|
+
return accepted(request, { destination });
|
|
253
|
+
}) };
|
|
254
|
+
}
|
|
255
|
+
case 'cancel': return { response: registry.cancelAdmitted(request.id, request.session, request.params.runId).then(() => accepted(request, {})) };
|
|
256
|
+
case 'executions.steer':
|
|
257
|
+
case 'executions.cancel': {
|
|
258
|
+
const executions = registrySessionExecutions(registry, request.session);
|
|
259
|
+
await executionRequest(() => executions.execution(request.params.executionId));
|
|
260
|
+
return { response: registry.mutateAdmitted(request.id, request.session, async ({ assertCurrent }) => {
|
|
261
|
+
if (request.op === 'executions.steer')
|
|
262
|
+
await executionRequest(() => executions.steer(request.params.executionId, request.params.message));
|
|
263
|
+
else
|
|
264
|
+
await executionRequest(() => executions.cancel(request.params.executionId, request.params.reason));
|
|
265
|
+
assertCurrent();
|
|
266
|
+
return accepted(request, await executions.execution(request.params.executionId));
|
|
267
|
+
}) };
|
|
268
|
+
}
|
|
269
|
+
case 'fork': return { response: forkSession(registry, request.id, request.session, request.params).then(value => accepted(request, value)) };
|
|
270
|
+
case 'session.clone': return { response: registry.cloneAdmitted(request.id, request.session).then(binding => accepted(request, { sessionId: binding.sessionId })) };
|
|
271
|
+
case 'shell.execute': {
|
|
272
|
+
const settings = registry.binding(request.session).runtime.session.settingsManager;
|
|
273
|
+
const prepared = registry.processes.prepareForeground(request.session, { shell: settings.getShellPath(), prefix: settings.getShellCommandPrefix() });
|
|
274
|
+
let resolve;
|
|
275
|
+
let reject;
|
|
276
|
+
const response = new Promise((done, fail) => { resolve = done; reject = fail; });
|
|
277
|
+
// Like Pi, a shell command during an active run joins that run; its result is queued for the next turn.
|
|
278
|
+
const active = registry.options.store.session(request.session).run;
|
|
279
|
+
const joinsRun = ['running', 'waiting', 'cancelling'].includes(active.state) && active.id !== undefined;
|
|
280
|
+
const execute = async (session) => {
|
|
281
|
+
const { resourceId } = await registry.processes.startForeground(request.id, request.session, request.params, prepared);
|
|
282
|
+
const projection = registry.projection(request.session);
|
|
283
|
+
let revision = 0;
|
|
284
|
+
let lastPublished = 0;
|
|
285
|
+
let pendingOutput;
|
|
286
|
+
let timer;
|
|
287
|
+
const flushOutput = () => {
|
|
288
|
+
timer = undefined;
|
|
289
|
+
if (pendingOutput === undefined)
|
|
290
|
+
return;
|
|
291
|
+
let command = boundedText(request.params.command, 16_384);
|
|
292
|
+
let retained = truncateTail(pendingOutput, { maxBytes: 16_384, maxLines: 2000 }).content;
|
|
293
|
+
pendingOutput = undefined;
|
|
294
|
+
lastPublished = Date.now();
|
|
295
|
+
const frame = () => {
|
|
296
|
+
const content = `$ ${command}\n${retained}`;
|
|
297
|
+
const fence = '`'.repeat(Math.max(3, ...[...content.matchAll(/`+/gu)].map(match => match[0].length + 1)));
|
|
298
|
+
return { id: resourceId, kind: 'document', revision: revision + 1, title: 'Shell execution',
|
|
299
|
+
hint: { id: 'shell.execution', version: '1.0.0' }, fallback: 'Shell command is running.', actions: [],
|
|
300
|
+
markdown: `${fence}\n${content}\n${fence}`, data: { commandLength: command.length, excludeFromContext: !request.params.includeInContext } };
|
|
301
|
+
};
|
|
302
|
+
let value = frame();
|
|
303
|
+
while (Buffer.byteLength(JSON.stringify(value)) > LIMITS.structuredBytes) {
|
|
304
|
+
if (retained.length) {
|
|
305
|
+
const points = [...retained];
|
|
306
|
+
retained = points.slice(Math.ceil(points.length / 2)).join('');
|
|
307
|
+
}
|
|
308
|
+
else
|
|
309
|
+
command = boundedText(command, Math.floor(Buffer.byteLength(command) / 2));
|
|
310
|
+
value = frame();
|
|
311
|
+
}
|
|
312
|
+
revision++;
|
|
313
|
+
try {
|
|
314
|
+
projection.setPresentation(value);
|
|
315
|
+
}
|
|
316
|
+
catch { }
|
|
317
|
+
};
|
|
318
|
+
const publishOutput = (output) => {
|
|
319
|
+
pendingOutput = output;
|
|
320
|
+
const delay = Math.max(0, 100 - (Date.now() - lastPublished));
|
|
321
|
+
if (!delay) {
|
|
322
|
+
if (timer)
|
|
323
|
+
clearTimeout(timer);
|
|
324
|
+
flushOutput();
|
|
325
|
+
}
|
|
326
|
+
else
|
|
327
|
+
timer ??= setTimeout(flushOutput, delay);
|
|
328
|
+
};
|
|
329
|
+
publishOutput('');
|
|
330
|
+
resolve(accepted(request, { resourceId, runId: registry.options.store.session(request.session).run.id }));
|
|
331
|
+
try {
|
|
332
|
+
const result = await registry.processes.waitForeground(request.session, resourceId, publishOutput);
|
|
333
|
+
session.recordBashResult(request.params.command, result, { excludeFromContext: !request.params.includeInContext });
|
|
334
|
+
projection.refresh();
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
if (timer)
|
|
338
|
+
clearTimeout(timer);
|
|
339
|
+
pendingOutput = undefined;
|
|
340
|
+
try {
|
|
341
|
+
projection.setPresentation({ id: resourceId, remove: true });
|
|
342
|
+
}
|
|
343
|
+
catch { }
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
const work = joinsRun ? registry.mutateAdmitted(request.id, request.session, ({ session }) => execute(session))
|
|
347
|
+
: registry.runAdmitted(request.id, request.session, context.origin, execute);
|
|
348
|
+
void work.catch(error => {
|
|
349
|
+
reject(error);
|
|
350
|
+
if (registry.binding(request.session)) {
|
|
351
|
+
try {
|
|
352
|
+
registry.projection(request.session).setPresentation({ id: 'shell-error', kind: 'notification', level: 'error', text: 'Shell execution failed. Inspect the process before retrying.' });
|
|
353
|
+
}
|
|
354
|
+
catch { }
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return { response };
|
|
358
|
+
}
|
|
359
|
+
case 'prompt': {
|
|
360
|
+
const session = registry.binding(request.session).runtime.session;
|
|
361
|
+
const model = session.model;
|
|
362
|
+
if (!model || !(await session.modelRuntime.getAvailable()).some(candidate => candidate.provider === model.provider && candidate.id === model.id))
|
|
363
|
+
throw commandError('authentication_required', model ? `Model ${model.provider}/${model.id} is not available. Log in with /login or choose another model.` : 'No model is selected. Choose one with /model.');
|
|
364
|
+
return this.start(request, context.origin, session => {
|
|
365
|
+
const input = promptInput(request.params);
|
|
366
|
+
return session.prompt(input.text, { images: input.images, source: 'rpc', expandPromptTemplates: false });
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
case 'compact': {
|
|
370
|
+
if (request.params.method && request.params.method !== 'summary')
|
|
371
|
+
throw commandError('unavailable');
|
|
372
|
+
return this.start(request, context.origin, async (session) => { await session.compact(request.params.instructions); });
|
|
373
|
+
}
|
|
374
|
+
case 'command.invoke': {
|
|
375
|
+
const target = registry.projection(request.session).catalog().commands.find(item => item.name === request.params.name);
|
|
376
|
+
if (!target)
|
|
377
|
+
throw commandError('unavailable');
|
|
378
|
+
const invoke = (session) => session.prompt(`/${request.params.name}${request.params.arguments ? ` ${request.params.arguments}` : ''}`, { source: 'rpc' });
|
|
379
|
+
// Like Pi, extension commands execute immediately during an active run; skill commands still wait for it.
|
|
380
|
+
const active = registry.options.store.session(request.session).run;
|
|
381
|
+
if (target.kind === 'extension' && ['running', 'waiting', 'cancelling'].includes(active.state) && active.id !== undefined) {
|
|
382
|
+
let started = false;
|
|
383
|
+
const work = registry.mutateAdmitted(request.id, request.session, ({ session }) => { started = true; return invoke(session); });
|
|
384
|
+
if (!started)
|
|
385
|
+
return { response: work.then(() => { throw commandError('unavailable'); }) };
|
|
386
|
+
void work.catch(() => {
|
|
387
|
+
if (!registry.binding(request.session))
|
|
388
|
+
return;
|
|
389
|
+
try {
|
|
390
|
+
registry.projection(request.session).setPresentation({ id: 'run-error', kind: 'notification', level: 'error', text: 'Command failed. Inspect the session before retrying.' });
|
|
391
|
+
}
|
|
392
|
+
catch { }
|
|
393
|
+
});
|
|
394
|
+
return { response: accepted(request, { runId: active.id, status: 'started' }) };
|
|
395
|
+
}
|
|
396
|
+
return this.start(request, context.origin, invoke);
|
|
397
|
+
}
|
|
398
|
+
case 'steer':
|
|
399
|
+
case 'follow_up': {
|
|
400
|
+
const projection = registry.projection(request.session);
|
|
401
|
+
if (!projection.canObserveQueue)
|
|
402
|
+
throw commandError('unavailable');
|
|
403
|
+
const work = registry.mutateAdmitted(request.id, request.session, async ({ session, assertCurrent }) => {
|
|
404
|
+
const input = promptInput(request.params);
|
|
405
|
+
const result = await projection.withQueueOrigin(context.origin, () => request.op === 'steer'
|
|
406
|
+
? session.steer(input.text, input.images, { source: 'rpc' }) : session.followUp(input.text, input.images, { source: 'rpc' }));
|
|
407
|
+
assertCurrent();
|
|
408
|
+
if (result.value === 'handled')
|
|
409
|
+
return { status: 'handled' };
|
|
410
|
+
if (result.value !== 'queued' || result.ids.length !== 1)
|
|
411
|
+
throw commandError('indeterminate');
|
|
412
|
+
return { itemId: result.ids[0] };
|
|
413
|
+
});
|
|
414
|
+
return { response: work.then(result => accepted(request, result)) };
|
|
415
|
+
}
|
|
416
|
+
case 'set_model': {
|
|
417
|
+
const model = await this.model(request.params, registry.binding(request.session).runtime.session);
|
|
418
|
+
if (!await registry.binding(request.session).runtime.session.modelRuntime.checkAuth(model.provider))
|
|
419
|
+
throw commandError('unavailable');
|
|
420
|
+
command.assertDispatch();
|
|
421
|
+
return this.mutate(request, async ({ session, assertCurrent }) => { await session.setModel(model, { persist: false }); assertCurrent(); });
|
|
422
|
+
}
|
|
423
|
+
case 'set_thinking': return this.mutate(request, async ({ session }) => { session.setThinkingLevel(request.params.thinking, { persist: false }); });
|
|
424
|
+
case 'session.rename': return this.mutate(request, async ({ session }) => { session.setSessionName(request.params.name); });
|
|
425
|
+
case 'session.entry.label':
|
|
426
|
+
case 'session.branch.select': {
|
|
427
|
+
const session = registry.binding(request.session).runtime.session;
|
|
428
|
+
if (!session.sessionManager.getEntry(request.params.entryId))
|
|
429
|
+
throw commandError('not_found');
|
|
430
|
+
return this.mutate(request, async ({ session, assertCurrent }) => {
|
|
431
|
+
if (request.op === 'session.branch.select') {
|
|
432
|
+
const result = await session.navigateTree(request.params.entryId, request.params.summarize === undefined ? {} : { summarize: request.params.summarize });
|
|
433
|
+
assertCurrent();
|
|
434
|
+
if (result.cancelled)
|
|
435
|
+
throw commandError('unavailable');
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
session.sessionManager.appendLabelChange(request.params.entryId, request.params.label);
|
|
439
|
+
(context.invalidateAfterResponse ?? this.options.invalidated)({ [request.session]: registry.options.store.session(request.session).revision });
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
case 'processes.write':
|
|
444
|
+
case 'processes.stop': {
|
|
445
|
+
this.assertRoot(request.session, request.params.root);
|
|
446
|
+
if (request.op === 'processes.write')
|
|
447
|
+
registry.processes.assertWritable(request.session, request.params.resourceId);
|
|
448
|
+
else
|
|
449
|
+
await registry.processes.prepareStop(request.session, request.params.resourceId);
|
|
450
|
+
command.assertDispatch();
|
|
451
|
+
const work = registry.mutateAdmitted(request.id, request.session, async ({ assertCurrent }) => {
|
|
452
|
+
if (request.op === 'processes.write') {
|
|
453
|
+
const writtenBytes = await registry.processes.write(request.session, request.params.resourceId, Buffer.from(request.params.data, 'base64'));
|
|
454
|
+
assertCurrent();
|
|
455
|
+
return { writtenBytes };
|
|
456
|
+
}
|
|
457
|
+
await registry.processes.stopForSession(request.session, request.params.resourceId, request.params.signal);
|
|
458
|
+
assertCurrent();
|
|
459
|
+
return {};
|
|
460
|
+
});
|
|
461
|
+
return { response: work.then(value => accepted(request, value)) };
|
|
462
|
+
}
|
|
463
|
+
default: throw commandError('unavailable');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
mutate(request, effect) {
|
|
467
|
+
return { response: this.options.registry.mutateAdmitted(request.id, request.session, effect).then(() => accepted(request, {})) };
|
|
468
|
+
}
|
|
469
|
+
start(request, origin, effect) {
|
|
470
|
+
const { registry } = this.options;
|
|
471
|
+
let started = false;
|
|
472
|
+
const work = registry.runAdmitted(request.id, request.session, origin, session => { started = true; return effect(session); });
|
|
473
|
+
if (!started)
|
|
474
|
+
return { response: work.then(() => { throw commandError('unavailable'); }) };
|
|
475
|
+
const runId = registry.options.store.session(request.session).run.id;
|
|
476
|
+
void work.catch(() => {
|
|
477
|
+
if (!registry.binding(request.session))
|
|
478
|
+
return;
|
|
479
|
+
try {
|
|
480
|
+
registry.projection(request.session).setPresentation({ id: 'run-error', kind: 'notification', level: 'error', text: 'Run failed. Inspect the session before retrying.' });
|
|
481
|
+
}
|
|
482
|
+
catch { }
|
|
483
|
+
});
|
|
484
|
+
return { response: accepted(request, request.op === 'command.invoke' ? { runId, status: 'started' } : { runId }) };
|
|
485
|
+
}
|
|
486
|
+
async model(reference, session) {
|
|
487
|
+
const available = await (session?.modelRuntime ?? this.options.models).getAvailable();
|
|
488
|
+
if (!available.length)
|
|
489
|
+
throw commandError('authentication_required', 'No model is available. Log in with /login or configure a provider API key.');
|
|
490
|
+
const model = available.find(model => model.provider === reference.provider && model.id === reference.id);
|
|
491
|
+
if (!model || session && session.scopedModels.length && !session.scopedModels.some(item => item.model.provider === model.provider && item.model.id === model.id))
|
|
492
|
+
throw commandError('not_found', `Model ${reference.provider}/${reference.id} is not available.`);
|
|
493
|
+
return model;
|
|
494
|
+
}
|
|
495
|
+
assertRoot(session, root) {
|
|
496
|
+
if (this.options.registry.options.store.session(session).root !== root)
|
|
497
|
+
throw commandError('forbidden');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
export function commandError(code, message = 'Operation unavailable or rejected.') { return Object.assign(new Error(message), { code }); }
|
|
501
|
+
export function accepted(request, value) {
|
|
502
|
+
return { v: 1, re: request.id, outcome: 'accepted', result: parseOperationResult(request.op, value) };
|
|
503
|
+
}
|
|
504
|
+
function preflight(prepare) {
|
|
505
|
+
try {
|
|
506
|
+
return prepare();
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
const code = error?.code;
|
|
510
|
+
if (code === undefined || ['ENOENT', 'ENOTDIR', 'EISDIR', 'ELOOP', 'EACCES'].includes(String(code)))
|
|
511
|
+
throw commandError('invalid_request', error instanceof Error ? error.message : undefined);
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/** Newest activity first, then session ID. The cursor holds the last row's position, so rows that vanish or move do not break paging. */
|
|
516
|
+
function recentPage(values, params, scope) {
|
|
517
|
+
const entries = [...values].sort((a, b) => b.lastActivity - a.lastActivity || (a.session < b.session ? -1 : a.session > b.session ? 1 : 0));
|
|
518
|
+
let start = 0;
|
|
519
|
+
if (params.cursor) {
|
|
520
|
+
let token;
|
|
521
|
+
try {
|
|
522
|
+
token = JSON.parse(Buffer.from(params.cursor, 'base64url').toString('utf8'));
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
throw commandError('resync_required');
|
|
526
|
+
}
|
|
527
|
+
if (!Array.isArray(token) || token.length !== 3 || token[0] !== scope || !Number.isSafeInteger(token[1]) || typeof token[2] !== 'string')
|
|
528
|
+
throw commandError('resync_required');
|
|
529
|
+
const [, activity, session] = token;
|
|
530
|
+
const at = entries.findIndex(entry => entry.lastActivity < activity || (entry.lastActivity === activity && entry.session > session));
|
|
531
|
+
start = at < 0 ? entries.length : at;
|
|
532
|
+
}
|
|
533
|
+
const result = [];
|
|
534
|
+
let size = 0;
|
|
535
|
+
let end = start;
|
|
536
|
+
while (end < entries.length && result.length < (params.limit ?? LIMITS.pageEntries)) {
|
|
537
|
+
const entry = entries[end];
|
|
538
|
+
const bytes = Buffer.byteLength(JSON.stringify(entry));
|
|
539
|
+
if (size + bytes > LIMITS.pageBytes / 2) {
|
|
540
|
+
if (!result.length)
|
|
541
|
+
throw commandError('unavailable');
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
result.push(entry);
|
|
545
|
+
size += bytes;
|
|
546
|
+
end++;
|
|
547
|
+
}
|
|
548
|
+
const last = result[result.length - 1];
|
|
549
|
+
return { entries: result, ...(end < entries.length && last ? { cursor: Buffer.from(JSON.stringify([scope, last.lastActivity, last.session])).toString('base64url') } : {}) };
|
|
550
|
+
}
|
|
551
|
+
function keysetPage(values, params, scope, key) {
|
|
552
|
+
const entries = [...values].sort((a, b) => key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0);
|
|
553
|
+
let start = 0;
|
|
554
|
+
if (params.cursor) {
|
|
555
|
+
let token;
|
|
556
|
+
try {
|
|
557
|
+
token = JSON.parse(Buffer.from(params.cursor, 'base64url').toString('utf8'));
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
throw commandError('resync_required');
|
|
561
|
+
}
|
|
562
|
+
if (!Array.isArray(token) || token.length !== 2 || token[0] !== scope || typeof token[1] !== 'string')
|
|
563
|
+
throw commandError('resync_required');
|
|
564
|
+
const at = entries.findIndex(entry => key(entry) === token[1]);
|
|
565
|
+
if (at < 0)
|
|
566
|
+
throw commandError('resync_required');
|
|
567
|
+
start = at + 1;
|
|
568
|
+
}
|
|
569
|
+
const result = [];
|
|
570
|
+
let size = 0;
|
|
571
|
+
let end = start;
|
|
572
|
+
while (end < entries.length && result.length < (params.limit ?? LIMITS.pageEntries)) {
|
|
573
|
+
const entry = entries[end];
|
|
574
|
+
const bytes = Buffer.byteLength(JSON.stringify(entry));
|
|
575
|
+
if (size + bytes > LIMITS.pageBytes / 2) {
|
|
576
|
+
if (!result.length)
|
|
577
|
+
throw commandError('unavailable');
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
result.push(entry);
|
|
581
|
+
size += bytes;
|
|
582
|
+
end++;
|
|
583
|
+
}
|
|
584
|
+
return { entries: result, ...(end < entries.length && result.length ? { cursor: Buffer.from(JSON.stringify([scope, key(result[result.length - 1])])).toString('base64url') } : {}) };
|
|
585
|
+
}
|
|
586
|
+
function page(entries, params, identity) {
|
|
587
|
+
let start = 0;
|
|
588
|
+
if (params.cursor) {
|
|
589
|
+
let cursor;
|
|
590
|
+
try {
|
|
591
|
+
cursor = JSON.parse(Buffer.from(params.cursor, 'base64url').toString('utf8'));
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
throw commandError('resync_required');
|
|
595
|
+
}
|
|
596
|
+
if (!Array.isArray(cursor) || cursor.length !== 2 || cursor[0] !== identity || !Number.isSafeInteger(cursor[1]) || cursor[1] < 0 || cursor[1] > entries.length)
|
|
597
|
+
throw commandError('resync_required');
|
|
598
|
+
start = cursor[1];
|
|
599
|
+
}
|
|
600
|
+
const result = [];
|
|
601
|
+
let bytes = 0;
|
|
602
|
+
let end = start;
|
|
603
|
+
while (end < entries.length && result.length < (params.limit ?? LIMITS.pageEntries)) {
|
|
604
|
+
const row = entries[end];
|
|
605
|
+
const size = Buffer.byteLength(JSON.stringify(row));
|
|
606
|
+
if (bytes + size > LIMITS.pageBytes / 2) {
|
|
607
|
+
if (!result.length)
|
|
608
|
+
throw commandError('unavailable');
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
result.push(row);
|
|
612
|
+
bytes += size;
|
|
613
|
+
end++;
|
|
614
|
+
}
|
|
615
|
+
return { entries: result, ...(end < entries.length ? { cursor: Buffer.from(JSON.stringify([identity, end])).toString('base64url') } : {}) };
|
|
616
|
+
}
|
|
617
|
+
function promptInput(prompt) {
|
|
618
|
+
const text = [prompt.text];
|
|
619
|
+
const images = [];
|
|
620
|
+
for (const part of prompt.attachments ?? []) {
|
|
621
|
+
if (part.type === 'image')
|
|
622
|
+
images.push({ type: 'image', data: part.data, mimeType: part.mimeType });
|
|
623
|
+
else
|
|
624
|
+
text.push(part.type === 'text' ? part.text : `${part.name ?? 'Resource'}: ${part.uri}${part.description ? `\n${part.description}` : ''}`);
|
|
625
|
+
}
|
|
626
|
+
return { text: text.join('\n'), images };
|
|
627
|
+
}
|
|
628
|
+
async function executionRequest(operation) {
|
|
629
|
+
try {
|
|
630
|
+
return await operation();
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
if (!(error instanceof SessionsServiceError))
|
|
634
|
+
throw error;
|
|
635
|
+
throw commandError(error.code === 'stale' ? 'resync_required' : error.code === 'inaccessible' ? 'forbidden'
|
|
636
|
+
: error.code === 'unsupported' ? 'unavailable' : 'invalid_request');
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
//# sourceMappingURL=commands.js.map
|