antechamber 1.0.41 → 2.0.1
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 +97 -0
- package/README.md +88 -141
- package/bin/opencode-antechamber +14 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/context.d.ts +5 -2
- package/dist/commands/context.js +8 -4
- package/dist/commands/doctor.js +4 -3
- package/dist/commands/setup.js +9 -28
- package/dist/config.d.ts +6 -2
- package/dist/config.js +18 -4
- package/dist/latency.d.ts +10 -0
- package/dist/latency.js +115 -0
- package/dist/misc-routes.js +2 -10
- package/dist/route-services.d.ts +0 -2
- package/dist/routes-v1.d.ts +1 -1
- package/dist/routes-v1.js +29 -418
- package/dist/routes-v2.js +1 -1
- package/dist/self-update.d.ts +2 -1
- package/dist/self-update.js +5 -1
- package/dist/server.d.ts +1 -3
- package/dist/server.js +9 -12
- package/dist/session-store.d.ts +12 -10
- package/dist/session-store.js +238 -128
- package/dist/session-tree.js +2 -2
- package/dist/sse.d.ts +0 -2
- package/dist/sse.js +13 -46
- package/dist/tool-map.js +1 -3
- package/dist/work-status.js +1 -1
- package/extensions/antechamber/README.md +100 -0
- package/extensions/antechamber/bin/opencode-antechamber +42 -0
- package/extensions/{omo-dag/docs/DESIGN.md → antechamber/docs/GRAPH.md} +5 -5
- package/extensions/{omo-work/docs/DESIGN.md → antechamber/docs/WORK.md} +3 -3
- package/extensions/antechamber/package.json +60 -0
- package/extensions/{omo-dag → antechamber}/panel/index.html +8 -1
- package/extensions/antechamber/panel/main.js +261 -0
- package/extensions/antechamber/runtime/launcher.js +201 -0
- package/extensions/antechamber/service/main.js +884 -0
- package/package.json +75 -26
- package/extensions/omo-compat/README.md +0 -48
- package/extensions/omo-compat/icon.svg +0 -1
- package/extensions/omo-compat/package.json +0 -43
- package/extensions/omo-dag/README.md +0 -167
- package/extensions/omo-dag/package.json +0 -33
- package/extensions/omo-dag/panel/main.js +0 -176
- package/extensions/omo-dag/service/main.js +0 -457
- package/extensions/omo-work/README.md +0 -88
- package/extensions/omo-work/package.json +0 -30
- package/extensions/omo-work/service/main.js +0 -149
- /package/extensions/{omo-dag → antechamber}/icon.svg +0 -0
- /package/extensions/{omo-work → antechamber}/status/index.html +0 -0
- /package/extensions/{omo-work → antechamber}/status/main.js +0 -0
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
// extensions/omo-dag/src/service.ts
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
|
|
5
|
-
// extensions/omo-dag/src/graph.ts
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
var TEXT_FIELDS = [
|
|
8
|
-
"status",
|
|
9
|
-
"parent_session_id",
|
|
10
|
-
"root_session_id",
|
|
11
|
-
"execution_mode",
|
|
12
|
-
"name",
|
|
13
|
-
"task_summary",
|
|
14
|
-
"description",
|
|
15
|
-
"agent_type",
|
|
16
|
-
"category",
|
|
17
|
-
"model",
|
|
18
|
-
"created_at",
|
|
19
|
-
"started_at",
|
|
20
|
-
"terminal_at",
|
|
21
|
-
"child_session_id",
|
|
22
|
-
"final_response",
|
|
23
|
-
"error_message"
|
|
24
|
-
];
|
|
25
|
-
var isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
26
|
-
var text = (v) => typeof v === "string" ? v : void 0;
|
|
27
|
-
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
28
|
-
function normalizeTask(raw) {
|
|
29
|
-
if (!isObject(raw) || typeof raw.task_id !== "string" || raw.task_id === "") return void 0;
|
|
30
|
-
const task = { task_id: raw.task_id };
|
|
31
|
-
for (const key of TEXT_FIELDS) {
|
|
32
|
-
const value = text(raw[key]);
|
|
33
|
-
if (value !== void 0) task[key] = value;
|
|
34
|
-
}
|
|
35
|
-
if (num(raw.depth) !== void 0) task.depth = num(raw.depth);
|
|
36
|
-
const host = isObject(raw.host_session) ? text(raw.host_session.session_path) : void 0;
|
|
37
|
-
if (host !== void 0) task.host_session = { session_path: host };
|
|
38
|
-
if (isObject(raw.spawn_spec))
|
|
39
|
-
task.spawn_spec = { prompt: text(raw.spawn_spec.prompt), instructions: text(raw.spawn_spec.instructions) };
|
|
40
|
-
if (isObject(raw.run_stats)) {
|
|
41
|
-
const s = raw.run_stats;
|
|
42
|
-
task.run_stats = {
|
|
43
|
-
total_tokens: num(s.total_tokens),
|
|
44
|
-
cost_usd: num(s.cost_usd),
|
|
45
|
-
tool_calls: num(s.tool_calls),
|
|
46
|
-
turns: num(s.turns)
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return task;
|
|
50
|
-
}
|
|
51
|
-
function normalizeRegistry(raw) {
|
|
52
|
-
if (!isObject(raw)) return void 0;
|
|
53
|
-
const out = {};
|
|
54
|
-
for (const [id, value] of Object.entries(raw)) {
|
|
55
|
-
if (!isObject(value)) continue;
|
|
56
|
-
const entry = {};
|
|
57
|
-
if (value.deleted === true) entry.deleted = true;
|
|
58
|
-
if (text(value.piSessionId) !== void 0) entry.piSessionId = text(value.piSessionId);
|
|
59
|
-
if (text(value.parentID) !== void 0) entry.parentID = text(value.parentID);
|
|
60
|
-
if (text(value.title) !== void 0) entry.title = text(value.title);
|
|
61
|
-
if (isObject(value.task)) entry.task = { id: text(value.task.id), parentPiId: text(value.task.parentPiId) };
|
|
62
|
-
out[id] = entry;
|
|
63
|
-
}
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
function ocIdFor(piSessionId) {
|
|
67
|
-
return `ses_${createHash("md5").update(piSessionId).digest("hex")}`;
|
|
68
|
-
}
|
|
69
|
-
function taskSessionId(task, tasks, registry) {
|
|
70
|
-
const registered = Object.entries(registry).find(
|
|
71
|
-
([, entry]) => entry.task?.id === task.task_id && entry.task.parentPiId === task.parent_session_id
|
|
72
|
-
);
|
|
73
|
-
if (registered) return registered[0];
|
|
74
|
-
const legacy = ocIdFor(task.task_id);
|
|
75
|
-
const parent = Object.entries(registry).find(([, entry]) => entry.piSessionId === task.parent_session_id)?.[0];
|
|
76
|
-
if (registry[legacy]?.deleted && registry[legacy]?.parentID === (parent ?? ocIdFor(task.parent_session_id ?? "")))
|
|
77
|
-
return legacy;
|
|
78
|
-
const qualified = ocIdFor(`${task.parent_session_id}:${task.task_id}`);
|
|
79
|
-
if (registry[legacy] !== void 0 || registry[qualified] !== void 0 || tasks.some((other) => other.task_id === task.task_id && other.parent_session_id !== task.parent_session_id))
|
|
80
|
-
return qualified;
|
|
81
|
-
return legacy;
|
|
82
|
-
}
|
|
83
|
-
var ACTIVE = /* @__PURE__ */ new Set(["running", "starting"]);
|
|
84
|
-
var WAITING = /* @__PURE__ */ new Set(["queued", "pending", "suspended"]);
|
|
85
|
-
var FAILED = /* @__PURE__ */ new Set(["failed", "error", "lost", "denied", "plan_error"]);
|
|
86
|
-
var CANCELLED = /* @__PURE__ */ new Set(["cancelled", "canceled", "killed"]);
|
|
87
|
-
function nodeState(status) {
|
|
88
|
-
const s = String(status ?? "");
|
|
89
|
-
if (ACTIVE.has(s)) return "running";
|
|
90
|
-
if (WAITING.has(s)) return "queued";
|
|
91
|
-
if (s === "completed") return "completed";
|
|
92
|
-
if (FAILED.has(s)) return "failed";
|
|
93
|
-
if (CANCELLED.has(s)) return "cancelled";
|
|
94
|
-
return "idle";
|
|
95
|
-
}
|
|
96
|
-
var finished = (state) => state === "completed" || state === "failed" || state === "cancelled";
|
|
97
|
-
function time(value) {
|
|
98
|
-
if (value === void 0) return void 0;
|
|
99
|
-
const t = Date.parse(value);
|
|
100
|
-
return Number.isFinite(t) ? t : void 0;
|
|
101
|
-
}
|
|
102
|
-
function firstLine(text2, max = 280) {
|
|
103
|
-
if (!text2) return void 0;
|
|
104
|
-
const clean = text2.replace(/\s*\[task_id:[^\]]*\]/g, "").replace(/\s+/g, " ").trim();
|
|
105
|
-
if (clean === "") return void 0;
|
|
106
|
-
return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
107
|
-
}
|
|
108
|
-
function ownSessionIds(task) {
|
|
109
|
-
const ids = [task.task_id];
|
|
110
|
-
if (task.child_session_id) ids.push(task.child_session_id);
|
|
111
|
-
const path = task.host_session?.session_path;
|
|
112
|
-
const match = path ? /_([0-9a-f-]{36})\.jsonl$/i.exec(path) : null;
|
|
113
|
-
if (match?.[1]) ids.push(match[1]);
|
|
114
|
-
return ids;
|
|
115
|
-
}
|
|
116
|
-
var TASK_REF = /(?:agent:\/\/)?\b(st_[0-9A-Za-z_-]{6,})\b/g;
|
|
117
|
-
function referencedTasks(task) {
|
|
118
|
-
const text2 = `${task.spawn_spec?.prompt ?? ""}
|
|
119
|
-
${task.spawn_spec?.instructions ?? ""}`;
|
|
120
|
-
const refs = /* @__PURE__ */ new Set();
|
|
121
|
-
for (const m of text2.matchAll(TASK_REF)) if (m[1] && m[1] !== task.task_id) refs.add(m[1]);
|
|
122
|
-
return refs;
|
|
123
|
-
}
|
|
124
|
-
function resolveSession(sessionId, tasks, registry) {
|
|
125
|
-
const entry = registry[sessionId];
|
|
126
|
-
const focusTask = tasks.find((t) => taskSessionId(t, tasks, registry) === sessionId);
|
|
127
|
-
let rootPi;
|
|
128
|
-
if (focusTask) rootPi = focusTask.root_session_id ?? focusTask.parent_session_id ?? entry?.task?.parentPiId;
|
|
129
|
-
else if (entry?.task?.parentPiId) rootPi = entry.task.parentPiId;
|
|
130
|
-
else if (entry?.piSessionId) rootPi = entry.piSessionId;
|
|
131
|
-
else {
|
|
132
|
-
const t = tasks.find(
|
|
133
|
-
(t2) => [t2.root_session_id, t2.parent_session_id].some((id) => id !== void 0 && ocIdFor(id) === sessionId)
|
|
134
|
-
);
|
|
135
|
-
rootPi = t ? t.root_session_id ?? t.parent_session_id : void 0;
|
|
136
|
-
}
|
|
137
|
-
if (rootPi === void 0) return void 0;
|
|
138
|
-
const rootOc = Object.entries(registry).find(([, v]) => v.piSessionId === rootPi && v.task === void 0)?.[0] ?? (focusTask || entry?.task ? entry?.parentID ?? ocIdFor(rootPi) : sessionId);
|
|
139
|
-
return { rootPi, rootOc, focusOc: sessionId };
|
|
140
|
-
}
|
|
141
|
-
function buildGraph(sessionId, tasks, registry) {
|
|
142
|
-
const resolved = resolveSession(sessionId, tasks, registry);
|
|
143
|
-
if (!resolved) {
|
|
144
|
-
return {
|
|
145
|
-
status: "unknown-session",
|
|
146
|
-
rootId: sessionId,
|
|
147
|
-
focusId: sessionId,
|
|
148
|
-
title: registry[sessionId]?.title ?? "Session",
|
|
149
|
-
nodes: [],
|
|
150
|
-
edges: [],
|
|
151
|
-
columns: [],
|
|
152
|
-
models: 0
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
const { rootPi, rootOc, focusOc } = resolved;
|
|
156
|
-
const members = tasks.filter((t) => (t.root_session_id ?? t.parent_session_id) === rootPi).sort((a, b) => (time(a.created_at) ?? 0) - (time(b.created_at) ?? 0) || a.task_id.localeCompare(b.task_id));
|
|
157
|
-
const owner = /* @__PURE__ */ new Map();
|
|
158
|
-
for (const t of members) for (const id of ownSessionIds(t)) owner.set(id, t.task_id);
|
|
159
|
-
const parentOf = (t) => (t.parent_session_id && t.parent_session_id !== rootPi ? owner.get(t.parent_session_id) : void 0) ?? rootOc;
|
|
160
|
-
const byId = new Map(members.map((t) => [t.task_id, t]));
|
|
161
|
-
const edges = [];
|
|
162
|
-
const uses = /* @__PURE__ */ new Map();
|
|
163
|
-
for (const t of members) {
|
|
164
|
-
const parent = parentOf(t);
|
|
165
|
-
edges.push({ id: `spawn:${parent}>${t.task_id}`, from: parent, to: t.task_id, kind: "spawn" });
|
|
166
|
-
for (const ref of referencedTasks(t)) {
|
|
167
|
-
const source = byId.get(ref);
|
|
168
|
-
if (!source || ref === parent) continue;
|
|
169
|
-
if ((time(source.created_at) ?? 0) > (time(t.created_at) ?? 0)) continue;
|
|
170
|
-
edges.push({ id: `uses:${ref}>${t.task_id}`, from: ref, to: t.task_id, kind: "uses" });
|
|
171
|
-
uses.set(t.task_id, [...uses.get(t.task_id) ?? [], ref]);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const wave = /* @__PURE__ */ new Map();
|
|
175
|
-
const column = /* @__PURE__ */ new Map([[rootOc, 0]]);
|
|
176
|
-
const siblingsSoFar = /* @__PURE__ */ new Map();
|
|
177
|
-
for (const t of members) {
|
|
178
|
-
const parent = parentOf(t);
|
|
179
|
-
const created = time(t.created_at) ?? 0;
|
|
180
|
-
const earlier = siblingsSoFar.get(parent) ?? [];
|
|
181
|
-
let w = 0;
|
|
182
|
-
for (const s of earlier) {
|
|
183
|
-
const ended = time(s.terminal_at);
|
|
184
|
-
if (ended !== void 0 && ended <= created) w = Math.max(w, (wave.get(s.task_id) ?? 0) + 1);
|
|
185
|
-
}
|
|
186
|
-
for (const ref of uses.get(t.task_id) ?? []) {
|
|
187
|
-
if (parentOf(byId.get(ref)) === parent) w = Math.max(w, (wave.get(ref) ?? 0) + 1);
|
|
188
|
-
}
|
|
189
|
-
wave.set(t.task_id, w);
|
|
190
|
-
column.set(t.task_id, (column.get(parent) ?? 0) + 1 + w);
|
|
191
|
-
for (const ref of uses.get(t.task_id) ?? []) {
|
|
192
|
-
column.set(t.task_id, Math.max(column.get(t.task_id) ?? 0, (column.get(ref) ?? 0) + 1));
|
|
193
|
-
}
|
|
194
|
-
siblingsSoFar.set(parent, [...earlier, t]);
|
|
195
|
-
}
|
|
196
|
-
const taskNodes = members.map((t) => {
|
|
197
|
-
const state = nodeState(t.status);
|
|
198
|
-
return {
|
|
199
|
-
id: t.task_id,
|
|
200
|
-
kind: "task",
|
|
201
|
-
sessionId: taskSessionId(t, tasks, registry),
|
|
202
|
-
title: firstLine(t.task_summary ?? t.description ?? t.name, 90) ?? t.task_id,
|
|
203
|
-
state,
|
|
204
|
-
rawStatus: String(t.status ?? "unknown"),
|
|
205
|
-
agent: t.agent_type ?? t.category,
|
|
206
|
-
model: t.model,
|
|
207
|
-
createdAt: time(t.created_at),
|
|
208
|
-
startedAt: time(t.started_at) ?? time(t.created_at),
|
|
209
|
-
endedAt: finished(state) ? time(t.terminal_at) : void 0,
|
|
210
|
-
summary: firstLine(t.final_response),
|
|
211
|
-
error: firstLine(t.error_message),
|
|
212
|
-
tokens: t.run_stats?.total_tokens,
|
|
213
|
-
costUsd: t.run_stats?.cost_usd,
|
|
214
|
-
toolCalls: t.run_stats?.tool_calls,
|
|
215
|
-
parentId: parentOf(t),
|
|
216
|
-
column: column.get(t.task_id) ?? 1,
|
|
217
|
-
wave: wave.get(t.task_id) ?? 0
|
|
218
|
-
};
|
|
219
|
-
});
|
|
220
|
-
const anyActive = taskNodes.some((n) => n.state === "running" || n.state === "queued");
|
|
221
|
-
const root = {
|
|
222
|
-
id: rootOc,
|
|
223
|
-
kind: "root",
|
|
224
|
-
sessionId: rootOc,
|
|
225
|
-
title: firstLine(registry[rootOc]?.title, 90) ?? "Session",
|
|
226
|
-
state: anyActive ? "running" : "idle",
|
|
227
|
-
rawStatus: anyActive ? "orchestrating" : "orchestrator",
|
|
228
|
-
createdAt: taskNodes[0]?.createdAt,
|
|
229
|
-
column: 0,
|
|
230
|
-
wave: 0
|
|
231
|
-
};
|
|
232
|
-
const nodes = [root, ...taskNodes];
|
|
233
|
-
const columns = /* @__PURE__ */ new Map();
|
|
234
|
-
for (const n of taskNodes) {
|
|
235
|
-
const c = columns.get(n.column) ?? { column: n.column, total: 0, done: 0, running: 0 };
|
|
236
|
-
c.total++;
|
|
237
|
-
if (finished(n.state)) c.done++;
|
|
238
|
-
if (n.state === "running") c.running++;
|
|
239
|
-
columns.set(n.column, c);
|
|
240
|
-
}
|
|
241
|
-
const focusId = taskNodes.find((n) => n.sessionId === focusOc)?.id ?? rootOc;
|
|
242
|
-
return {
|
|
243
|
-
status: "ok",
|
|
244
|
-
rootId: rootOc,
|
|
245
|
-
focusId,
|
|
246
|
-
title: root.title,
|
|
247
|
-
nodes,
|
|
248
|
-
edges,
|
|
249
|
-
columns: [...columns.values()].sort((a, b) => a.column - b.column),
|
|
250
|
-
models: new Set(taskNodes.map((n) => n.model).filter(Boolean)).size
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// extensions/omo-dag/src/store.ts
|
|
255
|
-
import { lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
256
|
-
import { homedir } from "node:os";
|
|
257
|
-
import { join } from "node:path";
|
|
258
|
-
function expandHome(path) {
|
|
259
|
-
if (path === "~") return homedir();
|
|
260
|
-
return path.startsWith("~/") ? join(homedir(), path.slice(2)) : path;
|
|
261
|
-
}
|
|
262
|
-
function defaultRoots() {
|
|
263
|
-
const home = homedir();
|
|
264
|
-
return {
|
|
265
|
-
agentDirs: [join(home, ".omo", "agent"), join(home, ".pi", "agent")],
|
|
266
|
-
stateDirs: [join(home, ".config", "antechamber"), join(home, ".config", "openchamber-pi-connector")]
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
function isMissing(error) {
|
|
270
|
-
return error instanceof Error && "code" in error && ["ENOENT", "ENOTDIR", "EACCES"].includes(String(error.code));
|
|
271
|
-
}
|
|
272
|
-
function list(dir) {
|
|
273
|
-
try {
|
|
274
|
-
return readdirSync(dir);
|
|
275
|
-
} catch (error) {
|
|
276
|
-
if (isMissing(error)) return [];
|
|
277
|
-
throw error;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function isRegularFile(path) {
|
|
281
|
-
try {
|
|
282
|
-
return lstatSync(path).isFile();
|
|
283
|
-
} catch (error) {
|
|
284
|
-
if (isMissing(error)) return false;
|
|
285
|
-
throw error;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
var JsonCache = class {
|
|
289
|
-
entries = /* @__PURE__ */ new Map();
|
|
290
|
-
read(path, accept) {
|
|
291
|
-
let stat;
|
|
292
|
-
try {
|
|
293
|
-
stat = statSync(path);
|
|
294
|
-
} catch (error) {
|
|
295
|
-
if (isMissing(error)) {
|
|
296
|
-
this.entries.delete(path);
|
|
297
|
-
return void 0;
|
|
298
|
-
}
|
|
299
|
-
throw error;
|
|
300
|
-
}
|
|
301
|
-
const hit = this.entries.get(path);
|
|
302
|
-
if (hit && hit.mtimeMs === stat.mtimeMs && hit.size === stat.size) return hit.value;
|
|
303
|
-
let value;
|
|
304
|
-
try {
|
|
305
|
-
value = accept(JSON.parse(readFileSync(path, "utf8")));
|
|
306
|
-
} catch (error) {
|
|
307
|
-
if (error instanceof SyntaxError || isMissing(error)) return hit?.value;
|
|
308
|
-
throw error;
|
|
309
|
-
}
|
|
310
|
-
this.entries.set(path, { mtimeMs: stat.mtimeMs, size: stat.size, value });
|
|
311
|
-
return value;
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
315
|
-
var TaskStore = class {
|
|
316
|
-
constructor(roots) {
|
|
317
|
-
this.roots = roots;
|
|
318
|
-
}
|
|
319
|
-
roots;
|
|
320
|
-
tasks = new JsonCache();
|
|
321
|
-
json = new JsonCache();
|
|
322
|
-
registries = new JsonCache();
|
|
323
|
-
storeDirs() {
|
|
324
|
-
const dirs = /* @__PURE__ */ new Set();
|
|
325
|
-
for (const agent of this.roots.agentDirs) {
|
|
326
|
-
const registry = this.json.read(join(agent, "rpc", "task-stores.json"), (v) => isRecord(v) ? v : void 0);
|
|
327
|
-
if (isRecord(registry?.stores)) for (const dir of Object.keys(registry.stores)) dirs.add(dir);
|
|
328
|
-
for (const project of list(join(agent, "projects"))) dirs.add(join(agent, "projects", project, "senpi-task"));
|
|
329
|
-
}
|
|
330
|
-
return [...dirs];
|
|
331
|
-
}
|
|
332
|
-
loadTasks() {
|
|
333
|
-
const out = /* @__PURE__ */ new Map();
|
|
334
|
-
for (const dir of this.storeDirs()) {
|
|
335
|
-
for (const file of list(join(dir, "tasks"))) {
|
|
336
|
-
if (!file.endsWith(".json")) continue;
|
|
337
|
-
const path = join(dir, "tasks", file);
|
|
338
|
-
if (!isRegularFile(path)) continue;
|
|
339
|
-
const task = this.tasks.read(path, normalizeTask);
|
|
340
|
-
if (task) out.set(JSON.stringify([task.parent_session_id, task.task_id]), task);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return [...out.values()];
|
|
344
|
-
}
|
|
345
|
-
loadRegistry() {
|
|
346
|
-
const merged = {};
|
|
347
|
-
for (const dir of [...this.roots.stateDirs].reverse()) {
|
|
348
|
-
const reg = this.registries.read(join(dir, "sessions.json"), normalizeRegistry);
|
|
349
|
-
if (reg) Object.assign(merged, reg);
|
|
350
|
-
}
|
|
351
|
-
return merged;
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
// extensions/omo-dag/src/service.ts
|
|
356
|
-
var MAX_TASK_STORES = 32;
|
|
357
|
-
var MAX_GRAPH_BODY_CHARS = 24e4;
|
|
358
|
-
function canonicalRoots(roots) {
|
|
359
|
-
return {
|
|
360
|
-
agentDirs: roots.agentDirs.map((path) => resolve(expandHome(path))),
|
|
361
|
-
stateDirs: roots.stateDirs.map((path) => resolve(expandHome(path)))
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function graphWithTaskLimit(graph, taskCount) {
|
|
365
|
-
const root = graph.nodes.find((node) => node.kind === "root");
|
|
366
|
-
if (!root) return graph;
|
|
367
|
-
const tasks = graph.nodes.filter((node) => node.kind === "task");
|
|
368
|
-
const shown = [root, ...tasks.slice(0, taskCount)];
|
|
369
|
-
const shownIds = new Set(shown.map((node) => node.id));
|
|
370
|
-
const columns = /* @__PURE__ */ new Map();
|
|
371
|
-
for (const node of shown) {
|
|
372
|
-
if (node.kind === "root") continue;
|
|
373
|
-
const column = columns.get(node.column) ?? { column: node.column, total: 0, done: 0, running: 0 };
|
|
374
|
-
column.total++;
|
|
375
|
-
if (node.state === "completed" || node.state === "failed" || node.state === "cancelled") column.done++;
|
|
376
|
-
if (node.state === "running") column.running++;
|
|
377
|
-
columns.set(node.column, column);
|
|
378
|
-
}
|
|
379
|
-
return {
|
|
380
|
-
...graph,
|
|
381
|
-
nodes: shown,
|
|
382
|
-
edges: graph.edges.filter((edge) => shownIds.has(edge.from) && shownIds.has(edge.to)),
|
|
383
|
-
columns: [...columns.values()].sort((a, b) => a.column - b.column),
|
|
384
|
-
...taskCount < tasks.length ? { truncated: { omittedNodes: tasks.length - taskCount } } : {}
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
function boundedGraph(graph) {
|
|
388
|
-
if (JSON.stringify(graph).length <= MAX_GRAPH_BODY_CHARS) return graph;
|
|
389
|
-
const totalTasks = graph.nodes.filter((node) => node.kind === "task").length;
|
|
390
|
-
let low = 0;
|
|
391
|
-
let high = totalTasks;
|
|
392
|
-
while (low < high) {
|
|
393
|
-
const count = Math.ceil((low + high) / 2);
|
|
394
|
-
if (JSON.stringify(graphWithTaskLimit(graph, count)).length <= MAX_GRAPH_BODY_CHARS) low = count;
|
|
395
|
-
else high = count - 1;
|
|
396
|
-
}
|
|
397
|
-
return graphWithTaskLimit(graph, low);
|
|
398
|
-
}
|
|
399
|
-
function rootsFrom(query, fallback = defaultRoots()) {
|
|
400
|
-
const pick = (key, base) => {
|
|
401
|
-
const value = query.get(key)?.trim();
|
|
402
|
-
return value ? [expandHome(value)] : base;
|
|
403
|
-
};
|
|
404
|
-
return canonicalRoots({
|
|
405
|
-
agentDirs: pick("agentDir", fallback.agentDirs),
|
|
406
|
-
stateDirs: pick("stateDir", fallback.stateDirs)
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
function createServer(token2, makeStore = (r) => new TaskStore(r)) {
|
|
410
|
-
const stores = /* @__PURE__ */ new Map();
|
|
411
|
-
const storeFor = (roots) => {
|
|
412
|
-
const key = JSON.stringify(roots);
|
|
413
|
-
const cached = stores.get(key);
|
|
414
|
-
if (cached) {
|
|
415
|
-
stores.delete(key);
|
|
416
|
-
stores.set(key, cached);
|
|
417
|
-
return cached;
|
|
418
|
-
}
|
|
419
|
-
const store = makeStore(roots);
|
|
420
|
-
stores.set(key, store);
|
|
421
|
-
if (stores.size > MAX_TASK_STORES) {
|
|
422
|
-
const oldest = stores.keys().next().value;
|
|
423
|
-
if (oldest !== void 0) stores.delete(oldest);
|
|
424
|
-
}
|
|
425
|
-
return store;
|
|
426
|
-
};
|
|
427
|
-
const json = (res, status, body) => {
|
|
428
|
-
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
429
|
-
res.end(JSON.stringify(body));
|
|
430
|
-
};
|
|
431
|
-
return http.createServer((req, res) => {
|
|
432
|
-
if (req.headers.authorization !== `Bearer ${token2}`) return json(res, 401, { error: "unauthorized" });
|
|
433
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
434
|
-
if (url.pathname === "/health") return json(res, 200, { ok: true });
|
|
435
|
-
if (url.pathname === "/graph" && req.method === "GET") {
|
|
436
|
-
const session = url.searchParams.get("session");
|
|
437
|
-
if (!session || !/^ses_[0-9A-Za-z]+$/.test(session))
|
|
438
|
-
return json(res, 400, { error: "session query must be an OpenChamber session id" });
|
|
439
|
-
try {
|
|
440
|
-
const store = storeFor(rootsFrom(url.searchParams));
|
|
441
|
-
return json(res, 200, boundedGraph(buildGraph(session, store.loadTasks(), store.loadRegistry())));
|
|
442
|
-
} catch (error) {
|
|
443
|
-
return json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return json(res, 404, { error: "not-found" });
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// extensions/omo-dag/src/service-main.ts
|
|
451
|
-
var port = Number(process.env.OPENCHAMBER_SERVICE_PORT);
|
|
452
|
-
var token = process.env.OPENCHAMBER_SERVICE_TOKEN ?? "";
|
|
453
|
-
if (!port || !token) {
|
|
454
|
-
console.error("OPENCHAMBER_SERVICE_PORT and OPENCHAMBER_SERVICE_TOKEN are required");
|
|
455
|
-
process.exit(1);
|
|
456
|
-
}
|
|
457
|
-
createServer(token).listen(port, "127.0.0.1");
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# OmO Work: OpenChamber extension
|
|
2
|
-
|
|
3
|
-
An **OmO Work** section in OpenChamber's Work Status panel, for the background
|
|
4
|
-
work of the session you are looking at. It ships with
|
|
5
|
-
[antechamber](../../README.md) and needs OpenChamber **2.3.2 or newer** (web or
|
|
6
|
-
desktop). OpenChamber 1.x has no extension sections, and VS Code and mobile
|
|
7
|
-
load no extensions.
|
|
8
|
-
|
|
9
|
-
For the open session it shows:
|
|
10
|
-
|
|
11
|
-
- **Todo**: OmO's todo list grouped by phase, with progress per phase. The
|
|
12
|
-
phase in progress is open; click any phase to open or close it.
|
|
13
|
-
- **Goal**: the goal continuation timer, what it waits on, and **Fire now**.
|
|
14
|
-
- **Other activity**: any other wake source OmO reports.
|
|
15
|
-
|
|
16
|
-
Terminal monitors, detached eval cells and background terminal sessions are not
|
|
17
|
-
listed here: antechamber reports them to OpenChamber as background commands,
|
|
18
|
-
so the session reads as working while they run and OpenChamber lists them above
|
|
19
|
-
the composer with their output (and Stop, for eval cells).
|
|
20
|
-
|
|
21
|
-
The section sizes itself to its content, up to OpenChamber's 320 px limit, and
|
|
22
|
-
scrolls inside beyond that.
|
|
23
|
-
|
|
24
|
-
## Install
|
|
25
|
-
|
|
26
|
-
In OpenChamber, open **Settings → Extensions → Add**:
|
|
27
|
-
|
|
28
|
-
- **Folder**, when OpenChamber runs on the machine where antechamber is
|
|
29
|
-
installed: the path `antechamber setup` prints,
|
|
30
|
-
`$(npm root -g)/antechamber/extensions/omo-work` for npm installs or
|
|
31
|
-
`<checkout>/extensions/omo-work` after `npm run build`.
|
|
32
|
-
- **URL**: `https://github.com/eliottness/antechamber#extension/omo-work`, a
|
|
33
|
-
Git install that OpenChamber offers to update after each release. The
|
|
34
|
-
release archive,
|
|
35
|
-
`https://github.com/eliottness/antechamber/releases/latest/download/antechamber-omo-work.zip`,
|
|
36
|
-
also installs by URL, but OpenChamber never updates ZIP installs.
|
|
37
|
-
- **ZIP file**: that archive, or
|
|
38
|
-
`npm run build && npm run pack:work-extension -- antechamber-omo-work.zip`.
|
|
39
|
-
|
|
40
|
-
Approve its **local service**. The section then appears in the Work Status
|
|
41
|
-
panel; the panel's sections dialog can hide or reorder it.
|
|
42
|
-
|
|
43
|
-
## Requirements
|
|
44
|
-
|
|
45
|
-
- antechamber with the `/antechamber/work-status` routes (the release that
|
|
46
|
-
added this extension or newer), running as OpenChamber's OpenCode server.
|
|
47
|
-
After upgrading antechamber, restart OpenChamber so it runs the new adapter;
|
|
48
|
-
`antechamber doctor` reports the running version.
|
|
49
|
-
- The extension service runs on the OpenChamber host and reads antechamber's
|
|
50
|
-
run records, so antechamber must run on the same machine.
|
|
51
|
-
|
|
52
|
-
## How it works
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
section (sandboxed iframe) --serviceRequest /status?session=ses_...--> OpenChamber host
|
|
56
|
-
| loopback + host token
|
|
57
|
-
v
|
|
58
|
-
service/main.js --Bearer <run token>--> antechamber
|
|
59
|
-
reads <state dir>/run/<pid>.json /antechamber/work-status/<session>
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
`antechamber serve` writes `<state dir>/run/<pid>.json` (mode 0600) with its
|
|
63
|
-
URL and a random token that opens only the `/antechamber/work-status` routes. The service
|
|
64
|
-
picks the running server whose agent holds the session and relays the snapshot
|
|
65
|
-
and the Fire now action. It keeps no state. See [docs/DESIGN.md](docs/DESIGN.md) for the snapshot, refresh,
|
|
66
|
-
server choice and security boundary, and
|
|
67
|
-
[docs/architecture.md](../../docs/architecture.md#omo-work-status-section) for
|
|
68
|
-
where each piece of data comes from in OmO.
|
|
69
|
-
|
|
70
|
-
The state directory defaults to `~/.config/antechamber`, then
|
|
71
|
-
`~/.config/openchamber-pi-connector`. OpenChamber starts extension services
|
|
72
|
-
with a minimal environment, so if your deployment sets `ANTECHAMBER_STATE_DIR`,
|
|
73
|
-
enter the same path under **Data path** in the section header. It is saved in
|
|
74
|
-
the extension's OpenChamber storage.
|
|
75
|
-
|
|
76
|
-
## Limitations
|
|
77
|
-
|
|
78
|
-
OmO does not publish continuation holds over RPC (the loop guard keeps them
|
|
79
|
-
in-process), so the section cannot show them.
|
|
80
|
-
|
|
81
|
-
## Development
|
|
82
|
-
|
|
83
|
-
From the repository root: `npm run build:extension` bundles `src/status.ts`
|
|
84
|
-
into `status/main.js` (browser IIFE) and `src/service-main.ts` into
|
|
85
|
-
`service/main.js` (Node ESM); `npm test`, `npm run typecheck`, `npm run lint`
|
|
86
|
-
and `npm run format:check` cover it. `src/model.ts` imports the snapshot types
|
|
87
|
-
from the adapter (`src/work-status.ts`), so a shape change on either side fails
|
|
88
|
-
the typecheck.
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "antechamber-omo-work",
|
|
3
|
-
"version": "1.0.41",
|
|
4
|
-
"description": "OpenChamber 2.x Work Status section: OmO todo phases, monitors, eval cells and background work of the current session",
|
|
5
|
-
"private": true,
|
|
6
|
-
"type": "module",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"openchamber": {
|
|
9
|
-
"apiVersion": 1,
|
|
10
|
-
"engines": {
|
|
11
|
-
"openchamber": ">=2.3.2"
|
|
12
|
-
},
|
|
13
|
-
"contributes": {
|
|
14
|
-
"panel": {
|
|
15
|
-
"id": "omo-work",
|
|
16
|
-
"name": "OmO Work",
|
|
17
|
-
"icon": "list-check-3"
|
|
18
|
-
},
|
|
19
|
-
"statusSection": {
|
|
20
|
-
"entry": "status/index.html",
|
|
21
|
-
"title": "OmO Work",
|
|
22
|
-
"height": 120
|
|
23
|
-
},
|
|
24
|
-
"service": {
|
|
25
|
-
"entry": "service/main.js",
|
|
26
|
-
"runtime": "host"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|