@oar-sh/oar 0.9.11 → 0.9.13
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/CHANGELOG.md +332 -0
- package/README.md +10 -10
- package/package.json +1 -1
- package/server/README.md +201 -12
- package/server/claude-worker/claude-remote-relay-tool.mjs +2 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +57 -4
- package/server/claude-worker/claude-session-process.mjs +179 -0
- package/server/claude-worker/claude-session-worker.mjs +17 -0
- package/server/claude-worker/task-output-discovery.mjs +152 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +11 -5
- package/server/copilot-worker/copilot-sdk-session-process.mjs +169 -3
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +2 -0
- package/server/copilot-worker/copilot-shell-output-mirror.mjs +94 -0
- package/server/copilot-worker/copilot-task-settle.mjs +96 -0
- package/server/db-schema.mjs +19 -0
- package/server/grok-worker/acp-host-services.mjs +48 -6
- package/server/grok-worker/grok-background-tasks.mjs +248 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +3 -1
- package/server/grok-worker/grok-session-worker.mjs +33 -1
- package/server/grok-worker/grok-turn-runner.mjs +11 -0
- package/server/migrations/0017-conversation-history-indexes.mjs +89 -0
- package/server/migrations/0018-background-task-rows.mjs +101 -0
- package/server/public/app/action-confirmations.js +12 -15
- package/server/public/app/api-client.js +70 -3
- package/server/public/app/background-tasks-view.mjs +334 -16
- package/server/public/app/bootstrap.js +102 -5
- package/server/public/app/claude-background-command-limit-options.mjs +21 -0
- package/server/public/app/conversation-payload-cache.mjs +357 -0
- package/server/public/app/conversation-preferences.mjs +10 -4
- package/server/public/app/conversation-turn-state.mjs +1 -1
- package/server/public/app/conversation-view.js +462 -8
- package/server/public/app/conversation-window-retention.mjs +96 -0
- package/server/public/app/host-suspend-ui.js +45 -44
- package/server/public/app/journal-view.js +259 -67
- package/server/public/app/push-settings.js +1 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +20 -6
- package/server/public/app/relay-restart-note-view.mjs +36 -0
- package/server/public/app/relay-restart-ui.js +568 -0
- package/server/public/app/settings-modal.js +162 -2
- package/server/public/app/socket-handlers.js +59 -7
- package/server/public/app/store.js +25 -0
- package/server/public/app/subagent-viewer.mjs +7 -1
- package/server/public/app/task-console-render.mjs +401 -0
- package/server/public/app/task-console-watch.mjs +42 -0
- package/server/public/app/task-console.mjs +730 -0
- package/server/public/app/task-output-text.mjs +243 -0
- package/server/public/app/transcript-island.mjs +73 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +42 -8
- package/server/public/app/transcript-jump-bottom.mjs +68 -11
- package/server/public/app/transcript-reading-anchor.mjs +256 -0
- package/server/public/app/transcript-reload-freshness.mjs +17 -1
- package/server/public/app/transcript-viewport-anchor.mjs +20 -1
- package/server/public/index.html +306 -6
- package/server/relay-tools.md +6 -0
- package/server/repositories/message-repository.mjs +11 -0
- package/server/repositories/question-repository.mjs +4 -0
- package/server/repositories/remote-relay-repository.mjs +4 -2
- package/server/routes/messages-routes.mjs +397 -23
- package/server/routes/relay-board-routes.mjs +19 -2
- package/server/routes/relay-restart-routes.mjs +73 -0
- package/server/routes/sessions-routes.mjs +208 -66
- package/server/server-runtime.mjs +269 -137
- package/server/services/about-service.mjs +13 -12
- package/server/services/background-task-persistence.mjs +133 -0
- package/server/services/background-task-store.mjs +470 -6
- package/server/services/cli-process-runner.mjs +4 -21
- package/server/services/conversation-history-page.mjs +199 -0
- package/server/services/conversation-model-fallback.mjs +57 -0
- package/server/services/host-suspend-command.mjs +2 -1
- package/server/services/preview-proxy-server.mjs +14 -1
- package/server/services/process-list-service.mjs +2 -1
- package/server/services/provider-model-selection.mjs +195 -0
- package/server/services/push-dispatch-service.mjs +9 -2
- package/server/services/relay-event-service.mjs +27 -9
- package/server/services/relay-prompt-sanitizer.mjs +18 -4
- package/server/services/relay-restart-picture.mjs +209 -0
- package/server/services/relay-restart-queue.mjs +380 -0
- package/server/services/relay-shutdown-gate.mjs +4 -1
- package/server/services/remote-relay-dispatcher.mjs +316 -20
- package/server/services/restart-resume-service.mjs +303 -0
- package/server/services/session-activity-tracker.mjs +6 -2
- package/server/services/session-worker-process-service.mjs +6 -4
- package/server/services/task-output-rooms.mjs +259 -0
- package/server/services/task-output-service.mjs +761 -0
- package/shared/claude-background-command-limit.mjs +83 -0
- package/shared/relay-events.mjs +55 -2
- package/shared/remote-relay-contract.mjs +47 -10
- package/shared/task-output-text.mjs +850 -0
- package/shared/worker-runtime/task-output-spool.mjs +181 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +96 -20
|
@@ -68,32 +68,33 @@ const COMMIT_PATTERN = /^[0-9a-f]{40}$/i;
|
|
|
68
68
|
* The commit a git checkout has checked out, read from its .git files rather
|
|
69
69
|
* than from a `git` process: HEAD, then the loose ref, then packed-refs. A
|
|
70
70
|
* linked worktree's .git is a file naming its git dir, whose refs live in the
|
|
71
|
-
* common dir. Anything unexpected reads as null.
|
|
71
|
+
* common dir. Anything unexpected reads as null. `pathImpl` is the host's path
|
|
72
|
+
* module; tests pass path.posix and path.win32.
|
|
72
73
|
*/
|
|
73
|
-
export function readGitCommit(root, { fsImpl = fs } = {}) {
|
|
74
|
+
export function readGitCommit(root, { fsImpl = fs, pathImpl = path } = {}) {
|
|
74
75
|
if (!root) return null;
|
|
75
|
-
const dotGit =
|
|
76
|
+
const dotGit = pathImpl.join(root, '.git');
|
|
76
77
|
let gitDir = dotGit;
|
|
77
78
|
const pointer = readFileText(fsImpl, dotGit);
|
|
78
79
|
if (pointer !== null) {
|
|
79
80
|
const match = pointer.match(/^gitdir:\s*(.+)$/m);
|
|
80
81
|
if (!match) return null;
|
|
81
|
-
gitDir =
|
|
82
|
+
gitDir = pathImpl.resolve(root, match[1].trim());
|
|
82
83
|
}
|
|
83
|
-
const head = readFileText(fsImpl,
|
|
84
|
+
const head = readFileText(fsImpl, pathImpl.join(gitDir, 'HEAD'));
|
|
84
85
|
if (head === null) return null;
|
|
85
86
|
const headText = head.trim();
|
|
86
87
|
if (COMMIT_PATTERN.test(headText)) return headText.toLowerCase();
|
|
87
88
|
const refMatch = headText.match(/^ref:\s*(\S+)$/);
|
|
88
89
|
if (!refMatch) return null;
|
|
89
90
|
const ref = refMatch[1];
|
|
90
|
-
const commonDirText = readFileText(fsImpl,
|
|
91
|
-
const commonDir = commonDirText ?
|
|
91
|
+
const commonDirText = readFileText(fsImpl, pathImpl.join(gitDir, 'commondir'));
|
|
92
|
+
const commonDir = commonDirText ? pathImpl.resolve(gitDir, commonDirText.trim()) : gitDir;
|
|
92
93
|
for (const dir of new Set([gitDir, commonDir])) {
|
|
93
|
-
const loose = readFileText(fsImpl,
|
|
94
|
+
const loose = readFileText(fsImpl, pathImpl.join(dir, ...ref.split('/')));
|
|
94
95
|
if (loose && COMMIT_PATTERN.test(loose.trim())) return loose.trim().toLowerCase();
|
|
95
96
|
}
|
|
96
|
-
const packed = readFileText(fsImpl,
|
|
97
|
+
const packed = readFileText(fsImpl, pathImpl.join(commonDir, 'packed-refs')) || '';
|
|
97
98
|
for (const line of packed.split(/\r?\n/)) {
|
|
98
99
|
const [hash, name] = line.trim().split(/\s+/);
|
|
99
100
|
if (name === ref && COMMIT_PATTERN.test(hash || '')) return hash.toLowerCase();
|
|
@@ -102,11 +103,11 @@ export function readGitCommit(root, { fsImpl = fs } = {}) {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/** The commit this build came from: the checkout's HEAD, else a `gitHead` stamp in package.json. */
|
|
105
|
-
export function resolveBuildCommit(packageRoot, { fsImpl = fs } = {}) {
|
|
106
|
-
const fromGit = readGitCommit(packageRoot, { fsImpl });
|
|
106
|
+
export function resolveBuildCommit(packageRoot, { fsImpl = fs, pathImpl = path } = {}) {
|
|
107
|
+
const fromGit = readGitCommit(packageRoot, { fsImpl, pathImpl });
|
|
107
108
|
if (fromGit) return fromGit;
|
|
108
109
|
try {
|
|
109
|
-
const stamp = String(JSON.parse(readFileText(fsImpl,
|
|
110
|
+
const stamp = String(JSON.parse(readFileText(fsImpl, pathImpl.join(packageRoot, 'package.json')) || '{}')?.gitHead || '').trim();
|
|
110
111
|
return COMMIT_PATTERN.test(stamp) ? stamp.toLowerCase() : null;
|
|
111
112
|
} catch {
|
|
112
113
|
return null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { backgroundTaskRowsTableProblem } from '../migrations/0018-background-task-rows.mjs';
|
|
4
|
+
|
|
5
|
+
// Persistence for the background-task store's console rows: the
|
|
6
|
+
// `background_task_rows` table (migration 0018) behind a small adapter, so
|
|
7
|
+
// the store restores its rows after a restart and stays testable without a
|
|
8
|
+
// database. Without a usable table every call is a no-op and the store keeps
|
|
9
|
+
// its rows in memory only, as before; a problem is logged once and never
|
|
10
|
+
// stops the relay.
|
|
11
|
+
|
|
12
|
+
function unavailable(reason) {
|
|
13
|
+
return {
|
|
14
|
+
available: false,
|
|
15
|
+
reason,
|
|
16
|
+
load: () => [],
|
|
17
|
+
saveLive: () => false,
|
|
18
|
+
saveEnded: () => false,
|
|
19
|
+
remove: () => false,
|
|
20
|
+
forget: () => false,
|
|
21
|
+
pruneEnded: () => 0,
|
|
22
|
+
touchLive: () => false,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseRow(text) {
|
|
27
|
+
try {
|
|
28
|
+
const row = JSON.parse(String(text || ''));
|
|
29
|
+
return row && typeof row === 'object' && !Array.isArray(row) ? row : null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A row as stored: no health reading (the poller reads the file again). */
|
|
36
|
+
function storedRowJson(row) {
|
|
37
|
+
const { health: _health, ...rest } = row || {};
|
|
38
|
+
return JSON.stringify(rest);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createSqliteBackgroundTaskPersistence(db, { logger = console, now = () => Date.now() } = {}) {
|
|
42
|
+
let stmts;
|
|
43
|
+
try {
|
|
44
|
+
const problem = backgroundTaskRowsTableProblem(db);
|
|
45
|
+
if (problem) return unavailable(problem);
|
|
46
|
+
stmts = {
|
|
47
|
+
list: db.prepare(`SELECT store_key, task_id, state, row_json, ended_at, updated_at FROM background_task_rows`),
|
|
48
|
+
touchLive: db.prepare(`UPDATE background_task_rows SET updated_at = ? WHERE store_key = ? AND state = 'live'`),
|
|
49
|
+
upsert: db.prepare(`
|
|
50
|
+
INSERT INTO background_task_rows (store_key, task_id, state, row_json, ended_at, updated_at)
|
|
51
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
52
|
+
ON CONFLICT(store_key, task_id) DO UPDATE SET
|
|
53
|
+
state = excluded.state, row_json = excluded.row_json, ended_at = excluded.ended_at, updated_at = excluded.updated_at
|
|
54
|
+
`),
|
|
55
|
+
liveOfKey: db.prepare(`SELECT task_id FROM background_task_rows WHERE store_key = ? AND state = 'live'`),
|
|
56
|
+
remove: db.prepare(`DELETE FROM background_task_rows WHERE store_key = ? AND task_id = ?`),
|
|
57
|
+
removeLive: db.prepare(`DELETE FROM background_task_rows WHERE store_key = ? AND task_id = ? AND state = 'live'`),
|
|
58
|
+
forget: db.prepare(`DELETE FROM background_task_rows WHERE store_key = ?`),
|
|
59
|
+
prune: db.prepare(`DELETE FROM background_task_rows WHERE state = 'ended' AND ended_at < ?`),
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const reason = error?.message || String(error);
|
|
63
|
+
logger?.warn?.(`[background-tasks] console rows are not persisted: ${reason}`);
|
|
64
|
+
return unavailable(reason);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// One warning per kind of failure: a write that fails keeps failing, and
|
|
68
|
+
// the store goes on in memory either way.
|
|
69
|
+
const warned = new Set();
|
|
70
|
+
function guard(what, run, fallback) {
|
|
71
|
+
try {
|
|
72
|
+
return run();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (!warned.has(what)) {
|
|
75
|
+
warned.add(what);
|
|
76
|
+
logger?.warn?.(`[background-tasks] could not ${what} a console row: ${error?.message || error}`);
|
|
77
|
+
}
|
|
78
|
+
return fallback;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const saveLiveTx = db.transaction((key, rows) => {
|
|
83
|
+
const keep = new Set(rows.map((row) => String(row.taskId)));
|
|
84
|
+
for (const { task_id: taskId } of stmts.liveOfKey.all(key)) {
|
|
85
|
+
if (!keep.has(taskId)) stmts.removeLive.run(key, taskId);
|
|
86
|
+
}
|
|
87
|
+
const at = now();
|
|
88
|
+
for (const row of rows) stmts.upsert.run(key, String(row.taskId), 'live', storedRowJson(row), null, at);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
available: true,
|
|
93
|
+
reason: '',
|
|
94
|
+
/** Every stored row: `{ key, taskId, state, row, endedAt, updatedAt }`. */
|
|
95
|
+
load() {
|
|
96
|
+
return guard('read', () => stmts.list.all().map((entry) => ({
|
|
97
|
+
key: String(entry.store_key),
|
|
98
|
+
taskId: String(entry.task_id),
|
|
99
|
+
state: String(entry.state),
|
|
100
|
+
row: parseRow(entry.row_json),
|
|
101
|
+
endedAt: Number.isFinite(Number(entry.ended_at)) && entry.ended_at !== null ? Number(entry.ended_at) : null,
|
|
102
|
+
updatedAt: Number.isFinite(Number(entry.updated_at)) ? Number(entry.updated_at) : null,
|
|
103
|
+
})).filter((entry) => entry.row), []);
|
|
104
|
+
},
|
|
105
|
+
/** The key's live console rows are exactly these (an ended row of the same task is replaced). */
|
|
106
|
+
saveLive(key, rows) {
|
|
107
|
+
return guard('write', () => { saveLiveTx(String(key), Array.isArray(rows) ? rows : []); return true; }, false);
|
|
108
|
+
},
|
|
109
|
+
saveEnded(key, row) {
|
|
110
|
+
return guard('write', () => {
|
|
111
|
+
stmts.upsert.run(String(key), String(row.taskId), 'ended', storedRowJson(row), Number(row.endedAt) || now(), now());
|
|
112
|
+
return true;
|
|
113
|
+
}, false);
|
|
114
|
+
},
|
|
115
|
+
remove(key, taskId) {
|
|
116
|
+
return guard('delete', () => stmts.remove.run(String(key), String(taskId)).changes > 0, false);
|
|
117
|
+
},
|
|
118
|
+
forget(keys) {
|
|
119
|
+
return guard('delete', () => {
|
|
120
|
+
for (const key of keys) stmts.forget.run(String(key));
|
|
121
|
+
return true;
|
|
122
|
+
}, false);
|
|
123
|
+
},
|
|
124
|
+
/** The key's live rows were confirmed running now (its worker's digest agreed). */
|
|
125
|
+
touchLive(key) {
|
|
126
|
+
return guard('write', () => stmts.touchLive.run(now(), String(key)).changes > 0, false);
|
|
127
|
+
},
|
|
128
|
+
/** Drops the ended rows that ended before `cutoff` (epoch ms). */
|
|
129
|
+
pruneEnded(cutoff) {
|
|
130
|
+
return guard('prune', () => stmts.prune.run(Number(cutoff) || 0).changes, 0);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|