@holoscript/holoscript-agent 2.0.7 → 2.1.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.
@@ -6,6 +6,8 @@ import { join as join2 } from "path";
6
6
  import { embedAcrossFleet, cosineSimilarity } from "@holoscript/llm-provider";
7
7
 
8
8
  // src/holomesh-client.ts
9
+ import { readFile, appendFile, mkdir } from "fs/promises";
10
+ import { dirname } from "path";
9
11
  var HolomeshClient = class {
10
12
  constructor(opts) {
11
13
  this.apiBase = opts.apiBase.replace(/\/$/, "");
@@ -13,6 +15,7 @@ var HolomeshClient = class {
13
15
  this.teamId = opts.teamId;
14
16
  this.fetchImpl = opts.fetchImpl ?? fetch;
15
17
  this.signer = opts.signer;
18
+ this.localKnowledgePath = opts.localKnowledgePath;
16
19
  }
17
20
  /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
18
21
  async signBody(body) {
@@ -141,6 +144,14 @@ var HolomeshClient = class {
141
144
  * client-side. Returns [] on any failure.
142
145
  */
143
146
  async queryPrivateKnowledge() {
147
+ if (this.localKnowledgePath) {
148
+ try {
149
+ const raw = await readFile(this.localKnowledgePath, "utf8");
150
+ return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
151
+ } catch {
152
+ return [];
153
+ }
154
+ }
144
155
  try {
145
156
  const data = await this.req("GET", `/knowledge/private`);
146
157
  return data.entries ?? [];
@@ -178,6 +189,23 @@ var HolomeshClient = class {
178
189
  }
179
190
  async writePrivateKnowledge(entries) {
180
191
  if (!entries.length) return false;
192
+ if (this.localKnowledgePath) {
193
+ try {
194
+ await mkdir(dirname(this.localKnowledgePath), { recursive: true });
195
+ const lines = entries.map((e) => JSON.stringify({
196
+ id: `local.${Date.now()}.${Math.random().toString(36).slice(2, 6)}`,
197
+ content: e.content,
198
+ type: e.type ?? "task-outcome",
199
+ tags: e.tags ?? [],
200
+ title: e.title,
201
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
202
+ })).join("\n") + "\n";
203
+ await appendFile(this.localKnowledgePath, lines, "utf8");
204
+ return true;
205
+ } catch {
206
+ return false;
207
+ }
208
+ }
181
209
  try {
182
210
  await this.req("POST", `/knowledge/private`, { entries });
183
211
  return true;
@@ -285,8 +313,8 @@ function buildCaelRecord(input) {
285
313
  }
286
314
 
287
315
  // src/tools.ts
288
- import { readFile, writeFile, readdir, mkdir, stat } from "fs/promises";
289
- import { resolve, dirname, delimiter, isAbsolute, sep } from "path";
316
+ import { readFile as readFile2, writeFile, readdir, mkdir as mkdir2, stat } from "fs/promises";
317
+ import { resolve, dirname as dirname2, delimiter, isAbsolute, sep } from "path";
290
318
  import { spawn } from "child_process";
291
319
  import { createHash as createHash2 } from "crypto";
292
320
  var FLEET_READ_ROOTS = [
@@ -577,7 +605,7 @@ async function runTool(use, opts = {}) {
577
605
  const path = use.input.path;
578
606
  const denied = checkReadAllowed(path);
579
607
  if (denied) return errResult(use.id, denied);
580
- const text = await readFile(path, "utf8");
608
+ const text = await readFile2(path, "utf8");
581
609
  const truncated = text.length > 2e5 ? text.slice(0, 2e5) + `
582
610
  \u2026[truncated, full file is ${text.length} bytes]` : text;
583
611
  return okResult(use.id, truncated);
@@ -595,7 +623,7 @@ async function runTool(use, opts = {}) {
595
623
  const content = use.input.content;
596
624
  const denied = checkWriteAllowed(path);
597
625
  if (denied) return errResult(use.id, denied);
598
- await mkdir(dirname(path), { recursive: true });
626
+ await mkdir2(dirname2(path), { recursive: true });
599
627
  await writeFile(path, content, "utf8");
600
628
  const s = await stat(path);
601
629
  return okResult(use.id, `wrote ${s.size} bytes to ${path}`);
@@ -710,7 +738,7 @@ ${truncated}`);
710
738
  const outPath = resolve(ALLOWED_WRITE_ROOTS[0], `hardware-receipt-${ts}.json`);
711
739
  const denied = checkWriteAllowed(outPath);
712
740
  if (denied) return errResult(use.id, `Cannot write receipt: ${denied}`);
713
- await mkdir(dirname(outPath), { recursive: true });
741
+ await mkdir2(dirname2(outPath), { recursive: true });
714
742
  await writeFile(outPath, JSON.stringify(sealed, null, 2), "utf8");
715
743
  return okResult(
716
744
  use.id,
@@ -723,7 +751,7 @@ ${truncated}`);
723
751
  const newStr = use.input.new;
724
752
  const denied = checkWriteAllowed(path);
725
753
  if (denied) return errResult(use.id, denied);
726
- const text = await readFile(path, "utf8");
754
+ const text = await readFile2(path, "utf8");
727
755
  const count = text.split(oldStr).length - 1;
728
756
  if (count === 0) return errResult(use.id, `str_replace: "old" string not found in ${path} \u2014 0 occurrences`);
729
757
  if (count > 1) return errResult(use.id, `str_replace: "old" string is ambiguous in ${path} \u2014 ${count} occurrences; add more surrounding context`);
@@ -1158,6 +1186,37 @@ var AgentRunner = class {
1158
1186
  finalText = resp.content;
1159
1187
  break;
1160
1188
  }
1189
+ if (productiveCallCount === 0 && toolsCalled.size > 0 && iters < MAX_TOOL_ITERS) {
1190
+ iters++;
1191
+ messages.push({
1192
+ role: "user",
1193
+ content: "You gathered data but did not write the task deliverable. Call write_file NOW with the exact output path from the task description. Embed all data you gathered into the write_file content field. Do NOT output text \u2014 your only valid response is a write_file tool call."
1194
+ });
1195
+ const reResp = await provider.complete(
1196
+ { messages, maxTokens: 8192, temperature: 0.4, tools: activeTools },
1197
+ identity.llmModel
1198
+ );
1199
+ aggUsage = {
1200
+ promptTokens: aggUsage.promptTokens + reResp.usage.promptTokens,
1201
+ completionTokens: aggUsage.completionTokens + reResp.usage.completionTokens,
1202
+ totalTokens: aggUsage.totalTokens + reResp.usage.totalTokens
1203
+ };
1204
+ if (reResp.finishReason === "tool_use" && reResp.toolUses && reResp.toolUses.length > 0) {
1205
+ log({ ev: "reprompt-tool-call", taskId: target.id, iter: iters, tools: reResp.toolUses.map((t) => t.name) });
1206
+ const reProd = summarizeToolProductivity(reResp.toolUses);
1207
+ for (const n of reProd.names) toolsCalled.add(n);
1208
+ productiveCallCount += reProd.productiveCount;
1209
+ messages.push({ role: "assistant", content: reResp.assistantBlocks ?? [] });
1210
+ const reResults = await Promise.all(
1211
+ reResp.toolUses.map(
1212
+ (u) => runTool(u, { signReceipt: this.opts.signReceipt, addTask: (tasks2) => mesh.addTasks(tasks2) })
1213
+ )
1214
+ );
1215
+ messages.push({ role: "user", content: reResults });
1216
+ }
1217
+ finalText = reResp.content;
1218
+ lastResponse = reResp;
1219
+ }
1161
1220
  const durationMs = Date.now() - start;
1162
1221
  if (productiveCallCount === 0) {
1163
1222
  log({
@@ -1433,7 +1492,7 @@ function jitter(base) {
1433
1492
 
1434
1493
  // src/cost-guard.ts
1435
1494
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
1436
- import { dirname as dirname2 } from "path";
1495
+ import { dirname as dirname3 } from "path";
1437
1496
  var ANTHROPIC_PRICING_USD_PER_MTOK = {
1438
1497
  "claude-opus-4-8": { input: 10, output: 50 },
1439
1498
  // 3× cheaper than 4.7 on total cost; A-020 2026-06-08
@@ -1503,7 +1562,7 @@ var CostGuard = class {
1503
1562
  return { date: todayUtc(), spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };
1504
1563
  }
1505
1564
  persist() {
1506
- mkdirSync(dirname2(this.statePath), { recursive: true });
1565
+ mkdirSync(dirname3(this.statePath), { recursive: true });
1507
1566
  writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), "utf8");
1508
1567
  }
1509
1568
  };
@@ -1512,9 +1571,9 @@ function todayUtc() {
1512
1571
  }
1513
1572
 
1514
1573
  // src/brain.ts
1515
- import { readFile as readFile2 } from "fs/promises";
1574
+ import { readFile as readFile3 } from "fs/promises";
1516
1575
  async function loadBrain(brainPath, scopeTier = "warm") {
1517
- const raw = await readFile2(brainPath, "utf8");
1576
+ const raw = await readFile3(brainPath, "utf8");
1518
1577
  const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);
1519
1578
  const systemPrompt = extractSystemPromptPreamble(raw);
1520
1579
  return {
@@ -1660,7 +1719,7 @@ function listField(block, key) {
1660
1719
 
1661
1720
  // src/commit-hook.ts
1662
1721
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1663
- import { dirname as dirname3, join, resolve as resolve2 } from "path";
1722
+ import { dirname as dirname4, join, resolve as resolve2 } from "path";
1664
1723
  import { spawnSync } from "child_process";
1665
1724
  var SAFE_HANDLE = /^[a-z0-9_-]{1,64}$/i;
1666
1725
  function makeCommitHook(opts) {
@@ -1680,7 +1739,7 @@ function makeCommitHook(opts) {
1680
1739
  const safeTaskId = task.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
1681
1740
  const fileName = `${date}_${safeTaskId}_${identity.handle}.md`;
1682
1741
  const filePath = join(outputDir, fileName);
1683
- mkdirSync2(dirname3(filePath), { recursive: true });
1742
+ mkdirSync2(dirname4(filePath), { recursive: true });
1684
1743
  writeFileSync2(filePath, renderMemo(result, task, identity, date), "utf8");
1685
1744
  const relPath = relativeTo(cwd, filePath);
1686
1745
  const addRes = spawn2("git", ["add", relPath], { cwd, encoding: "utf8" });
@@ -1764,12 +1823,12 @@ function relativeTo(base, target) {
1764
1823
 
1765
1824
  // src/audit-log.ts
1766
1825
  import { mkdirSync as mkdirSync3, appendFileSync, readFileSync as readFileSync2, existsSync as existsSync2, statSync, renameSync } from "fs";
1767
- import { dirname as dirname4 } from "path";
1826
+ import { dirname as dirname5 } from "path";
1768
1827
  var AuditLog = class {
1769
1828
  constructor(opts) {
1770
1829
  this.logPath = opts.logPath;
1771
1830
  this.maxBytes = opts.maxBytes ?? 50 * 1024 * 1024;
1772
- mkdirSync3(dirname4(this.logPath), { recursive: true });
1831
+ mkdirSync3(dirname5(this.logPath), { recursive: true });
1773
1832
  }
1774
1833
  record(event) {
1775
1834
  this.rotateIfFull();