neuralos 3.7.6 → 3.7.7

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/bin/gybackend.cjs +167 -88
  2. package/package.json +1 -1
package/bin/gybackend.cjs CHANGED
@@ -276397,9 +276397,12 @@ var init_compoundingStore = __esm({
276397
276397
  init_betterSqlite3Runtime();
276398
276398
  init_historyStoragePaths();
276399
276399
  COMPOUNDING_DB_FILE = "gyshell-compounding.sqlite";
276400
- CompoundingStore = class {
276400
+ CompoundingStore = class _CompoundingStore {
276401
276401
  filePath;
276402
276402
  db;
276403
+ /** Avoid sync SQLite on every agent turn (desktop freeze). Invalidated on writes. */
276404
+ promptCache = null;
276405
+ static PROMPT_CACHE_MS = 2e3;
276403
276406
  constructor(options) {
276404
276407
  this.filePath = options?.filePath || import_node_path28.default.join(resolveHistoryStorageDir(), COMPOUNDING_DB_FILE);
276405
276408
  import_node_fs12.default.mkdirSync(import_node_path28.default.dirname(this.filePath), { recursive: true });
@@ -276453,6 +276456,7 @@ var init_compoundingStore = __esm({
276453
276456
  recordLessons(lessons, runId) {
276454
276457
  const now = Date.now();
276455
276458
  const out = [];
276459
+ this.promptCache = null;
276456
276460
  try {
276457
276461
  const sel = this.db.prepare("SELECT * FROM lessons WHERE fingerprint = ?");
276458
276462
  const ins = this.db.prepare(
@@ -276506,6 +276510,7 @@ var init_compoundingStore = __esm({
276506
276510
  }
276507
276511
  }
276508
276512
  upsertEstateFact(input) {
276513
+ this.promptCache = null;
276509
276514
  try {
276510
276515
  const now = Date.now();
276511
276516
  this.db.prepare(
@@ -276552,6 +276557,7 @@ var init_compoundingStore = __esm({
276552
276557
  }
276553
276558
  }
276554
276559
  upsertGoal(input) {
276560
+ this.promptCache = null;
276555
276561
  try {
276556
276562
  this.db.prepare(
276557
276563
  `INSERT INTO goals (id, session_id, text, status, blocked_by, next_probe, updated_at)
@@ -276589,6 +276595,7 @@ var init_compoundingStore = __esm({
276589
276595
  }
276590
276596
  }
276591
276597
  recordProbe(sessionId, tag, hypothesis, command, ok) {
276598
+ this.promptCache = null;
276592
276599
  try {
276593
276600
  this.db.prepare(
276594
276601
  `INSERT INTO probes (session_id, tag, hypothesis, command, ok, at)
@@ -276620,6 +276627,11 @@ var init_compoundingStore = __esm({
276620
276627
  */
276621
276628
  promptBlock(maxChars = 4e3) {
276622
276629
  try {
276630
+ const now = Date.now();
276631
+ if (this.promptCache && now - this.promptCache.at < _CompoundingStore.PROMPT_CACHE_MS) {
276632
+ const cached2 = this.promptCache.text;
276633
+ return cached2.length > maxChars ? cached2.slice(0, maxChars) + "\n\u2026" : cached2;
276634
+ }
276623
276635
  const lessons = this.listLessons(20);
276624
276636
  const facts = this.listEstateFacts();
276625
276637
  const lines = ["# Compounding knowledge (auto)"];
@@ -276641,6 +276653,7 @@ var init_compoundingStore = __esm({
276641
276653
  }
276642
276654
  }
276643
276655
  const text = lines.join("\n");
276656
+ this.promptCache = { at: now, text };
276644
276657
  return text.length > maxChars ? text.slice(0, maxChars) + "\n\u2026" : text;
276645
276658
  } catch {
276646
276659
  return "";
@@ -366951,6 +366964,11 @@ function reconcileToolCalls(toolCalls) {
366951
366964
  }
366952
366965
  return result;
366953
366966
  }
366967
+ var TOOL_RESULT_MAX_CHARS = 32768;
366968
+ function stringifyToolResult(result) {
366969
+ const raw = typeof result === "string" ? result : JSON.stringify(result);
366970
+ return clipTextMiddle(raw, TOOL_RESULT_MAX_CHARS);
366971
+ }
366954
366972
  function clipTextMiddle(input, maxChars) {
366955
366973
  if (maxChars <= 0) return "";
366956
366974
  if (input.length <= maxChars) return input;
@@ -368873,7 +368891,7 @@ Actually, your intention might be different. Please re-read the description of t
368873
368891
  try {
368874
368892
  const pluginArgs = typeof toolCall.args === "string" ? JSON.parse(toolCall.args) : toolCall.args || {};
368875
368893
  const pluginResult = await pluginHandler(pluginArgs);
368876
- result = typeof pluginResult === "string" ? pluginResult : JSON.stringify(pluginResult);
368894
+ result = stringifyToolResult(pluginResult);
368877
368895
  } catch (err) {
368878
368896
  result = `Plugin tool "${toolCall.name}" error: ${err.message}`;
368879
368897
  }
@@ -369161,7 +369179,7 @@ Actually, your intention might be different. Please re-read the description of t
369161
369179
  const pluginHandler = this.pluginTools.get(name);
369162
369180
  if (pluginHandler) {
369163
369181
  const result = await pluginHandler(args);
369164
- return typeof result === "string" ? result : JSON.stringify(result);
369182
+ return stringifyToolResult(result);
369165
369183
  }
369166
369184
  return `Tool "${name}" is not supported in parallel execution mode.`;
369167
369185
  }
@@ -369242,7 +369260,7 @@ Actually, your intention might be different. Please re-read the description of t
369242
369260
  args,
369243
369261
  signal
369244
369262
  );
369245
- resultText = typeof result === "string" ? result : JSON.stringify(result, null, 2);
369263
+ resultText = stringifyToolResult(result);
369246
369264
  } catch (err) {
369247
369265
  if (this.helpers.isAbortError(err)) throw err;
369248
369266
  resultText = err instanceof Error ? err.message : String(err);
@@ -373021,6 +373039,22 @@ var WebSocketRpcError = class extends Error {
373021
373039
  this.code = code;
373022
373040
  }
373023
373041
  };
373042
+ function httpRouteMatches(pattern, pathname) {
373043
+ if (pattern === pathname) return true;
373044
+ if (pattern.endsWith("/*")) {
373045
+ const prefix = pattern.slice(0, -1);
373046
+ const base = pattern.slice(0, -2);
373047
+ return pathname === base || pathname.startsWith(prefix) || pathname.startsWith(base + "/");
373048
+ }
373049
+ const a = pattern.split("/").filter(Boolean);
373050
+ const b = pathname.split("/").filter(Boolean);
373051
+ if (a.length !== b.length) return false;
373052
+ for (let i = 0; i < a.length; i++) {
373053
+ if (a[i].startsWith(":")) continue;
373054
+ if (a[i] !== b[i]) return false;
373055
+ }
373056
+ return true;
373057
+ }
373024
373058
  function createDefaultWebSocketServerFactory(httpRoutes) {
373025
373059
  return ({ host, port }) => {
373026
373060
  if (!httpRoutes || httpRoutes.length === 0) {
@@ -373043,7 +373077,7 @@ function createDefaultWebSocketServerFactory(httpRoutes) {
373043
373077
  ).pathname;
373044
373078
  } catch {
373045
373079
  }
373046
- const route = routes.find((r) => r.path === pathname);
373080
+ const route = routes.find((r) => httpRouteMatches(r.path, pathname));
373047
373081
  if (!route) {
373048
373082
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
373049
373083
  res.end("not found");
@@ -384954,8 +384988,9 @@ var IdleTimeoutService = class {
384954
384988
  // ../../packages/backend/src/services/Gateway/restApi.ts
384955
384989
  function matchRestRoute(routes, method, path33) {
384956
384990
  const normalized = path33.replace(/\/+$/, "") || "/";
384991
+ const verb = method.toUpperCase();
384957
384992
  for (const route of routes) {
384958
- if (route.method !== method.toUpperCase()) continue;
384993
+ if (route.method !== verb) continue;
384959
384994
  const patternParts = route.path.split("/").filter(Boolean);
384960
384995
  const pathParts = normalized.split("/").filter(Boolean);
384961
384996
  if (patternParts.length !== pathParts.length) continue;
@@ -384976,36 +385011,18 @@ function matchRestRoute(routes, method, path33) {
384976
385011
  }
384977
385012
  function defaultRestRoutes() {
384978
385013
  return [
385014
+ { method: "GET", path: "/api/v1/health", gatewayMethod: "gateway:ping", description: "Liveness check" },
385015
+ { method: "GET", path: "/api/v1/methods", gatewayMethod: "gateway:describe", description: "List all gateway RPC methods" },
385016
+ { method: "GET", path: "/api/v1/openapi.json", gatewayMethod: "__openapi", description: "OpenAPI 3 document for this REST overlay + RPC escape hatch" },
385017
+ { method: "GET", path: "/api/v1/terminals", gatewayMethod: "terminal:list", description: "List terminal tabs" },
385018
+ { method: "GET", path: "/api/v1/sessions", gatewayMethod: "session:list", description: "List chat sessions" },
384979
385019
  {
384980
- method: "GET",
384981
- path: "/api/v1/health",
384982
- gatewayMethod: "gateway:ping",
384983
- description: "Liveness check"
384984
- },
384985
- {
384986
- method: "GET",
384987
- path: "/api/v1/methods",
384988
- gatewayMethod: "gateway:describe",
384989
- description: "List all gateway methods (self-describing)"
384990
- },
384991
- {
384992
- method: "GET",
384993
- path: "/api/v1/terminals",
384994
- gatewayMethod: "terminal:list",
384995
- description: "List terminal tabs"
384996
- },
384997
- {
384998
- method: "GET",
385020
+ method: "POST",
384999
385021
  path: "/api/v1/sessions",
385000
- gatewayMethod: "session:list",
385001
- description: "List chat sessions"
385002
- },
385003
- {
385004
- method: "GET",
385005
- path: "/api/v1/skills",
385006
- gatewayMethod: "skills:getAll",
385007
- description: "List loaded skills"
385022
+ gatewayMethod: "gateway:createSession",
385023
+ description: "Create a chat/agent session"
385008
385024
  },
385025
+ { method: "GET", path: "/api/v1/skills", gatewayMethod: "skills:getAll", description: "List loaded skills" },
385009
385026
  {
385010
385027
  method: "GET",
385011
385028
  path: "/api/v1/observability/metrics",
@@ -385022,13 +385039,13 @@ function defaultRestRoutes() {
385022
385039
  method: "GET",
385023
385040
  path: "/api/v1/observability/apm",
385024
385041
  gatewayMethod: "observability:apmSummary",
385025
- description: "APM summary (LLM + app traces)"
385042
+ description: "APM summary"
385026
385043
  },
385027
385044
  {
385028
385045
  method: "GET",
385029
385046
  path: "/api/v1/history/search",
385030
385047
  gatewayMethod: "history:search",
385031
- description: "Cross-session history search (?q=...)",
385048
+ description: "Cross-session history search (?q=)",
385032
385049
  buildParams: (_p, body) => {
385033
385050
  const b = body ?? {};
385034
385051
  return { query: b.q ?? b.query ?? "" };
@@ -385048,17 +385065,18 @@ function defaultRestRoutes() {
385048
385065
  method: "GET",
385049
385066
  path: "/api/v1/terminals/:id/buffer",
385050
385067
  gatewayMethod: "terminal:getBufferDelta",
385051
- description: "Read a terminal tab output delta",
385068
+ description: "Read terminal output delta (?fromOffset=)",
385052
385069
  buildParams: (p, body) => {
385053
385070
  const b = body ?? {};
385054
- return { terminalId: p.id, fromOffset: b.fromOffset ?? 0 };
385071
+ const n2 = Number(b.fromOffset ?? 0);
385072
+ return { terminalId: p.id, fromOffset: Number.isFinite(n2) ? n2 : 0 };
385055
385073
  }
385056
385074
  },
385057
385075
  {
385058
385076
  method: "POST",
385059
385077
  path: "/api/v1/sessions/:id/chat",
385060
385078
  gatewayMethod: "agent:startTask",
385061
- description: "Send a message to the agent (blocking)",
385079
+ description: "Send a message (blocking until the run finishes \u2014 prefer chat-async)",
385062
385080
  buildParams: (p, body) => {
385063
385081
  const b = body ?? {};
385064
385082
  return { sessionId: p.id, userInput: b.message ?? b.userInput ?? "" };
@@ -385066,17 +385084,66 @@ function defaultRestRoutes() {
385066
385084
  },
385067
385085
  {
385068
385086
  method: "POST",
385069
- path: "/api/v1/rpc",
385070
- gatewayMethod: "",
385071
- description: "Escape hatch: dispatch any gateway method",
385072
- buildParams: (_p, body) => {
385087
+ path: "/api/v1/sessions/:id/chat-async",
385088
+ gatewayMethod: "agent:startTaskAsync",
385089
+ description: "Start an agent turn without waiting; subscribe to WS gateway:event for tokens",
385090
+ buildParams: (p, body) => {
385073
385091
  const b = body ?? {};
385074
- return { __rpcMethod: b.method ?? "", ...b.params ?? {} };
385092
+ return { sessionId: p.id, userInput: b.message ?? b.userInput ?? "" };
385075
385093
  }
385094
+ },
385095
+ {
385096
+ method: "POST",
385097
+ path: "/api/v1/rpc",
385098
+ gatewayMethod: "",
385099
+ description: "Escape hatch: any gateway method {method, params}"
385076
385100
  }
385077
385101
  ];
385078
385102
  }
385103
+ function buildOpenApiDocument() {
385104
+ const routes = defaultRestRoutes();
385105
+ const paths = {};
385106
+ for (const r of routes) {
385107
+ if (r.gatewayMethod === "__openapi") continue;
385108
+ const item = paths[r.path] || {};
385109
+ item[r.method.toLowerCase()] = {
385110
+ summary: r.description,
385111
+ operationId: `${r.method}_${r.path.replace(/[^a-zA-Z0-9]+/g, "_")}`,
385112
+ tags: ["rest"],
385113
+ ...r.method === "POST" ? {
385114
+ requestBody: {
385115
+ content: { "application/json": { schema: { type: "object" } } }
385116
+ }
385117
+ } : {},
385118
+ responses: { "200": { description: "OK" }, "401": { description: "Unauthorized" } }
385119
+ };
385120
+ paths[r.path] = item;
385121
+ }
385122
+ const rpcMethods = [...CORE_METHODS, DESCRIBE_METHOD].map((m2) => m2.name);
385123
+ return {
385124
+ openapi: "3.0.3",
385125
+ info: {
385126
+ title: "RTerm HTTP overlay",
385127
+ version: "1.0.0",
385128
+ description: `Thin REST on the same port as the WebSocket JSON-RPC gateway. Streaming agent/PTY traffic stays on ws://. POST /api/v1/rpc reaches every RPC method. Core RPC methods: ${rpcMethods.length}. Categories: ${METHOD_CATEGORIES.join(", ")}.`
385129
+ },
385130
+ paths,
385131
+ "x-gateway-rpc-methods": rpcMethods
385132
+ };
385133
+ }
385079
385134
  async function handleRestRequest(routes, dispatch, req) {
385135
+ const verb = req.method.toUpperCase();
385136
+ if (verb === "OPTIONS") {
385137
+ return {
385138
+ status: 204,
385139
+ body: "",
385140
+ headers: {
385141
+ "access-control-allow-origin": "*",
385142
+ "access-control-allow-methods": "GET, POST, OPTIONS",
385143
+ "access-control-allow-headers": "Authorization, Content-Type"
385144
+ }
385145
+ };
385146
+ }
385080
385147
  const match = matchRestRoute(routes, req.method, req.path);
385081
385148
  if (!match) {
385082
385149
  return {
@@ -385084,12 +385151,18 @@ async function handleRestRequest(routes, dispatch, req) {
385084
385151
  body: { error: "not_found", message: `No REST route for ${req.method} ${req.path}` }
385085
385152
  };
385086
385153
  }
385154
+ if (match.route.gatewayMethod === "__openapi") {
385155
+ return { status: 200, body: buildOpenApiDocument() };
385156
+ }
385087
385157
  let gatewayMethod = match.route.gatewayMethod;
385088
385158
  let params;
385089
385159
  if (gatewayMethod === "") {
385090
385160
  const raw = req.body ?? {};
385091
385161
  if (!raw.method) {
385092
- return { status: 400, body: { error: "bad_request", message: 'POST /api/v1/rpc needs {"method": "...", "params": {...}}' } };
385162
+ return {
385163
+ status: 400,
385164
+ body: { error: "bad_request", message: 'POST /api/v1/rpc needs {"method": "...", "params": {...}}' }
385165
+ };
385093
385166
  }
385094
385167
  gatewayMethod = raw.method;
385095
385168
  params = raw.params ?? {};
@@ -385105,6 +385178,52 @@ async function handleRestRequest(routes, dispatch, req) {
385105
385178
  return { status, body: { error: "gateway_error", message } };
385106
385179
  }
385107
385180
  }
385181
+ var JSON_HEADERS = { "content-type": "application/json; charset=utf-8" };
385182
+ function readJsonBody(req) {
385183
+ return new Promise((resolve2) => {
385184
+ let data = "";
385185
+ req.on?.("data", (d) => {
385186
+ data += String(d ?? "");
385187
+ if (data.length > 2e6) {
385188
+ resolve2({});
385189
+ }
385190
+ });
385191
+ req.on?.("end", () => {
385192
+ try {
385193
+ resolve2(data ? JSON.parse(data) : {});
385194
+ } catch {
385195
+ resolve2({});
385196
+ }
385197
+ });
385198
+ });
385199
+ }
385200
+ function makeRestCatchAllHandler(opts) {
385201
+ const routes = defaultRestRoutes();
385202
+ return async (req, res) => {
385203
+ const r = req;
385204
+ const s = res;
385205
+ try {
385206
+ if (!await opts.isAuthorized(r)) {
385207
+ s.writeHead?.(401, JSON_HEADERS);
385208
+ s.end?.(JSON.stringify({ error: "unauthorized" }));
385209
+ return;
385210
+ }
385211
+ const url2 = new URL(r.url ?? "/", "http://localhost");
385212
+ const body = r.method === "POST" || r.method === "PUT" ? await readJsonBody(r) : Object.fromEntries(url2.searchParams.entries());
385213
+ const result = await handleRestRequest(routes, opts.dispatch, {
385214
+ method: r.method ?? "GET",
385215
+ path: url2.pathname,
385216
+ body
385217
+ });
385218
+ s.writeHead?.(result.status, { ...JSON_HEADERS, ...result.headers ?? {} });
385219
+ if (result.body === "" || result.body === void 0) s.end?.();
385220
+ else s.end?.(typeof result.body === "string" ? result.body : JSON.stringify(result.body));
385221
+ } catch (e) {
385222
+ s.writeHead?.(500, JSON_HEADERS);
385223
+ s.end?.(JSON.stringify({ error: "internal", message: e instanceof Error ? e.message : String(e) }));
385224
+ }
385225
+ };
385226
+ }
385108
385227
 
385109
385228
  // ../../packages/backend/src/services/Gateway/gatewayRateLimit.ts
385110
385229
  var GatewayRateLimiter = class {
@@ -396172,51 +396291,8 @@ async function startGyBackend() {
396172
396291
  return await target.handleRequest(method, params);
396173
396292
  };
396174
396293
  const buildRestHttpRoutesSync = (opts) => {
396175
- const routes = defaultRestRoutes();
396176
- const staticRoutes = routes.filter((r) => !r.path.includes(":")).map((route) => ({
396177
- path: route.path,
396178
- handler: makeRestHandler(routes, opts)
396179
- }));
396180
- return staticRoutes;
396294
+ return [{ path: "/api/v1/*", handler: makeRestCatchAllHandler(opts) }];
396181
396295
  };
396182
- const makeRestHandler = (routes, opts) => {
396183
- return async (req, res) => {
396184
- const r = req;
396185
- const s = res;
396186
- try {
396187
- if (!await opts.isAuthorized(r)) {
396188
- s.writeHead?.(401, { "content-type": "application/json" });
396189
- s.end?.(JSON.stringify({ error: "unauthorized" }));
396190
- return;
396191
- }
396192
- const url2 = new URL(r.url ?? "/", "http://localhost");
396193
- const body = r.method === "POST" ? await readJsonBody(r) : Object.fromEntries(url2.searchParams.entries());
396194
- const result = await handleRestRequest(routes, opts.dispatch, {
396195
- method: r.method ?? "GET",
396196
- path: url2.pathname,
396197
- body
396198
- });
396199
- s.writeHead?.(result.status, { "content-type": "application/json" });
396200
- s.end?.(JSON.stringify(result.body));
396201
- } catch (e) {
396202
- s.writeHead?.(500, { "content-type": "application/json" });
396203
- s.end?.(JSON.stringify({ error: "internal", message: e instanceof Error ? e.message : String(e) }));
396204
- }
396205
- };
396206
- };
396207
- const readJsonBody = (req) => new Promise((resolve2) => {
396208
- let data = "";
396209
- req.on?.("data", (d) => {
396210
- data += String(d ?? "");
396211
- });
396212
- req.on?.("end", () => {
396213
- try {
396214
- resolve2(data ? JSON.parse(data) : {});
396215
- } catch {
396216
- resolve2({});
396217
- }
396218
- });
396219
- });
396220
396296
  const wsGatewayControlService = new WebSocketGatewayControlService({
396221
396297
  createAdapter: (host, port, ipFilter) => {
396222
396298
  const adapter = new WebSocketGatewayAdapter(gatewayService, {
@@ -396712,6 +396788,9 @@ async function startGyBackend() {
396712
396788
  console.log(
396713
396789
  `[gybackend] Live dashboard: http://${wsState.host}:${wsState.port}/dashboard`
396714
396790
  );
396791
+ console.log(
396792
+ `[gybackend] REST API: http://${wsState.host}:${wsState.port}/api/v1/health (OpenAPI /api/v1/openapi.json)`
396793
+ );
396715
396794
  } else {
396716
396795
  console.log("[gybackend] WebSocket RPC endpoint: disabled");
396717
396796
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuralos",
3
- "version": "3.7.6",
3
+ "version": "3.7.7",
4
4
  "description": "Standalone neuralOS backend (rterm-backend): AI-native terminal & agentic-AI operations platform.",
5
5
  "keywords": [
6
6
  "forward-deployed-engineer",