@rynx-ai/daemon 0.1.10 → 0.1.11-beta.10
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/bundled-plugins/.rynx-plugin/marketplace.json +10 -0
- package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +25 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.d.ts +48 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +56 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.d.ts +85 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.js +333 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.d.ts +109 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.js +347 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +95 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +506 -0
- package/bundled-plugins/plugins/lark/dist/index.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/index.js +7 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.d.ts +363 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.js +1335 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.js +135 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.js +138 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.d.ts +65 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.js +106 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.d.ts +25 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.js +58 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.d.ts +16 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.js +141 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.d.ts +31 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.js +105 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.d.ts +18 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.js +95 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.d.ts +8 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.js +32 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.d.ts +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.js +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.d.ts +114 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.js +323 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.js +14 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.d.ts +55 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.js +158 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.d.ts +39 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.js +103 -0
- package/bundled-plugins/plugins/lark/dist/lark.d.ts +507 -0
- package/bundled-plugins/plugins/lark/dist/lark.js +1975 -0
- package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/runtime.js +131328 -0
- package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/settings.js +126971 -0
- package/bundled-plugins/plugins/lark/package.json +57 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-D8Amoe6a.js +201 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/index.html +13 -0
- package/dist/agent-file.js +0 -1
- package/dist/browser-artifact-management.d.ts +4 -1
- package/dist/browser-artifact-management.js +18 -10
- package/dist/browser-artifacts.d.ts +1 -0
- package/dist/browser-artifacts.js +1 -0
- package/dist/bundled-plugins.d.ts +15 -0
- package/dist/bundled-plugins.js +134 -0
- package/dist/chrome-for-testing-runtime.d.ts +27 -0
- package/dist/chrome-for-testing-runtime.js +193 -0
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +9 -0
- package/dist/control-deps.d.ts +6 -2
- package/dist/control-deps.js +88 -82
- package/dist/daemon-server.d.ts +7 -1
- package/dist/daemon-server.js +310 -87
- package/dist/db.js +181 -124
- package/dist/entry-path.d.ts +3 -0
- package/dist/entry-path.js +12 -0
- package/dist/index-daemon.js +3 -66
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.js +16 -0
- package/dist/maintenance-lease.d.ts +39 -0
- package/dist/maintenance-lease.js +250 -0
- package/dist/migrations/cleanup-legacy-sessions.d.ts +1 -1
- package/dist/migrations/cleanup-legacy-sessions.js +33 -15
- package/dist/official-plugins.d.ts +2 -0
- package/dist/official-plugins.js +2 -0
- package/dist/plugin-cli.d.ts +1 -3
- package/dist/plugin-cli.js +56 -45
- package/dist/plugin-host-leases.d.ts +4 -0
- package/dist/plugin-host-leases.js +7 -0
- package/dist/plugin-host-rpc.d.ts +73 -6
- package/dist/plugin-host-rpc.js +354 -81
- package/dist/plugin-installer.d.ts +24 -55
- package/dist/plugin-installer.js +258 -412
- package/dist/plugin-management-service.d.ts +30 -23
- package/dist/plugin-management-service.js +288 -105
- package/dist/plugin-marketplace.d.ts +35 -0
- package/dist/plugin-marketplace.js +299 -0
- package/dist/plugin-package.d.ts +22 -24
- package/dist/plugin-package.js +520 -571
- package/dist/plugin-skill-context.d.ts +4 -0
- package/dist/plugin-skill-context.js +35 -0
- package/dist/plugin-store.d.ts +36 -37
- package/dist/plugin-store.js +138 -92
- package/dist/plugin-supervisor.d.ts +50 -125
- package/dist/plugin-supervisor.js +678 -1030
- package/dist/pm2.d.ts +11 -2
- package/dist/pm2.js +130 -28
- package/dist/process.d.ts +12 -0
- package/dist/process.js +91 -0
- package/dist/runtime-project-store.d.ts +9 -0
- package/dist/runtime-project-store.js +83 -0
- package/dist/session-fallback-workspace.d.ts +22 -0
- package/dist/session-fallback-workspace.js +120 -0
- package/dist/session-fork-store.d.ts +41 -0
- package/dist/session-fork-store.js +288 -0
- package/dist/session-launch-operations.d.ts +23 -2
- package/dist/session-launch-operations.js +48 -7
- package/dist/session-meta-store.d.ts +12 -9
- package/dist/session-meta-store.js +50 -49
- package/dist/session-portal-grant-store.d.ts +29 -0
- package/dist/session-portal-grant-store.js +84 -0
- package/dist/setup-service.d.ts +21 -0
- package/dist/setup-service.js +39 -0
- package/dist/skills-catalog.d.ts +11 -2
- package/dist/skills-catalog.js +307 -7
- package/dist/system-dependencies.d.ts +26 -0
- package/dist/system-dependencies.js +149 -0
- package/package.json +36 -20
- package/dist/browser-cli-args.d.ts +0 -20
- package/dist/browser-cli-args.js +0 -100
- package/dist/channel-store.d.ts +0 -22
- package/dist/channel-store.js +0 -65
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -732
- package/dist/control-client.d.ts +0 -122
- package/dist/control-client.js +0 -855
- package/dist/control-endpoint.d.ts +0 -14
- package/dist/control-endpoint.js +0 -124
- package/dist/desktop-browser-host-client.d.ts +0 -35
- package/dist/desktop-browser-host-client.js +0 -415
- package/dist/instance-store.d.ts +0 -38
- package/dist/instance-store.js +0 -85
- package/dist/plugin-install-restart.d.ts +0 -13
- package/dist/plugin-install-restart.js +0 -24
- package/dist/plugin-runtime-control.d.ts +0 -2
- package/dist/plugin-runtime-control.js +0 -37
- package/dist/registry.d.ts +0 -10
- package/dist/registry.js +0 -52
- package/dist/setup.d.ts +0 -20
- package/dist/setup.js +0 -374
- package/dist/update.d.ts +0 -25
- package/dist/update.js +0 -133
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { newSessionId, newSessionItemId, normalizeSessionTitle, resolvedExecutionSnapshotSchema, rynxHome, sessionWorkspaceSnapshotSchema, } from "@rynx-ai/core";
|
|
5
|
+
import { MachineSessionServiceFailure, } from "@rynx-ai/server";
|
|
6
|
+
import { db } from "./db.js";
|
|
7
|
+
const FORK_COLS = [
|
|
8
|
+
"operation_id",
|
|
9
|
+
"request_hash",
|
|
10
|
+
"source_session_id",
|
|
11
|
+
"target_session_id",
|
|
12
|
+
"title",
|
|
13
|
+
"fork_position",
|
|
14
|
+
"state",
|
|
15
|
+
"error",
|
|
16
|
+
].join(", ");
|
|
17
|
+
/**
|
|
18
|
+
* SQLite/resource-file implementation of the fork publication boundary.
|
|
19
|
+
*
|
|
20
|
+
* Provider context is prepared before {@link commit}; this store then publishes
|
|
21
|
+
* Session metadata, an independent canonical transcript, referenced committed
|
|
22
|
+
* resources, and the completed operation in one SQLite transaction. Resource
|
|
23
|
+
* files use deterministic target ids, so a crash before the transaction can be
|
|
24
|
+
* retried safely.
|
|
25
|
+
*/
|
|
26
|
+
export class SqliteSessionForkStore {
|
|
27
|
+
resourceRoot;
|
|
28
|
+
constructor(resourceRoot = join(rynxHome(), "resources")) {
|
|
29
|
+
this.resourceRoot = resourceRoot;
|
|
30
|
+
}
|
|
31
|
+
claim(input) {
|
|
32
|
+
const existing = this.read(input.operationId);
|
|
33
|
+
if (existing) {
|
|
34
|
+
if (existing.request_hash !== input.requestHash ||
|
|
35
|
+
existing.source_session_id !== input.sourceSessionId) {
|
|
36
|
+
throw new MachineSessionServiceFailure("conflict", "operationId was already used for a different Session fork");
|
|
37
|
+
}
|
|
38
|
+
return toOperation(existing);
|
|
39
|
+
}
|
|
40
|
+
const now = new Date().toISOString();
|
|
41
|
+
let targetSessionId = newSessionId();
|
|
42
|
+
while (db().prepare("SELECT 1 FROM sessions WHERE id = ?").pluck().get(targetSessionId) ||
|
|
43
|
+
db().prepare("SELECT 1 FROM session_fork_operations WHERE target_session_id = ?")
|
|
44
|
+
.pluck().get(targetSessionId)) {
|
|
45
|
+
targetSessionId = newSessionId();
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
db().prepare(`INSERT INTO session_fork_operations (
|
|
49
|
+
operation_id, request_hash, source_session_id, target_session_id,
|
|
50
|
+
title, state, created_at, updated_at
|
|
51
|
+
) VALUES (?, ?, ?, ?, ?, 'reserved', ?, ?)`).run(input.operationId, input.requestHash, input.sourceSessionId, targetSessionId, normalizeSessionTitle(input.title) ?? null, now, now);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const raced = this.read(input.operationId);
|
|
55
|
+
if (raced)
|
|
56
|
+
return this.claim(input);
|
|
57
|
+
const busy = db().prepare(`SELECT operation_id
|
|
58
|
+
FROM session_fork_operations
|
|
59
|
+
WHERE source_session_id = ? AND state = 'reserved'`).get(input.sourceSessionId);
|
|
60
|
+
if (busy) {
|
|
61
|
+
throw new MachineSessionServiceFailure("conflict", "source Session already has a fork in progress");
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return toOperation(this.read(input.operationId));
|
|
66
|
+
}
|
|
67
|
+
commit(input) {
|
|
68
|
+
const operation = this.read(input.operationId);
|
|
69
|
+
if (!operation) {
|
|
70
|
+
throw new MachineSessionServiceFailure("not_found", "Session fork operation not found");
|
|
71
|
+
}
|
|
72
|
+
if (operation.state === "target_deleted") {
|
|
73
|
+
throw new MachineSessionServiceFailure("conflict", "fork target was deleted");
|
|
74
|
+
}
|
|
75
|
+
if (operation.target_session_id !== input.target.id) {
|
|
76
|
+
throw new MachineSessionServiceFailure("conflict", "fork target identity changed");
|
|
77
|
+
}
|
|
78
|
+
if (operation.state === "completed")
|
|
79
|
+
return;
|
|
80
|
+
this.publishTarget({
|
|
81
|
+
sourceSessionId: operation.source_session_id,
|
|
82
|
+
target: input.target,
|
|
83
|
+
items: input.items,
|
|
84
|
+
operationId: input.operationId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
publishNativeFork(input) {
|
|
88
|
+
const existing = db().prepare("SELECT forked_from_session_id FROM sessions WHERE id = ?").get(input.target.id);
|
|
89
|
+
if (existing) {
|
|
90
|
+
if (existing.forked_from_session_id === input.sourceSessionId)
|
|
91
|
+
return;
|
|
92
|
+
throw new MachineSessionServiceFailure("conflict", "target Session already exists");
|
|
93
|
+
}
|
|
94
|
+
this.publishTarget(input);
|
|
95
|
+
}
|
|
96
|
+
publishTarget(input) {
|
|
97
|
+
const itemIds = new Map();
|
|
98
|
+
for (const item of input.items)
|
|
99
|
+
itemIds.set(item.id, newSessionItemId(item.type));
|
|
100
|
+
const resourceIds = referencedResourceIds(input.items);
|
|
101
|
+
const resourceMap = new Map();
|
|
102
|
+
const resources = resourceIds.map((resourceId) => {
|
|
103
|
+
const source = db().prepare(`SELECT * FROM session_resources
|
|
104
|
+
WHERE session_id = ? AND id = ? AND state = 'committed'`).get(input.sourceSessionId, resourceId);
|
|
105
|
+
if (!source) {
|
|
106
|
+
throw new MachineSessionServiceFailure("failed_precondition", `fork history references unavailable resource ${resourceId}`);
|
|
107
|
+
}
|
|
108
|
+
const targetId = deterministicId("res", input.target.id, source.id);
|
|
109
|
+
resourceMap.set(source.id, targetId);
|
|
110
|
+
const target = {
|
|
111
|
+
...source,
|
|
112
|
+
id: targetId,
|
|
113
|
+
session_id: input.target.id,
|
|
114
|
+
client_upload_id: deterministicId("fork", input.target.id, source.client_upload_id),
|
|
115
|
+
upload_id: deterministicId("upl", input.target.id, source.upload_id),
|
|
116
|
+
message_item_id: source.message_item_id
|
|
117
|
+
? itemIds.get(source.message_item_id) ?? null
|
|
118
|
+
: null,
|
|
119
|
+
};
|
|
120
|
+
this.copyResourceFile(source, target);
|
|
121
|
+
return target;
|
|
122
|
+
});
|
|
123
|
+
const clonedItems = input.items.map((item, position) => {
|
|
124
|
+
const remapped = remapItemResources(item, resourceMap);
|
|
125
|
+
return {
|
|
126
|
+
...remapped,
|
|
127
|
+
id: itemIds.get(item.id),
|
|
128
|
+
sessionId: input.target.id,
|
|
129
|
+
position,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
const conn = db();
|
|
133
|
+
conn.transaction(() => {
|
|
134
|
+
if (input.operationId) {
|
|
135
|
+
const current = this.read(input.operationId);
|
|
136
|
+
if (current?.state === "completed")
|
|
137
|
+
return;
|
|
138
|
+
if (!current || current.state !== "reserved") {
|
|
139
|
+
throw new MachineSessionServiceFailure("conflict", "Session fork state changed");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const existing = conn.prepare("SELECT forked_from_session_id FROM sessions WHERE id = ?").get(input.target.id);
|
|
144
|
+
if (existing?.forked_from_session_id === input.sourceSessionId)
|
|
145
|
+
return;
|
|
146
|
+
if (existing) {
|
|
147
|
+
throw new MachineSessionServiceFailure("conflict", "target Session already exists");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
conn.prepare(`INSERT INTO sessions (
|
|
151
|
+
id, workspace, execution, title, forked_from_session_id,
|
|
152
|
+
source, created_at, updated_at
|
|
153
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.target.id, JSON.stringify(sessionWorkspaceSnapshotSchema.parse(input.target.workspace)), JSON.stringify(resolvedExecutionSnapshotSchema.parse(input.target.execution)), normalizeSessionTitle(input.target.title) ?? null, input.target.forkedFromSessionId ?? null, input.target.source, input.target.createdAt, input.target.updatedAt ?? input.target.createdAt);
|
|
154
|
+
const insertResource = conn.prepare(`INSERT INTO session_resources (
|
|
155
|
+
id, session_id, client_upload_id, upload_id, filename, media_type,
|
|
156
|
+
byte_length, uploaded_bytes, sha256, width, height, state,
|
|
157
|
+
message_item_id, created_at, committed_at
|
|
158
|
+
) VALUES (
|
|
159
|
+
@id, @session_id, @client_upload_id, @upload_id, @filename, @media_type,
|
|
160
|
+
@byte_length, @uploaded_bytes, @sha256, @width, @height, 'committed',
|
|
161
|
+
@message_item_id, @created_at, @committed_at
|
|
162
|
+
)`);
|
|
163
|
+
for (const resource of resources)
|
|
164
|
+
insertResource.run(resource);
|
|
165
|
+
const insertItem = conn.prepare(`INSERT INTO session_items (
|
|
166
|
+
id, session_id, position, response_id, type, status,
|
|
167
|
+
data, created_by, created_at
|
|
168
|
+
) VALUES (
|
|
169
|
+
@id, @session_id, @position, @response_id, @type, @status,
|
|
170
|
+
@data, @created_by, @created_at
|
|
171
|
+
)`);
|
|
172
|
+
for (const item of clonedItems) {
|
|
173
|
+
insertItem.run({
|
|
174
|
+
id: item.id,
|
|
175
|
+
session_id: item.sessionId,
|
|
176
|
+
position: item.position,
|
|
177
|
+
response_id: item.responseId,
|
|
178
|
+
type: item.type,
|
|
179
|
+
status: item.status,
|
|
180
|
+
data: JSON.stringify(item.data),
|
|
181
|
+
created_by: item.createdBy ?? null,
|
|
182
|
+
created_at: item.createdAt,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (input.operationId) {
|
|
186
|
+
const completedAt = new Date().toISOString();
|
|
187
|
+
conn.prepare(`UPDATE session_fork_operations
|
|
188
|
+
SET state = 'completed', fork_position = ?, error = NULL,
|
|
189
|
+
updated_at = ?, completed_at = ?
|
|
190
|
+
WHERE operation_id = ? AND state = 'reserved'`).run(clonedItems.at(-1)?.position ?? -1, completedAt, completedAt, input.operationId);
|
|
191
|
+
}
|
|
192
|
+
}).immediate();
|
|
193
|
+
}
|
|
194
|
+
markError(operationId, error) {
|
|
195
|
+
db().prepare(`UPDATE session_fork_operations
|
|
196
|
+
SET error = ?, updated_at = ?
|
|
197
|
+
WHERE operation_id = ? AND state = 'reserved'`).run(error.slice(0, 4_096), new Date().toISOString(), operationId);
|
|
198
|
+
}
|
|
199
|
+
listReserved() {
|
|
200
|
+
return db().prepare(`SELECT ${FORK_COLS}
|
|
201
|
+
FROM session_fork_operations
|
|
202
|
+
WHERE state = 'reserved'
|
|
203
|
+
ORDER BY created_at`).all().map(toOperation);
|
|
204
|
+
}
|
|
205
|
+
hasReservedSource(sessionId) {
|
|
206
|
+
return Boolean(db().prepare(`SELECT 1
|
|
207
|
+
FROM session_fork_operations
|
|
208
|
+
WHERE source_session_id = ? AND state = 'reserved'`).pluck().get(sessionId));
|
|
209
|
+
}
|
|
210
|
+
markTargetDeleted(sessionId) {
|
|
211
|
+
const now = new Date().toISOString();
|
|
212
|
+
db().prepare(`UPDATE session_fork_operations
|
|
213
|
+
SET state = 'target_deleted', updated_at = ?, deleted_at = ?
|
|
214
|
+
WHERE target_session_id = ? AND state IN ('reserved', 'completed')`).run(now, now, sessionId);
|
|
215
|
+
}
|
|
216
|
+
read(operationId) {
|
|
217
|
+
return db().prepare(`SELECT ${FORK_COLS}
|
|
218
|
+
FROM session_fork_operations
|
|
219
|
+
WHERE operation_id = ?`).get(operationId);
|
|
220
|
+
}
|
|
221
|
+
copyResourceFile(source, target) {
|
|
222
|
+
const sourcePath = this.resourcePath(source);
|
|
223
|
+
if (!existsSync(sourcePath)) {
|
|
224
|
+
throw new MachineSessionServiceFailure("failed_precondition", `fork resource file is missing: ${source.id}`);
|
|
225
|
+
}
|
|
226
|
+
const targetDir = this.sessionDirectory(target.session_id);
|
|
227
|
+
mkdirSync(targetDir, { recursive: true, mode: 0o700 });
|
|
228
|
+
chmodSync(targetDir, 0o700);
|
|
229
|
+
copyFileSync(sourcePath, this.resourcePath(target));
|
|
230
|
+
chmodSync(this.resourcePath(target), 0o600);
|
|
231
|
+
}
|
|
232
|
+
sessionDirectory(sessionId) {
|
|
233
|
+
const key = createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
234
|
+
return join(this.resourceRoot, key);
|
|
235
|
+
}
|
|
236
|
+
resourcePath(row) {
|
|
237
|
+
return join(this.sessionDirectory(row.session_id), `${row.id}.${extension(row.media_type)}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function toOperation(row) {
|
|
241
|
+
return {
|
|
242
|
+
operationId: row.operation_id,
|
|
243
|
+
requestHash: row.request_hash,
|
|
244
|
+
sourceSessionId: row.source_session_id,
|
|
245
|
+
targetSessionId: row.target_session_id,
|
|
246
|
+
...(row.title ? { title: row.title } : {}),
|
|
247
|
+
...(row.fork_position === null ? {} : { forkPosition: row.fork_position }),
|
|
248
|
+
state: row.state,
|
|
249
|
+
...(row.error ? { error: row.error } : {}),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function referencedResourceIds(items) {
|
|
253
|
+
const ids = new Set();
|
|
254
|
+
for (const item of items) {
|
|
255
|
+
if (item.type !== "message" || item.data.role !== "user")
|
|
256
|
+
continue;
|
|
257
|
+
for (const part of item.data.content) {
|
|
258
|
+
if (part.type === "input_image")
|
|
259
|
+
ids.add(part.resourceId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return [...ids];
|
|
263
|
+
}
|
|
264
|
+
function remapItemResources(item, resources) {
|
|
265
|
+
const cloned = structuredClone(item);
|
|
266
|
+
if (cloned.type !== "message" || cloned.data.role !== "user")
|
|
267
|
+
return cloned;
|
|
268
|
+
cloned.data.content = cloned.data.content.map((part) => part.type === "input_image"
|
|
269
|
+
? { ...part, resourceId: resources.get(part.resourceId) ?? part.resourceId }
|
|
270
|
+
: part);
|
|
271
|
+
return cloned;
|
|
272
|
+
}
|
|
273
|
+
function deterministicId(prefix, targetSessionId, sourceId) {
|
|
274
|
+
const digest = createHash("sha256")
|
|
275
|
+
.update(`${targetSessionId}\0${sourceId}`)
|
|
276
|
+
.digest("hex");
|
|
277
|
+
return `${prefix}_${digest.slice(0, 40)}`;
|
|
278
|
+
}
|
|
279
|
+
function extension(mediaType) {
|
|
280
|
+
switch (mediaType) {
|
|
281
|
+
case "image/png":
|
|
282
|
+
return "png";
|
|
283
|
+
case "image/jpeg":
|
|
284
|
+
return "jpg";
|
|
285
|
+
case "image/webp":
|
|
286
|
+
return "webp";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -9,12 +9,22 @@ export interface EnsurePluginSessionLaunchInput {
|
|
|
9
9
|
operationId: string;
|
|
10
10
|
/** SHA-256 of the host-normalized, execution-affecting launch request. */
|
|
11
11
|
requestHash: string;
|
|
12
|
+
/** SHA-256 before mutable filesystem selectors are canonicalized. */
|
|
13
|
+
rawRequestHash?: string;
|
|
12
14
|
session: PluginSessionLaunchMeta;
|
|
13
15
|
}
|
|
14
16
|
export interface EnsurePluginSessionLaunchResult {
|
|
15
17
|
sessionId: string;
|
|
16
18
|
disposition: "created" | "replayed";
|
|
17
19
|
}
|
|
20
|
+
export type ReplayPluginSessionLaunchInput = Omit<EnsurePluginSessionLaunchInput, "session"> & {
|
|
21
|
+
/**
|
|
22
|
+
* A caller doing a pre-canonicalization lookup may treat a hash mismatch as
|
|
23
|
+
* a cache miss, then retry with the canonical hash. The canonical lookup
|
|
24
|
+
* must keep the default strict conflict behavior.
|
|
25
|
+
*/
|
|
26
|
+
allowRequestHashMismatch?: boolean;
|
|
27
|
+
};
|
|
18
28
|
export declare class SessionLaunchOperationConflictError extends Error {
|
|
19
29
|
readonly pluginId: string;
|
|
20
30
|
readonly scopeId: string;
|
|
@@ -30,12 +40,23 @@ export declare class SessionLaunchOperationTargetDeletedError extends Error {
|
|
|
30
40
|
readonly code = "SESSION_LAUNCH_OPERATION_TARGET_DELETED";
|
|
31
41
|
constructor(pluginId: string, scopeId: string, operationId: string, sessionId: string);
|
|
32
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Read the durable idempotency fence before resolving mutable launch selectors
|
|
45
|
+
* such as a Project or Agent. A replay must not fail merely because the
|
|
46
|
+
* original template was edited or deleted after the Session snapshot existed.
|
|
47
|
+
*
|
|
48
|
+
* `undefined` means the caller must resolve snapshots and proceed through
|
|
49
|
+
* {@link ensurePluginSessionLaunch}; that function remains the atomic race
|
|
50
|
+
* winner when two first attempts arrive concurrently.
|
|
51
|
+
*/
|
|
52
|
+
export declare function replayPluginSessionLaunch(input: ReplayPluginSessionLaunchInput): EnsurePluginSessionLaunchResult | undefined;
|
|
33
53
|
/**
|
|
34
54
|
* Atomically allocate a persisted machine session for one plugin operation.
|
|
35
55
|
*
|
|
36
56
|
* The durable operation row is the idempotency boundary across runner crashes,
|
|
37
|
-
* daemon restarts, and remote lease re-delivery.
|
|
38
|
-
*
|
|
57
|
+
* daemon restarts, and remote lease re-delivery. `scopeId` and `operationId`
|
|
58
|
+
* remain opaque; the Session row stores only the already-resolved workspace
|
|
59
|
+
* snapshot, never a Project identity.
|
|
39
60
|
*/
|
|
40
61
|
export declare function ensurePluginSessionLaunch(input: EnsurePluginSessionLaunchInput): EnsurePluginSessionLaunchResult;
|
|
41
62
|
export {};
|
|
@@ -31,22 +31,63 @@ export class SessionLaunchOperationTargetDeletedError extends Error {
|
|
|
31
31
|
this.name = "SessionLaunchOperationTargetDeletedError";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Read the durable idempotency fence before resolving mutable launch selectors
|
|
36
|
+
* such as a Project or Agent. A replay must not fail merely because the
|
|
37
|
+
* original template was edited or deleted after the Session snapshot existed.
|
|
38
|
+
*
|
|
39
|
+
* `undefined` means the caller must resolve snapshots and proceed through
|
|
40
|
+
* {@link ensurePluginSessionLaunch}; that function remains the atomic race
|
|
41
|
+
* winner when two first attempts arrive concurrently.
|
|
42
|
+
*/
|
|
43
|
+
export function replayPluginSessionLaunch(input) {
|
|
44
|
+
const pluginId = requiredId(input.pluginId, "pluginId");
|
|
45
|
+
const scopeId = requiredId(input.scopeId, "scopeId");
|
|
46
|
+
const operationId = requiredId(input.operationId, "operationId");
|
|
47
|
+
const requestHash = requiredRequestHash(input.requestHash);
|
|
48
|
+
const existing = readOperation(pluginId, scopeId, operationId);
|
|
49
|
+
if (!existing)
|
|
50
|
+
return undefined;
|
|
51
|
+
if (existing.request_hash !== requestHash &&
|
|
52
|
+
existing.raw_request_hash !== requestHash) {
|
|
53
|
+
if (input.allowRequestHashMismatch)
|
|
54
|
+
return undefined;
|
|
55
|
+
throw new SessionLaunchOperationConflictError(pluginId, scopeId, operationId);
|
|
56
|
+
}
|
|
57
|
+
if (existing.state === "deleted") {
|
|
58
|
+
throw new SessionLaunchOperationTargetDeletedError(pluginId, scopeId, operationId, existing.session_id);
|
|
59
|
+
}
|
|
60
|
+
if (!sessionExists(existing.session_id)) {
|
|
61
|
+
db().transaction(() => {
|
|
62
|
+
tombstoneOperations(existing.session_id, new Date().toISOString());
|
|
63
|
+
}).immediate();
|
|
64
|
+
throw new SessionLaunchOperationTargetDeletedError(pluginId, scopeId, operationId, existing.session_id);
|
|
65
|
+
}
|
|
66
|
+
return { disposition: "replayed", sessionId: existing.session_id };
|
|
67
|
+
}
|
|
34
68
|
/**
|
|
35
69
|
* Atomically allocate a persisted machine session for one plugin operation.
|
|
36
70
|
*
|
|
37
71
|
* The durable operation row is the idempotency boundary across runner crashes,
|
|
38
|
-
* daemon restarts, and remote lease re-delivery.
|
|
39
|
-
*
|
|
72
|
+
* daemon restarts, and remote lease re-delivery. `scopeId` and `operationId`
|
|
73
|
+
* remain opaque; the Session row stores only the already-resolved workspace
|
|
74
|
+
* snapshot, never a Project identity.
|
|
40
75
|
*/
|
|
41
76
|
export function ensurePluginSessionLaunch(input) {
|
|
42
77
|
const pluginId = requiredId(input.pluginId, "pluginId");
|
|
43
78
|
const scopeId = requiredId(input.scopeId, "scopeId");
|
|
44
79
|
const operationId = requiredId(input.operationId, "operationId");
|
|
45
80
|
const requestHash = requiredRequestHash(input.requestHash);
|
|
81
|
+
const rawRequestHash = input.rawRequestHash
|
|
82
|
+
? requiredRequestHash(input.rawRequestHash)
|
|
83
|
+
: requestHash;
|
|
46
84
|
const ensure = db().transaction(() => {
|
|
47
85
|
const existing = readOperation(pluginId, scopeId, operationId);
|
|
48
86
|
if (existing) {
|
|
49
|
-
if (existing.request_hash !== requestHash
|
|
87
|
+
if (existing.request_hash !== requestHash &&
|
|
88
|
+
existing.raw_request_hash !== requestHash &&
|
|
89
|
+
existing.request_hash !== rawRequestHash &&
|
|
90
|
+
existing.raw_request_hash !== rawRequestHash) {
|
|
50
91
|
throw new SessionLaunchOperationConflictError(pluginId, scopeId, operationId);
|
|
51
92
|
}
|
|
52
93
|
if (existing.state === "deleted") {
|
|
@@ -78,10 +119,10 @@ export function ensurePluginSessionLaunch(input) {
|
|
|
78
119
|
}
|
|
79
120
|
db()
|
|
80
121
|
.prepare(`INSERT INTO plugin_session_launch_operations
|
|
81
|
-
(plugin_id, scope_id, operation_id, request_hash, session_id, state,
|
|
122
|
+
(plugin_id, scope_id, operation_id, request_hash, raw_request_hash, session_id, state,
|
|
82
123
|
created_at, updated_at, deleted_at)
|
|
83
|
-
VALUES (?, ?, ?, ?, ?, 'active', ?, ?, NULL)`)
|
|
84
|
-
.run(pluginId, scopeId, operationId, requestHash, sessionId, now, now);
|
|
124
|
+
VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, NULL)`)
|
|
125
|
+
.run(pluginId, scopeId, operationId, requestHash, rawRequestHash, sessionId, now, now);
|
|
85
126
|
return { disposition: "created", sessionId };
|
|
86
127
|
});
|
|
87
128
|
const result = ensure.immediate();
|
|
@@ -92,7 +133,7 @@ export function ensurePluginSessionLaunch(input) {
|
|
|
92
133
|
}
|
|
93
134
|
function readOperation(pluginId, scopeId, operationId) {
|
|
94
135
|
return db()
|
|
95
|
-
.prepare(`SELECT request_hash, session_id, state
|
|
136
|
+
.prepare(`SELECT request_hash, raw_request_hash, session_id, state
|
|
96
137
|
FROM plugin_session_launch_operations
|
|
97
138
|
WHERE plugin_id = ? AND scope_id = ? AND operation_id = ?`)
|
|
98
139
|
.get(pluginId, scopeId, operationId);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified machine-session record (SQLite) — one row per session regardless
|
|
3
|
-
* of origin (the web console AND every channel). Holds
|
|
4
|
-
*
|
|
5
|
-
* daemon restart
|
|
3
|
+
* of origin (the web console AND every channel). Holds immutable workspace /
|
|
4
|
+
* execution snapshots plus display metadata so the **session list survives a
|
|
5
|
+
* daemon restart** without re-reading a Project or Agent; the transcript lives
|
|
6
|
+
* in `session_items` (see
|
|
6
7
|
* {@link import("./session-log-store.js")}). Runtime `status` is intentionally
|
|
7
8
|
* not stored — it is reconstructed as `idle` after a restart.
|
|
8
9
|
*
|
|
9
10
|
* Exposes both the standalone functions the control-plane deps wire in and a
|
|
10
|
-
* {@link SessionRegistry} adapter ({@link sessionRegistry})
|
|
11
|
-
*
|
|
12
|
-
* console does.
|
|
11
|
+
* {@link SessionRegistry} adapter ({@link sessionRegistry}) used by daemon
|
|
12
|
+
* session services and capability-checked plugin Host RPC.
|
|
13
13
|
*/
|
|
14
14
|
import { type MachineSessionRecord, type SessionRegistry } from "@rynx-ai/core";
|
|
15
15
|
/** Back-compat alias for the unified record. */
|
|
@@ -17,9 +17,12 @@ export type StoredSessionMeta = MachineSessionRecord;
|
|
|
17
17
|
export declare function listSessionMetas(): StoredSessionMeta[];
|
|
18
18
|
export declare function getSessionMeta(id: string): StoredSessionMeta | undefined;
|
|
19
19
|
export declare function createSessionMeta(meta: StoredSessionMeta): void;
|
|
20
|
-
export declare function updateSessionMeta(meta: StoredSessionMeta): void;
|
|
21
20
|
export declare function setSessionTitle(id: string, title: string): void;
|
|
21
|
+
export declare function setSessionExecution(id: string, execution: MachineSessionRecord["execution"]): void;
|
|
22
22
|
export declare function removeSessionMeta(id: string): void;
|
|
23
|
-
/** {@link SessionRegistry} over the same `sessions` table
|
|
24
|
-
* receive through their `ChannelContext`. */
|
|
23
|
+
/** {@link SessionRegistry} over the same `sessions` table. */
|
|
25
24
|
export declare const sessionRegistry: SessionRegistry;
|
|
25
|
+
/** Generic plugin-principal ownership, including descendants forked from a
|
|
26
|
+
* plugin-created Session. Portal authorization calls this on every request so
|
|
27
|
+
* a grant cannot cross into console or another plugin's Sessions. */
|
|
28
|
+
export declare function sessionBelongsToPlugin(pluginId: string, sessionId: string): boolean;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified machine-session record (SQLite) — one row per session regardless
|
|
3
|
-
* of origin (the web console AND every channel). Holds
|
|
4
|
-
*
|
|
5
|
-
* daemon restart
|
|
3
|
+
* of origin (the web console AND every channel). Holds immutable workspace /
|
|
4
|
+
* execution snapshots plus display metadata so the **session list survives a
|
|
5
|
+
* daemon restart** without re-reading a Project or Agent; the transcript lives
|
|
6
|
+
* in `session_items` (see
|
|
6
7
|
* {@link import("./session-log-store.js")}). Runtime `status` is intentionally
|
|
7
8
|
* not stored — it is reconstructed as `idle` after a restart.
|
|
8
9
|
*
|
|
9
10
|
* Exposes both the standalone functions the control-plane deps wire in and a
|
|
10
|
-
* {@link SessionRegistry} adapter ({@link sessionRegistry})
|
|
11
|
-
*
|
|
12
|
-
* console does.
|
|
11
|
+
* {@link SessionRegistry} adapter ({@link sessionRegistry}) used by daemon
|
|
12
|
+
* session services and capability-checked plugin Host RPC.
|
|
13
13
|
*/
|
|
14
|
-
import { normalizeSessionTitle, } from "@rynx-ai/core";
|
|
14
|
+
import { normalizeSessionTitle, resolvedExecutionSnapshotSchema, sessionWorkspaceSnapshotSchema, } from "@rynx-ai/core";
|
|
15
15
|
import { db } from "./db.js";
|
|
16
|
-
const COLS = "id,
|
|
16
|
+
const COLS = "id, workspace, execution, title, forked_from_session_id, source, created_at, updated_at";
|
|
17
17
|
export function listSessionMetas() {
|
|
18
18
|
return db()
|
|
19
19
|
.prepare(`SELECT ${COLS} FROM sessions ORDER BY created_at`)
|
|
@@ -27,50 +27,32 @@ export function getSessionMeta(id) {
|
|
|
27
27
|
export function createSessionMeta(meta) {
|
|
28
28
|
db()
|
|
29
29
|
.prepare(`INSERT OR IGNORE INTO sessions
|
|
30
|
-
(id,
|
|
31
|
-
VALUES (
|
|
30
|
+
(id, workspace, execution, title, forked_from_session_id, source, created_at, updated_at)
|
|
31
|
+
VALUES (
|
|
32
|
+
@id, @workspace, @execution, @title, @forked_from_session_id,
|
|
33
|
+
@source, @created_at, @updated_at
|
|
34
|
+
)`)
|
|
32
35
|
.run({
|
|
33
36
|
id: meta.id,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
config: meta.config ? JSON.stringify(meta.config) : null,
|
|
37
|
-
model: meta.model ?? null,
|
|
38
|
-
reasoning_effort: meta.reasoningEffort ?? null,
|
|
37
|
+
workspace: JSON.stringify(sessionWorkspaceSnapshotSchema.parse(meta.workspace)),
|
|
38
|
+
execution: JSON.stringify(resolvedExecutionSnapshotSchema.parse(meta.execution)),
|
|
39
39
|
title: normalizeSessionTitle(meta.title) ?? null,
|
|
40
|
+
forked_from_session_id: meta.forkedFromSessionId ?? null,
|
|
40
41
|
source: meta.source,
|
|
41
42
|
created_at: meta.createdAt,
|
|
42
43
|
updated_at: meta.updatedAt ?? meta.createdAt,
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
|
-
export function updateSessionMeta(meta) {
|
|
46
|
-
db()
|
|
47
|
-
.prepare(`UPDATE sessions
|
|
48
|
-
SET provider = @provider,
|
|
49
|
-
agent = @agent,
|
|
50
|
-
config = @config,
|
|
51
|
-
model = @model,
|
|
52
|
-
reasoning_effort = @reasoning_effort,
|
|
53
|
-
title = @title,
|
|
54
|
-
source = @source,
|
|
55
|
-
updated_at = @updated_at
|
|
56
|
-
WHERE id = @id`)
|
|
57
|
-
.run({
|
|
58
|
-
id: meta.id,
|
|
59
|
-
provider: meta.provider ?? null,
|
|
60
|
-
agent: meta.agent ?? null,
|
|
61
|
-
config: meta.config ? JSON.stringify(meta.config) : null,
|
|
62
|
-
model: meta.model ?? null,
|
|
63
|
-
reasoning_effort: meta.reasoningEffort ?? null,
|
|
64
|
-
title: normalizeSessionTitle(meta.title) ?? null,
|
|
65
|
-
source: meta.source,
|
|
66
|
-
updated_at: meta.updatedAt ?? new Date().toISOString(),
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
46
|
export function setSessionTitle(id, title) {
|
|
70
47
|
db()
|
|
71
48
|
.prepare("UPDATE sessions SET title = ?, updated_at = ? WHERE id = ?")
|
|
72
49
|
.run(normalizeSessionTitle(title), new Date().toISOString(), id);
|
|
73
50
|
}
|
|
51
|
+
export function setSessionExecution(id, execution) {
|
|
52
|
+
db()
|
|
53
|
+
.prepare("UPDATE sessions SET execution = ?, updated_at = ? WHERE id = ?")
|
|
54
|
+
.run(JSON.stringify(resolvedExecutionSnapshotSchema.parse(execution)), new Date().toISOString(), id);
|
|
55
|
+
}
|
|
74
56
|
export function removeSessionMeta(id) {
|
|
75
57
|
db().transaction(() => {
|
|
76
58
|
const now = new Date().toISOString();
|
|
@@ -79,32 +61,51 @@ export function removeSessionMeta(id) {
|
|
|
79
61
|
SET state = 'deleted', updated_at = ?, deleted_at = ?
|
|
80
62
|
WHERE session_id = ? AND state = 'active'`)
|
|
81
63
|
.run(now, now, id);
|
|
64
|
+
db()
|
|
65
|
+
.prepare(`UPDATE session_fork_operations
|
|
66
|
+
SET state = 'target_deleted', updated_at = ?, deleted_at = ?
|
|
67
|
+
WHERE target_session_id = ? AND state IN ('reserved', 'completed')`)
|
|
68
|
+
.run(now, now, id);
|
|
82
69
|
db().prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
83
70
|
}).immediate();
|
|
84
71
|
}
|
|
85
|
-
/** {@link SessionRegistry} over the same `sessions` table
|
|
86
|
-
* receive through their `ChannelContext`. */
|
|
72
|
+
/** {@link SessionRegistry} over the same `sessions` table. */
|
|
87
73
|
export const sessionRegistry = {
|
|
88
74
|
create: createSessionMeta,
|
|
89
75
|
get: getSessionMeta,
|
|
90
76
|
list: listSessionMetas,
|
|
91
77
|
setTitle: setSessionTitle,
|
|
78
|
+
setExecution: setSessionExecution,
|
|
92
79
|
remove: removeSessionMeta,
|
|
93
80
|
};
|
|
81
|
+
/** Generic plugin-principal ownership, including descendants forked from a
|
|
82
|
+
* plugin-created Session. Portal authorization calls this on every request so
|
|
83
|
+
* a grant cannot cross into console or another plugin's Sessions. */
|
|
84
|
+
export function sessionBelongsToPlugin(pluginId, sessionId) {
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
let current = sessionRegistry.get(sessionId);
|
|
87
|
+
while (current && !seen.has(current.id)) {
|
|
88
|
+
if (current.source === `plugin:${pluginId}`)
|
|
89
|
+
return true;
|
|
90
|
+
seen.add(current.id);
|
|
91
|
+
current = current.forkedFromSessionId
|
|
92
|
+
? sessionRegistry.get(current.forkedFromSessionId)
|
|
93
|
+
: undefined;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
94
97
|
function rowToMeta(row) {
|
|
95
98
|
return {
|
|
96
99
|
id: row.id,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
: undefined,
|
|
100
|
-
agent: row.agent ?? undefined,
|
|
101
|
-
config: row.config ? JSON.parse(row.config) : undefined,
|
|
102
|
-
model: row.model ?? undefined,
|
|
103
|
-
reasoningEffort: (row.reasoning_effort ?? undefined),
|
|
100
|
+
workspace: sessionWorkspaceSnapshotSchema.parse(JSON.parse(row.workspace)),
|
|
101
|
+
execution: resolvedExecutionSnapshotSchema.parse(JSON.parse(row.execution)),
|
|
104
102
|
// Normalize on read as well so pre-boundary database rows cannot poison an
|
|
105
103
|
// entire Remote Session list page after upgrading the daemon.
|
|
106
104
|
title: normalizeSessionTitle(row.title ?? undefined),
|
|
107
|
-
|
|
105
|
+
...(row.forked_from_session_id
|
|
106
|
+
? { forkedFromSessionId: row.forked_from_session_id }
|
|
107
|
+
: {}),
|
|
108
|
+
source: row.source,
|
|
108
109
|
createdAt: row.created_at,
|
|
109
110
|
updatedAt: row.updated_at ?? row.created_at,
|
|
110
111
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type SessionPortalScope = "plugin_sessions" | "single_session";
|
|
2
|
+
export type SessionPortalAccess = "read_only" | "read_write";
|
|
3
|
+
export interface SessionPortalAuthorization {
|
|
4
|
+
portalId: string;
|
|
5
|
+
pluginId: string;
|
|
6
|
+
scope: SessionPortalScope;
|
|
7
|
+
access: SessionPortalAccess;
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
expiresAt: string;
|
|
10
|
+
}
|
|
11
|
+
/** Issue a browser handoff into the native Session workbench. The Host knows
|
|
12
|
+
* only the generic Session scope and access level; product/user policy stays in
|
|
13
|
+
* the plugin that asks for the ticket. */
|
|
14
|
+
export declare function issueSessionPortalTicket(input: {
|
|
15
|
+
pluginId: string;
|
|
16
|
+
scope: SessionPortalScope;
|
|
17
|
+
access: SessionPortalAccess;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
now?: Date;
|
|
20
|
+
}): {
|
|
21
|
+
path: string;
|
|
22
|
+
expiresAt: string;
|
|
23
|
+
};
|
|
24
|
+
/** Atomically consume a one-use ticket and mint a fixed-lifetime browser grant. */
|
|
25
|
+
export declare function redeemSessionPortalTicket(ticket: string, now?: Date): (SessionPortalAuthorization & {
|
|
26
|
+
secret: string;
|
|
27
|
+
}) | undefined;
|
|
28
|
+
export declare function authorizeSessionPortalGrant(portalId: string, secret: string, now?: Date): SessionPortalAuthorization | undefined;
|
|
29
|
+
export declare function pruneSessionPortalAuthorization(now?: Date): void;
|