@getpaseo/server 0.1.98 → 0.1.99

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.
Files changed (48) hide show
  1. package/dist/server/server/agent/agent-manager.js +2 -2
  2. package/dist/server/server/agent/agent-sdk-types.d.ts +11 -6
  3. package/dist/server/server/agent/provider-registry.d.ts +6 -3
  4. package/dist/server/server/agent/provider-registry.js +48 -22
  5. package/dist/server/server/agent/provider-snapshot-manager.js +26 -14
  6. package/dist/server/server/agent/providers/acp-agent.d.ts +5 -3
  7. package/dist/server/server/agent/providers/acp-agent.js +32 -19
  8. package/dist/server/server/agent/providers/claude/agent.d.ts +2 -2
  9. package/dist/server/server/agent/providers/claude/agent.js +5 -25
  10. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +3 -2
  11. package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -25
  12. package/dist/server/server/agent/providers/copilot-acp-agent.js +1 -31
  13. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +0 -1
  14. package/dist/server/server/agent/providers/generic-acp-agent.js +2 -108
  15. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -3
  16. package/dist/server/server/agent/providers/mock-load-test-agent.js +5 -5
  17. package/dist/server/server/agent/providers/mock-slow-provider.d.ts +2 -3
  18. package/dist/server/server/agent/providers/mock-slow-provider.js +2 -5
  19. package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -3
  20. package/dist/server/server/agent/providers/opencode-agent.js +48 -99
  21. package/dist/server/server/agent/providers/pi/agent.d.ts +2 -3
  22. package/dist/server/server/agent/providers/pi/agent.js +8 -73
  23. package/dist/server/server/agent/providers/pi/cli-runtime.js +2 -2
  24. package/dist/server/server/agent/providers/pi/runtime.d.ts +1 -1
  25. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +1 -1
  26. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +1 -1
  27. package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
  28. package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
  29. package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
  30. package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
  31. package/dist/server/server/session/checkout/checkout-session.d.ts +142 -0
  32. package/dist/server/server/session/checkout/checkout-session.js +925 -0
  33. package/dist/server/server/session/daemon/daemon-session.d.ts +50 -0
  34. package/dist/server/server/session/daemon/daemon-session.js +98 -0
  35. package/dist/server/server/session/files/workspace-files-session.d.ts +43 -0
  36. package/dist/server/server/session/files/workspace-files-session.js +218 -0
  37. package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
  38. package/dist/server/server/session/project-config/project-config-session.js +125 -0
  39. package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
  40. package/dist/server/server/session/provider/provider-catalog-session.js +339 -0
  41. package/dist/server/server/session/voice/voice-session.d.ts +166 -0
  42. package/dist/server/server/session/voice/voice-session.js +893 -0
  43. package/dist/server/server/{voice → session/voice}/voice-turn-controller.d.ts +2 -2
  44. package/dist/server/server/{voice → session/voice}/voice-turn-controller.js +2 -2
  45. package/dist/server/server/session.d.ts +13 -208
  46. package/dist/server/server/session.js +2132 -5105
  47. package/dist/server/utils/checkout-git.d.ts +6 -0
  48. package/package.json +5 -5
@@ -0,0 +1,50 @@
1
+ import type pino from "pino";
2
+ import type { ProviderAvailability } from "../../agent/agent-manager.js";
3
+ import type { SessionInboundMessage, SessionOutboundMessage } from "../../messages.js";
4
+ export interface DaemonRuntimeConfig {
5
+ listen: string | null;
6
+ appBaseUrl?: string;
7
+ relay: {
8
+ enabled: boolean;
9
+ endpoint: string;
10
+ publicEndpoint: string;
11
+ useTls: boolean;
12
+ publicUseTls: boolean;
13
+ } | null;
14
+ }
15
+ export interface DaemonSessionHost {
16
+ emit(msg: SessionOutboundMessage): void;
17
+ }
18
+ export interface DaemonSessionOptions {
19
+ host: DaemonSessionHost;
20
+ paseoHome: string;
21
+ serverId: string | undefined;
22
+ daemonVersion: string | undefined;
23
+ daemonRuntimeConfig: DaemonRuntimeConfig | undefined;
24
+ listProviderAvailability: () => Promise<ProviderAvailability[]>;
25
+ logger: pino.Logger;
26
+ }
27
+ /**
28
+ * A client's read surface for the daemon process itself: its runtime status
29
+ * (pid-lock start time, listen address, relay config, provider availability) and
30
+ * a fresh local pairing offer for connecting a new client. Owns the `daemon.*`
31
+ * RPCs. Reaches no state beyond the never-mutated runtime values injected at
32
+ * construction and the outbound channel.
33
+ */
34
+ export declare class DaemonSession {
35
+ private readonly host;
36
+ private readonly paseoHome;
37
+ private readonly serverId;
38
+ private readonly daemonVersion;
39
+ private readonly daemonRuntimeConfig;
40
+ private readonly listProviderAvailability;
41
+ private readonly logger;
42
+ constructor(options: DaemonSessionOptions);
43
+ handleGetStatusRequest(msg: Extract<SessionInboundMessage, {
44
+ type: "daemon.get_status.request";
45
+ }>): Promise<void>;
46
+ handleGetPairingOfferRequest(msg: Extract<SessionInboundMessage, {
47
+ type: "daemon.get_pairing_offer.request";
48
+ }>): Promise<void>;
49
+ }
50
+ //# sourceMappingURL=daemon-session.d.ts.map
@@ -0,0 +1,98 @@
1
+ import { getPidLockInfo } from "../../pid-lock.js";
2
+ import { generateLocalPairingOffer } from "../../pairing-offer.js";
3
+ /**
4
+ * A client's read surface for the daemon process itself: its runtime status
5
+ * (pid-lock start time, listen address, relay config, provider availability) and
6
+ * a fresh local pairing offer for connecting a new client. Owns the `daemon.*`
7
+ * RPCs. Reaches no state beyond the never-mutated runtime values injected at
8
+ * construction and the outbound channel.
9
+ */
10
+ export class DaemonSession {
11
+ constructor(options) {
12
+ this.host = options.host;
13
+ this.paseoHome = options.paseoHome;
14
+ this.serverId = options.serverId;
15
+ this.daemonVersion = options.daemonVersion;
16
+ this.daemonRuntimeConfig = options.daemonRuntimeConfig;
17
+ this.listProviderAvailability = options.listProviderAvailability;
18
+ this.logger = options.logger;
19
+ }
20
+ async handleGetStatusRequest(msg) {
21
+ try {
22
+ const pidInfo = await getPidLockInfo(this.paseoHome);
23
+ const providers = (await this.listProviderAvailability()).map((p) => ({
24
+ provider: p.provider,
25
+ available: p.available,
26
+ error: p.error ?? null,
27
+ }));
28
+ this.host.emit({
29
+ type: "daemon.get_status.response",
30
+ payload: {
31
+ requestId: msg.requestId,
32
+ serverId: this.serverId ?? "",
33
+ version: this.daemonVersion ?? null,
34
+ pid: process.pid,
35
+ nodePath: process.execPath,
36
+ startedAt: pidInfo?.startedAt ?? null,
37
+ listen: this.daemonRuntimeConfig?.listen ?? null,
38
+ relay: this.daemonRuntimeConfig?.relay ?? null,
39
+ providers,
40
+ },
41
+ });
42
+ }
43
+ catch (error) {
44
+ this.logger.error({ err: error }, "Failed to handle daemon status request");
45
+ this.host.emit({
46
+ type: "daemon.get_status.response",
47
+ payload: {
48
+ requestId: msg.requestId,
49
+ serverId: this.serverId ?? "",
50
+ version: this.daemonVersion ?? null,
51
+ pid: process.pid,
52
+ nodePath: process.execPath,
53
+ startedAt: null,
54
+ listen: null,
55
+ relay: null,
56
+ providers: [],
57
+ },
58
+ });
59
+ }
60
+ }
61
+ async handleGetPairingOfferRequest(msg) {
62
+ try {
63
+ const relay = this.daemonRuntimeConfig?.relay;
64
+ const pairing = await generateLocalPairingOffer({
65
+ paseoHome: this.paseoHome,
66
+ relayEnabled: relay?.enabled ?? true,
67
+ relayEndpoint: relay?.endpoint,
68
+ relayPublicEndpoint: relay?.publicEndpoint,
69
+ relayUseTls: relay?.useTls,
70
+ relayPublicUseTls: relay?.publicUseTls,
71
+ appBaseUrl: this.daemonRuntimeConfig?.appBaseUrl,
72
+ includeQr: true,
73
+ logger: this.logger,
74
+ });
75
+ this.host.emit({
76
+ type: "daemon.get_pairing_offer.response",
77
+ payload: {
78
+ requestId: msg.requestId,
79
+ url: pairing.url ?? "",
80
+ qr: pairing.qr ?? null,
81
+ relayEnabled: pairing.relayEnabled,
82
+ },
83
+ });
84
+ }
85
+ catch (error) {
86
+ this.logger.error({ err: error }, "Failed to handle daemon pairing offer request");
87
+ this.host.emit({
88
+ type: "rpc_error",
89
+ payload: {
90
+ requestId: msg.requestId,
91
+ requestType: "daemon.get_pairing_offer.request",
92
+ error: error instanceof Error ? error.message : String(error),
93
+ },
94
+ });
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=daemon-session.js.map
@@ -0,0 +1,43 @@
1
+ import type pino from "pino";
2
+ import { type FileTransferFrame } from "@getpaseo/protocol/binary-frames/index";
3
+ import type { FileDownloadTokenRequest, FileExplorerRequest, FileUploadRequest, SessionInboundMessage, SessionOutboundMessage } from "../../messages.js";
4
+ import type { DownloadTokenStore } from "../../file-download/token-store.js";
5
+ /**
6
+ * What a workspace file-access request reaches outside its own domain: the
7
+ * outbound message channel (text + binary). `hasBinaryChannel` gates the
8
+ * binary file-explorer transfer path the same way the terminal subsystem does
9
+ * — old clients without a binary channel fall back to inline JSON file content.
10
+ */
11
+ export interface WorkspaceFilesSessionHost {
12
+ emit(msg: SessionOutboundMessage): void;
13
+ emitBinary(frame: Uint8Array): void;
14
+ hasBinaryChannel(): boolean;
15
+ }
16
+ export interface WorkspaceFilesSessionOptions {
17
+ host: WorkspaceFilesSessionHost;
18
+ downloadTokenStore: DownloadTokenStore;
19
+ paseoHome: string;
20
+ logger: pino.Logger;
21
+ }
22
+ /**
23
+ * A client's workspace file-access surface: browsing directories, reading file
24
+ * contents (inline JSON or binary frames), receiving uploads, issuing download
25
+ * tokens, and reading project icons. It owns the upload store and reaches no
26
+ * workspace-git, registry, or subscription state — file I/O scoped to a cwd is
27
+ * the whole concern.
28
+ */
29
+ export declare class WorkspaceFilesSession {
30
+ private readonly host;
31
+ private readonly downloadTokenStore;
32
+ private readonly logger;
33
+ private readonly fileUploads;
34
+ constructor(options: WorkspaceFilesSessionOptions);
35
+ handleFileExplorerRequest(request: FileExplorerRequest): Promise<void>;
36
+ handleFileUploadRequest(request: FileUploadRequest): void;
37
+ handleFileTransferFrame(frame: FileTransferFrame): Promise<void>;
38
+ handleProjectIconRequest(request: Extract<SessionInboundMessage, {
39
+ type: "project_icon_request";
40
+ }>): Promise<void>;
41
+ handleFileDownloadTokenRequest(request: FileDownloadTokenRequest): Promise<void>;
42
+ }
43
+ //# sourceMappingURL=workspace-files-session.d.ts.map
@@ -0,0 +1,218 @@
1
+ import { getErrorMessage } from "@getpaseo/protocol/error-utils";
2
+ import { encodeFileTransferFrame, FileTransferOpcode, } from "@getpaseo/protocol/binary-frames/index";
3
+ import { FileUploadStore } from "../../file-upload/index.js";
4
+ import { getDownloadableFileInfo, listDirectoryEntries, readExplorerFile, readExplorerFileBytes, } from "../../file-explorer/service.js";
5
+ import { getProjectIcon } from "../../../utils/project-icon.js";
6
+ /**
7
+ * A client's workspace file-access surface: browsing directories, reading file
8
+ * contents (inline JSON or binary frames), receiving uploads, issuing download
9
+ * tokens, and reading project icons. It owns the upload store and reaches no
10
+ * workspace-git, registry, or subscription state — file I/O scoped to a cwd is
11
+ * the whole concern.
12
+ */
13
+ export class WorkspaceFilesSession {
14
+ constructor(options) {
15
+ this.host = options.host;
16
+ this.downloadTokenStore = options.downloadTokenStore;
17
+ this.logger = options.logger;
18
+ this.fileUploads = new FileUploadStore({ paseoHome: options.paseoHome });
19
+ }
20
+ async handleFileExplorerRequest(request) {
21
+ const { cwd: workspaceCwd, path: requestedPath = ".", mode, requestId } = request;
22
+ const cwd = workspaceCwd.trim();
23
+ if (!cwd) {
24
+ this.host.emit({
25
+ type: "file_explorer_response",
26
+ payload: {
27
+ cwd: workspaceCwd,
28
+ path: requestedPath,
29
+ mode,
30
+ directory: null,
31
+ file: null,
32
+ error: "cwd is required",
33
+ requestId,
34
+ },
35
+ });
36
+ return;
37
+ }
38
+ try {
39
+ if (mode === "list") {
40
+ const directory = await listDirectoryEntries({
41
+ root: cwd,
42
+ relativePath: requestedPath,
43
+ });
44
+ this.host.emit({
45
+ type: "file_explorer_response",
46
+ payload: {
47
+ cwd,
48
+ path: directory.path,
49
+ mode,
50
+ directory,
51
+ file: null,
52
+ error: null,
53
+ requestId,
54
+ },
55
+ });
56
+ }
57
+ else {
58
+ if (request.acceptBinary && this.host.hasBinaryChannel()) {
59
+ const file = await readExplorerFileBytes({
60
+ root: cwd,
61
+ relativePath: requestedPath,
62
+ });
63
+ this.host.emitBinary(encodeFileTransferFrame({
64
+ opcode: FileTransferOpcode.FileBegin,
65
+ requestId,
66
+ metadata: {
67
+ mime: file.mimeType,
68
+ size: file.size,
69
+ encoding: file.encoding,
70
+ modifiedAt: file.modifiedAt,
71
+ },
72
+ }));
73
+ this.host.emitBinary(encodeFileTransferFrame({
74
+ opcode: FileTransferOpcode.FileChunk,
75
+ requestId,
76
+ payload: file.bytes,
77
+ }));
78
+ this.host.emitBinary(encodeFileTransferFrame({
79
+ opcode: FileTransferOpcode.FileEnd,
80
+ requestId,
81
+ }));
82
+ }
83
+ else {
84
+ const file = await readExplorerFile({
85
+ root: cwd,
86
+ relativePath: requestedPath,
87
+ });
88
+ this.host.emit({
89
+ type: "file_explorer_response",
90
+ payload: {
91
+ cwd,
92
+ path: file.path,
93
+ mode,
94
+ directory: null,
95
+ file,
96
+ error: null,
97
+ requestId,
98
+ },
99
+ });
100
+ }
101
+ }
102
+ }
103
+ catch (error) {
104
+ this.logger.error({ err: error, cwd, path: requestedPath }, `Failed to fulfill file explorer request for workspace ${cwd}`);
105
+ this.host.emit({
106
+ type: "file_explorer_response",
107
+ payload: {
108
+ cwd,
109
+ path: requestedPath,
110
+ mode,
111
+ directory: null,
112
+ file: null,
113
+ error: getErrorMessage(error),
114
+ requestId,
115
+ },
116
+ });
117
+ }
118
+ }
119
+ handleFileUploadRequest(request) {
120
+ this.fileUploads.beginUpload(request);
121
+ }
122
+ async handleFileTransferFrame(frame) {
123
+ const response = await this.fileUploads.receiveFrame(frame);
124
+ if (response) {
125
+ this.host.emit(response);
126
+ }
127
+ }
128
+ async handleProjectIconRequest(request) {
129
+ const { cwd, requestId } = request;
130
+ try {
131
+ const icon = await getProjectIcon(cwd);
132
+ this.host.emit({
133
+ type: "project_icon_response",
134
+ payload: {
135
+ cwd,
136
+ icon,
137
+ error: null,
138
+ requestId,
139
+ },
140
+ });
141
+ }
142
+ catch (error) {
143
+ this.host.emit({
144
+ type: "project_icon_response",
145
+ payload: {
146
+ cwd,
147
+ icon: null,
148
+ error: getErrorMessage(error),
149
+ requestId,
150
+ },
151
+ });
152
+ }
153
+ }
154
+ async handleFileDownloadTokenRequest(request) {
155
+ const { cwd: workspaceCwd, path: requestedPath, requestId } = request;
156
+ const cwd = workspaceCwd.trim();
157
+ if (!cwd) {
158
+ this.host.emit({
159
+ type: "file_download_token_response",
160
+ payload: {
161
+ cwd: workspaceCwd,
162
+ path: requestedPath,
163
+ token: null,
164
+ fileName: null,
165
+ mimeType: null,
166
+ size: null,
167
+ error: "cwd is required",
168
+ requestId,
169
+ },
170
+ });
171
+ return;
172
+ }
173
+ this.logger.debug({ cwd, path: requestedPath }, `Handling file download token request for workspace ${cwd} (${requestedPath})`);
174
+ try {
175
+ const info = await getDownloadableFileInfo({
176
+ root: cwd,
177
+ relativePath: requestedPath,
178
+ });
179
+ const entry = this.downloadTokenStore.issueToken({
180
+ path: info.path,
181
+ absolutePath: info.absolutePath,
182
+ fileName: info.fileName,
183
+ mimeType: info.mimeType,
184
+ size: info.size,
185
+ });
186
+ this.host.emit({
187
+ type: "file_download_token_response",
188
+ payload: {
189
+ cwd,
190
+ path: info.path,
191
+ token: entry.token,
192
+ fileName: entry.fileName,
193
+ mimeType: entry.mimeType,
194
+ size: entry.size,
195
+ error: null,
196
+ requestId,
197
+ },
198
+ });
199
+ }
200
+ catch (error) {
201
+ this.logger.error({ err: error, cwd, path: requestedPath }, `Failed to issue download token for workspace ${cwd}`);
202
+ this.host.emit({
203
+ type: "file_download_token_response",
204
+ payload: {
205
+ cwd,
206
+ path: requestedPath,
207
+ token: null,
208
+ fileName: null,
209
+ mimeType: null,
210
+ size: null,
211
+ error: getErrorMessage(error),
212
+ requestId,
213
+ },
214
+ });
215
+ }
216
+ }
217
+ }
218
+ //# sourceMappingURL=workspace-files-session.js.map
@@ -0,0 +1,34 @@
1
+ import type pino from "pino";
2
+ import type { SessionInboundMessage, SessionOutboundMessage } from "../../messages.js";
3
+ import type { ProjectRegistry } from "../../workspace-registry.js";
4
+ export interface ProjectConfigSessionHost {
5
+ emit(msg: SessionOutboundMessage): void;
6
+ }
7
+ export interface ProjectConfigSessionOptions {
8
+ host: ProjectConfigSessionHost;
9
+ projectRegistry: Pick<ProjectRegistry, "list">;
10
+ logger: pino.Logger;
11
+ }
12
+ /**
13
+ * A client's read/write surface for a project's on-disk paseo.json. Resolves the
14
+ * request's repoRoot against the known (non-archived) project roots — accepting a
15
+ * trailing slash or a symlink via realpath — then reads or writes the config
16
+ * substrate and emits the matching response. Reaches no state beyond the injected
17
+ * project registry and the outbound channel.
18
+ */
19
+ export declare class ProjectConfigSession {
20
+ private readonly host;
21
+ private readonly projectRegistry;
22
+ private readonly logger;
23
+ constructor(options: ProjectConfigSessionOptions);
24
+ handleReadProjectConfigRequest(msg: Extract<SessionInboundMessage, {
25
+ type: "read_project_config_request";
26
+ }>): Promise<void>;
27
+ handleWriteProjectConfigRequest(msg: Extract<SessionInboundMessage, {
28
+ type: "write_project_config_request";
29
+ }>): Promise<void>;
30
+ private emitProjectConfigReadFailure;
31
+ private emitProjectConfigWriteFailure;
32
+ private resolveKnownProjectRoot;
33
+ }
34
+ //# sourceMappingURL=project-config-session.d.ts.map
@@ -0,0 +1,125 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { resolve, sep } from "path";
3
+ import { readPaseoConfigForEdit, writePaseoConfigForEdit, } from "../../../utils/paseo-config-file.js";
4
+ /**
5
+ * A client's read/write surface for a project's on-disk paseo.json. Resolves the
6
+ * request's repoRoot against the known (non-archived) project roots — accepting a
7
+ * trailing slash or a symlink via realpath — then reads or writes the config
8
+ * substrate and emits the matching response. Reaches no state beyond the injected
9
+ * project registry and the outbound channel.
10
+ */
11
+ export class ProjectConfigSession {
12
+ constructor(options) {
13
+ this.host = options.host;
14
+ this.projectRegistry = options.projectRegistry;
15
+ this.logger = options.logger;
16
+ }
17
+ async handleReadProjectConfigRequest(msg) {
18
+ const repoRoot = await this.resolveKnownProjectRoot(msg.repoRoot);
19
+ if (!repoRoot) {
20
+ this.emitProjectConfigReadFailure(msg, { code: "project_not_found" });
21
+ return;
22
+ }
23
+ const result = readPaseoConfigForEdit(repoRoot);
24
+ if (!result.ok) {
25
+ this.logger.warn({ repoRoot, requestId: msg.requestId, outcome: result.error.code }, "Failed to read project config");
26
+ this.emitProjectConfigReadFailure(msg, result.error, repoRoot);
27
+ return;
28
+ }
29
+ if (result.config === null) {
30
+ this.logger.debug({ repoRoot, requestId: msg.requestId, outcome: "missing_project_config" }, "Project config missing");
31
+ }
32
+ this.host.emit({
33
+ type: "read_project_config_response",
34
+ payload: {
35
+ requestId: msg.requestId,
36
+ repoRoot,
37
+ ok: true,
38
+ config: result.config,
39
+ revision: result.revision,
40
+ },
41
+ });
42
+ }
43
+ async handleWriteProjectConfigRequest(msg) {
44
+ const repoRoot = await this.resolveKnownProjectRoot(msg.repoRoot);
45
+ if (!repoRoot) {
46
+ this.emitProjectConfigWriteFailure(msg, { code: "project_not_found" });
47
+ return;
48
+ }
49
+ this.logger.debug({ repoRoot, requestId: msg.requestId, outcome: "write_attempt" }, "Writing project config");
50
+ const result = writePaseoConfigForEdit({
51
+ repoRoot,
52
+ config: msg.config,
53
+ expectedRevision: msg.expectedRevision,
54
+ });
55
+ if (!result.ok) {
56
+ this.logger.debug({ repoRoot, requestId: msg.requestId, outcome: result.error.code }, "Project config write did not complete");
57
+ this.emitProjectConfigWriteFailure(msg, result.error, repoRoot);
58
+ return;
59
+ }
60
+ this.logger.debug({ repoRoot, requestId: msg.requestId, outcome: "written" }, "Project config written");
61
+ this.host.emit({
62
+ type: "write_project_config_response",
63
+ payload: {
64
+ requestId: msg.requestId,
65
+ repoRoot,
66
+ ok: true,
67
+ config: result.config,
68
+ revision: result.revision,
69
+ },
70
+ });
71
+ }
72
+ emitProjectConfigReadFailure(msg, error, repoRoot = msg.repoRoot) {
73
+ this.host.emit({
74
+ type: "read_project_config_response",
75
+ payload: {
76
+ requestId: msg.requestId,
77
+ repoRoot,
78
+ ok: false,
79
+ error,
80
+ },
81
+ });
82
+ }
83
+ emitProjectConfigWriteFailure(msg, error, repoRoot = msg.repoRoot) {
84
+ this.host.emit({
85
+ type: "write_project_config_response",
86
+ payload: {
87
+ requestId: msg.requestId,
88
+ repoRoot,
89
+ ok: false,
90
+ error,
91
+ },
92
+ });
93
+ }
94
+ async resolveKnownProjectRoot(repoRoot) {
95
+ const requestedRoot = canonicalizeConfigRoot(repoRoot);
96
+ const projects = await this.projectRegistry.list();
97
+ for (const project of projects) {
98
+ if (project.archivedAt !== null) {
99
+ continue;
100
+ }
101
+ const projectRoot = canonicalizeConfigRoot(project.rootPath);
102
+ if (requestedRoot === projectRoot) {
103
+ return projectRoot;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ }
109
+ function canonicalizeConfigRoot(repoRoot) {
110
+ const resolved = resolve(repoRoot);
111
+ try {
112
+ return stripTrailingPathSeparators(realpathSync(resolved));
113
+ }
114
+ catch {
115
+ return stripTrailingPathSeparators(resolved);
116
+ }
117
+ }
118
+ function stripTrailingPathSeparators(path) {
119
+ let normalized = path;
120
+ while (normalized.length > 1 && normalized.endsWith(sep)) {
121
+ normalized = normalized.slice(0, -1);
122
+ }
123
+ return normalized;
124
+ }
125
+ //# sourceMappingURL=project-config-session.js.map
@@ -0,0 +1,74 @@
1
+ import type pino from "pino";
2
+ import type { SessionInboundMessage, SessionOutboundMessage } from "../../messages.js";
3
+ import { type ProviderSnapshotManager } from "../../agent/provider-snapshot-manager.js";
4
+ import type { AgentFeature, AgentSessionConfig } from "../../agent/agent-sdk-types.js";
5
+ import type { ProviderAvailability } from "../../agent/agent-manager.js";
6
+ import type { ProviderUsageService } from "../../../services/quota-fetcher/service.js";
7
+ /**
8
+ * The collaborators a provider-catalog request reaches that are NOT part of the
9
+ * provider domain. Two are CLIENT-COMPAT predicates the Session shell owns because
10
+ * agent-lifecycle shares the visibility gate: both read client state (appVersion /
11
+ * capabilities) LIVE, mutated post-construction via updateAppVersion /
12
+ * updateClientCapabilities. The two agent-control reads expose provider availability
13
+ * and draft features the AgentManager owns.
14
+ */
15
+ export interface ProviderCatalogSessionHost {
16
+ emit(msg: SessionOutboundMessage): void;
17
+ isProviderVisibleToClient(provider: string): boolean;
18
+ supportsCustomModeIcons(): boolean;
19
+ listProviderAvailability(): Promise<ProviderAvailability[]>;
20
+ listDraftFeatures(config: AgentSessionConfig): Promise<AgentFeature[]>;
21
+ }
22
+ export interface ProviderCatalogSessionOptions {
23
+ host: ProviderCatalogSessionHost;
24
+ providerSnapshotManager: ProviderSnapshotManager;
25
+ providerUsageService: ProviderUsageService;
26
+ logger: pino.Logger;
27
+ }
28
+ /**
29
+ * A client's provider catalog surface: model / mode / feature listing, the providers
30
+ * snapshot push + pull, provider diagnostics, and usage. The snapshot PUSH (start) and
31
+ * every PULL handler gate visibility and downgrade mode icons through the SAME predicates,
32
+ * so an older client sees one consistent provider set across both paths — the COMPAT
33
+ * invariant the shell could only enforce by code proximity before this carve.
34
+ */
35
+ export declare class ProviderCatalogSession {
36
+ private readonly host;
37
+ private readonly providerSnapshotManager;
38
+ private readonly providerUsageService;
39
+ private readonly logger;
40
+ private unsubscribeSnapshotEvents;
41
+ constructor(options: ProviderCatalogSessionOptions);
42
+ start(): void;
43
+ dispose(): void;
44
+ private downgradeModeIconsForClient;
45
+ private downgradeEntryModesForClient;
46
+ private emitProviderDisabledResponse;
47
+ handleListProviderModelsRequest(msg: Extract<SessionInboundMessage, {
48
+ type: "list_provider_models_request";
49
+ }>): Promise<void>;
50
+ handleListProviderModesRequest(msg: Extract<SessionInboundMessage, {
51
+ type: "list_provider_modes_request";
52
+ }>): Promise<void>;
53
+ private getProviderSnapshotEntryForRead;
54
+ private buildDraftAgentSessionConfig;
55
+ handleListProviderFeaturesRequest(msg: Extract<SessionInboundMessage, {
56
+ type: "list_provider_features_request";
57
+ }>): Promise<void>;
58
+ handleListAvailableProvidersRequest(msg: Extract<SessionInboundMessage, {
59
+ type: "list_available_providers_request";
60
+ }>): Promise<void>;
61
+ handleGetProvidersSnapshotRequest(msg: Extract<SessionInboundMessage, {
62
+ type: "get_providers_snapshot_request";
63
+ }>): Promise<void>;
64
+ handleRefreshProvidersSnapshotRequest(msg: Extract<SessionInboundMessage, {
65
+ type: "refresh_providers_snapshot_request";
66
+ }>): Promise<void>;
67
+ handleProviderDiagnosticRequest(msg: Extract<SessionInboundMessage, {
68
+ type: "provider_diagnostic_request";
69
+ }>): Promise<void>;
70
+ handleProviderUsageListRequest(msg: Extract<SessionInboundMessage, {
71
+ type: "provider.usage.list.request";
72
+ }>): Promise<void>;
73
+ }
74
+ //# sourceMappingURL=provider-catalog-session.d.ts.map