@guangnao/agent-os 1.0.0 → 1.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/dist/index.js CHANGED
@@ -1958,13 +1958,98 @@ async function lookup(sys, names, name) {
1958
1958
  }
1959
1959
  }
1960
1960
 
1961
+ // src/kernel/blob.ts
1962
+ var PAGE = 8 * 1024;
1963
+ var GREP_MAX = 50;
1964
+ var BlobStore = class {
1965
+ constructor(cap = 4 * 1024 * 1024) {
1966
+ this.cap = cap;
1967
+ }
1968
+ map = /* @__PURE__ */ new Map();
1969
+ // insertion order = LRU age (re-set on touch → newest)
1970
+ seq = 0;
1971
+ bytes = 0;
1972
+ // 4 MB total
1973
+ /** Store content; return a fresh handle. Evict the oldest blobs while over the char cap. */
1974
+ put(content) {
1975
+ const handle = `blob_${(++this.seq).toString(36)}`;
1976
+ this.map.set(handle, content);
1977
+ this.bytes += content.length;
1978
+ while (this.bytes > this.cap && this.map.size > 1) {
1979
+ const [old, v] = this.map.entries().next().value;
1980
+ this.map.delete(old);
1981
+ this.bytes -= v.length;
1982
+ }
1983
+ return handle;
1984
+ }
1985
+ touch(handle) {
1986
+ const v = this.map.get(handle);
1987
+ if (v === void 0) return void 0;
1988
+ this.map.delete(handle);
1989
+ this.map.set(handle, v);
1990
+ return v;
1991
+ }
1992
+ /** A character window of a blob (paged like read_file). */
1993
+ read(handle, offset = 0, limit = PAGE) {
1994
+ const v = this.touch(handle);
1995
+ if (v === void 0) return { error: `unknown blob handle: ${handle}` };
1996
+ const total = v.length;
1997
+ const start = Math.max(0, Math.floor(offset));
1998
+ if (start >= total) return { handle, content: "", total, note: `offset ${start} is at/past end (${total} chars)` };
1999
+ const slice = v.slice(start, start + Math.min(Math.max(1, Math.floor(limit)), PAGE));
2000
+ const end = start + slice.length;
2001
+ return end < total ? { handle, content: slice, truncated: true, total, offset: start, nextOffset: end, note: `chars ${start}\u2013${end} of ${total}; call blob_read with offset=${end} for more` } : { handle, content: slice, total, offset: start };
2002
+ }
2003
+ /** Search a blob for a JS regex; return a short SNIPPET (char offset + surrounding window) per match. Snippet
2004
+ * rather than whole line because a blob is often one long line (JSON.stringify escapes newlines) — a line view
2005
+ * would return the entire blob and just overflow again. The offset lets the model blob_read around it. */
2006
+ grep(handle, pattern, max = GREP_MAX) {
2007
+ const v = this.touch(handle);
2008
+ if (v === void 0) return { error: `unknown blob handle: ${handle}` };
2009
+ let re;
2010
+ try {
2011
+ re = new RegExp(pattern, "g");
2012
+ } catch {
2013
+ return { error: `invalid pattern: ${pattern}` };
2014
+ }
2015
+ const PAD = 80, matches = [];
2016
+ let m;
2017
+ while ((m = re.exec(v)) && matches.length < max) {
2018
+ const at = m.index, from = Math.max(0, at - PAD), to = Math.min(v.length, at + m[0].length + PAD);
2019
+ matches.push(`@${at}: ${from > 0 ? "\u2026" : ""}${v.slice(from, to)}${to < v.length ? "\u2026" : ""}`);
2020
+ if (re.lastIndex === at) re.lastIndex++;
2021
+ }
2022
+ return { handle, total: v.length, matches, ...matches.length >= max ? { truncated: true } : {} };
2023
+ }
2024
+ };
2025
+ var BLOB_TOOLS = [
2026
+ { name: "blob_read", description: "read a window of a large stored tool output by its blob handle (paged like read_file; use when a result said it was stored as blob_\u2026)", parameters: { type: "object", required: ["handle"], properties: {
2027
+ handle: { type: "string", description: "the blob_\u2026 handle from a truncated result" },
2028
+ offset: { type: "number", description: "char index to start at (default 0; use the previous result\u2019s nextOffset)" },
2029
+ limit: { type: "number", description: `max chars (default & cap ${PAGE})` }
2030
+ } } },
2031
+ { name: "blob_grep", description: "find lines matching a JS regex inside a large stored tool output, by blob handle (search without re-reading it all)", parameters: { type: "object", required: ["handle", "pattern"], properties: {
2032
+ handle: { type: "string", description: "the blob_\u2026 handle" },
2033
+ pattern: { type: "string", description: "JS regex, matched per line" }
2034
+ } } }
2035
+ ];
2036
+ var BLOB_NAMES = new Set(BLOB_TOOLS.map((t) => t.name));
2037
+ var isBlobTool = (name) => BLOB_NAMES.has(name);
2038
+ function execBlobTool(store, name, input) {
2039
+ const a = input ?? {};
2040
+ if (!a.handle) return { error: "handle is required" };
2041
+ if (name === "blob_read") return store.read(a.handle, a.offset, a.limit);
2042
+ if (name === "blob_grep") return a.pattern ? store.grep(a.handle, a.pattern) : { error: "pattern is required" };
2043
+ return { error: `not a blob tool: ${name}` };
2044
+ }
2045
+
1961
2046
  // src/userland/agent.ts
1962
2047
  var DEFAULT_SYSTEM = "You are a capable autonomous agent. Break the task into steps and keep going \u2014 investigate with tools, then act \u2014 until it is fully done; never stop at a plan or a partial result. Verify before answering, then reply concisely.";
1963
2048
  var HARD_MAX = 384;
1964
2049
  var MAX_TOOL_CHARS = 8e3;
1965
2050
  var isMessage = (b) => !!b && typeof b.role === "string";
1966
- var capResult = (s) => s.length > MAX_TOOL_CHARS ? s.slice(0, MAX_TOOL_CHARS) + `
1967
- \u2026[truncated ${s.length - MAX_TOOL_CHARS} chars]` : s;
2051
+ var capResult = (s, blobs) => s.length <= MAX_TOOL_CHARS ? s : s.slice(0, MAX_TOOL_CHARS) + `
2052
+ \u2026[+${s.length - MAX_TOOL_CHARS} chars \u2014 full result stored as ${blobs.put(s)}; call blob_read(handle, offset=${MAX_TOOL_CHARS}) or blob_grep(handle, pattern) for the rest]`;
1968
2053
  function orderForCache(segs) {
1969
2054
  const lead = [], sums = [], rest = [];
1970
2055
  for (const s of segs) (s.pinned && !s.summary ? lead : s.summary ? sums : rest).push(s);
@@ -1992,7 +2077,7 @@ function unproductive(out) {
1992
2077
  const st = o["status"];
1993
2078
  return typeof st === "number" && (st < 200 || st >= 300);
1994
2079
  }
1995
- async function runTurn(sys, llm, tools, spec) {
2080
+ async function runTurn(sys, llm, tools, spec, blobs) {
1996
2081
  const ceil = Math.max(1, Math.min(spec.maxSteps ?? HARD_MAX, HARD_MAX));
1997
2082
  const base = Math.max(1, spec.baseSteps ?? 16);
1998
2083
  const stallMax = Math.max(1, spec.stallLimit ?? 3);
@@ -2015,6 +2100,7 @@ async function runTurn(sys, llm, tools, spec) {
2015
2100
  }
2016
2101
  const messages = promptMessages();
2017
2102
  let offer = noTools ? void 0 : spec.tools;
2103
+ if (offer && tools) offer = [...offer, ...BLOB_TOOLS];
2018
2104
  if (steer && offer && del) offer = offer.filter((t) => !del.withhold.includes(t.name));
2019
2105
  const reply = await sys.read(llm, { messages, tools: offer, maxTokens: spec.maxTokens });
2020
2106
  for (const s of sys.workingSet()) {
@@ -2035,13 +2121,15 @@ async function runTurn(sys, llm, tools, spec) {
2035
2121
  }
2036
2122
  }
2037
2123
  if (fresh.length) {
2038
- const outs = await sys.readAll(tools, fresh.map((f) => f.req));
2039
- fresh.forEach((f, j) => cache.set(f.key, outs[j]));
2124
+ const viaDevice = fresh.filter((f) => !isBlobTool(f.req.tool));
2125
+ const devOuts = viaDevice.length ? await sys.readAll(tools, viaDevice.map((f) => f.req)) : [];
2126
+ let di = 0;
2127
+ for (const f of fresh) cache.set(f.key, isBlobTool(f.req.tool) ? execBlobTool(blobs, f.req.tool, f.req.input) : devOuts[di++]);
2040
2128
  }
2041
2129
  let progressed = false;
2042
2130
  for (let i = 0; i < calls.length; i++) {
2043
2131
  const out = cache.get(keys[i]);
2044
- await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out)) });
2132
+ await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out), blobs) });
2045
2133
  if (ran.has(keys[i]) && !unproductive(out)) progressed = true;
2046
2134
  }
2047
2135
  if (del) {
@@ -2092,7 +2180,7 @@ function reactAgent(spec) {
2092
2180
  const { llm, tools, parent } = await openDevices(sys);
2093
2181
  await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
2094
2182
  await sys.remember({ role: "user", content: spec.task ?? "" }, { pin: true });
2095
- const r = await runTurn(sys, llm, tools, spec);
2183
+ const r = await runTurn(sys, llm, tools, spec, new BlobStore());
2096
2184
  if (parent) await sys.send(parent, { $: "result", answer: r.answer, steps: r.steps });
2097
2185
  } };
2098
2186
  }
@@ -2110,13 +2198,14 @@ function conversationAgent(spec = {}) {
2110
2198
  } else {
2111
2199
  await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
2112
2200
  }
2201
+ const blobs = new BlobStore();
2113
2202
  for (; ; ) {
2114
2203
  const msg = await sys.recv();
2115
2204
  const userTurn = { role: "user", content: msg.body.task, ...msg.body.images?.length ? { images: msg.body.images } : {} };
2116
2205
  await sys.remember(userTurn);
2117
2206
  let r;
2118
2207
  try {
2119
- r = await runTurn(sys, llm, tools, spec);
2208
+ r = await runTurn(sys, llm, tools, spec, blobs);
2120
2209
  } catch (e) {
2121
2210
  r = { answer: "", steps: 0, error: e instanceof Error ? e.message : String(e) };
2122
2211
  }
@@ -0,0 +1,21 @@
1
+ import type { ToolSchema } from '../drivers/llm';
2
+ export declare class BlobStore {
3
+ private readonly cap;
4
+ private readonly map;
5
+ private seq;
6
+ private bytes;
7
+ constructor(cap?: number);
8
+ /** Store content; return a fresh handle. Evict the oldest blobs while over the char cap. */
9
+ put(content: string): string;
10
+ private touch;
11
+ /** A character window of a blob (paged like read_file). */
12
+ read(handle: string, offset?: number, limit?: number): unknown;
13
+ /** Search a blob for a JS regex; return a short SNIPPET (char offset + surrounding window) per match. Snippet
14
+ * rather than whole line because a blob is often one long line (JSON.stringify escapes newlines) — a line view
15
+ * would return the entire blob and just overflow again. The offset lets the model blob_read around it. */
16
+ grep(handle: string, pattern: string, max?: number): unknown;
17
+ }
18
+ export declare const BLOB_TOOLS: readonly ToolSchema[];
19
+ export declare const isBlobTool: (name: string) => boolean;
20
+ /** Run a blob_* tool against the store — invoked directly by the agent loop, not the tools device. */
21
+ export declare function execBlobTool(store: BlobStore, name: string, input: unknown): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guangnao/agent-os",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AgentOS Kernel — a lightweight, type-safe agent operating system in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",