@makerbi/remodex 2.0.1 → 2.3.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.
@@ -0,0 +1,20 @@
1
+ // FILE: rollout-turn-semantics.js
2
+ // Purpose: Shared desktop rollout turn-lifecycle semantics for the live mirror
3
+ // and the JSONL history fallback, so both agree on what ends a run.
4
+
5
+ // Any of these event_msg types ends a desktop run; task_complete is not the only
6
+ // terminal outcome (Stop on desktop writes turn_aborted, fatal failures write error).
7
+ const TERMINAL_TASK_EVENT_TYPES = new Set(["task_complete", "turn_aborted", "error"]);
8
+
9
+ // Desktop interleaves parallel turns in one rollout file. A terminal event only
10
+ // closes the tracked turn when it targets that turn, carries no explicit id, or
11
+ // nothing is tracked; a sibling turn's terminal event must leave the tracked
12
+ // run open.
13
+ function terminalEventClosesTrackedTurn(eventTurnId, trackedTurnId) {
14
+ return !eventTurnId || !trackedTurnId || eventTurnId === trackedTurnId;
15
+ }
16
+
17
+ module.exports = {
18
+ TERMINAL_TASK_EVENT_TYPES,
19
+ terminalEventClosesTrackedTurn,
20
+ };
@@ -0,0 +1,164 @@
1
+ // FILE: runtime-provider-models.js
2
+ // Purpose: Normalizes provider-aware model/thread metadata shared by local runtime providers.
3
+ // Layer: Bridge runtime provider helper
4
+ // Exports: provider constants plus model/provider parsing helpers.
5
+ // Depends on: none
6
+
7
+ const CODEX_PROVIDER_ID = "codex";
8
+ const CURSOR_PROVIDER_ID = "cursor";
9
+ const OPENCODE_PROVIDER_ID = "opencode";
10
+
11
+ const PROVIDER_FIELD_KEYS = [
12
+ "modelProvider",
13
+ "model_provider",
14
+ "provider",
15
+ "runtimeProvider",
16
+ "runtime_provider",
17
+ "harness",
18
+ ];
19
+
20
+ function normalizeRuntimeProvider(value) {
21
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
22
+ switch (normalized) {
23
+ case "":
24
+ return CODEX_PROVIDER_ID;
25
+ case "cursor-agent":
26
+ case "cursor_cli":
27
+ case "cursor-cli":
28
+ return CURSOR_PROVIDER_ID;
29
+ case "open-code":
30
+ case "open_code":
31
+ return OPENCODE_PROVIDER_ID;
32
+ case "claude-code":
33
+ case "claudecode":
34
+ return "claude";
35
+ default:
36
+ return normalized;
37
+ }
38
+ }
39
+
40
+ function readModelProvider(value = {}) {
41
+ if (!value || typeof value !== "object") {
42
+ return CODEX_PROVIDER_ID;
43
+ }
44
+
45
+ return normalizeRuntimeProvider(
46
+ value.modelProvider
47
+ || value.model_provider
48
+ || value.provider
49
+ || value.runtimeProvider
50
+ || value.runtime_provider
51
+ || value.harness
52
+ || value.collaborationMode?.settings?.modelProvider
53
+ || value.collaborationMode?.settings?.model_provider
54
+ || value.collaborationMode?.settings?.provider
55
+ || value.collaboration_mode?.settings?.modelProvider
56
+ || value.collaboration_mode?.settings?.model_provider
57
+ || value.collaboration_mode?.settings?.provider
58
+ );
59
+ }
60
+
61
+ function hasExplicitProviderField(value = {}) {
62
+ if (!value || typeof value !== "object") {
63
+ return false;
64
+ }
65
+
66
+ return hasOwnProviderField(value)
67
+ || hasOwnProviderField(value.collaborationMode?.settings)
68
+ || hasOwnProviderField(value.collaboration_mode?.settings);
69
+ }
70
+
71
+ function isCodexProvider(value) {
72
+ return normalizeRuntimeProvider(value) === CODEX_PROVIDER_ID;
73
+ }
74
+
75
+ function isCursorProvider(value) {
76
+ return normalizeRuntimeProvider(value) === CURSOR_PROVIDER_ID;
77
+ }
78
+
79
+ function isOpenCodeProvider(value) {
80
+ return normalizeRuntimeProvider(value) === OPENCODE_PROVIDER_ID;
81
+ }
82
+
83
+ function buildRuntimeModelOption({
84
+ provider,
85
+ id,
86
+ model = id,
87
+ displayName,
88
+ description = "",
89
+ isDefault = false,
90
+ supportsFastMode = false,
91
+ supportedReasoningEfforts = [],
92
+ defaultReasoningEffort = null,
93
+ }) {
94
+ const normalizedProvider = normalizeRuntimeProvider(provider);
95
+ const normalizedId = readString(id || model);
96
+ const normalizedModel = readString(model || id);
97
+ if (!normalizedProvider || !normalizedId || !normalizedModel) {
98
+ return null;
99
+ }
100
+
101
+ return {
102
+ id: normalizedId,
103
+ model: normalizedModel,
104
+ modelProvider: normalizedProvider,
105
+ provider: normalizedProvider,
106
+ displayName: readString(displayName) || normalizedModel,
107
+ description: readString(description),
108
+ isDefault,
109
+ supportsFastMode,
110
+ supportedReasoningEfforts,
111
+ defaultReasoningEffort,
112
+ };
113
+ }
114
+
115
+ function stripProviderFields(value) {
116
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
117
+ return value;
118
+ }
119
+
120
+ const clone = { ...value };
121
+ for (const key of PROVIDER_FIELD_KEYS) {
122
+ delete clone[key];
123
+ }
124
+
125
+ if (clone.collaborationMode?.settings) {
126
+ clone.collaborationMode = {
127
+ ...clone.collaborationMode,
128
+ settings: stripProviderFields(clone.collaborationMode.settings),
129
+ };
130
+ }
131
+ if (clone.collaboration_mode?.settings) {
132
+ clone.collaboration_mode = {
133
+ ...clone.collaboration_mode,
134
+ settings: stripProviderFields(clone.collaboration_mode.settings),
135
+ };
136
+ }
137
+ return clone;
138
+ }
139
+
140
+ function readString(value) {
141
+ return typeof value === "string" && value.trim() ? value.trim() : "";
142
+ }
143
+
144
+ function hasOwnProviderField(value = {}) {
145
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
146
+ return false;
147
+ }
148
+ return PROVIDER_FIELD_KEYS.some((key) => Object.prototype.hasOwnProperty.call(value, key));
149
+ }
150
+
151
+ module.exports = {
152
+ CODEX_PROVIDER_ID,
153
+ CURSOR_PROVIDER_ID,
154
+ OPENCODE_PROVIDER_ID,
155
+ PROVIDER_FIELD_KEYS,
156
+ buildRuntimeModelOption,
157
+ hasExplicitProviderField,
158
+ isCodexProvider,
159
+ isCursorProvider,
160
+ isOpenCodeProvider,
161
+ normalizeRuntimeProvider,
162
+ readModelProvider,
163
+ stripProviderFields,
164
+ };
@@ -0,0 +1,365 @@
1
+ // FILE: runtime-provider-router.js
2
+ // Purpose: Routes provider-aware Remodex RPCs between Codex app-server and local runtime providers.
3
+ // Layer: Bridge runtime routing
4
+ // Exports: createRuntimeProviderRouter plus merge helpers used by tests.
5
+ // Depends on: ./cursor-provider, ./opencode-provider, ./runtime-provider-models
6
+
7
+ const { createCursorProvider } = require("./cursor-provider");
8
+ const { createOpenCodeProvider } = require("./opencode-provider");
9
+ const {
10
+ CODEX_PROVIDER_ID,
11
+ readModelProvider,
12
+ stripProviderFields,
13
+ } = require("./runtime-provider-models");
14
+
15
+ const ROUTABLE_THREAD_METHODS = new Set([
16
+ "thread/start",
17
+ "thread/resume",
18
+ "thread/read",
19
+ "thread/turns/list",
20
+ "thread/name/set",
21
+ "thread/archive",
22
+ "thread/unarchive",
23
+ "turn/start",
24
+ "turn/interrupt",
25
+ ]);
26
+
27
+ function createRuntimeProviderRouter({
28
+ sendCodexRequest,
29
+ sendApplicationResponse,
30
+ sendRuntimeMessage,
31
+ providers = null,
32
+ projectRegistry = null,
33
+ logPrefix = "[remodex]",
34
+ } = {}) {
35
+ const runtimeProviders = providers || [
36
+ createCursorProvider({
37
+ sendApplicationMessage: (message) => sendRuntimeProviderMessage("cursor", message),
38
+ logPrefix,
39
+ }),
40
+ createOpenCodeProvider({
41
+ sendApplicationMessage: (message) => sendRuntimeProviderMessage("opencode", message),
42
+ projectRegistry,
43
+ logPrefix,
44
+ }),
45
+ ];
46
+
47
+ function sendRuntimeProviderMessage(provider, message) {
48
+ if (sendRuntimeMessage) {
49
+ sendRuntimeMessage(provider, message);
50
+ return;
51
+ }
52
+ sendApplicationResponse(message);
53
+ }
54
+
55
+ function handleApplicationMessage(rawMessage, options = {}) {
56
+ const sendResponse = options.sendResponse || sendApplicationResponse;
57
+ const parsed = safeParseJSON(rawMessage);
58
+ if (!parsed) {
59
+ return false;
60
+ }
61
+
62
+ const responseProvider = runtimeProviders.find((provider) => (
63
+ parsed.id != null
64
+ && !parsed.method
65
+ && typeof provider.handleApplicationResponse === "function"
66
+ && provider.handleApplicationResponse(parsed)
67
+ ));
68
+ if (responseProvider) {
69
+ return true;
70
+ }
71
+
72
+ const method = readString(parsed.method);
73
+ if (!method) {
74
+ return false;
75
+ }
76
+
77
+ if (method === "model/list") {
78
+ respondAsync(parsed, sendResponse, async () => {
79
+ const codexResult = await sendCodexRequest("model/list", stripProviderFields(parsed.params || {}));
80
+ const providerModels = await listProviderModels(runtimeProviders);
81
+ return mergeModelListResult(codexResult, providerModels);
82
+ });
83
+ return true;
84
+ }
85
+
86
+ if (method === "thread/list") {
87
+ respondAsync(parsed, sendResponse, async () => {
88
+ const codexResult = await sendCodexRequest("thread/list", stripProviderFields(parsed.params || {}));
89
+ const providerThreads = hasCursor(parsed.params)
90
+ ? []
91
+ : await listProviderThreads(runtimeProviders, parsed.params || {});
92
+ registerThreadProjects(projectRegistry, threadsFromListResult(codexResult), {
93
+ source: "codex-thread-list",
94
+ provider: CODEX_PROVIDER_ID,
95
+ });
96
+ registerThreadProjects(projectRegistry, providerThreads, {
97
+ source: "provider-thread-list",
98
+ });
99
+ return mergeThreadListResult(codexResult, providerThreads);
100
+ });
101
+ return true;
102
+ }
103
+
104
+ if (!ROUTABLE_THREAD_METHODS.has(method)) {
105
+ return false;
106
+ }
107
+
108
+ const provider = providerForRequest(parsed, runtimeProviders);
109
+ if (!provider) {
110
+ return false;
111
+ }
112
+
113
+ rememberProjectFromRequest(projectRegistry, parsed, {
114
+ source: "provider-request",
115
+ provider: provider.id,
116
+ });
117
+ respondAsync(parsed, sendResponse, () => provider.handleRequest(parsed));
118
+ return true;
119
+ }
120
+
121
+ function respondAsync(request, sendResponse, resolveResult) {
122
+ Promise.resolve()
123
+ .then(resolveResult)
124
+ .then((result) => {
125
+ if (request.id != null) {
126
+ sendResponse(JSON.stringify({
127
+ id: request.id,
128
+ result,
129
+ }));
130
+ }
131
+ })
132
+ .catch((error) => {
133
+ if (request.id != null) {
134
+ sendResponse(createJsonRpcErrorResponse(
135
+ request.id,
136
+ error,
137
+ error?.errorCode || "runtime_provider_failed"
138
+ ));
139
+ }
140
+ });
141
+ }
142
+
143
+ return {
144
+ handleApplicationMessage,
145
+ providers: runtimeProviders,
146
+ shutdown() {
147
+ for (const provider of runtimeProviders) {
148
+ provider.shutdown?.();
149
+ }
150
+ },
151
+ };
152
+ }
153
+
154
+ async function listProviderModels(providers) {
155
+ const settled = await Promise.allSettled(providers.map((provider) => provider.listModels()));
156
+ return settled.flatMap((result) => (result.status === "fulfilled" ? result.value : []));
157
+ }
158
+
159
+ async function listProviderThreads(providers, params) {
160
+ const settled = await Promise.allSettled(providers.map((provider) => provider.listThreads(params)));
161
+ return settled.flatMap((result) => {
162
+ if (result.status !== "fulfilled") {
163
+ return [];
164
+ }
165
+ const payload = result.value;
166
+ return Array.isArray(payload?.data) ? payload.data : [];
167
+ });
168
+ }
169
+
170
+ function providerForRequest(request, providers) {
171
+ const params = request.params || {};
172
+ const providerFromRequest = readModelProvider(params);
173
+ if (providerFromRequest !== CODEX_PROVIDER_ID) {
174
+ return providers.find((provider) => provider.canHandleProvider?.(providerFromRequest)) || null;
175
+ }
176
+ if (hasExplicitProviderField(params)) {
177
+ return null;
178
+ }
179
+
180
+ const threadId = readThreadId(params);
181
+ if (!threadId) {
182
+ return null;
183
+ }
184
+
185
+ return providers.find((provider) => provider.ownsThread?.(threadId)) || null;
186
+ }
187
+
188
+ function mergeModelListResult(codexResult, providerModels) {
189
+ const result = codexResult && typeof codexResult === "object" ? codexResult : {};
190
+ const key = firstArrayKey(result, ["items", "data", "models"]) || "items";
191
+ const codexModels = Array.isArray(result[key]) ? result[key] : [];
192
+ const normalizedCodexModels = codexModels.map((model) => ({
193
+ ...model,
194
+ modelProvider: CODEX_PROVIDER_ID,
195
+ provider: CODEX_PROVIDER_ID,
196
+ }));
197
+
198
+ return {
199
+ ...result,
200
+ [key]: [
201
+ ...normalizedCodexModels,
202
+ ...providerModels,
203
+ ],
204
+ };
205
+ }
206
+
207
+ function mergeThreadListResult(codexResult, providerThreads) {
208
+ const result = codexResult && typeof codexResult === "object" ? codexResult : {};
209
+ const key = firstArrayKey(result, ["data", "items", "threads"]) || "data";
210
+ const codexThreads = Array.isArray(result[key]) ? result[key] : [];
211
+ const merged = dedupeMergedThreads(codexThreads, providerThreads).sort(compareThreadsByUpdatedAt);
212
+
213
+ return {
214
+ ...result,
215
+ [key]: merged,
216
+ };
217
+ }
218
+
219
+ function dedupeMergedThreads(codexThreads, providerThreads) {
220
+ const mergedById = new Map();
221
+ for (const thread of codexThreads) {
222
+ const threadId = readThreadIdentifier(thread);
223
+ if (threadId) {
224
+ mergedById.set(threadId, thread);
225
+ }
226
+ }
227
+
228
+ for (const thread of providerThreads) {
229
+ const threadId = readThreadIdentifier(thread);
230
+ if (!threadId) {
231
+ continue;
232
+ }
233
+ if (!mergedById.has(threadId) || readModelProvider(thread) !== CODEX_PROVIDER_ID) {
234
+ mergedById.set(threadId, thread);
235
+ }
236
+ }
237
+ return Array.from(mergedById.values());
238
+ }
239
+
240
+ function threadsFromListResult(result) {
241
+ const key = firstArrayKey(result, ["data", "items", "threads"]);
242
+ return key && Array.isArray(result?.[key]) ? result[key] : [];
243
+ }
244
+
245
+ function registerThreadProjects(projectRegistry, threads, metadata = {}) {
246
+ if (!projectRegistry || !Array.isArray(threads) || !threads.length) {
247
+ return;
248
+ }
249
+
250
+ try {
251
+ projectRegistry.rememberProjectsFromThreads(threads, metadata);
252
+ } catch {
253
+ // Project history is a cache; provider routing should not fail when it cannot be persisted.
254
+ }
255
+ }
256
+
257
+ function rememberProjectFromRequest(projectRegistry, request, metadata = {}) {
258
+ if (!projectRegistry) {
259
+ return;
260
+ }
261
+
262
+ const params = request?.params || {};
263
+ const cwd = readString(params.cwd || params.current_working_directory || params.working_directory);
264
+ if (!cwd) {
265
+ return;
266
+ }
267
+
268
+ try {
269
+ projectRegistry.rememberProjectPath(cwd, metadata);
270
+ } catch {
271
+ // Best-effort cache write; the runtime request remains authoritative.
272
+ }
273
+ }
274
+
275
+ function stripRuntimeProviderFieldsForCodex(rawMessage) {
276
+ const parsed = safeParseJSON(rawMessage);
277
+ if (!parsed || !parsed.params || typeof parsed.params !== "object" || Array.isArray(parsed.params)) {
278
+ return rawMessage;
279
+ }
280
+
281
+ return JSON.stringify({
282
+ ...parsed,
283
+ params: stripProviderFields(parsed.params),
284
+ });
285
+ }
286
+
287
+ function compareThreadsByUpdatedAt(lhs, rhs) {
288
+ const lhsTime = Date.parse(lhs?.updatedAt || lhs?.updated_at || lhs?.createdAt || lhs?.created_at || 0) || 0;
289
+ const rhsTime = Date.parse(rhs?.updatedAt || rhs?.updated_at || rhs?.createdAt || rhs?.created_at || 0) || 0;
290
+ return rhsTime - lhsTime;
291
+ }
292
+
293
+ function firstArrayKey(value, keys) {
294
+ return keys.find((key) => Array.isArray(value?.[key])) || "";
295
+ }
296
+
297
+ function hasCursor(params = {}) {
298
+ const cursor = params?.cursor ?? params?.nextCursor ?? params?.next_cursor;
299
+ return cursor != null && cursor !== "" && cursor !== false;
300
+ }
301
+
302
+ function hasExplicitProviderField(params = {}) {
303
+ return readModelProvider(params) !== CODEX_PROVIDER_ID
304
+ || providerFieldHasValue(params)
305
+ || providerFieldHasValue(params.collaborationMode?.settings)
306
+ || providerFieldHasValue(params.collaboration_mode?.settings);
307
+ }
308
+
309
+ function providerFieldHasValue(value = {}) {
310
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
311
+ return false;
312
+ }
313
+
314
+ return [
315
+ "modelProvider",
316
+ "model_provider",
317
+ "provider",
318
+ "runtimeProvider",
319
+ "runtime_provider",
320
+ "harness",
321
+ ].some((key) => readString(value[key]));
322
+ }
323
+
324
+ function readThreadId(params = {}) {
325
+ return readString(params.threadId || params.thread_id || params.id);
326
+ }
327
+
328
+ function readThreadIdentifier(thread = {}) {
329
+ return readString(thread.id || thread.threadId || thread.thread_id);
330
+ }
331
+
332
+ function readString(value) {
333
+ return typeof value === "string" && value.trim() ? value.trim() : "";
334
+ }
335
+
336
+ function createJsonRpcErrorResponse(requestId, error, defaultErrorCode) {
337
+ return JSON.stringify({
338
+ id: requestId,
339
+ error: {
340
+ code: -32000,
341
+ message: error?.userMessage || error?.message || "Runtime provider request failed.",
342
+ data: {
343
+ errorCode: error?.errorCode || defaultErrorCode,
344
+ },
345
+ },
346
+ });
347
+ }
348
+
349
+ function safeParseJSON(rawMessage) {
350
+ try {
351
+ return JSON.parse(rawMessage);
352
+ } catch {
353
+ return null;
354
+ }
355
+ }
356
+
357
+ module.exports = {
358
+ createRuntimeProviderRouter,
359
+ mergeModelListResult,
360
+ mergeThreadListResult,
361
+ providerForRequest,
362
+ registerThreadProjects,
363
+ stripRuntimeProviderFieldsForCodex,
364
+ threadsFromListResult,
365
+ };
@@ -1,39 +1,50 @@
1
1
  -- FILE: codex-refresh.applescript
2
- -- Purpose: Forces a non-destructive route bounce inside Codex so the target thread remounts without killing runs.
2
+ -- Purpose: Opens the target Codex deep link so the desktop window lands on the phone-driven thread.
3
3
  -- Layer: UI automation helper
4
- -- Args: bundle id, app path fallback, optional target deep link
4
+ -- Args: bundle id, app path fallback, optional target deep link, optional launch-if-closed flag ("1" default)
5
+ -- Note: content updates stream over desktop IPC live sync, so no settings-route
6
+ -- bounce/remount is needed anymore; that bounce caused visible page flipping.
5
7
 
6
8
  on run argv
7
9
  set bundleId to item 1 of argv
8
10
  set appPath to item 2 of argv
9
11
  set targetUrl to ""
10
- set bounceUrl to "codex://settings"
12
+ set launchIfClosed to "1"
11
13
 
12
14
  if (count of argv) is greater than or equal to 3 then
13
15
  set targetUrl to item 3 of argv
14
16
  end if
15
17
 
16
- try
17
- tell application "Finder" to activate
18
- end try
19
-
20
- delay 0.12
21
-
22
- my openCodexUrl(bundleId, appPath, bounceUrl)
23
- delay 0.18
18
+ if (count of argv) is greater than or equal to 4 then
19
+ set launchIfClosed to item 4 of argv
20
+ end if
24
21
 
25
- if targetUrl is not "" then
26
- my openCodexUrl(bundleId, appPath, targetUrl)
27
- else
28
- my openCodexUrl(bundleId, appPath, "")
22
+ -- Navigation is a courtesy for an already-open Codex; cold-starting the app
23
+ -- just to show the phone-driven thread is disruptive and never required for
24
+ -- content sync (that streams over desktop IPC).
25
+ if launchIfClosed is "0" and not my isCodexRunning(bundleId) then
26
+ return
29
27
  end if
30
28
 
29
+ my openCodexUrl(bundleId, appPath, targetUrl)
30
+
31
31
  delay 0.18
32
32
  try
33
33
  tell application id bundleId to activate
34
34
  end try
35
35
  end run
36
36
 
37
+ on isCodexRunning(bundleId)
38
+ try
39
+ -- Avoid System Events here: LaunchAgents may not have automation permission.
40
+ set matches to do shell script "/usr/bin/lsappinfo find bundleid=" & quoted form of bundleId
41
+ return matches is not ""
42
+ on error
43
+ -- Unknown running state must fail closed for navigation-only callers.
44
+ return false
45
+ end try
46
+ end isCodexRunning
47
+
37
48
  on openCodexUrl(bundleId, appPath, targetUrl)
38
49
  try
39
50
  if targetUrl is not "" then