@inerrata-corporation/errata 2.0.6-dev.2081 → 2.0.6-dev.2115

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 (2) hide show
  1. package/errata.mjs +119 -14
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -77056,7 +77056,7 @@ function createWatchBreaker(deps) {
77056
77056
  }
77057
77057
 
77058
77058
  // src/engine.ts
77059
- var DAEMON_VERSION = true ? "2.0.6-dev.2081" : "2.0.0-alpha.0";
77059
+ var DAEMON_VERSION = true ? "2.0.6-dev.2115" : "2.0.0-alpha.0";
77060
77060
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
77061
77061
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
77062
77062
  var GIT_OP_MUTE_MS = 4e3;
@@ -82578,10 +82578,21 @@ init_cloud_auth();
82578
82578
  import { createServer } from "node:http";
82579
82579
  var DEFAULT_OAUTH_SCOPE = "openid profile graph:read graph:write mcp:tools";
82580
82580
  var strip = (u) => u.replace(/\/+$/, "");
82581
- function deviceEndpoints(tokenEndpoint) {
82581
+ function deviceEndpoints(tokenEndpoint, deviceAuthorizationEndpoint) {
82582
82582
  const token = new URL(tokenEndpoint);
82583
82583
  const authRoot = token.pathname.replace(/\/(?:oauth2|mcp)\/token\/?$/, "");
82584
- if (authRoot === token.pathname) throw new Error("authorization server exposes no device endpoint");
82584
+ if (authRoot === token.pathname) {
82585
+ if (deviceAuthorizationEndpoint) {
82586
+ const device = new URL(deviceAuthorizationEndpoint);
82587
+ const code = `${device.origin}${device.pathname.replace(/\/+$/, "")}`;
82588
+ return {
82589
+ code,
82590
+ token: `${code}/token`,
82591
+ resource: new URL("/api/gate/session-token", device.origin).toString()
82592
+ };
82593
+ }
82594
+ throw new Error("authorization server exposes no device endpoint");
82595
+ }
82585
82596
  const prefix = authRoot.replace(/\/api\/auth\/?$/, "");
82586
82597
  const at = (path2) => new URL(`${path2}`, `${token.origin}${prefix || "/"}`).toString();
82587
82598
  return {
@@ -82593,7 +82604,7 @@ function deviceEndpoints(tokenEndpoint) {
82593
82604
  async function loginOAuthDevice(opts) {
82594
82605
  const fetchFn = opts.fetchFn ?? fetch;
82595
82606
  const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
82596
- const device = deviceEndpoints(endpoints.tokenEndpoint);
82607
+ const device = deviceEndpoints(endpoints.tokenEndpoint, endpoints.deviceAuthorizationEndpoint);
82597
82608
  const clientId = oauthClientId();
82598
82609
  const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
82599
82610
  const codeResponse = await fetchFn(device.code, {
@@ -82692,7 +82703,8 @@ async function discoverEndpoints(cloudUrl, fetchFn = fetch) {
82692
82703
  return {
82693
82704
  authorizationEndpoint: j.authorization_endpoint,
82694
82705
  tokenEndpoint: j.token_endpoint,
82695
- ...j.registration_endpoint ? { registrationEndpoint: j.registration_endpoint } : {}
82706
+ ...j.registration_endpoint ? { registrationEndpoint: j.registration_endpoint } : {},
82707
+ ...j.device_authorization_endpoint ? { deviceAuthorizationEndpoint: j.device_authorization_endpoint } : {}
82696
82708
  };
82697
82709
  }
82698
82710
  }
@@ -82809,7 +82821,7 @@ function isDecision(value) {
82809
82821
  async function linkThroughConsole(opts) {
82810
82822
  const fetchFn = opts.fetchFn ?? fetch;
82811
82823
  const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
82812
- const device = deviceEndpoints(endpoints.tokenEndpoint);
82824
+ const device = deviceEndpoints(endpoints.tokenEndpoint, endpoints.deviceAuthorizationEndpoint);
82813
82825
  const clientId = oauthClientId();
82814
82826
  const link = {
82815
82827
  locator: opts.facts.locator,
@@ -83159,6 +83171,76 @@ function codexWakeCandidates(input) {
83159
83171
  return out2;
83160
83172
  }
83161
83173
 
83174
+ // src/hook-route.ts
83175
+ function msysToWindowsPath(p, platform = process.platform) {
83176
+ if (platform !== "win32") return p;
83177
+ const m = /^\/([a-zA-Z])(\/.*)?$/.exec(p);
83178
+ return m ? `${m[1].toUpperCase()}:${m[2] ?? "/"}` : p;
83179
+ }
83180
+ var SHELL_BREAK = /[\s"'`;&|()<>=]+/;
83181
+ function absolutePathsInCommand(command, platform = process.platform) {
83182
+ const out2 = [];
83183
+ for (const raw2 of command.split(SHELL_BREAK)) {
83184
+ const token = raw2.replace(/^[:,]+/, "").replace(/[,:]+$/, "");
83185
+ if (!token) continue;
83186
+ if (/^[a-zA-Z]:[\\/]/.test(token)) {
83187
+ out2.push(token);
83188
+ } else if (token.startsWith("/") && !token.startsWith("//")) {
83189
+ if (platform === "win32") {
83190
+ if (/^\/[a-zA-Z](\/|$)/.test(token)) out2.push(msysToWindowsPath(token, platform));
83191
+ } else if (token.length > 1) {
83192
+ out2.push(token);
83193
+ }
83194
+ }
83195
+ }
83196
+ return out2;
83197
+ }
83198
+ function hookPathCandidates(body2, platform = process.platform) {
83199
+ const input = body2["tool_input"] ?? {};
83200
+ const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
83201
+ const out2 = [];
83202
+ for (const key of ["file_path", "notebook_path", "path"]) {
83203
+ const v = str(input[key]);
83204
+ if (v) out2.push(msysToWindowsPath(v, platform));
83205
+ }
83206
+ const command = str(input["command"]);
83207
+ if (command) out2.push(...absolutePathsInCommand(command, platform));
83208
+ const cwd = str(body2["cwd"]);
83209
+ if (cwd) out2.push(msysToWindowsPath(cwd, platform));
83210
+ return [...new Set(out2)];
83211
+ }
83212
+ var SESSION_AFFINITY_TTL_MS = 30 * 6e4;
83213
+ var SessionAffinity = class {
83214
+ constructor(ttlMs = SESSION_AFFINITY_TTL_MS, maxSessions = 500) {
83215
+ this.ttlMs = ttlMs;
83216
+ this.maxSessions = maxSessions;
83217
+ }
83218
+ bySession = /* @__PURE__ */ new Map();
83219
+ note(sessionId, root, now = Date.now()) {
83220
+ if (typeof sessionId !== "string" || sessionId.length === 0) return;
83221
+ this.bySession.delete(sessionId);
83222
+ this.bySession.set(sessionId, { root, at: now });
83223
+ while (this.bySession.size > this.maxSessions) {
83224
+ const oldest = this.bySession.keys().next().value;
83225
+ if (oldest === void 0) break;
83226
+ this.bySession.delete(oldest);
83227
+ }
83228
+ }
83229
+ rootFor(sessionId, now = Date.now()) {
83230
+ if (typeof sessionId !== "string" || sessionId.length === 0) return null;
83231
+ const hit = this.bySession.get(sessionId);
83232
+ if (!hit) return null;
83233
+ if (now - hit.at > this.ttlMs) {
83234
+ this.bySession.delete(sessionId);
83235
+ return null;
83236
+ }
83237
+ return hit.root;
83238
+ }
83239
+ forget(sessionId) {
83240
+ if (typeof sessionId === "string") this.bySession.delete(sessionId);
83241
+ }
83242
+ };
83243
+
83162
83244
  // src/multi.ts
83163
83245
  var projectSymbolSalts = /* @__PURE__ */ new Map();
83164
83246
  async function resolveProjectSymbolSalt(client, projectId) {
@@ -83609,7 +83691,8 @@ async function startMultiDaemon(opts = {}) {
83609
83691
  }))
83610
83692
  })
83611
83693
  );
83612
- const forward = async (c, pathInBody) => {
83694
+ const sessionAffinity = new SessionAffinity();
83695
+ const forward = async (c, pathInBody, opts2 = {}) => {
83613
83696
  const raw2 = await c.req.text();
83614
83697
  let body2 = {};
83615
83698
  try {
@@ -83618,7 +83701,21 @@ async function startMultiDaemon(opts = {}) {
83618
83701
  return c.json({ error: "bad-json" }, 400);
83619
83702
  }
83620
83703
  lastHook = { at: Date.now(), event: typeof body2["hook_event_name"] === "string" ? body2["hook_event_name"] : c.req.path };
83621
- let owner = ownerOf(records, pathInBody(body2));
83704
+ const primary = pathInBody(body2);
83705
+ const others = (opts2.extraPaths?.(body2) ?? []).filter((p) => p !== primary);
83706
+ let owner = ownerOf(records, primary);
83707
+ for (const p of others) {
83708
+ if (owner) break;
83709
+ owner = ownerOf(records, p);
83710
+ }
83711
+ if (!owner && others.length > 0) {
83712
+ const registryRoots = listWorkspaceEntries().map((e) => e.path);
83713
+ const registered = others.map((p) => pickOwnerRoot(registryRoots, p)).find((r) => r != null);
83714
+ if (registered && attachWorkspace(registered).attached) {
83715
+ owner = records.find((r) => r.root === registered) ?? null;
83716
+ if (owner) console.log(`[errata] woke workspace ${owner.entry.name}`);
83717
+ }
83718
+ }
83622
83719
  if (!owner) {
83623
83720
  const missPath = pathInBody(body2);
83624
83721
  const registeredRoot = missPath ? pickOwnerRoot(listWorkspaceEntries().map((e) => e.path), missPath) : null;
@@ -83628,10 +83725,18 @@ async function startMultiDaemon(opts = {}) {
83628
83725
  if (owner) console.log(`[errata] woke workspace ${owner.entry.name}`);
83629
83726
  }
83630
83727
  }
83728
+ if (!owner && opts2.affinity === "follow") {
83729
+ const root = sessionAffinity.rootFor(body2["session_id"]);
83730
+ if (root) {
83731
+ owner = records.find((r) => r.root === root) ?? null;
83732
+ if (!owner && attachWorkspace(root).attached) owner = records.find((r) => r.root === root) ?? null;
83733
+ }
83734
+ }
83631
83735
  if (!owner) {
83632
- rootAdopter.noteMiss(pathInBody(body2));
83736
+ rootAdopter.noteMiss(primary);
83633
83737
  return c.json({ error: "no-matching-project" }, 404);
83634
83738
  }
83739
+ if (opts2.affinity === "note") sessionAffinity.note(body2["session_id"], owner.root);
83635
83740
  owner.lastHookAt = Date.now();
83636
83741
  const sub = c.req.path;
83637
83742
  const done = markPass(`${sub}:${owner.entry.name}`);
@@ -83654,7 +83759,7 @@ async function startMultiDaemon(opts = {}) {
83654
83759
  const fp = input["file_path"];
83655
83760
  const cwd = b["cwd"];
83656
83761
  return (typeof fp === "string" ? fp : void 0) ?? (typeof cwd === "string" ? cwd : void 0);
83657
- })
83762
+ }, { extraPaths: (b) => hookPathCandidates(b), affinity: "note" })
83658
83763
  );
83659
83764
  app.post(
83660
83765
  "/api/diagnostics",
@@ -83664,10 +83769,10 @@ async function startMultiDaemon(opts = {}) {
83664
83769
  })
83665
83770
  );
83666
83771
  const cwdOf = (b) => typeof b["cwd"] === "string" ? b["cwd"] : void 0;
83667
- app.post("/api/turn", (c) => forward(c, cwdOf));
83668
- app.post("/api/message", (c) => forward(c, cwdOf));
83669
- app.post("/api/session-end", (c) => forward(c, cwdOf));
83670
- app.post("/api/context-inject", (c) => forward(c, cwdOf));
83772
+ app.post("/api/turn", (c) => forward(c, cwdOf, { affinity: "follow" }));
83773
+ app.post("/api/message", (c) => forward(c, cwdOf, { affinity: "follow" }));
83774
+ app.post("/api/session-end", (c) => forward(c, cwdOf, { affinity: "follow" }));
83775
+ app.post("/api/context-inject", (c) => forward(c, cwdOf, { affinity: "follow" }));
83671
83776
  for (const r of records) {
83672
83777
  mountedIds.add(r.id);
83673
83778
  app.route(`/ws/${r.id}`, r.webApp);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.2081",
3
+ "version": "2.0.6-dev.2115",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {