stim 1.13.0 → 1.15.0
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/README.md +2 -0
- package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
- package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
- package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
- package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
- package/dist/android-cas-PnKfAdB2.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
- package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
- package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
- package/dist/build-progress-C2Jv1AtA.mjs +872 -0
- package/dist/build-slots-Df1ApSuH.mjs +127 -0
- package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
- package/dist/cli-0cX5s4NM.mjs +45 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-C2IS36Qf.mjs +1333 -0
- package/dist/collector-run.mjs +8 -7
- package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
- package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
- package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
- package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
- package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CjzLoNs0.mjs +80 -0
- package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
- package/dist/device-capacity-DMuKC7nx.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +769 -0
- package/dist/device-ios-DMQBUPus.mjs +381 -0
- package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
- package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
- package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
- package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
- package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
- package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
- package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
- package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
- package/dist/hosted-macos-DwowNU7W.mjs +447 -0
- package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
- package/dist/in-use-CndPMilN.mjs +110 -0
- package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
- package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
- package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
- package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
- package/dist/machine-usage-D_YYMzfR.mjs +283 -0
- package/dist/machines-CYtOCqcj.mjs +259 -0
- package/dist/macos-BwtbUtTN.mjs +468 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
- package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
- package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
- package/dist/native-run-DFDoIRbY.mjs +147 -0
- package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
- package/dist/offload-worker.d.mts +121 -0
- package/dist/offload-worker.mjs +601 -0
- package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
- package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
- package/dist/prebuild-BvxDlD6C.mjs +213 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-GEXp4iBT.mjs +37 -0
- package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
- package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
- package/dist/run-B-xqRrsg.mjs +119 -0
- package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
- package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
- package/dist/server-expo-KJeD109I.mjs +2 -0
- package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
- package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
- package/dist/settings.schema.json +357 -2
- package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
- package/dist/slot-launch-BY0BrOLh.mjs +65 -0
- package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-DncpJvWL.mjs +16 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CPO4wRg7.mjs +2 -0
- package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/state-BxCgeTQu.mjs +41 -0
- package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
- package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
- package/dist/stats-DBLLFmKd.mjs +66 -0
- package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
- package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
- package/dist/stop-Bvf1MflS.mjs +48 -0
- package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
- package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
- package/dist/supervisor-run.d.mts +44 -12
- package/dist/supervisor-run.mjs +276 -33
- package/dist/support-CDQbDkL5.mjs +315 -0
- package/dist/support-WLOzVKUv.mjs +176 -0
- package/dist/teardown-BYuQafiA.mjs +718 -0
- package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
- package/dist/warm-progress-DVHLN4uC.mjs +463 -0
- package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
- package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
- package/dist/web-run.mjs +326 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-CL2V2Ons.mjs +371 -0
- package/dist/worktree-BT_uULFx.mjs +2 -0
- package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
- package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
- package/package.json +10 -5
- package/shim/bundle-response.cjs +55 -10
- package/shim/bundle-response.d.cts +1 -0
- package/shim/expo-metro-config.cjs +10 -2
- package/dist/build-progress-l7d-X-ZP.mjs +0 -247
- package/dist/in-use-wYEaKQM9.mjs +0 -249
- package/dist/ownership-PUP4Dwai.mjs +0 -1138
- package/dist/server-expo-BeYwP_Xy.mjs +0 -2
- package/dist/slot-launch-BIWEW-wI.mjs +0 -19
- package/dist/start-Rw2GD3nV.mjs +0 -2
- package/dist/stats-CaSwmsSZ.mjs +0 -62
- package/dist/stats-D8rUTPHd.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/worktree-3cBa-bQt.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
+
import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
5
|
+
import { join as join$1, resolve as resolve$1 } from "path";
|
|
6
|
+
import { buildCacheRoot as sharedBuildCache, withDirLock as withDirLock$1, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
|
|
7
|
+
import { WORKSPACE_AGENT_KEY, emulatorLogFile, readJsonObject, readWorkspaceState, readWorkspaceState as readWorkspaceState$1, sharedCcache, sharedCompilationCache, supervisorLogFile, supervisorPidFile, workspaceDerivedData, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock } from "@stim-cli/core/state";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
10
|
+
//#region src/workspace/paths.ts
|
|
11
|
+
function ensureWorkspaceStorage(projectRoot) {
|
|
12
|
+
const canonicalRoot = resolve$1(projectRoot);
|
|
13
|
+
const dir = workspaceDir(canonicalRoot);
|
|
14
|
+
const file = workspaceMetadataFile(canonicalRoot);
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
return withDirLock$1(join$1(dir, "metadata.lock"), () => {
|
|
17
|
+
if (!existsSync(file)) {
|
|
18
|
+
const metadata = {
|
|
19
|
+
projectRoot: canonicalRoot,
|
|
20
|
+
workspace: workspaceName$1(canonicalRoot),
|
|
21
|
+
version: 1
|
|
22
|
+
};
|
|
23
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
24
|
+
writeFileSync(tmp, `${JSON.stringify(metadata, null, 2)}\n`);
|
|
25
|
+
try {
|
|
26
|
+
renameSync(tmp, file);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
rmSync(tmp, { force: true });
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
if (readJsonObject(file)?.projectRoot === canonicalRoot) return dir;
|
|
34
|
+
const error = /* @__PURE__ */ new Error(`Stim workspace collision at ${dir}: its workspace.json does not belong to ${canonicalRoot}.`);
|
|
35
|
+
error.code = "STIM_WORKSPACE_COLLISION";
|
|
36
|
+
throw error;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/agent-sessions.ts
|
|
41
|
+
const TITLE_MAX_LENGTH = 120;
|
|
42
|
+
const SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
43
|
+
const CODEX_ACTIVE_MS = 18e5;
|
|
44
|
+
const CODEX_QUERY_TIMEOUT_MS = 5e3;
|
|
45
|
+
const COMMAND_ATTRIBUTION_MS = 18e5;
|
|
46
|
+
/**
|
|
47
|
+
* Claude desktop opens a Claude Code session with `claude://code/continue?session=<id>`, and accepts only its own
|
|
48
|
+
* `local_` session ids there, which a session file carries as `hostSessionId` when the desktop app hosts the session.
|
|
49
|
+
*/
|
|
50
|
+
const CLAUDE_DESKTOP_SESSION = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
51
|
+
/**
|
|
52
|
+
* Claude Code writes a Remote Control session's claude.ai id to its session file as `bridgeSessionId`, and prints
|
|
53
|
+
* `https://claude.ai/code/<id>` as the session URL, which the Claude mobile app also opens as a universal link.
|
|
54
|
+
*/
|
|
55
|
+
const CLAUDE_REMOTE_SESSION = /^session_[A-Za-z0-9]{1,64}$/;
|
|
56
|
+
function shortTitle(value) {
|
|
57
|
+
if (typeof value !== "string") return void 0;
|
|
58
|
+
const title = value.replace(/\p{Cc}+/gu, " ").replace(/\s+/g, " ").trim();
|
|
59
|
+
if (!title) return void 0;
|
|
60
|
+
return title.length > TITLE_MAX_LENGTH ? `${title.slice(0, 117)}...` : title;
|
|
61
|
+
}
|
|
62
|
+
function isoFromMs(value) {
|
|
63
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? new Date(value).toISOString() : void 0;
|
|
64
|
+
}
|
|
65
|
+
function withOptional(session, fields) {
|
|
66
|
+
for (const [key, value] of Object.entries(fields)) if (value !== void 0) Object.assign(session, { [key]: value });
|
|
67
|
+
return session;
|
|
68
|
+
}
|
|
69
|
+
/** The agent session a shell's environment names, from `CLAUDE_CODE_SESSION_ID` or `CODEX_THREAD_ID`. */
|
|
70
|
+
function agentFromEnv(env) {
|
|
71
|
+
const claude = env.CLAUDE_CODE_SESSION_ID?.trim();
|
|
72
|
+
if (claude && SESSION_ID.test(claude)) return {
|
|
73
|
+
tool: "claude-code",
|
|
74
|
+
sessionId: claude
|
|
75
|
+
};
|
|
76
|
+
const codex = env.CODEX_THREAD_ID?.trim();
|
|
77
|
+
if (codex && SESSION_ID.test(codex)) return {
|
|
78
|
+
tool: "codex",
|
|
79
|
+
sessionId: codex
|
|
80
|
+
};
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A Claude Code session from one `~/.claude/sessions/<pid>.json` file, or null when the entry lacks a pid, session id
|
|
85
|
+
* or absolute cwd. The format is Claude Code's own and undocumented, so every other field is optional.
|
|
86
|
+
*/
|
|
87
|
+
function parseClaudeSession(entry, apps) {
|
|
88
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
89
|
+
const raw = entry;
|
|
90
|
+
if (!Number.isSafeInteger(raw.pid) || raw.pid <= 0) return null;
|
|
91
|
+
if (typeof raw.sessionId !== "string" || !SESSION_ID.test(raw.sessionId)) return null;
|
|
92
|
+
if (typeof raw.cwd !== "string" || !isAbsolute(raw.cwd)) return null;
|
|
93
|
+
const host = raw.hostSessionId;
|
|
94
|
+
const bridge = raw.bridgeSessionId;
|
|
95
|
+
return withOptional({
|
|
96
|
+
tool: "claude-code",
|
|
97
|
+
sessionId: raw.sessionId,
|
|
98
|
+
cwd: raw.cwd
|
|
99
|
+
}, {
|
|
100
|
+
title: shortTitle(raw.name),
|
|
101
|
+
startedAt: isoFromMs(raw.startedAt),
|
|
102
|
+
lastActiveAt: isoFromMs(raw.updatedAt),
|
|
103
|
+
pid: raw.pid,
|
|
104
|
+
openUrl: apps.claude && typeof host === "string" && CLAUDE_DESKTOP_SESSION.test(host) ? `claude://code/continue?session=${host}` : void 0,
|
|
105
|
+
webUrl: typeof bridge === "string" && CLAUDE_REMOTE_SESSION.test(bridge) ? `https://claude.ai/code/${bridge}` : void 0
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function isSubagentSource(source) {
|
|
109
|
+
if (typeof source !== "string" || !source.startsWith("{")) return false;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(source);
|
|
112
|
+
return Boolean(parsed && typeof parsed === "object" && "subagent" in parsed);
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Codex threads from rows of its `threads` table. `name` is the thread's short name; `title` there is the first
|
|
119
|
+
* prompt, so it is never read. Subagent threads, which Codex spawns inside a thread, are left out.
|
|
120
|
+
*/
|
|
121
|
+
function parseCodexThreads(rows, apps) {
|
|
122
|
+
if (!Array.isArray(rows)) return [];
|
|
123
|
+
return rows.flatMap((row) => {
|
|
124
|
+
if (!row || typeof row !== "object") return [];
|
|
125
|
+
const raw = row;
|
|
126
|
+
if (typeof raw.id !== "string" || !SESSION_ID.test(raw.id)) return [];
|
|
127
|
+
if (typeof raw.cwd !== "string" || !isAbsolute(raw.cwd) || isSubagentSource(raw.source)) return [];
|
|
128
|
+
return [withOptional({
|
|
129
|
+
tool: "codex",
|
|
130
|
+
sessionId: raw.id,
|
|
131
|
+
cwd: raw.cwd
|
|
132
|
+
}, {
|
|
133
|
+
title: shortTitle(raw.name),
|
|
134
|
+
startedAt: isoFromMs(raw.created_at_ms),
|
|
135
|
+
lastActiveAt: isoFromMs(raw.updated_at_ms),
|
|
136
|
+
openUrl: apps.codex ? `codex://threads/${raw.id}` : void 0
|
|
137
|
+
})];
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function codexStateDatabase(names) {
|
|
141
|
+
let best = null;
|
|
142
|
+
for (const name of names) {
|
|
143
|
+
const version = Number(/^state_(\d+)\.sqlite$/.exec(name)?.[1]);
|
|
144
|
+
if (Number.isFinite(version) && (!best || version > best.version)) best = {
|
|
145
|
+
name,
|
|
146
|
+
version
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return best?.name ?? null;
|
|
150
|
+
}
|
|
151
|
+
const APP_BUNDLES = {
|
|
152
|
+
claude: {
|
|
153
|
+
names: ["Claude"],
|
|
154
|
+
bundleId: "com.anthropic.claudefordesktop"
|
|
155
|
+
},
|
|
156
|
+
codex: {
|
|
157
|
+
names: ["ChatGPT", "Codex"],
|
|
158
|
+
bundleId: "com.openai.codex"
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
function bundleInstalled(home, { names, bundleId }) {
|
|
162
|
+
for (const folder of ["/Applications", join(home, "Applications")]) for (const name of names) try {
|
|
163
|
+
if (readFileSync(join(folder, `${name}.app`, "Contents", "Info.plist"), "latin1").includes(bundleId)) return true;
|
|
164
|
+
} catch {}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
function installedAgentApps(home = homedir()) {
|
|
168
|
+
if (process.platform !== "darwin") return {
|
|
169
|
+
claude: false,
|
|
170
|
+
codex: false
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
claude: bundleInstalled(home, APP_BUNDLES.claude),
|
|
174
|
+
codex: bundleInstalled(home, APP_BUNDLES.codex)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function canonical(session) {
|
|
178
|
+
try {
|
|
179
|
+
return {
|
|
180
|
+
...session,
|
|
181
|
+
cwd: realpathSync(session.cwd)
|
|
182
|
+
};
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function sessionProcessRuns(session) {
|
|
188
|
+
const start = (0, process_identity_exports.inspectProcessStart)(session.pid);
|
|
189
|
+
if (start.status === "gone") return false;
|
|
190
|
+
if (start.status === "unknown" || !session.startedAt) return true;
|
|
191
|
+
return start.startedAtMs <= Date.parse(session.startedAt) + 2e3;
|
|
192
|
+
}
|
|
193
|
+
async function claudeSessions(home, apps) {
|
|
194
|
+
const directory = join(home, ".claude", "sessions");
|
|
195
|
+
let names;
|
|
196
|
+
try {
|
|
197
|
+
names = (await readdir(directory)).filter((name) => name.endsWith(".json"));
|
|
198
|
+
} catch {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
return (await Promise.all(names.map(async (name) => {
|
|
202
|
+
try {
|
|
203
|
+
const session = parseClaudeSession(JSON.parse(await readFile(join(directory, name), "utf8")), apps);
|
|
204
|
+
return session && sessionProcessRuns(session) ? canonical(session) : null;
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}))).filter((session) => session !== null);
|
|
209
|
+
}
|
|
210
|
+
const CODEX_QUERY = `const { DatabaseSync } = require('node:sqlite');
|
|
211
|
+
const db = new DatabaseSync(process.argv[1], { readOnly: true });
|
|
212
|
+
const rows = db.prepare('SELECT id, cwd, name, source, created_at_ms, updated_at_ms FROM threads WHERE archived = 0 AND updated_at_ms >= ? AND source NOT LIKE ? ORDER BY updated_at_ms DESC LIMIT 50').all(Number(process.argv[2]), '%"subagent"%');
|
|
213
|
+
db.close();
|
|
214
|
+
process.stdout.write(JSON.stringify(rows));`;
|
|
215
|
+
async function codexSessions(codexHome, apps, now) {
|
|
216
|
+
let database;
|
|
217
|
+
try {
|
|
218
|
+
database = codexStateDatabase(await readdir(codexHome));
|
|
219
|
+
} catch {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
if (!database) return [];
|
|
223
|
+
try {
|
|
224
|
+
const out = await getExecutor().runFileAsync(process.execPath, [
|
|
225
|
+
"--no-warnings",
|
|
226
|
+
"--experimental-sqlite",
|
|
227
|
+
"-e",
|
|
228
|
+
CODEX_QUERY,
|
|
229
|
+
join(codexHome, database),
|
|
230
|
+
String(now - CODEX_ACTIVE_MS)
|
|
231
|
+
], { timeoutMs: CODEX_QUERY_TIMEOUT_MS });
|
|
232
|
+
return parseCodexThreads(JSON.parse(out), apps).map(canonical).filter((session) => session !== null);
|
|
233
|
+
} catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The Claude Code sessions whose process runs and the Codex threads updated in the last 30 minutes, with canonical
|
|
239
|
+
* working directories. Every source is optional: a missing, unreadable or changed format yields no sessions from it.
|
|
240
|
+
*/
|
|
241
|
+
async function discoverAgentSessions({ home = homedir(), codexHome = process.env.CODEX_HOME || join(home, ".codex"), apps = installedAgentApps(home), now = Date.now() } = {}) {
|
|
242
|
+
const [claude, codex] = await Promise.all([claudeSessions(home, apps), codexSessions(codexHome, apps, now)]);
|
|
243
|
+
return [...claude, ...codex];
|
|
244
|
+
}
|
|
245
|
+
function within(directory, candidate) {
|
|
246
|
+
const path = relative(directory, candidate);
|
|
247
|
+
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
248
|
+
}
|
|
249
|
+
function agentKey(session) {
|
|
250
|
+
return `${session.tool}:${session.sessionId}`;
|
|
251
|
+
}
|
|
252
|
+
function activeAt(session) {
|
|
253
|
+
const at = Date.parse(session.lastActiveAt ?? "");
|
|
254
|
+
return Number.isFinite(at) ? at : -Infinity;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* The sessions working in each workspace, most recently active first. A session belongs to the deepest workspace
|
|
258
|
+
* whose path holds its cwd, and to every workspace whose git worktree root is its cwd. A workspace's recorded session
|
|
259
|
+
* is added with the discovered session's details when it is running, or on its own for `COMMAND_ATTRIBUTION_MS`
|
|
260
|
+
* after the command.
|
|
261
|
+
*/
|
|
262
|
+
function attributeAgentSessions(workspaces, sessions, now) {
|
|
263
|
+
const found = workspaces.map(() => /* @__PURE__ */ new Map());
|
|
264
|
+
for (const session of sessions) {
|
|
265
|
+
const holders = workspaces.flatMap((workspace, i) => within(workspace.path, session.cwd) ? [i] : []);
|
|
266
|
+
const deepest = Math.max(-1, ...holders.map((i) => workspaces[i].path.length));
|
|
267
|
+
workspaces.forEach((workspace, i) => {
|
|
268
|
+
if (holders.includes(i) && workspace.path.length === deepest || workspace.root === session.cwd) found[i].set(agentKey(session), session);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
workspaces.forEach(({ recorded }, i) => {
|
|
272
|
+
if (!recorded) return;
|
|
273
|
+
const running = found[i].get(agentKey(recorded)) ?? sessions.find((session) => agentKey(session) === agentKey(recorded));
|
|
274
|
+
if (running) {
|
|
275
|
+
const newer = activeAt(recorded) > activeAt(running);
|
|
276
|
+
found[i].set(agentKey(recorded), newer ? {
|
|
277
|
+
...running,
|
|
278
|
+
lastActiveAt: recorded.lastActiveAt
|
|
279
|
+
} : running);
|
|
280
|
+
} else if (now - activeAt(recorded) <= COMMAND_ATTRIBUTION_MS) found[i].set(agentKey(recorded), recorded);
|
|
281
|
+
});
|
|
282
|
+
return found.map((map) => [...map.values()].toSorted((a, b) => activeAt(b) - activeAt(a)));
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/workspace/workspace-state.ts
|
|
286
|
+
function withWorkspaceStateLock(root, fn) {
|
|
287
|
+
const file = workspaceStateFile(root);
|
|
288
|
+
return withDirLock$1(workspaceStateLock(root), fn, { ensureParent: () => {
|
|
289
|
+
ensureWorkspaceStorage(root);
|
|
290
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
291
|
+
} });
|
|
292
|
+
}
|
|
293
|
+
function writeWorkspaceState(root, patch) {
|
|
294
|
+
return updateWorkspaceState(root, (state) => ({
|
|
295
|
+
...state,
|
|
296
|
+
...patch
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
function updateWorkspaceState(root, update) {
|
|
300
|
+
return withWorkspaceStateLock(root, () => replaceWorkspaceState(root, update(readWorkspaceState(root) ?? {})));
|
|
301
|
+
}
|
|
302
|
+
function replaceWorkspaceState(root, state) {
|
|
303
|
+
const file = workspaceStateFile(root);
|
|
304
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
305
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
306
|
+
writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
|
|
307
|
+
renameSync(tmp, file);
|
|
308
|
+
return state;
|
|
309
|
+
}
|
|
310
|
+
function clearWorkspaceStateKeys(root, keys) {
|
|
311
|
+
if (!existsSync(workspaceStateFile(root))) return;
|
|
312
|
+
withWorkspaceStateLock(root, () => {
|
|
313
|
+
const state = readWorkspaceState(root);
|
|
314
|
+
const file = workspaceStateFile(root);
|
|
315
|
+
if (!state) {
|
|
316
|
+
try {
|
|
317
|
+
rmSync(file, { force: true });
|
|
318
|
+
} catch {}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
let changed = false;
|
|
322
|
+
for (const key of keys) {
|
|
323
|
+
if (!(key in state)) continue;
|
|
324
|
+
delete state[key];
|
|
325
|
+
changed = true;
|
|
326
|
+
}
|
|
327
|
+
if (!changed) return;
|
|
328
|
+
if (Object.keys(state).length === 0) {
|
|
329
|
+
rmSync(file, { force: true });
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
replaceWorkspaceState(root, state);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function clearWorkspaceStateKey(root, key, shouldClear) {
|
|
336
|
+
return withWorkspaceStateLock(root, () => {
|
|
337
|
+
const state = readWorkspaceState(root);
|
|
338
|
+
if (!state || !(key in state)) return true;
|
|
339
|
+
if (!shouldClear(state[key])) return false;
|
|
340
|
+
delete state[key];
|
|
341
|
+
const file = workspaceStateFile(root);
|
|
342
|
+
if (Object.keys(state).length === 0) {
|
|
343
|
+
try {
|
|
344
|
+
rmSync(file, { force: true });
|
|
345
|
+
} catch {}
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
replaceWorkspaceState(root, state);
|
|
349
|
+
return true;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/** Records a command's use of the workspace, and the agent session whose shell ran it when there is one. */
|
|
353
|
+
function recordWorkspaceUse(root, now = /* @__PURE__ */ new Date(), env = process.env) {
|
|
354
|
+
const agent = agentFromEnv(env);
|
|
355
|
+
let cwd = process.cwd();
|
|
356
|
+
try {
|
|
357
|
+
cwd = realpathSync(cwd);
|
|
358
|
+
} catch {}
|
|
359
|
+
try {
|
|
360
|
+
writeWorkspaceState(root, {
|
|
361
|
+
lastUsedAt: now.toISOString(),
|
|
362
|
+
...agent ? { [WORKSPACE_AGENT_KEY]: {
|
|
363
|
+
...agent,
|
|
364
|
+
cwd,
|
|
365
|
+
lastActiveAt: now.toISOString()
|
|
366
|
+
} } : {}
|
|
367
|
+
});
|
|
368
|
+
} catch {}
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
export { workspaceLogsDir as C, withDirLock$1 as E, workspaceLogErrorIndex as S, workspaceStateFile as T, supervisorLogFile as _, updateWorkspaceState as a, workspaceDir as b, writeWorkspaceState as c, discoverAgentSessions as d, emulatorLogFile as f, sharedCompilationCache as g, sharedCcache as h, recordWorkspaceUse as i, agentKey as l, sharedBuildCache as m, clearWorkspaceStateKeys as n, withWorkspaceStateLock as o, ensureWorkspaceStorage as p, readWorkspaceState$1 as r, workspaceLastUsed as s, clearWorkspaceStateKey as t, attributeAgentSessions as u, supervisorPidFile as v, workspaceName$1 as w, workspaceId as x, workspaceDerivedData as y };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as porcelainPath, c as registerWarm, d as removalRemedy, f as removeWorktreeTarget, i as matchWorktreeEntry, l as removalBlockers, m as worktreeCommand, n as excludePodChurn, o as reclaimKeys, p as warmedWorkspace, r as excludeWatchmanCookies, s as registerRemove, t as dependencyInstallCommand, u as removalPath } from "./worktree-qXKzADSC.mjs";
|
|
2
|
+
export { worktreeCommand as default, dependencyInstallCommand, excludePodChurn, excludeWatchmanCookies, matchWorktreeEntry, porcelainPath, reclaimKeys, registerRemove, registerWarm, removalBlockers, removalPath, removalRemedy, removeWorktreeTarget, warmedWorkspace };
|