letagents 0.12.9 → 0.12.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.
Files changed (117) hide show
  1. package/README.md +16 -15
  2. package/dist/mcp/agent-prompt-delivery.js +20 -0
  3. package/dist/mcp/codex-session/app-server.js +89 -0
  4. package/dist/mcp/codex-session/index.js +10 -0
  5. package/dist/mcp/codex-session/rpc-client.js +85 -0
  6. package/dist/mcp/codex-session/runtime-bridge.js +254 -0
  7. package/dist/mcp/codex-session/runtime-summary.js +220 -0
  8. package/dist/mcp/codex-session/session-inspection.js +100 -0
  9. package/dist/mcp/codex-session/session-mapper.js +51 -0
  10. package/dist/mcp/codex-session/session-start.js +182 -0
  11. package/dist/mcp/codex-session/session-status.js +77 -0
  12. package/dist/mcp/codex-session/session-stop.js +43 -0
  13. package/dist/mcp/codex-session/session-supervisor.js +111 -0
  14. package/dist/mcp/codex-session/start-prompt.js +49 -0
  15. package/dist/mcp/codex-session/types.js +1 -0
  16. package/dist/mcp/codex-session.js +1 -672
  17. package/dist/mcp/config-reader.js +10 -2
  18. package/dist/mcp/git-remote.js +90 -14
  19. package/dist/mcp/local-state/agent-identity.js +26 -0
  20. package/dist/mcp/local-state/agent-sessions.js +66 -0
  21. package/dist/mcp/local-state/auth.js +57 -0
  22. package/dist/mcp/local-state/codex-live-sessions.js +59 -0
  23. package/dist/mcp/local-state/index.js +8 -0
  24. package/dist/mcp/local-state/local-chat.js +808 -0
  25. package/dist/mcp/local-state/room-sessions.js +51 -0
  26. package/dist/mcp/local-state/storage.js +95 -0
  27. package/dist/mcp/local-state/types.js +1 -0
  28. package/dist/mcp/local-state.js +1 -351
  29. package/dist/mcp/managed-agent-providers.js +65 -0
  30. package/dist/mcp/rental-tools/activity.js +37 -0
  31. package/dist/mcp/rental-tools/budget.js +36 -0
  32. package/dist/mcp/rental-tools/context.js +62 -0
  33. package/dist/mcp/rental-tools/lifecycle.js +47 -0
  34. package/dist/mcp/rental-tools/metering.js +73 -0
  35. package/dist/mcp/rental-tools/patches.js +100 -0
  36. package/dist/mcp/rental-tools/provider.js +117 -0
  37. package/dist/mcp/rental-tools/shared.js +19 -0
  38. package/dist/mcp/rental-tools/types.js +1 -0
  39. package/dist/mcp/rental-tools.js +7 -0
  40. package/dist/mcp/repo-visibility.js +8 -6
  41. package/dist/mcp/server/register-tools.js +19 -0
  42. package/dist/mcp/server/repo-context.js +28 -0
  43. package/dist/mcp/server/resources.js +77 -0
  44. package/dist/mcp/server/runtime/agent-sessions.js +134 -0
  45. package/dist/mcp/server/runtime/api.js +82 -0
  46. package/dist/mcp/server/runtime/auth-cache.js +20 -0
  47. package/dist/mcp/server/runtime/device-auth.js +48 -0
  48. package/dist/mcp/server/runtime/identity/config.js +58 -0
  49. package/dist/mcp/server/runtime/identity/directory.js +87 -0
  50. package/dist/mcp/server/runtime/identity/liveness.js +32 -0
  51. package/dist/mcp/server/runtime/identity/names.js +74 -0
  52. package/dist/mcp/server/runtime/identity/public.js +19 -0
  53. package/dist/mcp/server/runtime/identity/state.js +32 -0
  54. package/dist/mcp/server/runtime/identity.js +96 -0
  55. package/dist/mcp/server/runtime/messages.js +160 -0
  56. package/dist/mcp/server/runtime/presence.js +52 -0
  57. package/dist/mcp/server/runtime/room-api.js +42 -0
  58. package/dist/mcp/server/runtime/room-state.js +122 -0
  59. package/dist/mcp/server/runtime/rooms.js +318 -0
  60. package/dist/mcp/server/runtime.js +14 -0
  61. package/dist/mcp/server/tools/agent-sessions.js +374 -0
  62. package/dist/mcp/server/tools/messages/index.js +14 -0
  63. package/dist/mcp/server/tools/messages/read-tool.js +176 -0
  64. package/dist/mcp/server/tools/messages/reasoning-tool.js +179 -0
  65. package/dist/mcp/server/tools/messages/response.js +17 -0
  66. package/dist/mcp/server/tools/messages/send-tool.js +203 -0
  67. package/dist/mcp/server/tools/messages/status-tool.js +87 -0
  68. package/dist/mcp/server/tools/messages/wait-tool.js +333 -0
  69. package/dist/mcp/server/tools/messages.js +1 -0
  70. package/dist/mcp/server/tools/onboarding/device-auth-tools.js +148 -0
  71. package/dist/mcp/server/tools/onboarding/name-tool.js +89 -0
  72. package/dist/mcp/server/tools/onboarding/responses.js +10 -0
  73. package/dist/mcp/server/tools/onboarding/status-tool.js +108 -0
  74. package/dist/mcp/server/tools/onboarding.js +8 -0
  75. package/dist/mcp/server/tools/rental/activity-tools.js +43 -0
  76. package/dist/mcp/server/tools/rental/context-tools.js +35 -0
  77. package/dist/mcp/server/tools/rental/index.js +17 -0
  78. package/dist/mcp/server/tools/rental/metering-tools.js +39 -0
  79. package/dist/mcp/server/tools/rental/patch-tools.js +53 -0
  80. package/dist/mcp/server/tools/rental/provider-tools.js +44 -0
  81. package/dist/mcp/server/tools/rental/response.js +10 -0
  82. package/dist/mcp/server/tools/rental/types.js +1 -0
  83. package/dist/mcp/server/tools/rental.js +1 -0
  84. package/dist/mcp/server/tools/rooms/index.js +5 -0
  85. package/dist/mcp/server/tools/rooms/inspection-tools.js +124 -0
  86. package/dist/mcp/server/tools/rooms/join-tools.js +37 -0
  87. package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +113 -0
  88. package/dist/mcp/server/tools/rooms/repo-visibility-tool.js +20 -0
  89. package/dist/mcp/server/tools/rooms/response.js +10 -0
  90. package/dist/mcp/server/tools/rooms/resume-tool.js +48 -0
  91. package/dist/mcp/server/tools/rooms.js +1 -0
  92. package/dist/mcp/server/tools/tasks/api.js +222 -0
  93. package/dist/mcp/server/tools/tasks/artifact-tools.js +75 -0
  94. package/dist/mcp/server/tools/tasks/board-intent-tools.js +321 -0
  95. package/dist/mcp/server/tools/tasks/board-tools.js +73 -0
  96. package/dist/mcp/server/tools/tasks/context.js +35 -0
  97. package/dist/mcp/server/tools/tasks/event-tools.js +40 -0
  98. package/dist/mcp/server/tools/tasks/index.js +14 -0
  99. package/dist/mcp/server/tools/tasks/lease-tools.js +145 -0
  100. package/dist/mcp/server/tools/tasks/mutation-tools.js +121 -0
  101. package/dist/mcp/server/tools/tasks/response.js +13 -0
  102. package/dist/mcp/server/tools/tasks/schemas.js +68 -0
  103. package/dist/mcp/server/tools/tasks.js +1 -0
  104. package/dist/mcp/server.js +10 -3157
  105. package/dist/mcp/sse-client.js +6 -14
  106. package/dist/shared/activation-routing.js +263 -0
  107. package/dist/shared/agent-identity.js +21 -5
  108. package/dist/shared/agent-presence.js +4 -3
  109. package/dist/shared/rental/lrt.js +214 -0
  110. package/dist/shared/rental/meter-types.js +14 -0
  111. package/dist/shared/room-agent-prompts.js +14 -1
  112. package/package.json +15 -5
  113. package/dist/mcp/__tests__/config-reader.test.js +0 -93
  114. package/dist/mcp/__tests__/git-remote.test.js +0 -37
  115. package/dist/mcp/__tests__/server-helpers.test.js +0 -121
  116. package/dist/shared/agent-reasoning.js +0 -49
  117. /package/dist/{mcp → shared}/codenames.js +0 -0
@@ -0,0 +1,51 @@
1
+ import { readLocalState, updateLocalState } from "./storage.js";
2
+ export function getStoredCurrentRoom() {
3
+ const state = readLocalState();
4
+ return state.current_room ?? null;
5
+ }
6
+ export function getStoredRoomSession(roomId) {
7
+ const state = readLocalState();
8
+ return state.room_sessions?.[roomId] ?? null;
9
+ }
10
+ export function saveRoomSession(input) {
11
+ const now = new Date().toISOString();
12
+ const existing = getStoredRoomSession(input.room_id);
13
+ const session = {
14
+ room_id: input.room_id,
15
+ project_id: input.project_id ?? existing?.project_id ?? null,
16
+ code: input.code ?? existing?.code ?? null,
17
+ display_name: input.display_name ?? existing?.display_name ?? null,
18
+ git_room: input.git_room !== undefined ? input.git_room : existing?.git_room ?? null,
19
+ joined_via: input.joined_via,
20
+ joined_at: existing?.joined_at ?? now,
21
+ last_seen_at: now,
22
+ last_message_id: input.last_message_id ?? existing?.last_message_id,
23
+ };
24
+ updateLocalState((state) => {
25
+ state.room_sessions = state.room_sessions ?? {};
26
+ state.room_sessions[input.room_id] = session;
27
+ state.current_room = session;
28
+ return state;
29
+ });
30
+ return session;
31
+ }
32
+ export function touchRoomSession(roomId, lastMessageId) {
33
+ const existing = getStoredRoomSession(roomId);
34
+ if (!existing) {
35
+ return null;
36
+ }
37
+ const updated = {
38
+ ...existing,
39
+ last_seen_at: new Date().toISOString(),
40
+ last_message_id: lastMessageId ?? existing.last_message_id,
41
+ };
42
+ updateLocalState((state) => {
43
+ state.room_sessions = state.room_sessions ?? {};
44
+ state.room_sessions[roomId] = updated;
45
+ if (state.current_room?.room_id === roomId) {
46
+ state.current_room = updated;
47
+ }
48
+ return state;
49
+ });
50
+ return updated;
51
+ }
@@ -0,0 +1,95 @@
1
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "fs";
2
+ import { randomBytes } from "crypto";
3
+ import { homedir } from "os";
4
+ import { dirname, join } from "path";
5
+ const DEFAULT_STATE_PATH = join(homedir(), ".letagents", "mcp-state.json");
6
+ const STATE_LOCK_WAIT_MS = 25;
7
+ const STATE_LOCK_TIMEOUT_MS = 2_000;
8
+ const STATE_LOCK_STALE_MS = 10_000;
9
+ const STATE_LOCK_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
10
+ export function getLocalStatePath() {
11
+ return process.env.LETAGENTS_STATE_PATH || DEFAULT_STATE_PATH;
12
+ }
13
+ function readLocalStateFromPath(statePath) {
14
+ if (!existsSync(statePath)) {
15
+ return {};
16
+ }
17
+ try {
18
+ const raw = readFileSync(statePath, "utf-8");
19
+ const parsed = JSON.parse(raw);
20
+ return typeof parsed === "object" && parsed ? parsed : {};
21
+ }
22
+ catch {
23
+ return {};
24
+ }
25
+ }
26
+ export function readLocalState() {
27
+ return readLocalStateFromPath(getLocalStatePath());
28
+ }
29
+ function sleepSync(ms) {
30
+ if (ms > 0) {
31
+ Atomics.wait(STATE_LOCK_SLEEP_BUFFER, 0, 0, ms);
32
+ }
33
+ }
34
+ function writeLocalStateUnlocked(statePath, state) {
35
+ const tempPath = `${statePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
36
+ try {
37
+ writeFileSync(tempPath, JSON.stringify(state, null, 2) + "\n", "utf-8");
38
+ renameSync(tempPath, statePath);
39
+ }
40
+ finally {
41
+ rmSync(tempPath, { force: true });
42
+ }
43
+ }
44
+ function withStateLock(callback) {
45
+ const statePath = getLocalStatePath();
46
+ mkdirSync(dirname(statePath), { recursive: true });
47
+ const lockPath = `${statePath}.lock`;
48
+ const startedAt = Date.now();
49
+ while (true) {
50
+ let lockFd = null;
51
+ try {
52
+ lockFd = openSync(lockPath, "wx");
53
+ return callback(statePath);
54
+ }
55
+ catch (error) {
56
+ const err = error;
57
+ if (err.code !== "EEXIST") {
58
+ throw error;
59
+ }
60
+ try {
61
+ const stats = statSync(lockPath);
62
+ if (Date.now() - stats.mtimeMs > STATE_LOCK_STALE_MS) {
63
+ rmSync(lockPath, { force: true });
64
+ continue;
65
+ }
66
+ }
67
+ catch {
68
+ continue;
69
+ }
70
+ if (Date.now() - startedAt >= STATE_LOCK_TIMEOUT_MS) {
71
+ throw new Error(`Timed out acquiring local state lock at ${lockPath}`);
72
+ }
73
+ sleepSync(STATE_LOCK_WAIT_MS);
74
+ }
75
+ finally {
76
+ if (lockFd !== null) {
77
+ closeSync(lockFd);
78
+ rmSync(lockPath, { force: true });
79
+ }
80
+ }
81
+ }
82
+ }
83
+ export function writeLocalState(state) {
84
+ withStateLock((statePath) => {
85
+ writeLocalStateUnlocked(statePath, state);
86
+ });
87
+ }
88
+ export function updateLocalState(updater) {
89
+ return withStateLock((statePath) => {
90
+ const current = readLocalStateFromPath(statePath);
91
+ const updated = updater(current) ?? current;
92
+ writeLocalStateUnlocked(statePath, updated);
93
+ return updated;
94
+ });
95
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,351 +1 @@
1
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "fs";
2
- import { randomBytes } from "crypto";
3
- import { homedir } from "os";
4
- import { dirname, join } from "path";
5
- const DEFAULT_STATE_PATH = join(homedir(), ".letagents", "mcp-state.json");
6
- const STATE_LOCK_WAIT_MS = 25;
7
- const STATE_LOCK_TIMEOUT_MS = 2_000;
8
- const STATE_LOCK_STALE_MS = 10_000;
9
- const STATE_LOCK_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
10
- export function getLocalStatePath() {
11
- return process.env.LETAGENTS_STATE_PATH || DEFAULT_STATE_PATH;
12
- }
13
- function readLocalStateFromPath(statePath) {
14
- if (!existsSync(statePath)) {
15
- return {};
16
- }
17
- try {
18
- const raw = readFileSync(statePath, "utf-8");
19
- const parsed = JSON.parse(raw);
20
- return typeof parsed === "object" && parsed ? parsed : {};
21
- }
22
- catch {
23
- return {};
24
- }
25
- }
26
- export function readLocalState() {
27
- const statePath = getLocalStatePath();
28
- return readLocalStateFromPath(statePath);
29
- }
30
- function sleepSync(ms) {
31
- if (ms > 0) {
32
- Atomics.wait(STATE_LOCK_SLEEP_BUFFER, 0, 0, ms);
33
- }
34
- }
35
- function writeLocalStateUnlocked(statePath, state) {
36
- const tempPath = `${statePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
37
- try {
38
- writeFileSync(tempPath, JSON.stringify(state, null, 2) + "\n", "utf-8");
39
- renameSync(tempPath, statePath);
40
- }
41
- finally {
42
- rmSync(tempPath, { force: true });
43
- }
44
- }
45
- function withStateLock(callback) {
46
- const statePath = getLocalStatePath();
47
- mkdirSync(dirname(statePath), { recursive: true });
48
- const lockPath = `${statePath}.lock`;
49
- const startedAt = Date.now();
50
- while (true) {
51
- let lockFd = null;
52
- try {
53
- lockFd = openSync(lockPath, "wx");
54
- return callback(statePath);
55
- }
56
- catch (error) {
57
- const err = error;
58
- if (err.code !== "EEXIST") {
59
- throw error;
60
- }
61
- try {
62
- const stats = statSync(lockPath);
63
- if (Date.now() - stats.mtimeMs > STATE_LOCK_STALE_MS) {
64
- rmSync(lockPath, { force: true });
65
- continue;
66
- }
67
- }
68
- catch {
69
- continue;
70
- }
71
- if (Date.now() - startedAt >= STATE_LOCK_TIMEOUT_MS) {
72
- throw new Error(`Timed out acquiring local state lock at ${lockPath}`);
73
- }
74
- sleepSync(STATE_LOCK_WAIT_MS);
75
- }
76
- finally {
77
- if (lockFd !== null) {
78
- closeSync(lockFd);
79
- rmSync(lockPath, { force: true });
80
- }
81
- }
82
- }
83
- }
84
- export function writeLocalState(state) {
85
- withStateLock((statePath) => {
86
- writeLocalStateUnlocked(statePath, state);
87
- });
88
- }
89
- export function updateLocalState(updater) {
90
- return withStateLock((statePath) => {
91
- const current = readLocalStateFromPath(statePath);
92
- const updated = updater(current) ?? current;
93
- writeLocalStateUnlocked(statePath, updated);
94
- return updated;
95
- });
96
- }
97
- function isExpired(expiresAt) {
98
- if (!expiresAt) {
99
- return false;
100
- }
101
- const expiresAtMs = Date.parse(expiresAt);
102
- return Number.isFinite(expiresAtMs) && expiresAtMs <= Date.now();
103
- }
104
- export function getStoredAuth() {
105
- const state = readLocalState();
106
- if (!state.auth) {
107
- return null;
108
- }
109
- if (isExpired(state.auth.expires_at)) {
110
- clearStoredAuth();
111
- return null;
112
- }
113
- return state.auth;
114
- }
115
- export function setStoredAuth(auth) {
116
- updateLocalState((state) => {
117
- state.auth = auth;
118
- delete state.pending_device_auth;
119
- return state;
120
- });
121
- return auth;
122
- }
123
- export function clearStoredAuth() {
124
- updateLocalState((state) => {
125
- delete state.auth;
126
- return state;
127
- });
128
- }
129
- export function getPendingDeviceAuth() {
130
- const state = readLocalState();
131
- if (!state.pending_device_auth) {
132
- return null;
133
- }
134
- if (isExpired(state.pending_device_auth.expires_at)) {
135
- clearPendingDeviceAuth();
136
- return null;
137
- }
138
- return state.pending_device_auth;
139
- }
140
- export function setPendingDeviceAuth(pendingDeviceAuth) {
141
- updateLocalState((state) => {
142
- state.pending_device_auth = pendingDeviceAuth;
143
- return state;
144
- });
145
- return pendingDeviceAuth;
146
- }
147
- export function clearPendingDeviceAuth() {
148
- updateLocalState((state) => {
149
- delete state.pending_device_auth;
150
- return state;
151
- });
152
- }
153
- export function getStoredAgentIdentity(identityKey) {
154
- const state = readLocalState();
155
- if (identityKey?.trim()) {
156
- const scoped = state.agent_identities?.[identityKey.trim()];
157
- if (scoped) {
158
- return scoped;
159
- }
160
- // For UUID-based identity keys, do NOT fall back to the shared global
161
- // agent_identity — that would cause different processes to inherit each
162
- // other's identity. Only legacy (non-UUID) keys use the global fallback.
163
- if (identityKey.startsWith("instance:")) {
164
- return null;
165
- }
166
- }
167
- return state.agent_identity ?? null;
168
- }
169
- export function setStoredAgentIdentity(agentIdentity, identityKey) {
170
- updateLocalState((state) => {
171
- state.agent_identity = agentIdentity;
172
- if (identityKey?.trim()) {
173
- state.agent_identities = state.agent_identities ?? {};
174
- state.agent_identities[identityKey.trim()] = agentIdentity;
175
- }
176
- return state;
177
- });
178
- return agentIdentity;
179
- }
180
- export function getStoredAgentSession(sessionId) {
181
- if (!sessionId) {
182
- return null;
183
- }
184
- const state = readLocalState();
185
- return state.agent_sessions?.[sessionId] ?? null;
186
- }
187
- export function getCurrentAgentSession(roomId) {
188
- const state = readLocalState();
189
- const sessionIds = state.current_agent_session_ids;
190
- if (!sessionIds) {
191
- return null;
192
- }
193
- if (roomId) {
194
- const sessionId = sessionIds[roomId];
195
- const session = sessionId ? (state.agent_sessions?.[sessionId] ?? null) : null;
196
- return session && !session.ended_at ? session : null;
197
- }
198
- let best = null;
199
- for (const id of Object.values(sessionIds)) {
200
- const session = state.agent_sessions?.[id];
201
- if (session && !session.ended_at && (!best || session.updated_at > best.updated_at)) {
202
- best = session;
203
- }
204
- }
205
- return best;
206
- }
207
- export function saveAgentSession(session, makeCurrent = true) {
208
- updateLocalState((state) => {
209
- state.agent_sessions = state.agent_sessions ?? {};
210
- state.agent_sessions[session.session_id] = session;
211
- if (makeCurrent) {
212
- state.current_agent_session_ids = state.current_agent_session_ids ?? {};
213
- state.current_agent_session_ids[session.room_id] = session.session_id;
214
- }
215
- return state;
216
- });
217
- return session;
218
- }
219
- export function endStoredAgentSession(sessionId, endedAt = new Date().toISOString()) {
220
- let endedSession = null;
221
- updateLocalState((state) => {
222
- const session = state.agent_sessions?.[sessionId];
223
- if (!session) {
224
- return state;
225
- }
226
- endedSession = {
227
- ...session,
228
- ended_at: endedAt,
229
- updated_at: endedAt,
230
- last_seen_at: endedAt,
231
- };
232
- state.agent_sessions = state.agent_sessions ?? {};
233
- state.agent_sessions[sessionId] = endedSession;
234
- if (state.current_agent_session_ids) {
235
- for (const [roomId, currentSessionId] of Object.entries(state.current_agent_session_ids)) {
236
- if (currentSessionId === sessionId) {
237
- delete state.current_agent_session_ids[roomId];
238
- }
239
- }
240
- }
241
- return state;
242
- });
243
- return endedSession;
244
- }
245
- export function getStoredCurrentRoom() {
246
- const state = readLocalState();
247
- return state.current_room ?? null;
248
- }
249
- export function getStoredRoomSession(roomId) {
250
- const state = readLocalState();
251
- return state.room_sessions?.[roomId] ?? null;
252
- }
253
- export function saveRoomSession(input) {
254
- const now = new Date().toISOString();
255
- const existing = getStoredRoomSession(input.room_id);
256
- const session = {
257
- room_id: input.room_id,
258
- project_id: input.project_id ?? existing?.project_id ?? null,
259
- code: input.code ?? existing?.code ?? null,
260
- display_name: input.display_name ?? existing?.display_name ?? null,
261
- joined_via: input.joined_via,
262
- joined_at: existing?.joined_at ?? now,
263
- last_seen_at: now,
264
- last_message_id: input.last_message_id ?? existing?.last_message_id,
265
- };
266
- updateLocalState((state) => {
267
- state.room_sessions = state.room_sessions ?? {};
268
- state.room_sessions[input.room_id] = session;
269
- state.current_room = session;
270
- return state;
271
- });
272
- return session;
273
- }
274
- export function touchRoomSession(roomId, lastMessageId) {
275
- const existing = getStoredRoomSession(roomId);
276
- if (!existing) {
277
- return null;
278
- }
279
- const updated = {
280
- ...existing,
281
- last_seen_at: new Date().toISOString(),
282
- last_message_id: lastMessageId ?? existing.last_message_id,
283
- };
284
- updateLocalState((state) => {
285
- state.room_sessions = state.room_sessions ?? {};
286
- state.room_sessions[roomId] = updated;
287
- if (state.current_room?.room_id === roomId) {
288
- state.current_room = updated;
289
- }
290
- return state;
291
- });
292
- return updated;
293
- }
294
- export function getCurrentCodexLiveSession(roomId) {
295
- const state = readLocalState();
296
- const sessionIds = state.current_codex_live_session_ids;
297
- if (!sessionIds) {
298
- return null;
299
- }
300
- if (roomId) {
301
- const sessionId = sessionIds[roomId];
302
- return sessionId ? (state.codex_live_sessions?.[sessionId] ?? null) : null;
303
- }
304
- let best = null;
305
- for (const id of Object.values(sessionIds)) {
306
- const session = state.codex_live_sessions?.[id];
307
- if (session && (!best || session.updated_at > best.updated_at)) {
308
- best = session;
309
- }
310
- }
311
- return best;
312
- }
313
- export function getStoredCodexLiveSession(sessionId) {
314
- const state = readLocalState();
315
- return state.codex_live_sessions?.[sessionId] ?? null;
316
- }
317
- export function listStoredCodexLiveSessions() {
318
- const state = readLocalState();
319
- return Object.values(state.codex_live_sessions ?? {}).sort((left, right) => right.updated_at.localeCompare(left.updated_at));
320
- }
321
- export function saveCodexLiveSession(session, makeCurrent = true) {
322
- updateLocalState((state) => {
323
- state.codex_live_sessions = state.codex_live_sessions ?? {};
324
- state.codex_live_sessions[session.session_id] = session;
325
- if (makeCurrent) {
326
- state.current_codex_live_session_ids = state.current_codex_live_session_ids ?? {};
327
- state.current_codex_live_session_ids[session.room_id] = session.session_id;
328
- }
329
- return state;
330
- });
331
- return session;
332
- }
333
- export function updateCodexLiveSession(sessionId, updater) {
334
- let updatedSession = null;
335
- updateLocalState((state) => {
336
- const existing = state.codex_live_sessions?.[sessionId];
337
- if (!existing) {
338
- return state;
339
- }
340
- const updated = updater(existing);
341
- state.codex_live_sessions = state.codex_live_sessions ?? {};
342
- state.codex_live_sessions[sessionId] = updated;
343
- state.current_codex_live_session_ids = state.current_codex_live_session_ids ?? {};
344
- if (!state.current_codex_live_session_ids[updated.room_id]) {
345
- state.current_codex_live_session_ids[updated.room_id] = sessionId;
346
- }
347
- updatedSession = updated;
348
- return state;
349
- });
350
- return updatedSession;
351
- }
1
+ export * from "./local-state/index.js";
@@ -0,0 +1,65 @@
1
+ import { getCurrentCodexLiveSession, } from "./local-state.js";
2
+ import { inspectLocalCodexSession, startLocalCodexSession, stopLocalCodexSession, toPublicCodexLiveSession, } from "./codex-session.js";
3
+ const providers = new Map();
4
+ let defaultProviderId = "codex";
5
+ function createCodexManagedAgentProvider() {
6
+ return {
7
+ id: "codex",
8
+ displayName: "Codex",
9
+ responseKeys: {
10
+ localSession: "local_codex_session",
11
+ localSessionStarted: "local_codex_session_started",
12
+ localSessionReused: "local_codex_session_reused",
13
+ },
14
+ getCurrentLiveSessionPayload(roomId) {
15
+ const session = getCurrentCodexLiveSession(roomId);
16
+ return session ? toPublicCodexLiveSession(session) : null;
17
+ },
18
+ startLocalSession: startLocalCodexSession,
19
+ inspectLocalSession: inspectLocalCodexSession,
20
+ stopLocalSession: stopLocalCodexSession,
21
+ toPublicLiveSession(session) {
22
+ return toPublicCodexLiveSession(session);
23
+ },
24
+ };
25
+ }
26
+ function installBuiltinManagedAgentProviders() {
27
+ providers.clear();
28
+ const codexProvider = createCodexManagedAgentProvider();
29
+ providers.set(codexProvider.id, codexProvider);
30
+ defaultProviderId = codexProvider.id;
31
+ }
32
+ installBuiltinManagedAgentProviders();
33
+ export function registerManagedAgentProvider(provider, options = {}) {
34
+ const id = provider.id.trim();
35
+ if (!id) {
36
+ throw new Error("Managed agent provider id is required.");
37
+ }
38
+ if (providers.has(id) && !options.replace) {
39
+ throw new Error(`Managed agent provider '${id}' is already registered.`);
40
+ }
41
+ providers.set(id, { ...provider, id });
42
+ if (options.setDefault) {
43
+ defaultProviderId = id;
44
+ }
45
+ }
46
+ export function getManagedAgentProvider(id) {
47
+ const provider = providers.get(id);
48
+ if (!provider) {
49
+ throw new Error(`Managed agent provider '${id}' is not registered.`);
50
+ }
51
+ return provider;
52
+ }
53
+ export function getDefaultManagedAgentProvider() {
54
+ return getManagedAgentProvider(defaultProviderId);
55
+ }
56
+ export function toManagedAgentStartResponse(provider, result) {
57
+ return {
58
+ [provider.responseKeys.localSession]: provider.toPublicLiveSession(result.session),
59
+ [provider.responseKeys.localSessionStarted]: !result.reused,
60
+ [provider.responseKeys.localSessionReused]: result.reused,
61
+ };
62
+ }
63
+ export function resetManagedAgentProvidersForTest() {
64
+ installBuiltinManagedAgentProviders();
65
+ }
@@ -0,0 +1,37 @@
1
+ import { encodeSessionId, errorMessage, validateSessionId } from "./shared.js";
2
+ export async function rentalEmitActivity(deps, input) {
3
+ const sessionIdError = validateSessionId(input);
4
+ if (sessionIdError)
5
+ return { success: false, error: sessionIdError };
6
+ if (typeof input.event_type !== "string" || !input.event_type.trim()) {
7
+ return { success: false, error: "event_type is required" };
8
+ }
9
+ const body = {
10
+ event_type: input.event_type.trim(),
11
+ source: typeof input.source === "string" && input.source.trim()
12
+ ? input.source.trim()
13
+ : "agent",
14
+ payload: typeof input.payload === "object" &&
15
+ input.payload !== null &&
16
+ !Array.isArray(input.payload)
17
+ ? input.payload
18
+ : {},
19
+ };
20
+ if (typeof input.verified === "boolean") {
21
+ body.verified = input.verified;
22
+ }
23
+ try {
24
+ const event = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/activity`, {
25
+ method: "POST",
26
+ headers: { "content-type": "application/json" },
27
+ body: JSON.stringify(body),
28
+ });
29
+ return { success: true, event };
30
+ }
31
+ catch (err) {
32
+ return {
33
+ success: false,
34
+ error: errorMessage(err),
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,36 @@
1
+ import { encodeSessionId, errorMessage, isPositiveInteger, validateSessionId, } from "./shared.js";
2
+ export async function rentalRequestBudgetExtension(deps, input) {
3
+ const sessionIdError = validateSessionId(input);
4
+ if (sessionIdError)
5
+ return { success: false, error: sessionIdError };
6
+ if (!isPositiveInteger(input.requested_additional_lrt)) {
7
+ return {
8
+ success: false,
9
+ error: "requested_additional_lrt must be a finite positive integer",
10
+ };
11
+ }
12
+ const bodyPayload = {
13
+ requestedAdditionalLrt: input.requested_additional_lrt,
14
+ };
15
+ if (typeof input.reason === "string" && input.reason.trim()) {
16
+ bodyPayload.reason = input.reason.trim();
17
+ }
18
+ try {
19
+ const body = await deps.apiCall(`/api/rental/sessions/${encodeSessionId(input.session_id)}/budget-extension-requests`, {
20
+ method: "POST",
21
+ headers: { "content-type": "application/json" },
22
+ body: JSON.stringify(bodyPayload),
23
+ });
24
+ return {
25
+ success: true,
26
+ request: body?.request ?? null,
27
+ session: body?.session ?? null,
28
+ };
29
+ }
30
+ catch (err) {
31
+ return {
32
+ success: false,
33
+ error: errorMessage(err),
34
+ };
35
+ }
36
+ }