@makerbi/remodex 2.4.0 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/remodex.js +25 -2
- package/package.json +1 -1
- package/src/bridge.js +46 -61
- package/src/codex-desktop-refresher.js +173 -8
- package/src/codex-tool-wrapper.js +523 -0
- package/src/desktop-ipc-action-follower.js +292 -54
- package/src/desktop-ipc-live-owner.js +148 -6
- package/src/desktop-ipc-owner-transport.js +143 -73
- package/src/desktop-ipc-shared.js +125 -8
- package/src/git-handler.js +189 -108
- package/src/index.js +2 -0
- package/src/macos-launch-agent.js +65 -34
- package/src/rollout-live-mirror.js +134 -13
- package/src/rollout-watch.js +176 -54
- package/src/session-jsonl-history.js +72 -32
- package/src/thread-list-provenance.js +105 -0
- package/src/thread-row-enrichment.js +43 -0
- package/src/thread-runtime-settings-store.js +3 -21
- package/src/worktree-origin.js +192 -0
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// Purpose: Persists the last accepted model, reasoning effort, and service tier for each local thread.
|
|
3
3
|
// Layer: CLI helper
|
|
4
4
|
// Exports: createThreadRuntimeSettingsStore, runtimeSettingsFromTurnParams
|
|
5
|
-
// Depends on: fs, os, path
|
|
5
|
+
// Depends on: fs, os, path, ./thread-row-enrichment
|
|
6
6
|
|
|
7
7
|
const fs = require("fs");
|
|
8
8
|
const os = require("os");
|
|
9
9
|
const path = require("path");
|
|
10
|
+
const { forEachThreadRowInResponse } = require("./thread-row-enrichment");
|
|
10
11
|
|
|
11
12
|
const STORE_VERSION = 1;
|
|
12
13
|
const DEFAULT_MAX_THREADS = 500;
|
|
@@ -110,26 +111,7 @@ function createThreadRuntimeSettingsStore({
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
function enrichResponse(method, envelope) {
|
|
113
|
-
|
|
114
|
-
return envelope;
|
|
115
|
-
}
|
|
116
|
-
const result = envelope.result;
|
|
117
|
-
if (!result || typeof result !== "object") {
|
|
118
|
-
return envelope;
|
|
119
|
-
}
|
|
120
|
-
if (method === "thread/read" || method === "thread/resume") {
|
|
121
|
-
if (result.thread && typeof result.thread === "object") {
|
|
122
|
-
attachToThread(result.thread);
|
|
123
|
-
}
|
|
124
|
-
return envelope;
|
|
125
|
-
}
|
|
126
|
-
if (method === "thread/list") {
|
|
127
|
-
const key = ["data", "items", "threads"].find((candidate) => Array.isArray(result[candidate]));
|
|
128
|
-
for (const thread of key ? result[key] : []) {
|
|
129
|
-
attachToThread(thread);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return envelope;
|
|
114
|
+
return forEachThreadRowInResponse(method, envelope, attachToThread);
|
|
133
115
|
}
|
|
134
116
|
|
|
135
117
|
function attachToThread(thread) {
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// FILE: worktree-origin.js
|
|
2
|
+
// Purpose: Resolves the checkout that owns a Codex-managed worktree so thread rows can be
|
|
3
|
+
// grouped under their source project instead of a separate look-alike project.
|
|
4
|
+
// Layer: CLI helper
|
|
5
|
+
// Exports: createWorktreeOriginEnricher
|
|
6
|
+
// Depends on: fs, os, path, ./thread-row-enrichment
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const { forEachThreadRowInResponse } = require("./thread-row-enrichment");
|
|
12
|
+
|
|
13
|
+
// One entry per managed-worktree cwd seen on this machine, and managed worktree paths are
|
|
14
|
+
// single-use, so the map is bounded by real worktrees rather than by list refreshes.
|
|
15
|
+
const DEFAULT_MAX_ENTRIES = 1024;
|
|
16
|
+
|
|
17
|
+
const THREAD_ROW_CWD_KEYS = ["cwd", "workingDirectory", "working_directory", "current_working_directory"];
|
|
18
|
+
|
|
19
|
+
// Managed worktrees live at CODEX_HOME/worktrees/<token>/<repo>, so their basename matches the
|
|
20
|
+
// source repo while their path does not: the phone would otherwise show one project row per
|
|
21
|
+
// worktree next to the repo the worktree was cut from. Git already records the owner in the
|
|
22
|
+
// worktree's `.git` file (`gitdir: <checkout>/.git/worktrees/<name>`), so a single small read
|
|
23
|
+
// per worktree recovers the source checkout without shelling out to git.
|
|
24
|
+
function createWorktreeOriginEnricher({
|
|
25
|
+
fsModule = fs,
|
|
26
|
+
codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex"),
|
|
27
|
+
maxEntries = DEFAULT_MAX_ENTRIES,
|
|
28
|
+
} = {}) {
|
|
29
|
+
const worktreesRoots = managedWorktreesRoots(codexHome, fsModule);
|
|
30
|
+
const cache = new Map();
|
|
31
|
+
|
|
32
|
+
function originPathForCwd(cwd) {
|
|
33
|
+
if (cache.has(cwd)) {
|
|
34
|
+
const cached = cache.get(cwd);
|
|
35
|
+
// Refresh insertion order so the Map behaves as an LRU.
|
|
36
|
+
cache.delete(cwd);
|
|
37
|
+
cache.set(cwd, cached);
|
|
38
|
+
return cached;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const originPath = resolveOriginPath(cwd);
|
|
42
|
+
cache.set(cwd, originPath);
|
|
43
|
+
while (cache.size > Math.max(1, maxEntries)) {
|
|
44
|
+
cache.delete(cache.keys().next().value);
|
|
45
|
+
}
|
|
46
|
+
return originPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A removed worktree resolves to "" and stays cached: its path is never reused, so the
|
|
50
|
+
// thread keeps the standalone project row it has today instead of re-reading every refresh.
|
|
51
|
+
function resolveOriginPath(cwd) {
|
|
52
|
+
const scope = resolveManagedWorktreeScope(cwd, worktreesRoots);
|
|
53
|
+
if (!scope) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const checkoutRoot = readOwningCheckoutRoot(scope.worktreeRoot, fsModule);
|
|
58
|
+
if (!checkoutRoot) {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
if (!scope.relativePath) {
|
|
62
|
+
return checkoutRoot;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Package-scoped chats point at a subdirectory, so mirror it into the checkout when it
|
|
66
|
+
// exists there; otherwise the repo root is still the right project to group under.
|
|
67
|
+
const scopedPath = path.join(checkoutRoot, scope.relativePath);
|
|
68
|
+
return isExistingDirectory(scopedPath, fsModule) ? scopedPath : checkoutRoot;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function attachToThread(thread) {
|
|
72
|
+
if (!thread || typeof thread !== "object" || normalizeString(thread.worktreeOriginPath)) {
|
|
73
|
+
return thread;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const cwd = threadRowCwd(thread);
|
|
77
|
+
if (!cwd) {
|
|
78
|
+
return thread;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const originPath = originPathForCwd(cwd);
|
|
82
|
+
if (originPath) {
|
|
83
|
+
thread.worktreeOriginPath = originPath;
|
|
84
|
+
}
|
|
85
|
+
return thread;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function enrichResponse(method, envelope) {
|
|
89
|
+
return forEachThreadRowInResponse(method, envelope, attachToThread);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
attachToThread,
|
|
94
|
+
enrichResponse,
|
|
95
|
+
// Exposed for focused tests so cache growth stays provable.
|
|
96
|
+
cacheSize: () => cache.size,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// CODEX_HOME can sit behind a symlink while thread rows carry resolved paths (or the reverse),
|
|
101
|
+
// so both spellings of the worktrees root are accepted.
|
|
102
|
+
function managedWorktreesRoots(codexHome, fsModule) {
|
|
103
|
+
const joinedRoot = path.join(normalizeString(codexHome) || path.join(os.homedir(), ".codex"), "worktrees");
|
|
104
|
+
const roots = [joinedRoot];
|
|
105
|
+
const realRoot = realPathOrNull(joinedRoot, fsModule);
|
|
106
|
+
if (realRoot && realRoot !== joinedRoot) {
|
|
107
|
+
roots.push(realRoot);
|
|
108
|
+
}
|
|
109
|
+
return roots;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveManagedWorktreeScope(cwd, worktreesRoots) {
|
|
113
|
+
for (const root of worktreesRoots) {
|
|
114
|
+
const relativePath = path.relative(root, cwd);
|
|
115
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const segments = relativePath.split(path.sep).filter(Boolean);
|
|
120
|
+
// <token>/<repo> is the worktree itself; anything deeper is a chat scoped inside it.
|
|
121
|
+
if (segments.length < 2) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
worktreeRoot: path.join(root, segments[0], segments[1]),
|
|
127
|
+
relativePath: segments.slice(2).join(path.sep),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function readOwningCheckoutRoot(worktreeRoot, fsModule) {
|
|
135
|
+
let gitFileContents = "";
|
|
136
|
+
try {
|
|
137
|
+
gitFileContents = fsModule.readFileSync(path.join(worktreeRoot, ".git"), "utf8");
|
|
138
|
+
} catch {
|
|
139
|
+
// A removed worktree, or a plain repo copied under the worktrees root, has no `.git` file.
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const gitDirMatch = /^\s*gitdir:\s*(.+?)\s*$/m.exec(gitFileContents);
|
|
144
|
+
if (!gitDirMatch) {
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const gitDir = path.resolve(worktreeRoot, gitDirMatch[1]);
|
|
149
|
+
const gitDirSegments = gitDir.split(path.sep);
|
|
150
|
+
// Only <checkout>/.git/worktrees/<name> identifies a linked worktree of a real checkout.
|
|
151
|
+
if (gitDirSegments.length < 4
|
|
152
|
+
|| gitDirSegments[gitDirSegments.length - 2] !== "worktrees"
|
|
153
|
+
|| gitDirSegments[gitDirSegments.length - 3] !== ".git") {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return path.dirname(path.dirname(path.dirname(gitDir)));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function threadRowCwd(thread) {
|
|
161
|
+
for (const key of THREAD_ROW_CWD_KEYS) {
|
|
162
|
+
const candidate = normalizeString(thread[key]);
|
|
163
|
+
if (candidate) {
|
|
164
|
+
return candidate;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isExistingDirectory(candidatePath, fsModule) {
|
|
171
|
+
try {
|
|
172
|
+
return fsModule.statSync(candidatePath).isDirectory();
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function realPathOrNull(candidatePath, fsModule) {
|
|
179
|
+
try {
|
|
180
|
+
return fsModule.realpathSync(candidatePath);
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeString(value) {
|
|
187
|
+
return typeof value === "string" ? value.trim() : "";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
createWorktreeOriginEnricher,
|
|
192
|
+
};
|