antechamber 1.0.40 → 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.
Files changed (70) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +88 -141
  3. package/bin/opencode-antechamber +14 -4
  4. package/dist/approvals.d.ts +9 -1
  5. package/dist/approvals.js +10 -2
  6. package/dist/catalog.d.ts +13 -2
  7. package/dist/catalog.js +88 -52
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.js +1 -1
  10. package/dist/commands/context.d.ts +5 -2
  11. package/dist/commands/context.js +8 -4
  12. package/dist/commands/doctor.js +4 -3
  13. package/dist/commands/setup.js +9 -28
  14. package/dist/config.d.ts +6 -2
  15. package/dist/config.js +18 -4
  16. package/dist/file-watch.d.ts +35 -0
  17. package/dist/file-watch.js +163 -0
  18. package/dist/latency.d.ts +10 -0
  19. package/dist/latency.js +115 -0
  20. package/dist/misc-routes.js +14 -12
  21. package/dist/pi-event-translation.js +44 -2
  22. package/dist/route-services.d.ts +8 -3
  23. package/dist/routes-v1.d.ts +1 -1
  24. package/dist/routes-v1.js +29 -418
  25. package/dist/routes-v2.js +252 -34
  26. package/dist/self-update.d.ts +2 -1
  27. package/dist/self-update.js +5 -1
  28. package/dist/server.d.ts +17 -3
  29. package/dist/server.js +191 -18
  30. package/dist/session-commands.d.ts +3 -1
  31. package/dist/session-commands.js +13 -7
  32. package/dist/session-history.js +3 -0
  33. package/dist/session-store.d.ts +19 -10
  34. package/dist/session-store.js +266 -128
  35. package/dist/session-tree.d.ts +15 -1
  36. package/dist/session-tree.js +2 -2
  37. package/dist/sessions.d.ts +85 -1
  38. package/dist/sessions.js +238 -10
  39. package/dist/sse.d.ts +0 -2
  40. package/dist/sse.js +13 -46
  41. package/dist/text-generation.d.ts +20 -0
  42. package/dist/text-generation.js +93 -0
  43. package/dist/tool-map.js +1 -3
  44. package/dist/types.d.ts +60 -0
  45. package/dist/v2.d.ts +8 -1
  46. package/dist/v2.js +24 -4
  47. package/dist/work-status.js +1 -1
  48. package/extensions/antechamber/README.md +100 -0
  49. package/extensions/antechamber/bin/opencode-antechamber +42 -0
  50. package/extensions/{omo-dag/docs/DESIGN.md → antechamber/docs/GRAPH.md} +5 -5
  51. package/extensions/{omo-work/docs/DESIGN.md → antechamber/docs/WORK.md} +3 -3
  52. package/extensions/antechamber/package.json +60 -0
  53. package/extensions/{omo-dag → antechamber}/panel/index.html +8 -1
  54. package/extensions/antechamber/panel/main.js +261 -0
  55. package/extensions/antechamber/runtime/launcher.js +201 -0
  56. package/extensions/antechamber/service/main.js +884 -0
  57. package/package.json +75 -26
  58. package/extensions/omo-compat/README.md +0 -48
  59. package/extensions/omo-compat/icon.svg +0 -1
  60. package/extensions/omo-compat/package.json +0 -43
  61. package/extensions/omo-dag/README.md +0 -167
  62. package/extensions/omo-dag/package.json +0 -33
  63. package/extensions/omo-dag/panel/main.js +0 -176
  64. package/extensions/omo-dag/service/main.js +0 -457
  65. package/extensions/omo-work/README.md +0 -88
  66. package/extensions/omo-work/package.json +0 -30
  67. package/extensions/omo-work/service/main.js +0 -149
  68. /package/extensions/{omo-dag → antechamber}/icon.svg +0 -0
  69. /package/extensions/{omo-work → antechamber}/status/index.html +0 -0
  70. /package/extensions/{omo-work → antechamber}/status/main.js +0 -0
@@ -0,0 +1,115 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import { envSetting } from "./config.js";
3
+ const SAMPLE_MS = 100;
4
+ // The router uses explicit path comparisons rather than a routing framework. Unknown
5
+ // paths get a constant label so neither identifiers nor query credentials enter logs.
6
+ const ROUTE_PATTERNS = `
7
+ / /health /global/health /global/upgrade /event /global/event
8
+ /session /session/status /project /project/current /config /global/config /provider
9
+ /config/providers /experimental/session /question /permission /lsp /formatter
10
+ /command /path /vcs /agent /file /file/content /file/status /find /find/file /find/symbol
11
+ /provider/auth /auth/:providerID /provider/:providerID/oauth/authorize /provider/:providerID/oauth/callback
12
+ /question/:requestID/reply /question/:requestID/reject /permission/:requestID/reply
13
+ /api/info /api/location /api/config /api/provider /api/model /api/model/default
14
+ /api/agent /api/agent/:agentID /api/command /api/skill /api/debug/location
15
+ /api/credential /api/credential/:credentialID /api/credential/:credentialID/activate
16
+ /api/integration/:integrationID /api/integration/:integrationID/connect/key
17
+ /api/integration/:integrationID/connect/external /api/integration/:integrationID/connect/oauth
18
+ /api/integration/:integrationID/connect/command /api/integration/:integrationID/connect/oauth/:flowID
19
+ /api/integration/:integrationID/connect/command/:flowID
20
+ /api/integration/:integrationID/connect/oauth/:flowID/complete
21
+ /api/plugin /api/plugin/check /api/plugin/update /api/experimental/generate /api/experimental/generate/text
22
+ /api/shell /api/shell/:shellID /api/shell/:shellID/output /api/websearch/provider
23
+ /api/experimental/session/stats /api/experimental/usage/limits /api/experimental/fs/watch
24
+ /api/fs/find /api/project /api/project/:projectID /api/permission/request /api/question/request
25
+ /api/permission/saved /api/permission/saved/:permissionID /api/form /api/vcs/init /api/vcs
26
+ /api/session /api/session/active /api/event /api/global/event/ws
27
+ /api/experimental/session/:sessionID/instructions/entries/:entryID
28
+ /antechamber/work-status/:sessionID
29
+ `
30
+ .trim()
31
+ .split(/\s+/);
32
+ for (const prefix of ["", "/api"]) {
33
+ const base = `${prefix}/session/:sessionID`;
34
+ ROUTE_PATTERNS.push(base, `${base}/message/:messageID`);
35
+ for (const action of [
36
+ "diff",
37
+ "children",
38
+ "todo",
39
+ "message",
40
+ "prompt_async",
41
+ "abort",
42
+ "fork",
43
+ "revert",
44
+ "unrevert",
45
+ "summarize",
46
+ "shell",
47
+ "command",
48
+ "share",
49
+ "generate",
50
+ "background",
51
+ "move",
52
+ "view",
53
+ "compact",
54
+ "prompt",
55
+ "synthetic",
56
+ "context",
57
+ "interrupt",
58
+ "revert/stage",
59
+ "revert/commit",
60
+ "agent",
61
+ "model",
62
+ "form",
63
+ "question",
64
+ "permission",
65
+ "inbox",
66
+ ])
67
+ ROUTE_PATTERNS.push(`${base}/${action}`);
68
+ for (const resource of ["form", "question", "permission"])
69
+ for (const suffix of ["", "/reply", "/reject"])
70
+ ROUTE_PATTERNS.push(`${base}/${resource}/:requestID${suffix}`);
71
+ }
72
+ const routes = ROUTE_PATTERNS.map((pattern) => ({
73
+ pattern,
74
+ match: new RegExp(`^${pattern.replace(/:[A-Za-z]+/g, "[^/]+")}$`),
75
+ }));
76
+ function routePattern(req) {
77
+ const path = new URL(req.url ?? "/", "http://localhost").pathname.replace(/\/+$/, "") || "/";
78
+ return routes.find((route) => route.match.test(path))?.pattern ?? "unmatched";
79
+ }
80
+ function threshold(name, fallback) {
81
+ const value = Number(envSetting(name));
82
+ return Number.isFinite(value) && value > 0 ? value : fallback;
83
+ }
84
+ export class LatencyMonitor {
85
+ lagThreshold = threshold("EVENT_LOOP_LAG_MS", 250);
86
+ requestThreshold = threshold("SLOW_REQUEST_MS", 1000);
87
+ timer;
88
+ start() {
89
+ let expected = performance.now() + SAMPLE_MS;
90
+ this.timer = setInterval(() => {
91
+ const now = performance.now();
92
+ const lag = now - expected;
93
+ expected = now + SAMPLE_MS;
94
+ if (lag > this.lagThreshold)
95
+ console.warn(`[latency] event-loop-lag duration_ms=${Math.round(lag)} threshold_ms=${this.lagThreshold}`);
96
+ }, SAMPLE_MS);
97
+ this.timer.unref();
98
+ }
99
+ stop() {
100
+ clearInterval(this.timer);
101
+ this.timer = undefined;
102
+ }
103
+ async request(req, res, handle) {
104
+ const start = performance.now();
105
+ try {
106
+ await handle();
107
+ }
108
+ finally {
109
+ // SSE handlers return after setup; connection lifetime is not request latency.
110
+ const duration = performance.now() - start;
111
+ if (duration > this.requestThreshold)
112
+ console.warn(`[latency] slow-request method=${req.method ?? "GET"} route=${routePattern(req)} status=${res.statusCode} duration_ms=${Math.round(duration)}`);
113
+ }
114
+ }
115
+ }
@@ -42,18 +42,10 @@ async function confinedDirectory(root, path) {
42
42
  }
43
43
  }
44
44
  export async function miscRoute(method, path, url, directory) {
45
- if (method === "GET" && ["/skill", "/lsp", "/formatter", "/experimental/tool/ids"].includes(path)) {
45
+ if (method === "GET" && ["/skill", "/formatter"].includes(path)) {
46
46
  return { status: 200, body: [] };
47
47
  }
48
- if (method === "GET" && path === "/provider/auth")
49
- return { status: 200, body: {} };
50
- if ((method === "PUT" && /^\/auth\/[^/]+$/.test(path)) ||
51
- (method === "POST" && /^\/provider\/[^/]+\/oauth\/(authorize|callback)$/.test(path)) ||
52
- (method === "PATCH" && path === "/config")) {
53
- return unsupported("Not supported with OmO; configure providers in OmO");
54
- }
55
- if ((["POST", "DELETE"].includes(method) && /^\/mcp\/[^/]+\/(connect|disconnect|auth)$/.test(path)) ||
56
- (method === "POST" && path === "/experimental/control-plane/move-session")) {
48
+ if (["POST", "DELETE"].includes(method) && /^\/mcp\/[^/]+\/(connect|disconnect|auth)$/.test(path)) {
57
49
  return unsupported("Not supported with OmO; configure MCP and session directories in OmO");
58
50
  }
59
51
  if (method !== "GET" || !["/file", "/find/file"].includes(path))
@@ -93,6 +85,7 @@ export async function miscRoute(method, path, url, directory) {
93
85
  const dirs = url.searchParams.get("dirs") !== "false" && url.searchParams.get("type") !== "file";
94
86
  const onlyDirs = url.searchParams.get("type") === "directory";
95
87
  const files = [];
88
+ const matchedDirs = new Set();
96
89
  let visited = 0;
97
90
  const lowerQuery = query.toLowerCase();
98
91
  async function visit(directory, depth) {
@@ -107,12 +100,21 @@ export async function miscRoute(method, path, url, directory) {
107
100
  continue;
108
101
  const absolute = resolve(directory, entry.name);
109
102
  const path = relative(root, absolute).split(sep).join("/");
103
+ if (onlyDirs && entry.isFile() && path.toLowerCase().includes(lowerQuery)) {
104
+ matchedDirs.add("/");
105
+ const segments = path.split("/");
106
+ for (let i = 1; i < segments.length; i++)
107
+ matchedDirs.add(`${segments.slice(0, i).join("/")}/`);
108
+ }
110
109
  if (((entry.isFile() && !onlyDirs) || (entry.isDirectory() && dirs)) && path.toLowerCase().includes(lowerQuery))
111
- files.push(path);
110
+ if (onlyDirs)
111
+ matchedDirs.add(`${path}/`);
112
+ else
113
+ files.push(path);
112
114
  if (entry.isDirectory() && entry.name !== "node_modules")
113
115
  await visit(absolute, depth + 1);
114
116
  }
115
117
  }
116
118
  await visit(root, 0);
117
- return { status: 200, body: files.sort() };
119
+ return { status: 200, body: onlyDirs ? [...matchedDirs].sort().slice(0, limit) : files.sort() };
118
120
  }
@@ -202,6 +202,24 @@ export class PiEventTranslator {
202
202
  }
203
203
  delete s.compactingFromIdle;
204
204
  delete s.info.time.compacting;
205
+ // A compaction the run never finished must not stay "running": a later Compact would join it and report
206
+ // success for work that will never happen.
207
+ if (s.compactionID !== undefined) {
208
+ const inputID = s.compactionID;
209
+ const reason = s.compactionReason ?? "manual";
210
+ delete s.compactionID;
211
+ delete s.compactionReason;
212
+ delete s.heldCompactionEnd;
213
+ this.services.emit(s.info.directory, {
214
+ type: "session.compaction.failed",
215
+ properties: {
216
+ sessionID: s.info.id,
217
+ inputID,
218
+ reason,
219
+ error: { name: "CompactionError", data: { message: error.data.message } },
220
+ },
221
+ });
222
+ }
205
223
  }
206
224
  setStatus(s, status) {
207
225
  s.status = status;
@@ -211,6 +229,14 @@ export class PiEventTranslator {
211
229
  });
212
230
  if (status.type === "idle") {
213
231
  s.info.time.idle = Date.now();
232
+ // Restored as time.idle after a restart, so /view can still accept the watermark a client saw.
233
+ s.tree.lastIdle = s.info.time.idle;
234
+ try {
235
+ this.services.persistMeta(s);
236
+ }
237
+ catch (error) {
238
+ console.error(`[session] could not save the idle time of ${s.info.id}:`, error);
239
+ }
214
240
  const last = s.messages?.findLast((message) => message.info.role === "assistant" && !message.info.id.startsWith("msg_background_"));
215
241
  s.info.outcome =
216
242
  s.interruptRequested || (last?.info.role === "assistant" && last.info.error?.name === "MessageAbortedError")
@@ -323,6 +349,7 @@ export class PiEventTranslator {
323
349
  this.onMessageEvent(s, ev);
324
350
  break;
325
351
  case "compaction_start":
352
+ case "compaction_progress":
326
353
  case "compaction_end":
327
354
  this.onCompactionEvent(s, ev);
328
355
  break;
@@ -641,7 +668,8 @@ export class PiEventTranslator {
641
668
  }
642
669
  // An auto-compaction can fall between steps; its summary is a new step.
643
670
  this.completeAwaitingTools(s);
644
- s.compactionID = id("compaction");
671
+ s.compactionID ??= id("msg");
672
+ s.compactionReason = typeof ev.reason === "string" ? ev.reason : "manual";
645
673
  this.services.emit(s.info.directory, {
646
674
  type: "session.compaction.started",
647
675
  properties: {
@@ -658,6 +686,15 @@ export class PiEventTranslator {
658
686
  this.setStatus(s, { type: "busy" });
659
687
  }
660
688
  return;
689
+ case "compaction_progress":
690
+ // Extension compaction feedback carries actual partial summary fragments
691
+ // (senpi/dist/core/agent-session.js _updateExtensionCompactionFeedback).
692
+ if (s.compactionID !== undefined && typeof ev.delta === "string" && ev.delta !== "")
693
+ this.services.emit(s.info.directory, {
694
+ type: "session.compaction.delta",
695
+ properties: { sessionID: s.info.id, text: ev.delta },
696
+ });
697
+ return;
661
698
  case "compaction_end": {
662
699
  const retry = s.compactRetry;
663
700
  if (ev.aborted === true && retry !== undefined && retry.left > 0 && (s.abortRequests ?? 0) === retry.aborts) {
@@ -666,8 +703,9 @@ export class PiEventTranslator {
666
703
  }
667
704
  delete s.info.time.compacting;
668
705
  const result = ev.result;
669
- const inputID = s.compactionID ?? id("compaction");
706
+ const inputID = s.compactionID ?? id("msg");
670
707
  delete s.compactionID;
708
+ delete s.compactionReason;
671
709
  this.services.emit(s.info.directory, ev.aborted === true || typeof result?.summary !== "string"
672
710
  ? {
673
711
  type: "session.compaction.failed",
@@ -702,6 +740,10 @@ export class PiEventTranslator {
702
740
  key = entry.id;
703
741
  }
704
742
  }
743
+ if (key !== undefined) {
744
+ s.tree.messageEntries[inputID] ??= key;
745
+ (s.tree.compactionReasons ??= {})[key] = ev.reason === "manual" ? "manual" : "auto";
746
+ }
705
747
  for (const message of compactionMessages(s, key ?? id("compaction"), result.summary, Date.now(), s.model, s.thinkingLevel)) {
706
748
  if (messages.some((item) => item.info.id === message.info.id))
707
749
  continue;
@@ -5,15 +5,20 @@ import type { Catalog } from "./catalog.js";
5
5
  import type { ModelSelection } from "./thinking-levels.js";
6
6
  import type { OCEvent, OCMessageWithParts, OCPromptPart } from "./types.js";
7
7
  import type { TodoUpdated } from "./todo-bridge.js";
8
- import type { SelfUpdateResult } from "./self-update.js";
8
+ import type { FileWatchRegistry } from "./file-watch.js";
9
+ import type { getCommands } from "./session-commands.js";
10
+ import type { TextGeneration } from "./text-generation.js";
9
11
  export interface RouteServices {
10
12
  readonly sessions: SessionManager;
11
13
  readonly defaultDirectory: string;
12
14
  readonly url: string;
13
15
  readonly body: (req: IncomingMessage) => Promise<unknown>;
14
- readonly catalog: () => Promise<Catalog>;
16
+ readonly catalog: (directory?: string, refresh?: boolean) => Promise<Catalog>;
15
17
  readonly broadcast: (directory: string, event: OCEvent | TodoUpdated) => void;
16
- readonly selfUpdate: () => Promise<SelfUpdateResult>;
18
+ readonly fileWatches: FileWatchRegistry;
19
+ readonly commands: (directory: string) => ReturnType<typeof getCommands>;
20
+ readonly vcsBranch: (directory: string) => Promise<string | undefined>;
21
+ readonly textGeneration: TextGeneration;
17
22
  }
18
23
  export declare const OPENCODE_COMPAT_VERSION: string | null;
19
24
  export declare function numberParam(value: string | null): number | undefined;
@@ -1,4 +1,4 @@
1
- /** OpenCode v1 routes, using the services composed by AdapterServer. */
1
+ /** Unprefixed compatibility routes retained for evidenced or uncertain callers. */
2
2
  import type { IncomingMessage, ServerResponse } from "node:http";
3
3
  import { type RouteServices } from "./route-services.js";
4
4
  export declare function handleV1(services: RouteServices, req: IncomingMessage, res: ServerResponse, url: URL, path: string, method: string, directory: string): Promise<void>;