omp-mobile 0.4.3 → 0.4.4
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/package.json +1 -1
- package/src/omp-extension.mjs +2 -2
- package/src/server.mjs +6 -6
- package/src/takeover.mjs +15 -3
package/package.json
CHANGED
package/src/omp-extension.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { homedir } from "node:os";
|
|
|
21
21
|
import { delimiter, join } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { renderQr } from "./qr.mjs";
|
|
24
|
-
import { requestPath } from "./takeover.mjs";
|
|
24
|
+
import { requestPath, shellWord } from "./takeover.mjs";
|
|
25
25
|
|
|
26
26
|
const HOME = join(homedir(), ".omp-mobile");
|
|
27
27
|
const STATE_FILE = join(HOME, "daemon.json");
|
|
@@ -229,7 +229,7 @@ export default function ompMobile(pi) {
|
|
|
229
229
|
rmSync(requestPath(file), { force: true });
|
|
230
230
|
// OMP closes once the session is idle; the daemon signals it if that does not come soon,
|
|
231
231
|
// and tells the terminal where the session went once omp is gone.
|
|
232
|
-
ctx.ui.notify(`OMP Mobile: this session continues on ${request.device}. Closing omp here
|
|
232
|
+
ctx.ui.notify(`OMP Mobile: this session continues on ${request.device}. Closing omp here; resume it later with: omp --resume ${shellWord(file)}`, "warning");
|
|
233
233
|
if (!ctx.isIdle()) {
|
|
234
234
|
ctx.abort();
|
|
235
235
|
const deadline = Date.now() + TAKEOVER_IDLE_WAIT_MS;
|
package/src/server.mjs
CHANGED
|
@@ -58,8 +58,6 @@ const IDLE_SESSION_MS = 30 * 60_000;
|
|
|
58
58
|
const REAP_INTERVAL_MS = 60_000;
|
|
59
59
|
/** Messages a live session keeps in memory; older ones leave its front (its `base` counts them). */
|
|
60
60
|
const MAX_LIVE_MESSAGES = 5_000;
|
|
61
|
-
/** Events that are a session being used: a turn and what it streams. State and setting echoes are not. */
|
|
62
|
-
const ACTIVITY_EVENT = /^(agent_|turn_|message_|tool_execution_|auto_compaction_|auto_retry_)/;
|
|
63
61
|
/**
|
|
64
62
|
* The fields of a tool result's `details` the app reads: an edit's diff, a
|
|
65
63
|
* read's line count, a command's run time, a screenshot's file. The rest
|
|
@@ -638,8 +636,10 @@ class SessionEntry {
|
|
|
638
636
|
|
|
639
637
|
bind() {
|
|
640
638
|
this.rpc.on("event", (event) => {
|
|
641
|
-
if (typeof event?.type === "string" && ACTIVITY_EVENT.test(event.type)) this.updatedAt = Date.now();
|
|
642
639
|
if (event.type === "message_end" && event.message) {
|
|
640
|
+
// A new message is what makes a session the most recent one; opening it,
|
|
641
|
+
// a lifecycle event from a resumed omp, or a state echo are not.
|
|
642
|
+
this.updatedAt = Date.now();
|
|
643
643
|
this.messages.push(phoneMessage(event.message, this.blobDir));
|
|
644
644
|
if (this.messages.length > MAX_LIVE_MESSAGES) {
|
|
645
645
|
// Indices stay the transcript's: a phone's `since` still lines up after the front is dropped.
|
|
@@ -1125,7 +1125,7 @@ class Daemon {
|
|
|
1125
1125
|
const deadline = Date.now() + 3_000;
|
|
1126
1126
|
while ((await this.owners())?.get(id) === pid && Date.now() < deadline) await new Promise((wait) => setTimeout(wait, 100));
|
|
1127
1127
|
// Whoever looks at the terminal sees why omp closed.
|
|
1128
|
-
if (info?.tty !== undefined) tellTerminal(info.tty, takeoverFarewell(device));
|
|
1128
|
+
if (info?.tty !== undefined) tellTerminal(info.tty, takeoverFarewell({ device, sessionFile: stored.sessionFile }));
|
|
1129
1129
|
const forks = forksOf(dirname(stored.sessionFile), id, since);
|
|
1130
1130
|
if (forks.length > 0) this.log(new Error(`taking over ${id} from pid ${pid} left forked session files: ${forks.join(", ")}`));
|
|
1131
1131
|
outcome = { pid, via: left.via, waitedMs: left.waitedMs, busy, forks };
|
|
@@ -1253,6 +1253,8 @@ class Daemon {
|
|
|
1253
1253
|
const text = typeof message.text === "string" ? message.text : "";
|
|
1254
1254
|
if (text.trim() === "" && images.length === 0) throw new Error("empty prompt");
|
|
1255
1255
|
await entry.rpc.prompt(text, images);
|
|
1256
|
+
entry.updatedAt = Date.now();
|
|
1257
|
+
entry.onChange?.(entry);
|
|
1256
1258
|
ws.send(JSON.stringify({ type: "accepted", command: "prompt" }));
|
|
1257
1259
|
return;
|
|
1258
1260
|
}
|
|
@@ -1284,7 +1286,6 @@ class Daemon {
|
|
|
1284
1286
|
}
|
|
1285
1287
|
const state = await entry.rpc.setModel(message.provider, message.modelId);
|
|
1286
1288
|
entry.state = state;
|
|
1287
|
-
entry.updatedAt = Date.now();
|
|
1288
1289
|
entry.onChange?.(entry);
|
|
1289
1290
|
entry.broadcast({ type: "state", state });
|
|
1290
1291
|
ws.send(JSON.stringify({ type: "accepted", command: "set_model" }));
|
|
@@ -1301,7 +1302,6 @@ class Daemon {
|
|
|
1301
1302
|
if (typeof message.level !== "string" || message.level === "") throw new Error("set_thinking_level needs a level");
|
|
1302
1303
|
const state = await entry.rpc.setThinkingLevel(message.level);
|
|
1303
1304
|
entry.state = state;
|
|
1304
|
-
entry.updatedAt = Date.now();
|
|
1305
1305
|
entry.broadcast({ type: "state", state });
|
|
1306
1306
|
ws.send(JSON.stringify({ type: "accepted", command: "set_thinking_level" }));
|
|
1307
1307
|
return;
|
package/src/takeover.mjs
CHANGED
|
@@ -175,9 +175,21 @@ export function tellTerminal(tty, text) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
/**
|
|
179
|
-
export function
|
|
180
|
-
return
|
|
178
|
+
/** A path as a shell word: quoted only when it has to be. */
|
|
179
|
+
export function shellWord(value) {
|
|
180
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* What the terminal is told once omp left it: where the session went, and the
|
|
185
|
+
* command that brings it back -- the hint leaving omp by hand also leaves.
|
|
186
|
+
* @param {{device: string, sessionFile: string}} leaving
|
|
187
|
+
*/
|
|
188
|
+
export function takeoverFarewell({ device, sessionFile }) {
|
|
189
|
+
const lines = [`This session continues on the phone (${device}).`, "omp closed here so the phone could take it over."];
|
|
190
|
+
const file = typeof sessionFile === "string" && sessionFile !== "" ? shellWord(sessionFile) : "";
|
|
191
|
+
if (file !== "") lines.push("", "Resume it in this terminal with:", ` omp --resume ${file}`);
|
|
192
|
+
return lines.join("\n");
|
|
181
193
|
}
|
|
182
194
|
|
|
183
195
|
const sleep = (ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|