@nowcrew/daemon 0.6.6 → 0.6.7
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 +26 -18
- package/dist/agent-memory/bridge.js +9 -6
- package/dist/agent-memory/client.js +2 -2
- package/dist/computer-service.js +9 -1
- package/dist/execution-protocol.js +1 -0
- package/dist/execution-runner.js +2 -2
- package/dist/machine-info.js +1 -0
- package/dist/main.js +0 -0
- package/dist/prompt.js +15 -10
- package/dist/workspace.js +30 -13
- package/package.json +8 -9
- package/dist/remote/claude-bridge.js +0 -558
- package/dist/remote/claude-channel.js +0 -164
- package/dist/remote/codex-client.js +0 -451
- package/dist/remote/codex-runtime.js +0 -77
- package/dist/remote/config.js +0 -135
- package/dist/remote/gateway.js +0 -879
- package/dist/remote/identity.js +0 -39
- package/dist/remote/owner.js +0 -77
- package/dist/remote/protocol.js +0 -211
- package/dist/remote/remote-cli.js +0 -254
- package/dist/remote/runtime-probe.js +0 -182
- package/dist/remote/session-discovery.js +0 -249
- package/dist/remote/wrapper.js +0 -40
- package/dist/remote-web/assets/index-B_6VM_tw.js +0 -94
- package/dist/remote-web/assets/index-L6EiQbJn.css +0 -1
- package/dist/remote-web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/remote-web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/remote-web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/remote-web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/remote-web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/remote-web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/remote-web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/remote-web/icons/nowwork-192.png +0 -0
- package/dist/remote-web/icons/nowwork-512.png +0 -0
- package/dist/remote-web/icons/nowwork.svg +0 -7
- package/dist/remote-web/index.html +0 -20
- package/dist/remote-web/manifest.webmanifest +0 -13
- package/dist/remote-web/sw.js +0 -12
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import spawn from "cross-spawn";
|
|
2
|
-
const PROBE_TIMEOUT_MS = 3_000;
|
|
3
|
-
const PROBE_OUTPUT_BYTES = 128 * 1024;
|
|
4
|
-
function appendBounded(current, chunk, remaining) {
|
|
5
|
-
if (remaining <= 0)
|
|
6
|
-
return { text: current, bytes: 0, exceeded: chunk.length > 0 };
|
|
7
|
-
const kept = chunk.subarray(0, remaining);
|
|
8
|
-
return {
|
|
9
|
-
text: current + kept.toString("utf8"),
|
|
10
|
-
bytes: kept.length,
|
|
11
|
-
exceeded: chunk.length > kept.length,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
export const systemRuntimeProbeRunner = (bin, args) => new Promise((resolve) => {
|
|
15
|
-
let stdout = "";
|
|
16
|
-
let stderr = "";
|
|
17
|
-
let capturedBytes = 0;
|
|
18
|
-
let failure;
|
|
19
|
-
let settled = false;
|
|
20
|
-
let forceTimer;
|
|
21
|
-
let child;
|
|
22
|
-
const finish = (exitCode) => {
|
|
23
|
-
if (settled)
|
|
24
|
-
return;
|
|
25
|
-
settled = true;
|
|
26
|
-
clearTimeout(timeout);
|
|
27
|
-
if (forceTimer)
|
|
28
|
-
clearTimeout(forceTimer);
|
|
29
|
-
resolve({
|
|
30
|
-
exitCode,
|
|
31
|
-
stdout,
|
|
32
|
-
stderr,
|
|
33
|
-
...(failure === undefined ? {} : { failure }),
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
const stop = (nextFailure) => {
|
|
37
|
-
if (failure !== undefined)
|
|
38
|
-
return;
|
|
39
|
-
failure = nextFailure;
|
|
40
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
41
|
-
return;
|
|
42
|
-
child.kill("SIGTERM");
|
|
43
|
-
forceTimer = setTimeout(() => {
|
|
44
|
-
if (child.exitCode === null && child.signalCode === null)
|
|
45
|
-
child.kill("SIGKILL");
|
|
46
|
-
}, 250);
|
|
47
|
-
};
|
|
48
|
-
const capture = (stream, raw) => {
|
|
49
|
-
const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
50
|
-
const appended = appendBounded(stream === "stdout" ? stdout : stderr, chunk, PROBE_OUTPUT_BYTES - capturedBytes);
|
|
51
|
-
capturedBytes += appended.bytes;
|
|
52
|
-
if (stream === "stdout")
|
|
53
|
-
stdout = appended.text;
|
|
54
|
-
else
|
|
55
|
-
stderr = appended.text;
|
|
56
|
-
if (appended.exceeded || capturedBytes >= PROBE_OUTPUT_BYTES)
|
|
57
|
-
stop("output_limit");
|
|
58
|
-
};
|
|
59
|
-
try {
|
|
60
|
-
child = spawn(bin, [...args], { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
resolve({ exitCode: 127, stdout: "", stderr: "", failure: "unavailable" });
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const timeout = setTimeout(() => stop("timeout"), PROBE_TIMEOUT_MS);
|
|
67
|
-
child.stdout?.on("data", (chunk) => capture("stdout", chunk));
|
|
68
|
-
child.stderr?.on("data", (chunk) => capture("stderr", chunk));
|
|
69
|
-
child.once("error", () => {
|
|
70
|
-
failure = "unavailable";
|
|
71
|
-
finish(127);
|
|
72
|
-
});
|
|
73
|
-
child.once("close", (code, signal) => finish(code ?? (signal ? 1 : 0)));
|
|
74
|
-
});
|
|
75
|
-
function parseVersion(output) {
|
|
76
|
-
const match = output.match(/\b(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?\b/);
|
|
77
|
-
if (!match)
|
|
78
|
-
return null;
|
|
79
|
-
return {
|
|
80
|
-
major: Number(match[1]),
|
|
81
|
-
minor: Number(match[2]),
|
|
82
|
-
patch: Number(match[3]),
|
|
83
|
-
text: `${match[1]}.${match[2]}.${match[3]}${match[4] ?? ""}`,
|
|
84
|
-
prerelease: match[4] !== undefined,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function atLeast(value, minimum) {
|
|
88
|
-
const actual = [value.major, value.minor, value.patch];
|
|
89
|
-
for (let index = 0; index < actual.length; index += 1) {
|
|
90
|
-
if (actual[index] > minimum[index])
|
|
91
|
-
return true;
|
|
92
|
-
if (actual[index] < minimum[index])
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
function failedCommand(runtime, result, phase) {
|
|
98
|
-
const name = runtime === "claude" ? "Claude Code" : "Codex";
|
|
99
|
-
if (!result)
|
|
100
|
-
return { runtime, state: "unsupported", reason: `${name} ${phase} probe did not run` };
|
|
101
|
-
if (result.failure === "unavailable") {
|
|
102
|
-
return { runtime, state: "offline", reason: `${name} executable is not available` };
|
|
103
|
-
}
|
|
104
|
-
if (result.failure === "timeout") {
|
|
105
|
-
return { runtime, state: "offline", reason: `${name} ${phase} probe timed out` };
|
|
106
|
-
}
|
|
107
|
-
if (result.failure === "output_limit") {
|
|
108
|
-
return { runtime, state: "unsupported", reason: `${name} ${phase} output exceeded the safety limit` };
|
|
109
|
-
}
|
|
110
|
-
if (result.exitCode !== 0) {
|
|
111
|
-
return {
|
|
112
|
-
runtime,
|
|
113
|
-
state: phase === "version" ? "offline" : "unsupported",
|
|
114
|
-
reason: `${name} ${phase} probe exited with code ${result.exitCode}`,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
function missingCapabilities(output, capabilities) {
|
|
120
|
-
return capabilities.filter((capability) => !output.includes(capability));
|
|
121
|
-
}
|
|
122
|
-
export function evaluateRuntimeProbe(runtime, evidence) {
|
|
123
|
-
const versionFailure = failedCommand(runtime, evidence.version, "version");
|
|
124
|
-
if (versionFailure)
|
|
125
|
-
return versionFailure;
|
|
126
|
-
const version = parseVersion(`${evidence.version.stdout}\n${evidence.version.stderr}`);
|
|
127
|
-
const name = runtime === "claude" ? "Claude Code" : "Codex";
|
|
128
|
-
if (!version)
|
|
129
|
-
return { runtime, state: "unsupported", reason: `${name} returned an unrecognized version` };
|
|
130
|
-
const minimum = runtime === "claude" ? [2, 1, 170] : [0, 145, 0];
|
|
131
|
-
const expectedMajor = runtime === "claude" ? 2 : 0;
|
|
132
|
-
if (version.prerelease || version.major !== expectedMajor || !atLeast(version, minimum)) {
|
|
133
|
-
return {
|
|
134
|
-
runtime,
|
|
135
|
-
state: "unsupported",
|
|
136
|
-
version: version.text,
|
|
137
|
-
reason: `${name} ${version.text} is outside the supported range >=${minimum.join(".")} and <${expectedMajor + 1}.0.0`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
const helpFailure = failedCommand(runtime, evidence.help, "capability");
|
|
141
|
-
if (helpFailure)
|
|
142
|
-
return { ...helpFailure, version: version.text };
|
|
143
|
-
const help = `${evidence.help.stdout}\n${evidence.help.stderr}`;
|
|
144
|
-
const required = runtime === "claude"
|
|
145
|
-
? ["--input-format", "stream-json", "--output-format", "--replay-user-messages", "--session-id", "--resume"]
|
|
146
|
-
: ["app-server", "--remote", "unix://"];
|
|
147
|
-
const missing = missingCapabilities(help, required);
|
|
148
|
-
if (runtime === "codex") {
|
|
149
|
-
const appServerFailure = failedCommand(runtime, evidence.appServerHelp, "app-server capability");
|
|
150
|
-
if (appServerFailure)
|
|
151
|
-
return { ...appServerFailure, version: version.text };
|
|
152
|
-
const appServerHelp = `${evidence.appServerHelp.stdout}\n${evidence.appServerHelp.stderr}`;
|
|
153
|
-
missing.push(...missingCapabilities(appServerHelp, ["--listen", "unix://"]));
|
|
154
|
-
}
|
|
155
|
-
const uniqueMissing = [...new Set(missing)];
|
|
156
|
-
if (uniqueMissing.length > 0) {
|
|
157
|
-
return {
|
|
158
|
-
runtime,
|
|
159
|
-
state: "unsupported",
|
|
160
|
-
version: version.text,
|
|
161
|
-
reason: `${name} ${version.text} is missing required capabilities: ${uniqueMissing.join(", ")}`,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
return { runtime, state: "supported", version: version.text, reason: `${name} ${version.text} is supported` };
|
|
165
|
-
}
|
|
166
|
-
export async function probeRuntime(runtime, runner = systemRuntimeProbeRunner) {
|
|
167
|
-
const bin = runtime === "claude"
|
|
168
|
-
? process.env.NOWCREW_CLAUDE_BIN ?? "claude"
|
|
169
|
-
: process.env.NOWCREW_CODEX_BIN ?? "codex";
|
|
170
|
-
const version = await runner(bin, ["--version"]);
|
|
171
|
-
if (failedCommand(runtime, version, "version"))
|
|
172
|
-
return evaluateRuntimeProbe(runtime, { version });
|
|
173
|
-
const help = await runner(bin, ["--help"]);
|
|
174
|
-
if (failedCommand(runtime, help, "capability"))
|
|
175
|
-
return evaluateRuntimeProbe(runtime, { version, help });
|
|
176
|
-
const appServerHelp = runtime === "codex" ? await runner(bin, ["app-server", "--help"]) : undefined;
|
|
177
|
-
return evaluateRuntimeProbe(runtime, {
|
|
178
|
-
version,
|
|
179
|
-
help,
|
|
180
|
-
...(appServerHelp === undefined ? {} : { appServerHelp }),
|
|
181
|
-
});
|
|
182
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { open, readdir, stat } from "node:fs/promises";
|
|
2
|
-
import { basename, join } from "node:path";
|
|
3
|
-
const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
-
const DEFAULT_HISTORY_BYTES = 4 * 1024 * 1024;
|
|
5
|
-
const DEFAULT_TIMELINE_ITEMS = 400;
|
|
6
|
-
async function collectJsonl(root, depth, cap) {
|
|
7
|
-
const result = [];
|
|
8
|
-
const walk = async (dir, remaining) => {
|
|
9
|
-
if (remaining < 0 || result.length >= cap)
|
|
10
|
-
return;
|
|
11
|
-
let entries;
|
|
12
|
-
try {
|
|
13
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
if (error.code === "ENOENT")
|
|
17
|
-
return;
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
for (const entry of entries) {
|
|
21
|
-
if (result.length >= cap)
|
|
22
|
-
return;
|
|
23
|
-
const path = join(dir, entry.name);
|
|
24
|
-
if (entry.isDirectory()) {
|
|
25
|
-
await walk(path, remaining - 1);
|
|
26
|
-
}
|
|
27
|
-
else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
28
|
-
const name = basename(entry.name, ".jsonl");
|
|
29
|
-
const id = name.startsWith("rollout-") ? name.slice(name.lastIndexOf("-") + 1) : name;
|
|
30
|
-
const match = entry.name.match(/([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.jsonl$/i);
|
|
31
|
-
const sessionId = match?.[1] ?? id;
|
|
32
|
-
if (!SESSION_ID.test(sessionId))
|
|
33
|
-
continue;
|
|
34
|
-
const info = await stat(path);
|
|
35
|
-
result.push({ path, id: sessionId, mtimeMs: info.mtimeMs, updatedAt: info.mtime.toISOString() });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
await walk(root, depth);
|
|
40
|
-
return result.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
41
|
-
}
|
|
42
|
-
async function readTailLines(path, maxBytes) {
|
|
43
|
-
const file = await open(path, "r");
|
|
44
|
-
try {
|
|
45
|
-
const info = await file.stat();
|
|
46
|
-
const length = Math.min(info.size, maxBytes);
|
|
47
|
-
if (length === 0)
|
|
48
|
-
return [];
|
|
49
|
-
const buffer = Buffer.allocUnsafe(length);
|
|
50
|
-
await file.read(buffer, 0, length, info.size - length);
|
|
51
|
-
const text = buffer.toString("utf8");
|
|
52
|
-
const lines = text.split("\n");
|
|
53
|
-
if (info.size > length)
|
|
54
|
-
lines.shift();
|
|
55
|
-
return lines.filter(Boolean);
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
await file.close();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function object(value) {
|
|
62
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
63
|
-
? value
|
|
64
|
-
: null;
|
|
65
|
-
}
|
|
66
|
-
function string(value) {
|
|
67
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
68
|
-
}
|
|
69
|
-
function textFromContent(value) {
|
|
70
|
-
if (typeof value === "string")
|
|
71
|
-
return string(value);
|
|
72
|
-
if (!Array.isArray(value))
|
|
73
|
-
return null;
|
|
74
|
-
const parts = [];
|
|
75
|
-
for (const raw of value) {
|
|
76
|
-
const block = object(raw);
|
|
77
|
-
if (!block)
|
|
78
|
-
continue;
|
|
79
|
-
if (["text", "input_text", "output_text"].includes(String(block.type))) {
|
|
80
|
-
const text = string(block.text);
|
|
81
|
-
if (text)
|
|
82
|
-
parts.push(text);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return parts.length ? parts.join("\n") : null;
|
|
86
|
-
}
|
|
87
|
-
function cleanChannelText(value) {
|
|
88
|
-
return value
|
|
89
|
-
.replace(/^<channel\b[^>]*>\s*/i, "")
|
|
90
|
-
.replace(/\s*<\/channel>$/i, "")
|
|
91
|
-
.trim();
|
|
92
|
-
}
|
|
93
|
-
function parseLine(line) {
|
|
94
|
-
try {
|
|
95
|
-
return object(JSON.parse(line));
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function claudeItems(lines, fallbackAt) {
|
|
102
|
-
let cwd = "";
|
|
103
|
-
let title = null;
|
|
104
|
-
const items = [];
|
|
105
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
106
|
-
const row = parseLine(lines[index]);
|
|
107
|
-
if (!row)
|
|
108
|
-
continue;
|
|
109
|
-
cwd = string(row.cwd) ?? cwd;
|
|
110
|
-
const message = object(row.message);
|
|
111
|
-
const role = row.type === "user" || row.type === "assistant" ? row.type : null;
|
|
112
|
-
const text = message ? textFromContent(message.content) : null;
|
|
113
|
-
const createdAt = string(row.timestamp) ?? fallbackAt;
|
|
114
|
-
const id = string(row.uuid) ?? `claude:${index}`;
|
|
115
|
-
if (role && text) {
|
|
116
|
-
const cleaned = cleanChannelText(text);
|
|
117
|
-
if (!cleaned)
|
|
118
|
-
continue;
|
|
119
|
-
if (role === "user")
|
|
120
|
-
title = cleaned.slice(0, 120);
|
|
121
|
-
items.push({ id, kind: "message", role, text: cleaned, createdAt });
|
|
122
|
-
}
|
|
123
|
-
if (role !== "assistant" || !message || !Array.isArray(message.content))
|
|
124
|
-
continue;
|
|
125
|
-
for (let blockIndex = 0; blockIndex < message.content.length; blockIndex += 1) {
|
|
126
|
-
const block = object(message.content[blockIndex]);
|
|
127
|
-
if (!block || block.type !== "tool_use")
|
|
128
|
-
continue;
|
|
129
|
-
const name = string(block.name) ?? "tool";
|
|
130
|
-
const summary = JSON.stringify(block.input ?? {}).slice(0, 12_000);
|
|
131
|
-
items.push({
|
|
132
|
-
id: `${id}:tool:${blockIndex}`,
|
|
133
|
-
kind: "tool",
|
|
134
|
-
name,
|
|
135
|
-
summary,
|
|
136
|
-
status: "requested",
|
|
137
|
-
createdAt,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return { cwd, title, items };
|
|
142
|
-
}
|
|
143
|
-
function codexItems(lines, fallbackAt) {
|
|
144
|
-
let cwd = "";
|
|
145
|
-
let title = null;
|
|
146
|
-
const items = [];
|
|
147
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
148
|
-
const row = parseLine(lines[index]);
|
|
149
|
-
if (!row)
|
|
150
|
-
continue;
|
|
151
|
-
const payload = object(row.payload);
|
|
152
|
-
if (!payload)
|
|
153
|
-
continue;
|
|
154
|
-
if (row.type === "session_meta")
|
|
155
|
-
cwd = string(payload.cwd) ?? cwd;
|
|
156
|
-
if (row.type !== "response_item" || payload.type !== "message")
|
|
157
|
-
continue;
|
|
158
|
-
const role = payload.role === "user" || payload.role === "assistant" ? payload.role : null;
|
|
159
|
-
const text = textFromContent(payload.content);
|
|
160
|
-
if (!role || !text)
|
|
161
|
-
continue;
|
|
162
|
-
const createdAt = string(row.timestamp) ?? fallbackAt;
|
|
163
|
-
const id = `codex:${index}:${createdAt}`;
|
|
164
|
-
if (role === "user")
|
|
165
|
-
title = text.slice(0, 120);
|
|
166
|
-
items.push({ id, kind: "message", role, text, createdAt });
|
|
167
|
-
}
|
|
168
|
-
return { cwd, title, items };
|
|
169
|
-
}
|
|
170
|
-
function capItems(items, max) {
|
|
171
|
-
return items.length <= max ? [...items] : items.slice(items.length - max);
|
|
172
|
-
}
|
|
173
|
-
export class RemoteSessionDiscovery {
|
|
174
|
-
options;
|
|
175
|
-
files = new Map();
|
|
176
|
-
constructor(options) {
|
|
177
|
-
this.options = options;
|
|
178
|
-
}
|
|
179
|
-
async list() {
|
|
180
|
-
const maxSessions = this.options.maxSessions ?? 100;
|
|
181
|
-
const historyBytes = this.options.maxHistoryBytes ?? DEFAULT_HISTORY_BYTES;
|
|
182
|
-
const [claude, codex] = await Promise.all([
|
|
183
|
-
collectJsonl(this.options.claudeProjectsRoot, 2, maxSessions * 4),
|
|
184
|
-
collectJsonl(this.options.codexSessionsRoot, 4, maxSessions * 4),
|
|
185
|
-
]);
|
|
186
|
-
this.files.clear();
|
|
187
|
-
const summaries = [];
|
|
188
|
-
for (const [runtime, candidates] of [["claude", claude], ["codex", codex]]) {
|
|
189
|
-
for (const candidate of candidates.slice(0, maxSessions)) {
|
|
190
|
-
const key = `${runtime}:${candidate.id}`;
|
|
191
|
-
if (this.files.has(key))
|
|
192
|
-
continue;
|
|
193
|
-
this.files.set(key, candidate);
|
|
194
|
-
const lines = await readTailLines(candidate.path, historyBytes);
|
|
195
|
-
const parsed = runtime === "claude"
|
|
196
|
-
? claudeItems(lines, candidate.updatedAt)
|
|
197
|
-
: codexItems(lines, candidate.updatedAt);
|
|
198
|
-
const active = runtime === "claude"
|
|
199
|
-
? this.options.activeClaude?.get(candidate.id)
|
|
200
|
-
: this.options.activeCodex?.get(candidate.id);
|
|
201
|
-
summaries.push({
|
|
202
|
-
id: candidate.id,
|
|
203
|
-
runtime,
|
|
204
|
-
title: active?.title ?? parsed.title ?? `${runtime === "claude" ? "Claude" : "Codex"} session`,
|
|
205
|
-
cwd: active?.cwd ?? parsed.cwd,
|
|
206
|
-
updatedAt: active?.updatedAt ?? candidate.updatedAt,
|
|
207
|
-
controlState: this.options.runtimeStates?.[runtime] ?? (active ? "live" : "read_only"),
|
|
208
|
-
busy: active?.busy ?? false,
|
|
209
|
-
source: active ? (runtime === "claude" ? "channel" : "app_server") : "history",
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
for (const [runtime, activeSessions] of [["claude", this.options.activeClaude], ["codex", this.options.activeCodex]]) {
|
|
214
|
-
for (const [id, active] of activeSessions ?? []) {
|
|
215
|
-
if (summaries.some((session) => session.runtime === runtime && session.id === id))
|
|
216
|
-
continue;
|
|
217
|
-
summaries.push({
|
|
218
|
-
id,
|
|
219
|
-
runtime,
|
|
220
|
-
title: active.title ?? `${runtime === "claude" ? "Claude" : "Codex"} session`,
|
|
221
|
-
cwd: active.cwd,
|
|
222
|
-
updatedAt: active.updatedAt ?? new Date().toISOString(),
|
|
223
|
-
controlState: this.options.runtimeStates?.[runtime] ?? "live",
|
|
224
|
-
busy: active.busy,
|
|
225
|
-
source: runtime === "claude" ? "channel" : "app_server",
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, maxSessions);
|
|
230
|
-
}
|
|
231
|
-
async timeline(runtime, sessionId) {
|
|
232
|
-
if (!SESSION_ID.test(sessionId))
|
|
233
|
-
return null;
|
|
234
|
-
let candidate = this.files.get(`${runtime}:${sessionId}`);
|
|
235
|
-
if (!candidate) {
|
|
236
|
-
await this.list();
|
|
237
|
-
candidate = this.files.get(`${runtime}:${sessionId}`);
|
|
238
|
-
}
|
|
239
|
-
if (!candidate) {
|
|
240
|
-
const active = runtime === "claude" ? this.options.activeClaude?.has(sessionId) : this.options.activeCodex?.has(sessionId);
|
|
241
|
-
return active ? [] : null;
|
|
242
|
-
}
|
|
243
|
-
const lines = await readTailLines(candidate.path, this.options.maxHistoryBytes ?? DEFAULT_HISTORY_BYTES);
|
|
244
|
-
const parsed = runtime === "claude"
|
|
245
|
-
? claudeItems(lines, candidate.updatedAt)
|
|
246
|
-
: codexItems(lines, candidate.updatedAt);
|
|
247
|
-
return capItems(parsed.items, this.options.maxTimelineItems ?? DEFAULT_TIMELINE_ITEMS);
|
|
248
|
-
}
|
|
249
|
-
}
|
package/dist/remote/wrapper.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3
|
-
function optionValue(args, long, short) {
|
|
4
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
5
|
-
const value = args[index];
|
|
6
|
-
if (value.startsWith(`${long}=`))
|
|
7
|
-
return value.slice(long.length + 1);
|
|
8
|
-
if (value === long || (short && value === short)) {
|
|
9
|
-
const next = args[index + 1];
|
|
10
|
-
return next && !next.startsWith("-") ? next : null;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
export function resolveClaudeWrapperSession(args, createId = randomUUID) {
|
|
16
|
-
if (args.includes("--continue") || args.includes("-c")) {
|
|
17
|
-
throw new Error("NowCrew remote requires an explicit session ID; use --resume <session-id> instead of --continue");
|
|
18
|
-
}
|
|
19
|
-
if (args.includes("--fork-session")) {
|
|
20
|
-
throw new Error("NowCrew remote cannot identify a fork chosen inside Claude; start a new session or resume without --fork-session");
|
|
21
|
-
}
|
|
22
|
-
const sessionId = optionValue(args, "--session-id") ?? optionValue(args, "--resume", "-r") ?? createId();
|
|
23
|
-
if (!UUID.test(sessionId)) {
|
|
24
|
-
throw new Error("Claude remote sessions require a UUID in --session-id or --resume");
|
|
25
|
-
}
|
|
26
|
-
const hasSessionSelector = args.some((value) => value === "--session-id" || value.startsWith("--session-id=")
|
|
27
|
-
|| value === "--resume" || value.startsWith("--resume=") || value === "-r");
|
|
28
|
-
return {
|
|
29
|
-
sessionId,
|
|
30
|
-
args: hasSessionSelector ? [...args] : ["--session-id", sessionId, ...args],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
export function buildCodexWrappedInvocation(userArgs, socketPath = "", bin = process.env.NOWCREW_CODEX_BIN ?? "codex") {
|
|
34
|
-
const hasRemote = userArgs.some((value) => value === "--remote" || value.startsWith("--remote="));
|
|
35
|
-
return {
|
|
36
|
-
bin,
|
|
37
|
-
args: hasRemote ? [...userArgs] : ["--remote", socketPath ? `unix://${socketPath}` : "unix://", ...userArgs],
|
|
38
|
-
env: process.env,
|
|
39
|
-
};
|
|
40
|
-
}
|