@jmfederico/pi-web 1.202606.1 → 1.202606.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-BcQUZop_.js → CodeViewer-iYSv2-dg.js} +1 -1
- package/dist/client/assets/{TerminalPanel-1o4j3QP_.js → TerminalPanel-BpFW6j27.js} +1 -1
- package/dist/client/assets/index-D4dbiKXF.js +2210 -0
- package/dist/client/index.html +1 -1
- package/dist/pi-web-plugins/updates/package.json +1 -1
- package/dist/server/app.js +15 -6
- package/dist/server/app.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +8 -0
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineRoutes.js +6 -0
- package/dist/server/machines/machineRoutes.js.map +1 -1
- package/dist/server/machines/machineService.js +97 -5
- package/dist/server/machines/machineService.js.map +1 -1
- package/dist/server/piWebPluginService.js +24 -4
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +134 -39
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/piWebStatusCache.js +32 -0
- package/dist/server/piWebStatusCache.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -1
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +18 -7
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/oauthLoginFlowService.js +7 -0
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +96 -0
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +129 -61
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionArchiveStore.js +16 -2
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +132 -43
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/terminals/terminalRoutes.js +10 -4
- package/dist/server/terminals/terminalRoutes.js.map +1 -1
- package/dist/server/workingDirectory.js +44 -0
- package/dist/server/workingDirectory.js.map +1 -0
- package/dist/server/workspaces/fileSuggestions.js +96 -16
- package/dist/server/workspaces/fileSuggestions.js.map +1 -1
- package/dist/shared/apiTypes.d.ts +36 -2
- package/dist/shared/apiTypes.js +3 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +25 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/federatedRoutes.js +2 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/piWebStatusParsing.js +43 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/docs/plugins.md +22 -26
- package/package.json +5 -5
- package/dist/client/assets/index-CM8WX5nf.js +0 -1994
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { getAgentDir, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
|
|
6
|
+
export const PI_SESSION_DIR_ENV = "PI_CODING_AGENT_SESSION_DIR";
|
|
7
|
+
export class SessionDirResolver {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.agentDir = options.agentDir ?? getAgentDir();
|
|
10
|
+
this.env = options.env ?? process.env;
|
|
11
|
+
}
|
|
12
|
+
defaultSessionsRoot() {
|
|
13
|
+
return defaultPiSessionsRoot(this.agentDir);
|
|
14
|
+
}
|
|
15
|
+
resolve(cwd) {
|
|
16
|
+
const envSessionDir = this.env[PI_SESSION_DIR_ENV];
|
|
17
|
+
if (envSessionDir !== undefined && envSessionDir !== "") {
|
|
18
|
+
return { source: "env", sessionDir: resolveConfiguredPath(envSessionDir, cwd), usesConfiguredSessionDir: true };
|
|
19
|
+
}
|
|
20
|
+
const settingsSessionDir = SettingsManager.create(cwd, this.agentDir).getSessionDir();
|
|
21
|
+
if (settingsSessionDir !== undefined && settingsSessionDir !== "") {
|
|
22
|
+
return { source: "settings", sessionDir: resolveConfiguredPath(settingsSessionDir, cwd), usesConfiguredSessionDir: true };
|
|
23
|
+
}
|
|
24
|
+
return { source: "pi-default", sessionDir: defaultPiSessionDir(cwd, this.agentDir), usesConfiguredSessionDir: false };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function createPiSessionManagerGateway(options = {}) {
|
|
28
|
+
return new SettingsAwarePiSessionManagerGateway(new SessionDirResolver(options));
|
|
29
|
+
}
|
|
30
|
+
class SettingsAwarePiSessionManagerGateway {
|
|
31
|
+
constructor(resolver) {
|
|
32
|
+
this.resolver = resolver;
|
|
33
|
+
}
|
|
34
|
+
async list(cwd) {
|
|
35
|
+
const resolution = this.resolver.resolve(cwd);
|
|
36
|
+
return filterSessionsForCwd(await listSessionsInDir(resolution.sessionDir), cwd);
|
|
37
|
+
}
|
|
38
|
+
create(cwd) {
|
|
39
|
+
const resolution = this.resolver.resolve(cwd);
|
|
40
|
+
return SessionManager.create(cwd, resolution.sessionDir);
|
|
41
|
+
}
|
|
42
|
+
listAll() {
|
|
43
|
+
return listSessionsInDefaultPiStore(this.resolver.defaultSessionsRoot());
|
|
44
|
+
}
|
|
45
|
+
open(path) {
|
|
46
|
+
return SessionManager.open(path, dirname(path));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function listSessionsInDir(sessionDir) {
|
|
50
|
+
// listAll(sessionDir) lists without the SDK's internal cwd filter, which would
|
|
51
|
+
// otherwise compare against this process's cwd and drop other projects' sessions.
|
|
52
|
+
// Cwd filtering is applied explicitly by filterSessionsForCwd where needed.
|
|
53
|
+
// Session file headers are written by external tools (Pi CLI, SDK consumers),
|
|
54
|
+
// so their cwd is canonicalized here before it enters pi-web.
|
|
55
|
+
const sessions = await SessionManager.listAll(sessionDir);
|
|
56
|
+
return sessions.map((session) => ({ ...session, cwd: canonicalizeStoredCwd(session.cwd) }));
|
|
57
|
+
}
|
|
58
|
+
export async function listSessionsInDefaultPiStore(storeRoot = defaultPiSessionsRoot()) {
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
entries = await readdir(storeRoot, { withFileTypes: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const sessionDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => join(storeRoot, entry.name));
|
|
67
|
+
const sessions = (await Promise.all(sessionDirs.map((dir) => listSessionsInDir(dir)))).flat();
|
|
68
|
+
return sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
69
|
+
}
|
|
70
|
+
export function filterSessionsForCwd(sessions, cwd) {
|
|
71
|
+
// Sessions with an empty cwd (old session files) are excluded: resolve("") would
|
|
72
|
+
// resolve to this process's cwd and produce false matches.
|
|
73
|
+
return sessions.filter((session) => session.cwd !== "" && cwdPathsEqual(session.cwd, cwd));
|
|
74
|
+
}
|
|
75
|
+
export function defaultPiSessionsRoot(agentDir = getAgentDir()) {
|
|
76
|
+
return join(agentDir, "sessions");
|
|
77
|
+
}
|
|
78
|
+
export function defaultPiSessionDir(cwd, agentDir = getAgentDir()) {
|
|
79
|
+
return sessionDirInDefaultPiStore(defaultPiSessionsRoot(agentDir), cwd);
|
|
80
|
+
}
|
|
81
|
+
export function sessionDirInDefaultPiStore(storeRoot, cwd) {
|
|
82
|
+
const safePath = `--${cwd.replace(/^[/\\]/u, "").replace(/[/\\:]/gu, "-")}--`;
|
|
83
|
+
return join(storeRoot, safePath);
|
|
84
|
+
}
|
|
85
|
+
export function resolveConfiguredPath(path, cwd) {
|
|
86
|
+
const expanded = expandTildePath(path);
|
|
87
|
+
return isAbsolute(expanded) ? expanded : resolve(cwd, expanded);
|
|
88
|
+
}
|
|
89
|
+
function expandTildePath(path) {
|
|
90
|
+
if (path === "~")
|
|
91
|
+
return homedir();
|
|
92
|
+
if (path.startsWith("~/"))
|
|
93
|
+
return join(homedir(), path.slice(2));
|
|
94
|
+
return path;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=piSessionManagerGateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piSessionManagerGateway.js","sourceRoot":"","sources":["../../../src/server/sessions/piSessionManagerGateway.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,12 +7,23 @@ import { SessionArchiveStore } from "./sessionArchiveStore.js";
|
|
|
7
7
|
import { findArchiveCandidateByIdOrPrefix, planSessionArchiveTree } from "./sessionArchiveTree.js";
|
|
8
8
|
import { fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
|
|
9
9
|
import { computeEditPreview } from "./editPreview.js";
|
|
10
|
+
import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
|
|
11
|
+
import { cwdPathsEqual } from "../workingDirectory.js";
|
|
10
12
|
function noop() {
|
|
11
13
|
// Intentionally empty default unsubscribe callback.
|
|
12
14
|
}
|
|
13
15
|
function authLossWarningKey(sessionId, provider, modelId) {
|
|
14
16
|
return `${sessionId}:${provider}/${modelId}`;
|
|
15
17
|
}
|
|
18
|
+
function sessionIdFromLookup(ref) {
|
|
19
|
+
return typeof ref === "string" ? ref : ref.id;
|
|
20
|
+
}
|
|
21
|
+
function isPiSessionRef(ref) {
|
|
22
|
+
return typeof ref !== "string";
|
|
23
|
+
}
|
|
24
|
+
function lookupMatchesActiveSession(ref, active) {
|
|
25
|
+
return !isPiSessionRef(ref) || cwdPathsEqual(active.runtime.cwd, ref.cwd);
|
|
26
|
+
}
|
|
16
27
|
function requirePromptText(value) {
|
|
17
28
|
if (typeof value !== "string")
|
|
18
29
|
throw new Error("Prompt text is required");
|
|
@@ -72,7 +83,7 @@ export class PiSessionService {
|
|
|
72
83
|
this.authLossWarnings = new Set();
|
|
73
84
|
this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
|
|
74
85
|
this.agentDir = deps.agentDir ?? getAgentDir();
|
|
75
|
-
this.sessionManager = deps.sessionManager ??
|
|
86
|
+
this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({ agentDir: this.agentDir });
|
|
76
87
|
this.modelRegistry = deps.modelRegistry ?? ModelRegistry.create(AuthStorage.create());
|
|
77
88
|
this.createRuntime = deps.createRuntime ?? createDefaultRuntimeFactory(this.modelRegistry.authStorage, this.modelRegistry);
|
|
78
89
|
this.createAgentRuntime = deps.createAgentRuntime ?? defaultCreateAgentRuntime;
|
|
@@ -135,24 +146,24 @@ export class PiSessionService {
|
|
|
135
146
|
firstMessage: "",
|
|
136
147
|
};
|
|
137
148
|
}
|
|
138
|
-
async messages(
|
|
139
|
-
const session = await this.getOrOpen(
|
|
149
|
+
async messages(ref, page) {
|
|
150
|
+
const session = await this.getOrOpen(ref);
|
|
140
151
|
return pageMessagesAtSafeBoundary(historyMessages(session), page);
|
|
141
152
|
}
|
|
142
|
-
async status(
|
|
143
|
-
return this.statusFromSession(await this.getOrOpen(
|
|
153
|
+
async status(ref) {
|
|
154
|
+
return this.statusFromSession(await this.getOrOpen(ref));
|
|
144
155
|
}
|
|
145
|
-
async availableModels(
|
|
146
|
-
const session = await this.getOrOpen(
|
|
156
|
+
async availableModels(ref) {
|
|
157
|
+
const session = await this.getOrOpen(ref);
|
|
147
158
|
session.modelRegistry.refresh();
|
|
148
159
|
const models = session.scopedModels.length > 0
|
|
149
160
|
? session.scopedModels.map((scoped) => scoped.model)
|
|
150
161
|
: session.modelRegistry.getAvailable();
|
|
151
162
|
return models.map(modelToClientModel);
|
|
152
163
|
}
|
|
153
|
-
async setModel(
|
|
154
|
-
await this.assertWritable(
|
|
155
|
-
const session = await this.getOrOpen(
|
|
164
|
+
async setModel(ref, provider, modelId) {
|
|
165
|
+
await this.assertWritable(ref);
|
|
166
|
+
const session = await this.getOrOpen(ref);
|
|
156
167
|
session.modelRegistry.refresh();
|
|
157
168
|
const candidates = session.scopedModels.length > 0
|
|
158
169
|
? session.scopedModels.map((scoped) => scoped.model)
|
|
@@ -166,9 +177,9 @@ export class PiSessionService {
|
|
|
166
177
|
this.publishStatus(session);
|
|
167
178
|
return this.statusFromSession(session);
|
|
168
179
|
}
|
|
169
|
-
async cycleModel(
|
|
170
|
-
await this.assertWritable(
|
|
171
|
-
const session = await this.getOrOpen(
|
|
180
|
+
async cycleModel(ref, direction) {
|
|
181
|
+
await this.assertWritable(ref);
|
|
182
|
+
const session = await this.getOrOpen(ref);
|
|
172
183
|
const result = await session.cycleModel(direction);
|
|
173
184
|
if (result === undefined)
|
|
174
185
|
throw new Error(session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available");
|
|
@@ -176,21 +187,21 @@ export class PiSessionService {
|
|
|
176
187
|
this.publishStatus(session);
|
|
177
188
|
return this.statusFromSession(session);
|
|
178
189
|
}
|
|
179
|
-
async availableThinkingLevels(
|
|
180
|
-
const session = await this.getOrOpen(
|
|
190
|
+
async availableThinkingLevels(ref) {
|
|
191
|
+
const session = await this.getOrOpen(ref);
|
|
181
192
|
return session.getAvailableThinkingLevels();
|
|
182
193
|
}
|
|
183
|
-
async setThinkingLevel(
|
|
184
|
-
await this.assertWritable(
|
|
185
|
-
const session = await this.getOrOpen(
|
|
194
|
+
async setThinkingLevel(ref, level) {
|
|
195
|
+
await this.assertWritable(ref);
|
|
196
|
+
const session = await this.getOrOpen(ref);
|
|
186
197
|
session.setThinkingLevel(level);
|
|
187
198
|
this.publishActivity(session, `thinking: ${session.thinkingLevel}`, "idle");
|
|
188
199
|
this.publishStatus(session);
|
|
189
200
|
return this.statusFromSession(session);
|
|
190
201
|
}
|
|
191
|
-
async cycleThinkingLevel(
|
|
192
|
-
await this.assertWritable(
|
|
193
|
-
const session = await this.getOrOpen(
|
|
202
|
+
async cycleThinkingLevel(ref) {
|
|
203
|
+
await this.assertWritable(ref);
|
|
204
|
+
const session = await this.getOrOpen(ref);
|
|
194
205
|
const level = session.cycleThinkingLevel();
|
|
195
206
|
if (level === undefined)
|
|
196
207
|
throw new Error("Current model does not support thinking");
|
|
@@ -198,8 +209,8 @@ export class PiSessionService {
|
|
|
198
209
|
this.publishStatus(session);
|
|
199
210
|
return this.statusFromSession(session);
|
|
200
211
|
}
|
|
201
|
-
async commands(
|
|
202
|
-
const session = await this.getOrOpen(
|
|
212
|
+
async commands(ref) {
|
|
213
|
+
const session = await this.getOrOpen(ref);
|
|
203
214
|
const commands = [...BUILTIN_COMMANDS];
|
|
204
215
|
for (const command of session.extensionRunner.getRegisteredCommands()) {
|
|
205
216
|
commands.push({ name: command.invocationName, ...(command.description === undefined ? {} : { description: command.description }), source: "extension" });
|
|
@@ -212,11 +223,11 @@ export class PiSessionService {
|
|
|
212
223
|
}
|
|
213
224
|
return commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
214
225
|
}
|
|
215
|
-
async prompt(
|
|
226
|
+
async prompt(ref, text, streamingBehavior) {
|
|
216
227
|
const promptText = requirePromptText(text);
|
|
217
228
|
const requestedBehavior = parsePromptStreamingBehavior(streamingBehavior);
|
|
218
|
-
await this.assertWritable(
|
|
219
|
-
const session = await this.getOrOpen(
|
|
229
|
+
await this.assertWritable(ref);
|
|
230
|
+
const session = await this.getOrOpen(ref);
|
|
220
231
|
this.maybeGenerateSessionName(session, promptText);
|
|
221
232
|
const isQueued = session.isStreaming || session.isCompacting;
|
|
222
233
|
const behavior = isQueued ? requestedBehavior ?? "followUp" : undefined;
|
|
@@ -250,9 +261,9 @@ export class PiSessionService {
|
|
|
250
261
|
this.publishActivity(session, "message queued during compaction", "active");
|
|
251
262
|
this.publishStatus(session);
|
|
252
263
|
}
|
|
253
|
-
async shell(
|
|
254
|
-
await this.assertWritable(
|
|
255
|
-
const active = await this.getActive(
|
|
264
|
+
async shell(ref, text) {
|
|
265
|
+
await this.assertWritable(ref);
|
|
266
|
+
const active = await this.getActive(ref);
|
|
256
267
|
const { session } = active.runtime;
|
|
257
268
|
const isExcluded = text.startsWith("!!");
|
|
258
269
|
const command = (isExcluded ? text.slice(2) : text.slice(1)).trim();
|
|
@@ -285,24 +296,26 @@ export class PiSessionService {
|
|
|
285
296
|
this.publishStatus(session);
|
|
286
297
|
});
|
|
287
298
|
}
|
|
288
|
-
async runCommand(
|
|
289
|
-
await this.assertWritable(
|
|
290
|
-
|
|
299
|
+
async runCommand(ref, text) {
|
|
300
|
+
await this.assertWritable(ref);
|
|
301
|
+
const active = await this.getActive(ref);
|
|
302
|
+
return this.commandService.run(active.runtime.session.sessionId, text);
|
|
291
303
|
}
|
|
292
|
-
async respondToCommand(
|
|
293
|
-
await this.assertWritable(
|
|
294
|
-
|
|
304
|
+
async respondToCommand(ref, requestId, value) {
|
|
305
|
+
await this.assertWritable(ref);
|
|
306
|
+
const active = await this.getActive(ref);
|
|
307
|
+
return this.commandService.respond(active.runtime.session.sessionId, requestId, value);
|
|
295
308
|
}
|
|
296
|
-
async archive(
|
|
297
|
-
const session = await this.getOrOpen(
|
|
309
|
+
async archive(ref) {
|
|
310
|
+
const session = await this.getOrOpen(ref);
|
|
298
311
|
if (this.hasActiveWork(session))
|
|
299
312
|
throw new Error("Stop current session activity before archiving");
|
|
300
313
|
const archiveInput = await this.archiveInputForSession(session);
|
|
301
314
|
await this.closeActive(session.sessionId);
|
|
302
315
|
await this.archiveStore.archive(archiveInput);
|
|
303
316
|
}
|
|
304
|
-
async archiveTree(
|
|
305
|
-
const session = await this.getOrOpen(
|
|
317
|
+
async archiveTree(ref) {
|
|
318
|
+
const session = await this.getOrOpen(ref);
|
|
306
319
|
const catalog = await this.workspaceArchiveCandidates(session.sessionManager.getCwd());
|
|
307
320
|
const root = findArchiveCandidateByIdOrPrefix(catalog, session.sessionId) ?? archiveCandidateFromActiveSession(session, false);
|
|
308
321
|
const plan = planSessionArchiveTree(root, catalog);
|
|
@@ -321,29 +334,47 @@ export class PiSessionService {
|
|
|
321
334
|
skippedAlreadyArchivedCount: plan.skippedAlreadyArchivedCount,
|
|
322
335
|
};
|
|
323
336
|
}
|
|
324
|
-
async restore(
|
|
325
|
-
await this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
337
|
+
async restore(ref) {
|
|
338
|
+
const archived = await this.getArchived(ref);
|
|
339
|
+
if (archived === undefined)
|
|
340
|
+
throw new Error("Session not found");
|
|
341
|
+
await this.closeActive(archived.sessionId);
|
|
342
|
+
await this.archiveStore.restore(archived.sessionId);
|
|
343
|
+
}
|
|
344
|
+
async deleteArchived(ref) {
|
|
345
|
+
const record = await this.getArchived(ref);
|
|
346
|
+
if (record === undefined)
|
|
347
|
+
throw new Error("Archived session not found");
|
|
348
|
+
if (this.archiveStore.deleteArchived === undefined)
|
|
349
|
+
throw new Error("Archive store does not support deletion");
|
|
350
|
+
await this.closeActive(record.sessionId);
|
|
351
|
+
if (record.archivePath === undefined)
|
|
352
|
+
await this.ensureArchivedRecordMoved(record);
|
|
353
|
+
await this.archiveStore.deleteArchived(record.sessionId);
|
|
354
|
+
}
|
|
355
|
+
async detachParent(ref) {
|
|
356
|
+
const session = await this.getOrOpen(ref);
|
|
330
357
|
const sessionFile = session.sessionFile;
|
|
331
358
|
if (sessionFile === undefined || sessionFile === "")
|
|
332
359
|
throw new Error("Session is not persisted");
|
|
333
360
|
await clearParentSession(sessionFile);
|
|
334
361
|
}
|
|
335
|
-
async abort(
|
|
336
|
-
const active = this.
|
|
337
|
-
if (
|
|
362
|
+
async abort(ref) {
|
|
363
|
+
const active = this.activeForLookup(ref);
|
|
364
|
+
if (active === undefined)
|
|
338
365
|
return;
|
|
366
|
+
const sessionId = active.runtime.session.sessionId;
|
|
339
367
|
this.clearCompactionPromptQueue(sessionId);
|
|
340
368
|
clearSessionQueue(active.runtime.session);
|
|
341
369
|
await active.runtime.session.abort();
|
|
342
370
|
this.publishActivity(active.runtime.session, "stopped", "idle");
|
|
343
371
|
this.publishStatus(active.runtime.session);
|
|
344
372
|
}
|
|
345
|
-
stop(
|
|
346
|
-
|
|
373
|
+
stop(ref) {
|
|
374
|
+
const active = this.activeForLookup(ref);
|
|
375
|
+
if (active === undefined)
|
|
376
|
+
return;
|
|
377
|
+
void this.closeActive(active.runtime.session.sessionId).catch(() => {
|
|
347
378
|
// Best-effort shutdown; callers that need errors await closeActive directly.
|
|
348
379
|
});
|
|
349
380
|
}
|
|
@@ -366,6 +397,12 @@ export class PiSessionService {
|
|
|
366
397
|
return record;
|
|
367
398
|
}
|
|
368
399
|
}
|
|
400
|
+
async ensureArchivedRecordMoved(record) {
|
|
401
|
+
const session = (await this.sessionManager.list(record.cwd)).find((candidate) => candidate.id === record.sessionId);
|
|
402
|
+
if (session === undefined)
|
|
403
|
+
return record;
|
|
404
|
+
return this.archiveStore.archive(archiveInputFromListEntry(session));
|
|
405
|
+
}
|
|
369
406
|
async archiveInputForSession(session) {
|
|
370
407
|
const cwd = session.sessionManager.getCwd();
|
|
371
408
|
const sessionFile = session.sessionFile;
|
|
@@ -444,37 +481,68 @@ export class PiSessionService {
|
|
|
444
481
|
await active.runtime.dispose();
|
|
445
482
|
}
|
|
446
483
|
}
|
|
447
|
-
async assertWritable(
|
|
448
|
-
if (await this.
|
|
484
|
+
async assertWritable(ref) {
|
|
485
|
+
if (await this.getArchived(ref) !== undefined)
|
|
449
486
|
throw new Error("Archived sessions are read-only. Restore the session to continue.");
|
|
450
487
|
}
|
|
451
|
-
async getOrOpen(
|
|
452
|
-
return (await this.getActive(
|
|
488
|
+
async getOrOpen(ref) {
|
|
489
|
+
return (await this.getActive(ref)).runtime.session;
|
|
453
490
|
}
|
|
454
|
-
async getActive(
|
|
455
|
-
const active = this.
|
|
456
|
-
if (active)
|
|
491
|
+
async getActive(ref) {
|
|
492
|
+
const active = this.activeForLookup(ref);
|
|
493
|
+
if (active !== undefined)
|
|
457
494
|
return active;
|
|
458
|
-
const archived = await this.
|
|
495
|
+
const archived = await this.getArchived(ref);
|
|
459
496
|
if (archived?.archivePath !== undefined)
|
|
460
497
|
return this.create(this.sessionManager.open(archived.archivePath), archived.cwd);
|
|
461
|
-
const match = (
|
|
498
|
+
const match = isPiSessionRef(ref)
|
|
499
|
+
? (await this.sessionManager.list(ref.cwd)).find((s) => s.id === ref.id || s.id.startsWith(ref.id))
|
|
500
|
+
: (await this.sessionManager.listAll?.() ?? []).find((s) => s.id === ref || s.id.startsWith(ref));
|
|
462
501
|
if (!match)
|
|
463
502
|
throw new Error("Session not found");
|
|
464
503
|
return this.create(this.sessionManager.open(match.path), match.cwd);
|
|
465
504
|
}
|
|
505
|
+
async getArchived(ref) {
|
|
506
|
+
const archived = await this.archiveStore.get(sessionIdFromLookup(ref));
|
|
507
|
+
if (archived === undefined)
|
|
508
|
+
return undefined;
|
|
509
|
+
if (isPiSessionRef(ref) && archived.cwd !== ref.cwd)
|
|
510
|
+
return undefined;
|
|
511
|
+
return archived;
|
|
512
|
+
}
|
|
513
|
+
activeForLookup(ref) {
|
|
514
|
+
const sessionId = sessionIdFromLookup(ref);
|
|
515
|
+
const exact = this.active.get(sessionId);
|
|
516
|
+
if (exact !== undefined && lookupMatchesActiveSession(ref, exact))
|
|
517
|
+
return exact;
|
|
518
|
+
for (const [candidateId, active] of this.active.entries()) {
|
|
519
|
+
if (candidateId.startsWith(sessionId) && lookupMatchesActiveSession(ref, active))
|
|
520
|
+
return active;
|
|
521
|
+
}
|
|
522
|
+
return undefined;
|
|
523
|
+
}
|
|
466
524
|
async create(sessionManager, cwd) {
|
|
467
525
|
const runtime = await this.createAgentRuntime(this.createRuntime, { cwd, agentDir: this.agentDir, sessionManager });
|
|
526
|
+
await this.bindSessionExtensions(runtime.session);
|
|
468
527
|
const active = { runtime, unsubscribe: noop };
|
|
469
528
|
this.bindRuntime(active);
|
|
470
|
-
runtime.setRebindSession(() => {
|
|
529
|
+
runtime.setRebindSession(async (session) => {
|
|
530
|
+
await this.bindSessionExtensions(session);
|
|
471
531
|
this.bindRuntime(active);
|
|
472
|
-
return Promise.resolve();
|
|
473
532
|
});
|
|
474
533
|
this.active.set(runtime.session.sessionId, active);
|
|
475
534
|
this.publishStatus(runtime.session);
|
|
476
535
|
return active;
|
|
477
536
|
}
|
|
537
|
+
async bindSessionExtensions(session) {
|
|
538
|
+
await session.bindExtensions({
|
|
539
|
+
onError: (error) => {
|
|
540
|
+
const message = `${error.extensionPath}: ${error.error}`;
|
|
541
|
+
this.publishActivity(session, "extension error", "error", message);
|
|
542
|
+
this.events.publish(session.sessionId, { type: "session.error", message });
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
}
|
|
478
546
|
bindRuntime(active) {
|
|
479
547
|
active.unsubscribe();
|
|
480
548
|
const { session } = active.runtime;
|