@roamcode.ai/server 1.0.0 → 1.0.1

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 (99) hide show
  1. package/dist/index.d.ts +2406 -82
  2. package/dist/index.js +8314 -44
  3. package/dist/managed-update-helper.js +621 -26
  4. package/dist/mcp-send.js +76 -90
  5. package/dist/start.d.ts +591 -9
  6. package/dist/start.js +7629 -353
  7. package/package.json +3 -3
  8. package/dist/auth.d.ts +0 -63
  9. package/dist/auth.js +0 -133
  10. package/dist/claude-auth-service.d.ts +0 -76
  11. package/dist/claude-auth-service.js +0 -217
  12. package/dist/claude-latest-service.d.ts +0 -34
  13. package/dist/claude-latest-service.js +0 -61
  14. package/dist/config.d.ts +0 -78
  15. package/dist/config.js +0 -59
  16. package/dist/data-dir.d.ts +0 -42
  17. package/dist/data-dir.js +0 -70
  18. package/dist/diag.d.ts +0 -43
  19. package/dist/diag.js +0 -83
  20. package/dist/fs-service.d.ts +0 -90
  21. package/dist/fs-service.js +0 -290
  22. package/dist/managed-runtime.d.ts +0 -51
  23. package/dist/managed-runtime.js +0 -411
  24. package/dist/managed-update-helper.d.ts +0 -2
  25. package/dist/mcp-send.d.ts +0 -33
  26. package/dist/origin-check.d.ts +0 -37
  27. package/dist/origin-check.js +0 -101
  28. package/dist/pane-status.d.ts +0 -61
  29. package/dist/pane-status.js +0 -145
  30. package/dist/providers/claude-metadata-service.d.ts +0 -58
  31. package/dist/providers/claude-metadata-service.js +0 -352
  32. package/dist/providers/claude-provider.d.ts +0 -11
  33. package/dist/providers/claude-provider.js +0 -166
  34. package/dist/providers/codex-activity.d.ts +0 -21
  35. package/dist/providers/codex-activity.js +0 -122
  36. package/dist/providers/codex-app-server-client.d.ts +0 -90
  37. package/dist/providers/codex-app-server-client.js +0 -485
  38. package/dist/providers/codex-latest-service.d.ts +0 -50
  39. package/dist/providers/codex-latest-service.js +0 -174
  40. package/dist/providers/codex-metadata-service.d.ts +0 -161
  41. package/dist/providers/codex-metadata-service.js +0 -686
  42. package/dist/providers/codex-profile-client.d.ts +0 -16
  43. package/dist/providers/codex-profile-client.js +0 -52
  44. package/dist/providers/codex-profile-security.d.ts +0 -23
  45. package/dist/providers/codex-profile-security.js +0 -161
  46. package/dist/providers/codex-provider.d.ts +0 -15
  47. package/dist/providers/codex-provider.js +0 -174
  48. package/dist/providers/codex-thread-coordinator.d.ts +0 -18
  49. package/dist/providers/codex-thread-coordinator.js +0 -93
  50. package/dist/providers/codex-thread-persistence.d.ts +0 -9
  51. package/dist/providers/codex-thread-persistence.js +0 -45
  52. package/dist/providers/codex-thread-resolver.d.ts +0 -59
  53. package/dist/providers/codex-thread-resolver.js +0 -322
  54. package/dist/providers/options.d.ts +0 -7
  55. package/dist/providers/options.js +0 -155
  56. package/dist/providers/provider-artifacts.d.ts +0 -3
  57. package/dist/providers/provider-artifacts.js +0 -30
  58. package/dist/providers/registry.d.ts +0 -7
  59. package/dist/providers/registry.js +0 -23
  60. package/dist/providers/types.d.ts +0 -95
  61. package/dist/providers/types.js +0 -8
  62. package/dist/push-dispatch.d.ts +0 -81
  63. package/dist/push-dispatch.js +0 -100
  64. package/dist/push-store.d.ts +0 -25
  65. package/dist/push-store.js +0 -79
  66. package/dist/rate-limit.d.ts +0 -52
  67. package/dist/rate-limit.js +0 -72
  68. package/dist/server-config.d.ts +0 -60
  69. package/dist/server-config.js +0 -77
  70. package/dist/service-install.d.ts +0 -60
  71. package/dist/service-install.js +0 -221
  72. package/dist/session-defaults.d.ts +0 -26
  73. package/dist/session-defaults.js +0 -60
  74. package/dist/session-store.d.ts +0 -81
  75. package/dist/session-store.js +0 -654
  76. package/dist/static-routes.d.ts +0 -101
  77. package/dist/static-routes.js +0 -188
  78. package/dist/terminal-capability.d.ts +0 -5
  79. package/dist/terminal-capability.js +0 -27
  80. package/dist/terminal-manager.d.ts +0 -224
  81. package/dist/terminal-manager.js +0 -917
  82. package/dist/terminal-process.d.ts +0 -85
  83. package/dist/terminal-process.js +0 -238
  84. package/dist/terminal-shared.d.ts +0 -36
  85. package/dist/terminal-shared.js +0 -43
  86. package/dist/tmux-list.d.ts +0 -11
  87. package/dist/tmux-list.js +0 -39
  88. package/dist/transport.d.ts +0 -123
  89. package/dist/transport.js +0 -1559
  90. package/dist/updater.d.ts +0 -161
  91. package/dist/updater.js +0 -451
  92. package/dist/usage-service.d.ts +0 -118
  93. package/dist/usage-service.js +0 -173
  94. package/dist/vapid.d.ts +0 -17
  95. package/dist/vapid.js +0 -31
  96. package/dist/web-push-send.d.ts +0 -20
  97. package/dist/web-push-send.js +0 -21
  98. package/dist/ws-ticket.d.ts +0 -47
  99. package/dist/ws-ticket.js +0 -62
@@ -1,16 +0,0 @@
1
- import type { ZodType } from "zod";
2
- import { type SpawnCodexAppServerTransport } from "./codex-app-server-client.js";
3
- export interface CodexProfileClientLifecycle {
4
- readConfig<T>(profile: string, codexHome: string, cwd: string, schema: ZodType<T>): Promise<T>;
5
- }
6
- export interface CreateCodexProfileClientLifecycleOptions {
7
- readonly codexBin: string;
8
- readonly env?: NodeJS.ProcessEnv;
9
- readonly spawnTransport?: SpawnCodexAppServerTransport;
10
- readonly timeoutMs?: number;
11
- }
12
- /** Required selected-profile lifecycle. It owns Task 6 client construction, canonical env, start, and stop. */
13
- export declare function createCodexProfileClientLifecycle(options: CreateCodexProfileClientLifecycleOptions): CodexProfileClientLifecycle;
14
- /** Isolated protocol seam for service tests; unavailable outside the test process. */
15
- export declare function createCodexProfileClientLifecycleForTests(readConfig: CodexProfileClientLifecycle["readConfig"]): CodexProfileClientLifecycle;
16
- export declare function isCodexProfileClientLifecycle(value: unknown): value is CodexProfileClientLifecycle;
@@ -1,52 +0,0 @@
1
- import { CodexAppServerClient, } from "./codex-app-server-client.js";
2
- import { ProviderError } from "./types.js";
3
- const lifecycles = new WeakSet();
4
- function unavailable() {
5
- return new ProviderError("OSS_PROVIDER_DEFERRED", "Codex profile capability proof is unavailable");
6
- }
7
- function brand(lifecycle) {
8
- lifecycles.add(lifecycle);
9
- return lifecycle;
10
- }
11
- /** Required selected-profile lifecycle. It owns Task 6 client construction, canonical env, start, and stop. */
12
- export function createCodexProfileClientLifecycle(options) {
13
- return brand({
14
- async readConfig(profile, codexHome, cwd, schema) {
15
- const clientOptions = {
16
- codexBin: options.codexBin,
17
- profile,
18
- env: { ...(options.env ?? process.env), CODEX_HOME: codexHome },
19
- ...(options.spawnTransport ? { spawnTransport: options.spawnTransport } : {}),
20
- ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
21
- };
22
- const client = new CodexAppServerClient(clientOptions);
23
- let result;
24
- let failed = false;
25
- try {
26
- await client.start();
27
- result = await client.request("config/read", { cwd, includeLayers: false }, schema);
28
- }
29
- catch {
30
- failed = true;
31
- }
32
- try {
33
- await client.stop();
34
- }
35
- catch {
36
- failed = true;
37
- }
38
- if (failed || result === undefined)
39
- throw unavailable();
40
- return result;
41
- },
42
- });
43
- }
44
- /** Isolated protocol seam for service tests; unavailable outside the test process. */
45
- export function createCodexProfileClientLifecycleForTests(readConfig) {
46
- if (process.env.NODE_ENV !== "test")
47
- throw unavailable();
48
- return brand({ readConfig });
49
- }
50
- export function isCodexProfileClientLifecycle(value) {
51
- return typeof value === "object" && value !== null && lifecycles.has(value);
52
- }
@@ -1,23 +0,0 @@
1
- export interface CodexProfileFingerprint {
2
- readonly path: string;
3
- readonly realPath: string;
4
- readonly dev: bigint;
5
- readonly ino: bigint;
6
- readonly size: bigint;
7
- readonly mtimeNs: bigint;
8
- readonly digest: string;
9
- }
10
- export interface CodexLaunchConfigFingerprint {
11
- readonly homeDev: bigint;
12
- readonly homeIno: bigint;
13
- readonly homeSize: bigint;
14
- readonly homeMtimeNs: bigint;
15
- readonly base: CodexProfileFingerprint | null;
16
- readonly profile: CodexProfileFingerprint;
17
- }
18
- export declare function resolveSecureCodexHome(configured: string | undefined): Promise<string | undefined>;
19
- export declare function listSecureProfileNames(codexHome: string, validName: (name: string) => boolean): Promise<string[]>;
20
- export declare function captureProfileFingerprint(codexHome: string, profile: string): Promise<CodexProfileFingerprint | undefined>;
21
- export declare function sameProfileFingerprint(left: CodexProfileFingerprint, right: CodexProfileFingerprint | undefined): boolean;
22
- export declare function captureLaunchConfigFingerprint(codexHome: string, profile: string): Promise<CodexLaunchConfigFingerprint | undefined>;
23
- export declare function sameLaunchConfigFingerprint(left: CodexLaunchConfigFingerprint, right: CodexLaunchConfigFingerprint | undefined): boolean;
@@ -1,161 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { lstat, readFile, readdir, realpath } from "node:fs/promises";
3
- import { isAbsolute, join, parse, relative, resolve, sep } from "node:path";
4
- const PROFILE_SUFFIX = ".config.toml";
5
- const MAX_DIRECTORY_ENTRIES = 1_024;
6
- const MAX_CONFIG_BYTES = 1024 * 1024;
7
- async function hasSymlinkComponent(path) {
8
- const absolute = resolve(path);
9
- const root = parse(absolute).root;
10
- const segments = absolute.slice(root.length).split(sep).filter(Boolean);
11
- let current = root;
12
- for (const segment of segments) {
13
- current = join(current, segment);
14
- if ((await lstat(current)).isSymbolicLink())
15
- return true;
16
- }
17
- return false;
18
- }
19
- export async function resolveSecureCodexHome(configured) {
20
- if (!configured || !isAbsolute(configured))
21
- return undefined;
22
- try {
23
- if (await hasSymlinkComponent(configured))
24
- return undefined;
25
- const stat = await lstat(configured, { bigint: true });
26
- if (!stat.isDirectory() || stat.isSymbolicLink())
27
- return undefined;
28
- const canonical = await realpath(configured);
29
- const after = await lstat(configured, { bigint: true });
30
- return canonical === resolve(configured) &&
31
- after.isDirectory() &&
32
- !after.isSymbolicLink() &&
33
- stat.dev === after.dev &&
34
- stat.ino === after.ino
35
- ? canonical
36
- : undefined;
37
- }
38
- catch {
39
- return undefined;
40
- }
41
- }
42
- export async function listSecureProfileNames(codexHome, validName) {
43
- if ((await resolveSecureCodexHome(codexHome)) !== codexHome)
44
- return [];
45
- const before = await lstat(codexHome, { bigint: true });
46
- const entries = await readdir(codexHome, { withFileTypes: true });
47
- const profiles = [];
48
- for (const entry of entries.slice(0, MAX_DIRECTORY_ENTRIES)) {
49
- if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith(PROFILE_SUFFIX))
50
- continue;
51
- const profile = entry.name.slice(0, -PROFILE_SUFFIX.length);
52
- if (!validName(profile))
53
- continue;
54
- const fingerprint = await captureProfileFingerprint(codexHome, profile);
55
- if (fingerprint)
56
- profiles.push(profile);
57
- }
58
- const after = await lstat(codexHome, { bigint: true });
59
- if ((await resolveSecureCodexHome(codexHome)) !== codexHome ||
60
- before.dev !== after.dev ||
61
- before.ino !== after.ino ||
62
- before.size !== after.size ||
63
- before.mtimeNs !== after.mtimeNs) {
64
- return [];
65
- }
66
- return [...new Set(profiles)].sort((left, right) => left.localeCompare(right));
67
- }
68
- export async function captureProfileFingerprint(codexHome, profile) {
69
- return captureConfigFileFingerprint(codexHome, join(codexHome, `${profile}${PROFILE_SUFFIX}`));
70
- }
71
- async function captureConfigFileFingerprint(codexHome, path) {
72
- try {
73
- if (relative(codexHome, path).startsWith(".."))
74
- return undefined;
75
- const stat = await lstat(path, { bigint: true });
76
- if (!stat.isFile() || stat.isSymbolicLink())
77
- return undefined;
78
- if (stat.size > BigInt(MAX_CONFIG_BYTES))
79
- return undefined;
80
- const realPath = await realpath(path);
81
- if (realPath !== path)
82
- return undefined;
83
- const contents = await readFile(path);
84
- const after = await lstat(path, { bigint: true });
85
- if (!after.isFile() ||
86
- after.isSymbolicLink() ||
87
- stat.dev !== after.dev ||
88
- stat.ino !== after.ino ||
89
- stat.size !== after.size ||
90
- stat.mtimeNs !== after.mtimeNs) {
91
- return undefined;
92
- }
93
- return {
94
- path,
95
- realPath,
96
- dev: stat.dev,
97
- ino: stat.ino,
98
- size: stat.size,
99
- mtimeNs: stat.mtimeNs,
100
- digest: createHash("sha256").update(contents).digest("hex"),
101
- };
102
- }
103
- catch {
104
- return undefined;
105
- }
106
- }
107
- export function sameProfileFingerprint(left, right) {
108
- return Boolean(right &&
109
- left.path === right.path &&
110
- left.realPath === right.realPath &&
111
- left.dev === right.dev &&
112
- left.ino === right.ino &&
113
- left.size === right.size &&
114
- left.mtimeNs === right.mtimeNs &&
115
- left.digest === right.digest);
116
- }
117
- export async function captureLaunchConfigFingerprint(codexHome, profile) {
118
- try {
119
- if ((await resolveSecureCodexHome(codexHome)) !== codexHome)
120
- return undefined;
121
- const home = await lstat(codexHome, { bigint: true });
122
- const selected = await captureProfileFingerprint(codexHome, profile);
123
- if (!selected)
124
- return undefined;
125
- const basePath = join(codexHome, "config.toml");
126
- let base = null;
127
- try {
128
- const baseStat = await lstat(basePath);
129
- if (!baseStat.isFile() || baseStat.isSymbolicLink())
130
- return undefined;
131
- const capturedBase = await captureConfigFileFingerprint(codexHome, basePath);
132
- if (!capturedBase)
133
- return undefined;
134
- base = capturedBase;
135
- }
136
- catch (error) {
137
- if (error.code !== "ENOENT")
138
- return undefined;
139
- }
140
- return {
141
- homeDev: home.dev,
142
- homeIno: home.ino,
143
- homeSize: home.size,
144
- homeMtimeNs: home.mtimeNs,
145
- base,
146
- profile: selected,
147
- };
148
- }
149
- catch {
150
- return undefined;
151
- }
152
- }
153
- export function sameLaunchConfigFingerprint(left, right) {
154
- return Boolean(right &&
155
- left.homeDev === right.homeDev &&
156
- left.homeIno === right.homeIno &&
157
- left.homeSize === right.homeSize &&
158
- left.homeMtimeNs === right.homeMtimeNs &&
159
- (left.base === null ? right.base === null : right.base !== null && sameProfileFingerprint(left.base, right.base)) &&
160
- sameProfileFingerprint(left.profile, right.profile));
161
- }
@@ -1,15 +0,0 @@
1
- import { type AttachSpawnOptions } from "../config.js";
2
- import { type AgentProvider, type CodexProfileLaunchProof, type ProviderProcessContext, type ProviderAvailability } from "./types.js";
3
- export interface CreateCodexProviderOptions {
4
- codexBin: string;
5
- env?: NodeJS.ProcessEnv;
6
- attach?: AttachSpawnOptions;
7
- getAttach?: () => AttachSpawnOptions | undefined;
8
- probe?: () => Promise<ProviderAvailability>;
9
- /** Task 7 supplies the effective-provider capability check; this adapter never reads profile files. */
10
- validateProfile?(profile: string, cwd: string): CodexProfileLaunchProof | Promise<CodexProfileLaunchProof>;
11
- }
12
- type CodexAttachContext = Pick<AttachSpawnOptions, "baseUrl" | "token" | "mcpScriptPath">;
13
- export declare function buildCodexArgs(context: ProviderProcessContext, attach?: CodexAttachContext): string[];
14
- export declare function createCodexProvider(options: CreateCodexProviderOptions): AgentProvider;
15
- export {};
@@ -1,174 +0,0 @@
1
- import { codexMcpTokenPathFor } from "../config.js";
2
- import { isAbsolute } from "node:path";
3
- import { classifyCodexPane, createCodexOscParser, parseCodexOscNotifications, parseCodexRuntimeMetadata, } from "./codex-activity.js";
4
- import { assertExactCodexResumeArgs } from "./codex-thread-resolver.js";
5
- import { cleanupProviderArtifacts, writeProviderArtifact0600 } from "./provider-artifacts.js";
6
- import { ProviderError, } from "./types.js";
7
- function configArg(key, value) {
8
- return ["-c", `${key}=${JSON.stringify(value)}`];
9
- }
10
- function validResumeId(id) {
11
- return (id !== undefined &&
12
- id.trim().length > 0 &&
13
- id.length <= 2048 &&
14
- !/[\p{Cc}\p{Zl}\p{Zp}]/u.test(id) &&
15
- !id.trimStart().startsWith("-"));
16
- }
17
- function usableAttach(attach) {
18
- return Boolean(attach?.baseUrl &&
19
- attach.token &&
20
- Buffer.byteLength(attach.token, "utf8") <= 4096 &&
21
- !/[\p{Cc}\p{Zl}\p{Zp}]/u.test(attach.token) &&
22
- attach.mcpScriptPath &&
23
- attach.dataDir);
24
- }
25
- function profileUnavailable() {
26
- return new ProviderError("OSS_PROVIDER_DEFERRED", "Codex profile capability proof is unavailable");
27
- }
28
- export function buildCodexArgs(context, attach) {
29
- if (context.options.provider !== "codex") {
30
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Codex provider received non-Codex options");
31
- }
32
- if (context.intent === "fresh" && context.providerSessionId !== undefined) {
33
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Fresh Codex launch cannot include a resume identity");
34
- }
35
- const options = context.options;
36
- const args = [];
37
- let resumeId;
38
- if (context.intent === "resume") {
39
- resumeId = context.providerSessionId;
40
- if (!validResumeId(resumeId)) {
41
- throw new ProviderError("RESUME_IDENTITY_UNAVAILABLE", "Codex resume requires an exact safe session id");
42
- }
43
- args.push("resume");
44
- }
45
- if (options.model)
46
- args.push("--model", options.model);
47
- if (options.profile)
48
- args.push("--profile", options.profile);
49
- if (options.dangerouslyBypassApprovalsAndSandbox) {
50
- args.push("--dangerously-bypass-approvals-and-sandbox");
51
- }
52
- else {
53
- if (options.sandbox)
54
- args.push("--sandbox", options.sandbox);
55
- if (options.approvalPolicy)
56
- args.push("--ask-for-approval", options.approvalPolicy);
57
- }
58
- if (options.webSearch)
59
- args.push("--search");
60
- for (const dir of options.addDirs ?? [])
61
- args.push("--add-dir", dir);
62
- if (options.reasoningEffort)
63
- args.push(...configArg("model_reasoning_effort", options.reasoningEffort));
64
- if (attach) {
65
- args.push(...configArg("mcp_servers.roamcode.command", process.execPath));
66
- args.push(...configArg("mcp_servers.roamcode.args", [attach.mcpScriptPath]));
67
- args.push(...configArg("mcp_servers.roamcode.env_vars", ["RC_BASE_URL", "RC_SESSION_ID", "RC_TOKEN_FILE"]));
68
- }
69
- args.push(...configArg("tui.notifications", ["agent-turn-complete", "approval-requested", "plan-mode-prompt"]));
70
- args.push(...configArg("tui.notification_method", "osc9"));
71
- args.push(...configArg("tui.notification_condition", "always"));
72
- // Keep the conversation in tmux's ordinary pane history. With tmux mouse mode enabled, wheel/trackpad
73
- // and mobile scroll can read that history in place instead of opening Codex's separate transcript UI.
74
- args.push("--no-alt-screen");
75
- if (resumeId)
76
- args.push("--", resumeId);
77
- if (context.intent === "resume")
78
- assertExactCodexResumeArgs(args);
79
- return args;
80
- }
81
- export function createCodexProvider(options) {
82
- return {
83
- id: "codex",
84
- displayName: "Codex",
85
- resumeIdentity: "required",
86
- probe: options.probe ?? (() => Promise.resolve({ terminalAvailable: true, metadataAvailable: false })),
87
- buildProcess: async (context) => {
88
- if (context.options.provider !== "codex") {
89
- throw new ProviderError("INVALID_PROVIDER_OPTIONS", "Codex provider received non-Codex options");
90
- }
91
- let profileProof;
92
- if (context.options.profile) {
93
- let validateProfile;
94
- try {
95
- validateProfile = options.validateProfile;
96
- }
97
- catch {
98
- throw profileUnavailable();
99
- }
100
- if (!validateProfile)
101
- throw profileUnavailable();
102
- try {
103
- profileProof = await validateProfile(context.options.profile, context.cwd);
104
- }
105
- catch {
106
- throw profileUnavailable();
107
- }
108
- if (profileProof.profile !== context.options.profile ||
109
- !isAbsolute(profileProof.codexHome) ||
110
- /[\p{Cc}\p{Zl}\p{Zp}]/u.test(profileProof.codexHome) ||
111
- typeof profileProof.assertUnchanged !== "function") {
112
- throw profileUnavailable();
113
- }
114
- }
115
- const env = { ...(options.env ?? process.env) };
116
- delete env.RC_TOKEN;
117
- delete env.RC_TOKEN_FILE;
118
- if (profileProof)
119
- env.CODEX_HOME = profileProof.codexHome;
120
- const preSpawnCheck = profileProof
121
- ? async () => {
122
- try {
123
- await profileProof.assertUnchanged();
124
- }
125
- catch {
126
- throw profileUnavailable();
127
- }
128
- }
129
- : undefined;
130
- const ownedPaths = [];
131
- const candidateAttach = context.attach ?? options.getAttach?.() ?? options.attach;
132
- let attach;
133
- try {
134
- if (usableAttach(candidateAttach)) {
135
- const tokenPath = codexMcpTokenPathFor(candidateAttach.dataDir, context.roamSessionId);
136
- if (writeProviderArtifact0600(tokenPath, candidateAttach.token, context, ownedPaths)) {
137
- attach = candidateAttach;
138
- env.RC_BASE_URL = candidateAttach.baseUrl;
139
- env.RC_SESSION_ID = context.roamSessionId;
140
- env.RC_TOKEN_FILE = tokenPath;
141
- }
142
- }
143
- const args = buildCodexArgs(context, attach);
144
- return {
145
- executable: options.codexBin,
146
- args,
147
- env,
148
- cleanupPaths: ownedPaths,
149
- ...(preSpawnCheck ? { preSpawnCheck } : {}),
150
- integration: attach
151
- ? {
152
- attachments: "ready",
153
- activity: "degraded",
154
- detail: "Codex activity uses display-text signals with pane fallback",
155
- }
156
- : {
157
- attachments: "degraded",
158
- activity: "degraded",
159
- detail: "RoamCode attachment MCP is not configured; Codex activity uses display-text signals with pane fallback",
160
- },
161
- };
162
- }
163
- catch (error) {
164
- cleanupProviderArtifacts(ownedPaths);
165
- throw error;
166
- }
167
- },
168
- createRuntimeSignalParser: createCodexOscParser,
169
- runtimeSignals: parseCodexOscNotifications,
170
- classifyPane: classifyCodexPane,
171
- runtimeMetadata: parseCodexRuntimeMetadata,
172
- cleanup: cleanupProviderArtifacts,
173
- };
174
- }
@@ -1,18 +0,0 @@
1
- export interface CodexSpawnLease {
2
- readonly started: Promise<void>;
3
- cancel(): Promise<void>;
4
- }
5
- declare class CodexThreadResolutionCoordinator {
6
- private locked;
7
- private poisoned;
8
- private readonly queue;
9
- acquire(signal?: AbortSignal): Promise<() => void>;
10
- acknowledgeCancellation(lease: CodexSpawnLease, timeoutMs: number): Promise<void>;
11
- /** A launch callback ran but did not return a cancellable lease, so later discovery is permanently unsafe. */
12
- poisonUnknownSpawnOutcome(): void;
13
- resetForTests(): void;
14
- private poison;
15
- private grant;
16
- }
17
- export declare const codexThreadResolutionCoordinator: CodexThreadResolutionCoordinator;
18
- export {};
@@ -1,93 +0,0 @@
1
- import { ProviderError } from "./types.js";
2
- function unavailable() {
3
- return new ProviderError("RESUME_IDENTITY_UNAVAILABLE", "Codex resume identity is unavailable");
4
- }
5
- class CodexThreadResolutionCoordinator {
6
- locked = false;
7
- poisoned = false;
8
- queue = [];
9
- acquire(signal) {
10
- if (this.poisoned || signal?.aborted)
11
- return Promise.reject(unavailable());
12
- return new Promise((resolve, reject) => {
13
- const waiter = { resolve, reject, signal };
14
- if (signal) {
15
- waiter.abort = () => {
16
- const index = this.queue.indexOf(waiter);
17
- if (index >= 0)
18
- this.queue.splice(index, 1);
19
- reject(unavailable());
20
- };
21
- signal.addEventListener("abort", waiter.abort, { once: true });
22
- }
23
- if (!this.locked) {
24
- this.locked = true;
25
- this.grant(waiter);
26
- }
27
- else {
28
- this.queue.push(waiter);
29
- }
30
- });
31
- }
32
- async acknowledgeCancellation(lease, timeoutMs) {
33
- let timer;
34
- try {
35
- const acknowledgement = lease.cancel();
36
- if (!(acknowledgement instanceof Promise))
37
- throw unavailable();
38
- await Promise.race([
39
- acknowledgement,
40
- new Promise((_, reject) => {
41
- timer = setTimeout(() => reject(unavailable()), timeoutMs);
42
- timer.unref?.();
43
- }),
44
- ]);
45
- }
46
- catch {
47
- this.poison();
48
- throw unavailable();
49
- }
50
- finally {
51
- if (timer)
52
- clearTimeout(timer);
53
- }
54
- }
55
- /** A launch callback ran but did not return a cancellable lease, so later discovery is permanently unsafe. */
56
- poisonUnknownSpawnOutcome() {
57
- this.poison();
58
- }
59
- resetForTests() {
60
- if (process.env.NODE_ENV !== "test" || this.locked || this.queue.length > 0)
61
- throw unavailable();
62
- this.poisoned = false;
63
- }
64
- poison() {
65
- this.poisoned = true;
66
- for (const waiter of this.queue.splice(0)) {
67
- if (waiter.abort)
68
- waiter.signal?.removeEventListener("abort", waiter.abort);
69
- waiter.reject(unavailable());
70
- }
71
- }
72
- grant(waiter) {
73
- if (waiter.abort)
74
- waiter.signal?.removeEventListener("abort", waiter.abort);
75
- if (this.poisoned) {
76
- waiter.reject(unavailable());
77
- this.locked = false;
78
- return;
79
- }
80
- let released = false;
81
- waiter.resolve(() => {
82
- if (released)
83
- return;
84
- released = true;
85
- const next = this.queue.shift();
86
- if (next)
87
- this.grant(next);
88
- else
89
- this.locked = false;
90
- });
91
- }
92
- }
93
- export const codexThreadResolutionCoordinator = new CodexThreadResolutionCoordinator();
@@ -1,9 +0,0 @@
1
- import { type SessionStore } from "../session-store.js";
2
- export interface CodexThreadPersistence {
3
- markProvisional(id: string): void;
4
- clear(id: string): void;
5
- commit(id: string): void;
6
- }
7
- /** Creates the only persistence object accepted by the exact-thread resolver. */
8
- export declare function createCodexThreadPersistence(store: SessionStore, roamSessionId: string): CodexThreadPersistence;
9
- export declare function isCodexThreadPersistence(value: unknown): value is CodexThreadPersistence;
@@ -1,45 +0,0 @@
1
- import { isConcreteSessionStore } from "../session-store.js";
2
- const capabilities = new WeakSet();
3
- class StoreBoundCodexThreadPersistence {
4
- store;
5
- roamSessionId;
6
- constructor(store, roamSessionId) {
7
- this.store = store;
8
- this.roamSessionId = roamSessionId;
9
- }
10
- markProvisional(id) {
11
- this.requireCodexSession();
12
- this.store.markProvisionalProviderSessionId(this.roamSessionId, id);
13
- if (this.store.get(this.roamSessionId)?.providerSessionId !== undefined)
14
- throw new Error("Persistence failed");
15
- }
16
- clear(id) {
17
- this.requireCodexSession();
18
- this.store.clearProvisionalProviderSessionId(this.roamSessionId, id);
19
- if (this.store.get(this.roamSessionId)?.providerSessionId !== undefined)
20
- throw new Error("Rollback failed");
21
- }
22
- commit(id) {
23
- this.requireCodexSession();
24
- this.store.commitProvisionalProviderSessionId(this.roamSessionId, id);
25
- if (this.store.get(this.roamSessionId)?.providerSessionId !== id)
26
- throw new Error("Persistence identity changed");
27
- }
28
- requireCodexSession() {
29
- const session = this.store.get(this.roamSessionId);
30
- if (!session || session.provider !== "codex")
31
- throw new Error("Codex session is unavailable");
32
- return session;
33
- }
34
- }
35
- /** Creates the only persistence object accepted by the exact-thread resolver. */
36
- export function createCodexThreadPersistence(store, roamSessionId) {
37
- if (!isConcreteSessionStore(store) || roamSessionId.length === 0)
38
- throw new Error("Invalid Codex persistence");
39
- const capability = new StoreBoundCodexThreadPersistence(store, roamSessionId);
40
- capabilities.add(capability);
41
- return capability;
42
- }
43
- export function isCodexThreadPersistence(value) {
44
- return typeof value === "object" && value !== null && capabilities.has(value);
45
- }
@@ -1,59 +0,0 @@
1
- import { type ZodType } from "zod";
2
- import { type CodexSpawnLease } from "./codex-thread-coordinator.js";
3
- import { type CodexThreadPersistence } from "./codex-thread-persistence.js";
4
- export interface CodexThreadInventoryEntry {
5
- readonly id: string;
6
- readonly cwd: string;
7
- readonly source: "cli" | "vscode" | "exec" | "appServer" | "unknown" | Record<string, unknown>;
8
- /** Current protocol uses Unix seconds; milliseconds are normalized defensively for compatible revisions. */
9
- readonly createdAt: number;
10
- }
11
- export type ReadCodexThreadInventory = () => Promise<readonly CodexThreadInventoryEntry[]>;
12
- export interface CodexThreadRpc {
13
- request<T>(method: string, params: unknown, schema: ZodType<T>): Promise<T>;
14
- }
15
- export interface CreateCodexThreadInventoryOptions {
16
- readonly cwd: string;
17
- readonly maxPages?: number;
18
- readonly maxItems?: number;
19
- }
20
- export interface CodexThreadResolverOptions {
21
- readonly inventory: ReadCodexThreadInventory;
22
- readonly now?: () => number;
23
- readonly sleep?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
24
- readonly deadlineMs?: number;
25
- readonly pollIntervalMs?: number;
26
- readonly maxInventoryItems?: number;
27
- readonly creationSkewMs?: number;
28
- readonly cancellationAckMs?: number;
29
- }
30
- export interface ResolveCodexThreadOptions {
31
- readonly cwd: string;
32
- /** The launch callback is intentionally inside the process-wide identity mutex. */
33
- readonly spawn: (signal: AbortSignal) => CodexSpawnLease;
34
- readonly persistence: CodexThreadPersistence;
35
- readonly signal?: AbortSignal;
36
- }
37
- export declare function resetCodexThreadResolutionCoordinatorForTests(): void;
38
- /** Shared assertion used by the provider's generated resume argv regression. */
39
- export declare function assertExactCodexResumeArgs(args: readonly string[]): void;
40
- /** Creates the bounded current-protocol `thread/list` reader used by resolver snapshots and polls. */
41
- export declare function createCodexThreadInventory(rpc: CodexThreadRpc, options: CreateCodexThreadInventoryOptions): ReadCodexThreadInventory;
42
- export declare class CodexThreadResolver {
43
- private readonly inventory;
44
- private readonly now;
45
- private readonly sleep;
46
- private readonly deadlineMs;
47
- private readonly pollIntervalMs;
48
- private readonly maxInventoryItems;
49
- private readonly creationSkewMs;
50
- private readonly cancellationAckMs;
51
- private readonly maxPolls;
52
- constructor(options: CodexThreadResolverOptions);
53
- resolveAfterSpawn(options: ResolveCodexThreadOptions): Promise<string>;
54
- private resolveLocked;
55
- private readInventory;
56
- private candidates;
57
- private sameIdentity;
58
- private throwIfCancelled;
59
- }