@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.
@@ -36,6 +36,14 @@ interface HolomeshClientOptions {
36
36
  fetchImpl?: typeof fetch;
37
37
  /** EIP-191 signing function. When present, used on strict endpoints like joinTeam(). */
38
38
  signer?: RequestSigner;
39
+ /**
40
+ * Local JSONL path for private knowledge (sovereign edge store).
41
+ * When set, writePrivateKnowledge appends to this file and queryPrivateKnowledge
42
+ * reads from it instead of routing through the remote orchestrator.
43
+ * Bypasses the mcp-orchestrator /knowledge/sync 401 gap for edge nodes.
44
+ * Set via HOLOSCRIPT_AGENT_LOCAL_KNOWLEDGE_PATH env var (index.ts).
45
+ */
46
+ localKnowledgePath?: string;
39
47
  }
40
48
  interface TeamMessage {
41
49
  id: string;
@@ -59,6 +67,7 @@ declare class HolomeshClient {
59
67
  private readonly teamId;
60
68
  private readonly fetchImpl;
61
69
  private readonly signer?;
70
+ private readonly localKnowledgePath?;
62
71
  constructor(opts: HolomeshClientOptions);
63
72
  /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
64
73
  private signBody;
@@ -1,4 +1,6 @@
1
1
  // src/holomesh-client.ts
2
+ import { readFile, appendFile, mkdir } from "fs/promises";
3
+ import { dirname } from "path";
2
4
  var HolomeshClient = class {
3
5
  constructor(opts) {
4
6
  this.apiBase = opts.apiBase.replace(/\/$/, "");
@@ -6,6 +8,7 @@ var HolomeshClient = class {
6
8
  this.teamId = opts.teamId;
7
9
  this.fetchImpl = opts.fetchImpl ?? fetch;
8
10
  this.signer = opts.signer;
11
+ this.localKnowledgePath = opts.localKnowledgePath;
9
12
  }
10
13
  /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
11
14
  async signBody(body) {
@@ -134,6 +137,14 @@ var HolomeshClient = class {
134
137
  * client-side. Returns [] on any failure.
135
138
  */
136
139
  async queryPrivateKnowledge() {
140
+ if (this.localKnowledgePath) {
141
+ try {
142
+ const raw = await readFile(this.localKnowledgePath, "utf8");
143
+ return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
144
+ } catch {
145
+ return [];
146
+ }
147
+ }
137
148
  try {
138
149
  const data = await this.req("GET", `/knowledge/private`);
139
150
  return data.entries ?? [];
@@ -171,6 +182,23 @@ var HolomeshClient = class {
171
182
  }
172
183
  async writePrivateKnowledge(entries) {
173
184
  if (!entries.length) return false;
185
+ if (this.localKnowledgePath) {
186
+ try {
187
+ await mkdir(dirname(this.localKnowledgePath), { recursive: true });
188
+ const lines = entries.map((e) => JSON.stringify({
189
+ id: `local.${Date.now()}.${Math.random().toString(36).slice(2, 6)}`,
190
+ content: e.content,
191
+ type: e.type ?? "task-outcome",
192
+ tags: e.tags ?? [],
193
+ title: e.title,
194
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
195
+ })).join("\n") + "\n";
196
+ await appendFile(this.localKnowledgePath, lines, "utf8");
197
+ return true;
198
+ } catch {
199
+ return false;
200
+ }
201
+ }
174
202
  try {
175
203
  await this.req("POST", `/knowledge/private`, { entries });
176
204
  return true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/holomesh-client.ts"],"sourcesContent":["import type { BoardTask } from './types.js';\nimport type { CaelAuditRecord } from './cael-builder.js';\n\n/** Wraps a request body in a signed envelope for strict-mode endpoints (e.g. /team/:id/join). */\nexport type RequestSigner = (\n body: Record<string, unknown>\n) => Promise<Record<string, unknown>>;\n\nexport interface HolomeshClientOptions {\n apiBase: string;\n bearer: string;\n teamId: string;\n fetchImpl?: typeof fetch;\n /** EIP-191 signing function. When present, used on strict endpoints like joinTeam(). */\n signer?: RequestSigner;\n}\n\nexport interface TeamMessage {\n id: string;\n fromAgentId: string;\n fromAgentName: string;\n content: string;\n messageType: string;\n createdAt: string;\n}\n\n/** Minimal knowledge-entry shape returned by the mesh knowledge endpoints. */\nexport interface KnowledgeEntry {\n id: string;\n content: string;\n domain?: string;\n type?: string;\n createdAt?: string;\n}\n\nexport class HolomeshClient {\n private readonly apiBase: string;\n private readonly bearer: string;\n private readonly teamId: string;\n private readonly fetchImpl: typeof fetch;\n private readonly signer?: RequestSigner;\n\n constructor(opts: HolomeshClientOptions) {\n this.apiBase = opts.apiBase.replace(/\\/$/, '');\n this.bearer = opts.bearer;\n this.teamId = opts.teamId;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.signer = opts.signer;\n }\n\n /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */\n private async signBody(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n return this.signer ? await this.signer(body) : body;\n }\n\n async heartbeat(payload: { agentName: string; surface: string; capabilityTags?: string[] }): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/presence`, await this.signBody(payload as Record<string, unknown>));\n }\n\n async getOpenTasks(): Promise<BoardTask[]> {\n const data = await this.req<{ tasks?: BoardTask[]; open?: BoardTask[] }>(\n 'GET',\n `/team/${this.teamId}/board`\n );\n return data.tasks ?? data.open ?? [];\n }\n\n async claim(taskId: string): Promise<BoardTask> {\n return this.req<BoardTask>('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'claim' }));\n }\n\n async joinTeam(): Promise<{ success: boolean; role?: string; members?: number }> {\n return this.req<{ success: boolean; role?: string; members?: number }>(\n 'POST',\n `/team/${this.teamId}/join`,\n await this.signBody({})\n );\n }\n\n async sendMessageOnTask(taskId: string, body: string): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, await this.signBody({\n to: 'team',\n subject: `task:${taskId}`,\n content: body,\n }));\n }\n\n async markDone(taskId: string, summary: string, commitHash?: string): Promise<void> {\n await this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({\n action: 'done',\n summary,\n // verification_evidence required by server before task can be closed.\n verification_evidence: summary,\n // Exclude commitHash when undefined — JSON.stringify drops undefined but\n // canonicalizeSigning preserves it as the literal string \"undefined\",\n // causing a signature-mismatch vs what the server sees after JSON.parse.\n ...(commitHash !== undefined ? { commitHash } : {}),\n }));\n }\n\n // POST CAEL audit records for this agent. Server validator at\n // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires\n // bearer == handle owner OR founder; the per-surface x402 bearer is the\n // handle owner so this resolves correctly. Records that fail shape\n // validation (layer_hashes != 7 elements, missing tick_iso/operation/\n // fnv1a_chain) are silently dropped server-side, not rejected as a batch.\n async postAuditRecords(\n handle: string,\n records: CaelAuditRecord[]\n ): Promise<{ appended: number; rejected: number }> {\n // Audit endpoint uses bearer-only auth — no signed envelope wrapper.\n return this.req<{ appended: number; rejected: number }>(\n 'POST',\n `/agent/${encodeURIComponent(handle)}/audit`,\n { records } as unknown as Record<string, unknown>\n );\n }\n\n async whoAmI(): Promise<{ agentId: string; surface: string; wallet?: string }> {\n // GET /api/holomesh/me returns { agentId, name, wallet, isFounder, teamId, teams, permissions }\n // (see packages/mcp-server/src/holomesh/routes/core-routes.ts §/me handler).\n // It does NOT return a `surface` field — derive it from the seat name on the\n // client side. Seat naming convention (set by the provisioning admin path):\n // claudecode-claude-x402 → claude-code\n // cursor-claude-x402 → claude-cursor\n // gemini-antigravity → gemini-antigravity\n // copilot-vscode → copilot-vscode\n // Founder → unknown (shared key, no surface attribution)\n const raw = await this.req<{\n agentId: string;\n name?: string;\n wallet?: string;\n }>('GET', '/me');\n return {\n agentId: raw.agentId,\n surface: deriveSurface(raw.name),\n wallet: raw.wallet,\n };\n }\n\n // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────\n\n /** Read recent team messages. */\n async getTeamMessages(limit = 20): Promise<TeamMessage[]> {\n const data = await this.req<{ messages?: TeamMessage[]; success?: boolean }>(\n 'GET',\n `/team/${this.teamId}/messages?limit=${limit}`\n );\n return data.messages ?? [];\n }\n\n /** Post a message to the team feed. */\n async sendTeamMessage(content: string, messageType = 'text'): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));\n }\n\n // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────\n\n /** Switch team mode. Requires owner or founder role. */\n async setTeamMode(mode: string, reason?: string): Promise<{ mode: string; unchanged?: boolean }> {\n return this.req('POST', `/team/${this.teamId}/mode`, await this.signBody({ mode, reason } as Record<string, unknown>));\n }\n\n /** Update room preferences. Requires config:write permission. */\n async patchRoomPrefs(prefs: { communicationStyle?: string; objective?: string }): Promise<{\n communicationStyle: string;\n objective: string;\n }> {\n return this.req('PATCH', `/team/${this.teamId}/room`, await this.signBody(prefs as Record<string, unknown>));\n }\n\n /** Update a board task. */\n async updateTask(\n taskId: string,\n updates: {\n title?: string;\n description?: string;\n priority?: number;\n tags?: string[];\n }\n ): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'update', ...updates } as Record<string, unknown>));\n }\n\n /** Delete a board task. */\n async deleteTask(taskId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'delete' }));\n }\n\n /** Delegate a board task to another agent. */\n async delegateTask(taskId: string, toAgentId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'delegate', toAgentId }));\n }\n\n /** Add new tasks to the board (used by the delegate_task tool to spawn sub-work). */\n async addTasks(\n tasks: Array<{ title: string; description?: string; priority?: number; source?: string; role?: string; tags?: string[] }>\n ): Promise<{ added: number }> {\n const result = await this.req<{ added?: number }>('POST', `/team/${this.teamId}/board`, await this.signBody({ tasks } as Record<string, unknown>));\n return { added: result.added ?? tasks.length };\n }\n\n // ── Cognitive-verb knowledge surface (Phase 2.2 — recall / rag_query) ────────\n\n /**\n * Query the TEAM knowledge base (the `rag_query` cognitive verb). Bearer-only\n * GET; `q` is the server-side search filter. Returns [] on any failure so a\n * retrieval miss never breaks a tick.\n */\n async queryTeamKnowledge(query: string, limit = 5): Promise<KnowledgeEntry[]> {\n const qs = new URLSearchParams({ q: query, limit: String(limit) }).toString();\n try {\n const data = await this.req<{ entries?: KnowledgeEntry[] }>(\n 'GET',\n `/team/${this.teamId}/knowledge?${qs}`\n );\n return data.entries ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * Query this agent's PRIVATE workspace knowledge (the `recall` cognitive verb).\n * The endpoint has no server-side search param, so the caller filters by query\n * client-side. Returns [] on any failure.\n */\n async queryPrivateKnowledge(): Promise<KnowledgeEntry[]> {\n try {\n const data = await this.req<{ entries?: KnowledgeEntry[] }>('GET', `/knowledge/private`);\n return data.entries ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * Persist facts to this agent's PRIVATE workspace — the WRITE side of `recall`\n * (POST /knowledge/private). The server stamps id / workspaceId (`private:<wallet>`)\n * / author / timestamps; the caller supplies `content` (+ optional type/tags/title).\n * This is what gives `recall` something to recall: without it the private store\n * stays empty and every `recall` returns [] (the W.752 loop-gap). Best-effort —\n * returns false on any failure so a write miss never breaks a tick (the task is\n * already done by the time this runs).\n */\n /**\n * Query the server-side in-process codebase GraphRAG index via the mesh bearer-gated\n * route (POST /api/holomesh/codebase/search). Called by the `rag_query` cognitive verb\n * (Phase 2.3 W.753) so edge agents can reach HoloEmbed semantic search without a direct\n * Absorb-service dep. Returns [] (not an error) when the graph isn't loaded yet — the\n * caller falls back to team-knowledge search.\n *\n * @param query Natural-language search string.\n * @param topK Max results to return (1–20, server-capped).\n */\n async queryCodebase(query: string, topK = 8): Promise<Array<{ name: string; file: string; line?: number; type: string; score: number; signature?: string | null }>> {\n try {\n const data = await this.req<{\n success?: boolean;\n result?: {\n results?: Array<{ name: string; file: string; line?: number; type: string; score: number; signature?: string | null }>;\n error?: string;\n };\n error?: string;\n }>('POST', `/codebase/search`, { query, topK });\n if (data.error || data.result?.error) return [];\n return data.result?.results ?? [];\n } catch {\n return [];\n }\n }\n\n async writePrivateKnowledge(\n entries: Array<{ content: string; type?: string; tags?: string[]; title?: string }>\n ): Promise<boolean> {\n if (!entries.length) return false;\n try {\n await this.req('POST', `/knowledge/private`, { entries });\n return true;\n } catch {\n return false;\n }\n }\n\n private async req<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.apiBase}${path}`;\n // HoloMesh REST auth: server (packages/mcp-server/src/holomesh/auth-utils.ts\n // resolveRequestingAgent) accepts EITHER `Authorization: Bearer <token>`\n // (HTTP-standard, used here) OR `x-mcp-api-key: <token>` (orchestrator\n // convention). Both resolve through the same key-registry / agent-store /\n // env-fallback chain. Bearer is preferred for new code (W.087 vertex B,\n // task_1777073616424_klls).\n const res = await this.fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.bearer}`,\n 'content-type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!res.ok) {\n const txt = await res.text().catch(() => '');\n throw new Error(`HoloMesh ${method} ${path} ${res.status}: ${txt.slice(0, 300)}`);\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n\n/**\n * Derive a surface tag from a seat name returned by /me. Mirrors the surface\n * detection in scripts/probe-surface-bearers.mjs and hooks/lib/holomesh-env.mjs\n * so a single agent's surface attribution is consistent across read and write\n * paths. Returns 'unknown' when the seat name doesn't encode a surface\n * (e.g. shared-key resolution to \"Founder\").\n */\nexport function deriveSurface(seatName: string | undefined): string {\n if (!seatName) return 'unknown';\n const n = seatName.toLowerCase();\n if (n.startsWith('claudecode')) return 'claude-code';\n if (n.startsWith('cursor')) return 'claude-cursor';\n if (n.startsWith('claudedesktop')) return 'claude-desktop';\n if (n.startsWith('vscode-claude') || n.startsWith('claude-vscode')) return 'claude-vscode';\n if (n.startsWith('gemini')) return 'gemini-antigravity';\n if (n.startsWith('copilot')) return 'copilot-vscode';\n return 'unknown';\n}\n\nexport function pickClaimableTask(\n tasks: BoardTask[],\n brainCapabilityTags: string[]\n): BoardTask | undefined {\n const wanted = new Set(brainCapabilityTags.map((t) => t.toLowerCase()));\n const open = tasks.filter((t) => t.status === 'open' && !t.claimedBy);\n const scored = open\n .map((t) => ({ task: t, score: scoreTask(t, wanted) }))\n .filter((s) => s.score > 0)\n .sort((a, b) => b.score - a.score || priority(a.task) - priority(b.task));\n return scored[0]?.task;\n}\n\nfunction scoreTask(task: BoardTask, wanted: Set<string>): number {\n const tags = (task.tags ?? []).map((t) => t.toLowerCase());\n const text = `${task.title} ${task.description ?? ''}`.toLowerCase();\n let score = 0;\n for (const tag of tags) if (wanted.has(tag)) score += 2;\n for (const w of wanted) if (text.includes(w)) score += 1;\n return score;\n}\n\nfunction priority(t: BoardTask): number {\n if (typeof t.priority === 'number') return t.priority;\n const map: Record<string, number> = { critical: 1, high: 2, medium: 4, low: 6 };\n return map[String(t.priority).toLowerCase()] ?? 5;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/holomesh-client.ts"],"sourcesContent":["import { readFile, appendFile, mkdir } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { BoardTask } from './types.js';\nimport type { CaelAuditRecord } from './cael-builder.js';\n\n/** Wraps a request body in a signed envelope for strict-mode endpoints (e.g. /team/:id/join). */\nexport type RequestSigner = (\n body: Record<string, unknown>\n) => Promise<Record<string, unknown>>;\n\nexport interface HolomeshClientOptions {\n apiBase: string;\n bearer: string;\n teamId: string;\n fetchImpl?: typeof fetch;\n /** EIP-191 signing function. When present, used on strict endpoints like joinTeam(). */\n signer?: RequestSigner;\n /**\n * Local JSONL path for private knowledge (sovereign edge store).\n * When set, writePrivateKnowledge appends to this file and queryPrivateKnowledge\n * reads from it instead of routing through the remote orchestrator.\n * Bypasses the mcp-orchestrator /knowledge/sync 401 gap for edge nodes.\n * Set via HOLOSCRIPT_AGENT_LOCAL_KNOWLEDGE_PATH env var (index.ts).\n */\n localKnowledgePath?: string;\n}\n\nexport interface TeamMessage {\n id: string;\n fromAgentId: string;\n fromAgentName: string;\n content: string;\n messageType: string;\n createdAt: string;\n}\n\n/** Minimal knowledge-entry shape returned by the mesh knowledge endpoints. */\nexport interface KnowledgeEntry {\n id: string;\n content: string;\n domain?: string;\n type?: string;\n createdAt?: string;\n}\n\nexport class HolomeshClient {\n private readonly apiBase: string;\n private readonly bearer: string;\n private readonly teamId: string;\n private readonly fetchImpl: typeof fetch;\n private readonly signer?: RequestSigner;\n private readonly localKnowledgePath?: string;\n\n constructor(opts: HolomeshClientOptions) {\n this.apiBase = opts.apiBase.replace(/\\/$/, '');\n this.bearer = opts.bearer;\n this.teamId = opts.teamId;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.signer = opts.signer;\n this.localKnowledgePath = opts.localKnowledgePath;\n }\n\n /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */\n private async signBody(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n return this.signer ? await this.signer(body) : body;\n }\n\n async heartbeat(payload: { agentName: string; surface: string; capabilityTags?: string[] }): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/presence`, await this.signBody(payload as Record<string, unknown>));\n }\n\n async getOpenTasks(): Promise<BoardTask[]> {\n const data = await this.req<{ tasks?: BoardTask[]; open?: BoardTask[] }>(\n 'GET',\n `/team/${this.teamId}/board`\n );\n return data.tasks ?? data.open ?? [];\n }\n\n async claim(taskId: string): Promise<BoardTask> {\n return this.req<BoardTask>('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'claim' }));\n }\n\n async joinTeam(): Promise<{ success: boolean; role?: string; members?: number }> {\n return this.req<{ success: boolean; role?: string; members?: number }>(\n 'POST',\n `/team/${this.teamId}/join`,\n await this.signBody({})\n );\n }\n\n async sendMessageOnTask(taskId: string, body: string): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, await this.signBody({\n to: 'team',\n subject: `task:${taskId}`,\n content: body,\n }));\n }\n\n async markDone(taskId: string, summary: string, commitHash?: string): Promise<void> {\n await this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({\n action: 'done',\n summary,\n // verification_evidence required by server before task can be closed.\n verification_evidence: summary,\n // Exclude commitHash when undefined — JSON.stringify drops undefined but\n // canonicalizeSigning preserves it as the literal string \"undefined\",\n // causing a signature-mismatch vs what the server sees after JSON.parse.\n ...(commitHash !== undefined ? { commitHash } : {}),\n }));\n }\n\n // POST CAEL audit records for this agent. Server validator at\n // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires\n // bearer == handle owner OR founder; the per-surface x402 bearer is the\n // handle owner so this resolves correctly. Records that fail shape\n // validation (layer_hashes != 7 elements, missing tick_iso/operation/\n // fnv1a_chain) are silently dropped server-side, not rejected as a batch.\n async postAuditRecords(\n handle: string,\n records: CaelAuditRecord[]\n ): Promise<{ appended: number; rejected: number }> {\n // Audit endpoint uses bearer-only auth — no signed envelope wrapper.\n return this.req<{ appended: number; rejected: number }>(\n 'POST',\n `/agent/${encodeURIComponent(handle)}/audit`,\n { records } as unknown as Record<string, unknown>\n );\n }\n\n async whoAmI(): Promise<{ agentId: string; surface: string; wallet?: string }> {\n // GET /api/holomesh/me returns { agentId, name, wallet, isFounder, teamId, teams, permissions }\n // (see packages/mcp-server/src/holomesh/routes/core-routes.ts §/me handler).\n // It does NOT return a `surface` field — derive it from the seat name on the\n // client side. Seat naming convention (set by the provisioning admin path):\n // claudecode-claude-x402 → claude-code\n // cursor-claude-x402 → claude-cursor\n // gemini-antigravity → gemini-antigravity\n // copilot-vscode → copilot-vscode\n // Founder → unknown (shared key, no surface attribution)\n const raw = await this.req<{\n agentId: string;\n name?: string;\n wallet?: string;\n }>('GET', '/me');\n return {\n agentId: raw.agentId,\n surface: deriveSurface(raw.name),\n wallet: raw.wallet,\n };\n }\n\n // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────\n\n /** Read recent team messages. */\n async getTeamMessages(limit = 20): Promise<TeamMessage[]> {\n const data = await this.req<{ messages?: TeamMessage[]; success?: boolean }>(\n 'GET',\n `/team/${this.teamId}/messages?limit=${limit}`\n );\n return data.messages ?? [];\n }\n\n /** Post a message to the team feed. */\n async sendTeamMessage(content: string, messageType = 'text'): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));\n }\n\n // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────\n\n /** Switch team mode. Requires owner or founder role. */\n async setTeamMode(mode: string, reason?: string): Promise<{ mode: string; unchanged?: boolean }> {\n return this.req('POST', `/team/${this.teamId}/mode`, await this.signBody({ mode, reason } as Record<string, unknown>));\n }\n\n /** Update room preferences. Requires config:write permission. */\n async patchRoomPrefs(prefs: { communicationStyle?: string; objective?: string }): Promise<{\n communicationStyle: string;\n objective: string;\n }> {\n return this.req('PATCH', `/team/${this.teamId}/room`, await this.signBody(prefs as Record<string, unknown>));\n }\n\n /** Update a board task. */\n async updateTask(\n taskId: string,\n updates: {\n title?: string;\n description?: string;\n priority?: number;\n tags?: string[];\n }\n ): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'update', ...updates } as Record<string, unknown>));\n }\n\n /** Delete a board task. */\n async deleteTask(taskId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'delete' }));\n }\n\n /** Delegate a board task to another agent. */\n async delegateTask(taskId: string, toAgentId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'delegate', toAgentId }));\n }\n\n /** Add new tasks to the board (used by the delegate_task tool to spawn sub-work). */\n async addTasks(\n tasks: Array<{ title: string; description?: string; priority?: number; source?: string; role?: string; tags?: string[] }>\n ): Promise<{ added: number }> {\n const result = await this.req<{ added?: number }>('POST', `/team/${this.teamId}/board`, await this.signBody({ tasks } as Record<string, unknown>));\n return { added: result.added ?? tasks.length };\n }\n\n // ── Cognitive-verb knowledge surface (Phase 2.2 — recall / rag_query) ────────\n\n /**\n * Query the TEAM knowledge base (the `rag_query` cognitive verb). Bearer-only\n * GET; `q` is the server-side search filter. Returns [] on any failure so a\n * retrieval miss never breaks a tick.\n */\n async queryTeamKnowledge(query: string, limit = 5): Promise<KnowledgeEntry[]> {\n const qs = new URLSearchParams({ q: query, limit: String(limit) }).toString();\n try {\n const data = await this.req<{ entries?: KnowledgeEntry[] }>(\n 'GET',\n `/team/${this.teamId}/knowledge?${qs}`\n );\n return data.entries ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * Query this agent's PRIVATE workspace knowledge (the `recall` cognitive verb).\n * The endpoint has no server-side search param, so the caller filters by query\n * client-side. Returns [] on any failure.\n */\n async queryPrivateKnowledge(): Promise<KnowledgeEntry[]> {\n if (this.localKnowledgePath) {\n try {\n const raw = await readFile(this.localKnowledgePath, 'utf8');\n return raw.trim().split('\\n').filter(Boolean).map(l => JSON.parse(l) as KnowledgeEntry);\n } catch {\n return [];\n }\n }\n try {\n const data = await this.req<{ entries?: KnowledgeEntry[] }>('GET', `/knowledge/private`);\n return data.entries ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * Persist facts to this agent's PRIVATE workspace — the WRITE side of `recall`\n * (POST /knowledge/private). The server stamps id / workspaceId (`private:<wallet>`)\n * / author / timestamps; the caller supplies `content` (+ optional type/tags/title).\n * This is what gives `recall` something to recall: without it the private store\n * stays empty and every `recall` returns [] (the W.752 loop-gap). Best-effort —\n * returns false on any failure so a write miss never breaks a tick (the task is\n * already done by the time this runs).\n */\n /**\n * Query the server-side in-process codebase GraphRAG index via the mesh bearer-gated\n * route (POST /api/holomesh/codebase/search). Called by the `rag_query` cognitive verb\n * (Phase 2.3 W.753) so edge agents can reach HoloEmbed semantic search without a direct\n * Absorb-service dep. Returns [] (not an error) when the graph isn't loaded yet — the\n * caller falls back to team-knowledge search.\n *\n * @param query Natural-language search string.\n * @param topK Max results to return (1–20, server-capped).\n */\n async queryCodebase(query: string, topK = 8): Promise<Array<{ name: string; file: string; line?: number; type: string; score: number; signature?: string | null }>> {\n try {\n const data = await this.req<{\n success?: boolean;\n result?: {\n results?: Array<{ name: string; file: string; line?: number; type: string; score: number; signature?: string | null }>;\n error?: string;\n };\n error?: string;\n }>('POST', `/codebase/search`, { query, topK });\n if (data.error || data.result?.error) return [];\n return data.result?.results ?? [];\n } catch {\n return [];\n }\n }\n\n async writePrivateKnowledge(\n entries: Array<{ content: string; type?: string; tags?: string[]; title?: string }>\n ): Promise<boolean> {\n if (!entries.length) return false;\n if (this.localKnowledgePath) {\n try {\n await mkdir(dirname(this.localKnowledgePath), { recursive: true });\n const lines = entries.map(e => JSON.stringify({\n id: `local.${Date.now()}.${Math.random().toString(36).slice(2, 6)}`,\n content: e.content,\n type: e.type ?? 'task-outcome',\n tags: e.tags ?? [],\n title: e.title,\n createdAt: new Date().toISOString(),\n })).join('\\n') + '\\n';\n await appendFile(this.localKnowledgePath, lines, 'utf8');\n return true;\n } catch {\n return false;\n }\n }\n try {\n await this.req('POST', `/knowledge/private`, { entries });\n return true;\n } catch {\n return false;\n }\n }\n\n private async req<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.apiBase}${path}`;\n // HoloMesh REST auth: server (packages/mcp-server/src/holomesh/auth-utils.ts\n // resolveRequestingAgent) accepts EITHER `Authorization: Bearer <token>`\n // (HTTP-standard, used here) OR `x-mcp-api-key: <token>` (orchestrator\n // convention). Both resolve through the same key-registry / agent-store /\n // env-fallback chain. Bearer is preferred for new code (W.087 vertex B,\n // task_1777073616424_klls).\n const res = await this.fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.bearer}`,\n 'content-type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!res.ok) {\n const txt = await res.text().catch(() => '');\n throw new Error(`HoloMesh ${method} ${path} ${res.status}: ${txt.slice(0, 300)}`);\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n\n/**\n * Derive a surface tag from a seat name returned by /me. Mirrors the surface\n * detection in scripts/probe-surface-bearers.mjs and hooks/lib/holomesh-env.mjs\n * so a single agent's surface attribution is consistent across read and write\n * paths. Returns 'unknown' when the seat name doesn't encode a surface\n * (e.g. shared-key resolution to \"Founder\").\n */\nexport function deriveSurface(seatName: string | undefined): string {\n if (!seatName) return 'unknown';\n const n = seatName.toLowerCase();\n if (n.startsWith('claudecode')) return 'claude-code';\n if (n.startsWith('cursor')) return 'claude-cursor';\n if (n.startsWith('claudedesktop')) return 'claude-desktop';\n if (n.startsWith('vscode-claude') || n.startsWith('claude-vscode')) return 'claude-vscode';\n if (n.startsWith('gemini')) return 'gemini-antigravity';\n if (n.startsWith('copilot')) return 'copilot-vscode';\n return 'unknown';\n}\n\nexport function pickClaimableTask(\n tasks: BoardTask[],\n brainCapabilityTags: string[]\n): BoardTask | undefined {\n const wanted = new Set(brainCapabilityTags.map((t) => t.toLowerCase()));\n const open = tasks.filter((t) => t.status === 'open' && !t.claimedBy);\n const scored = open\n .map((t) => ({ task: t, score: scoreTask(t, wanted) }))\n .filter((s) => s.score > 0)\n .sort((a, b) => b.score - a.score || priority(a.task) - priority(b.task));\n return scored[0]?.task;\n}\n\nfunction scoreTask(task: BoardTask, wanted: Set<string>): number {\n const tags = (task.tags ?? []).map((t) => t.toLowerCase());\n const text = `${task.title} ${task.description ?? ''}`.toLowerCase();\n let score = 0;\n for (const tag of tags) if (wanted.has(tag)) score += 2;\n for (const w of wanted) if (text.includes(w)) score += 1;\n return score;\n}\n\nfunction priority(t: BoardTask): number {\n if (typeof t.priority === 'number') return t.priority;\n const map: Record<string, number> = { critical: 1, high: 2, medium: 4, low: 6 };\n return map[String(t.priority).toLowerCase()] ?? 5;\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -512,6 +512,8 @@ function orderCandidates(candidates, tieBreaker) {
512
512
  }
513
513
 
514
514
  // src/holomesh-client.ts
515
+ import { readFile as readFile2, appendFile, mkdir } from "fs/promises";
516
+ import { dirname as dirname2 } from "path";
515
517
  var HolomeshClient = class {
516
518
  constructor(opts) {
517
519
  this.apiBase = opts.apiBase.replace(/\/$/, "");
@@ -519,6 +521,7 @@ var HolomeshClient = class {
519
521
  this.teamId = opts.teamId;
520
522
  this.fetchImpl = opts.fetchImpl ?? fetch;
521
523
  this.signer = opts.signer;
524
+ this.localKnowledgePath = opts.localKnowledgePath;
522
525
  }
523
526
  /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
524
527
  async signBody(body) {
@@ -647,6 +650,14 @@ var HolomeshClient = class {
647
650
  * client-side. Returns [] on any failure.
648
651
  */
649
652
  async queryPrivateKnowledge() {
653
+ if (this.localKnowledgePath) {
654
+ try {
655
+ const raw = await readFile2(this.localKnowledgePath, "utf8");
656
+ return raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
657
+ } catch {
658
+ return [];
659
+ }
660
+ }
650
661
  try {
651
662
  const data = await this.req("GET", `/knowledge/private`);
652
663
  return data.entries ?? [];
@@ -684,6 +695,23 @@ var HolomeshClient = class {
684
695
  }
685
696
  async writePrivateKnowledge(entries) {
686
697
  if (!entries.length) return false;
698
+ if (this.localKnowledgePath) {
699
+ try {
700
+ await mkdir(dirname2(this.localKnowledgePath), { recursive: true });
701
+ const lines = entries.map((e) => JSON.stringify({
702
+ id: `local.${Date.now()}.${Math.random().toString(36).slice(2, 6)}`,
703
+ content: e.content,
704
+ type: e.type ?? "task-outcome",
705
+ tags: e.tags ?? [],
706
+ title: e.title,
707
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
708
+ })).join("\n") + "\n";
709
+ await appendFile(this.localKnowledgePath, lines, "utf8");
710
+ return true;
711
+ } catch {
712
+ return false;
713
+ }
714
+ }
687
715
  try {
688
716
  await this.req("POST", `/knowledge/private`, { entries });
689
717
  return true;
@@ -832,8 +860,8 @@ function buildCaelRecord(input) {
832
860
  }
833
861
 
834
862
  // src/tools.ts
835
- import { readFile as readFile2, writeFile, readdir, mkdir, stat } from "fs/promises";
836
- import { resolve, dirname as dirname2, delimiter, isAbsolute, sep } from "path";
863
+ import { readFile as readFile3, writeFile, readdir, mkdir as mkdir2, stat } from "fs/promises";
864
+ import { resolve, dirname as dirname3, delimiter, isAbsolute, sep } from "path";
837
865
  import { spawn } from "child_process";
838
866
  import { createHash as createHash2 } from "crypto";
839
867
  var FLEET_READ_ROOTS = [
@@ -1124,7 +1152,7 @@ async function runTool(use, opts = {}) {
1124
1152
  const path = use.input.path;
1125
1153
  const denied = checkReadAllowed(path);
1126
1154
  if (denied) return errResult(use.id, denied);
1127
- const text = await readFile2(path, "utf8");
1155
+ const text = await readFile3(path, "utf8");
1128
1156
  const truncated = text.length > 2e5 ? text.slice(0, 2e5) + `
1129
1157
  \u2026[truncated, full file is ${text.length} bytes]` : text;
1130
1158
  return okResult(use.id, truncated);
@@ -1142,7 +1170,7 @@ async function runTool(use, opts = {}) {
1142
1170
  const content = use.input.content;
1143
1171
  const denied = checkWriteAllowed(path);
1144
1172
  if (denied) return errResult(use.id, denied);
1145
- await mkdir(dirname2(path), { recursive: true });
1173
+ await mkdir2(dirname3(path), { recursive: true });
1146
1174
  await writeFile(path, content, "utf8");
1147
1175
  const s = await stat(path);
1148
1176
  return okResult(use.id, `wrote ${s.size} bytes to ${path}`);
@@ -1257,7 +1285,7 @@ ${truncated}`);
1257
1285
  const outPath = resolve(ALLOWED_WRITE_ROOTS[0], `hardware-receipt-${ts}.json`);
1258
1286
  const denied = checkWriteAllowed(outPath);
1259
1287
  if (denied) return errResult(use.id, `Cannot write receipt: ${denied}`);
1260
- await mkdir(dirname2(outPath), { recursive: true });
1288
+ await mkdir2(dirname3(outPath), { recursive: true });
1261
1289
  await writeFile(outPath, JSON.stringify(sealed, null, 2), "utf8");
1262
1290
  return okResult(
1263
1291
  use.id,
@@ -1270,7 +1298,7 @@ ${truncated}`);
1270
1298
  const newStr = use.input.new;
1271
1299
  const denied = checkWriteAllowed(path);
1272
1300
  if (denied) return errResult(use.id, denied);
1273
- const text = await readFile2(path, "utf8");
1301
+ const text = await readFile3(path, "utf8");
1274
1302
  const count = text.split(oldStr).length - 1;
1275
1303
  if (count === 0) return errResult(use.id, `str_replace: "old" string not found in ${path} \u2014 0 occurrences`);
1276
1304
  if (count > 1) return errResult(use.id, `str_replace: "old" string is ambiguous in ${path} \u2014 ${count} occurrences; add more surrounding context`);
@@ -1705,6 +1733,37 @@ var AgentRunner = class {
1705
1733
  finalText = resp.content;
1706
1734
  break;
1707
1735
  }
1736
+ if (productiveCallCount === 0 && toolsCalled.size > 0 && iters < MAX_TOOL_ITERS) {
1737
+ iters++;
1738
+ messages.push({
1739
+ role: "user",
1740
+ 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."
1741
+ });
1742
+ const reResp = await provider.complete(
1743
+ { messages, maxTokens: 8192, temperature: 0.4, tools: activeTools },
1744
+ identity.llmModel
1745
+ );
1746
+ aggUsage = {
1747
+ promptTokens: aggUsage.promptTokens + reResp.usage.promptTokens,
1748
+ completionTokens: aggUsage.completionTokens + reResp.usage.completionTokens,
1749
+ totalTokens: aggUsage.totalTokens + reResp.usage.totalTokens
1750
+ };
1751
+ if (reResp.finishReason === "tool_use" && reResp.toolUses && reResp.toolUses.length > 0) {
1752
+ log({ ev: "reprompt-tool-call", taskId: target.id, iter: iters, tools: reResp.toolUses.map((t) => t.name) });
1753
+ const reProd = summarizeToolProductivity(reResp.toolUses);
1754
+ for (const n of reProd.names) toolsCalled.add(n);
1755
+ productiveCallCount += reProd.productiveCount;
1756
+ messages.push({ role: "assistant", content: reResp.assistantBlocks ?? [] });
1757
+ const reResults = await Promise.all(
1758
+ reResp.toolUses.map(
1759
+ (u) => runTool(u, { signReceipt: this.opts.signReceipt, addTask: (tasks2) => mesh.addTasks(tasks2) })
1760
+ )
1761
+ );
1762
+ messages.push({ role: "user", content: reResults });
1763
+ }
1764
+ finalText = reResp.content;
1765
+ lastResponse = reResp;
1766
+ }
1708
1767
  const durationMs = Date.now() - start;
1709
1768
  if (productiveCallCount === 0) {
1710
1769
  log({
@@ -1980,7 +2039,7 @@ function jitter(base) {
1980
2039
 
1981
2040
  // src/commit-hook.ts
1982
2041
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1983
- import { dirname as dirname3, join, resolve as resolve2 } from "path";
2042
+ import { dirname as dirname4, join, resolve as resolve2 } from "path";
1984
2043
  import { spawnSync } from "child_process";
1985
2044
  var SAFE_HANDLE = /^[a-z0-9_-]{1,64}$/i;
1986
2045
  function makeCommitHook(opts) {
@@ -2000,7 +2059,7 @@ function makeCommitHook(opts) {
2000
2059
  const safeTaskId = task.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
2001
2060
  const fileName = `${date}_${safeTaskId}_${identity.handle}.md`;
2002
2061
  const filePath = join(outputDir, fileName);
2003
- mkdirSync2(dirname3(filePath), { recursive: true });
2062
+ mkdirSync2(dirname4(filePath), { recursive: true });
2004
2063
  writeFileSync2(filePath, renderMemo(result, task, identity, date), "utf8");
2005
2064
  const relPath = relativeTo(cwd, filePath);
2006
2065
  const addRes = spawn2("git", ["add", relPath], { cwd, encoding: "utf8" });
@@ -2270,12 +2329,12 @@ import { join as join2 } from "path";
2270
2329
 
2271
2330
  // src/audit-log.ts
2272
2331
  import { mkdirSync as mkdirSync3, appendFileSync, readFileSync as readFileSync2, existsSync as existsSync2, statSync, renameSync } from "fs";
2273
- import { dirname as dirname4 } from "path";
2332
+ import { dirname as dirname5 } from "path";
2274
2333
  var AuditLog = class {
2275
2334
  constructor(opts) {
2276
2335
  this.logPath = opts.logPath;
2277
2336
  this.maxBytes = opts.maxBytes ?? 50 * 1024 * 1024;
2278
- mkdirSync3(dirname4(this.logPath), { recursive: true });
2337
+ mkdirSync3(dirname5(this.logPath), { recursive: true });
2279
2338
  }
2280
2339
  record(event) {
2281
2340
  this.rotateIfFull();
@@ -2899,7 +2958,7 @@ function envVarLinesFor(handle, walletAddress, bearer) {
2899
2958
 
2900
2959
  // src/index.ts
2901
2960
  import { writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync5 } from "fs";
2902
- import { dirname as dirname5, resolve as resolve3 } from "path";
2961
+ import { dirname as dirname6, resolve as resolve3 } from "path";
2903
2962
  async function main() {
2904
2963
  const args = process.argv.slice(2);
2905
2964
  const cmd = args[0] ?? "help";
@@ -2989,7 +3048,8 @@ async function cmdRun(opts) {
2989
3048
  apiBase: identity.meshApiBase,
2990
3049
  bearer,
2991
3050
  teamId: identity.teamId,
2992
- signer: buildRequestSigner(seat)
3051
+ signer: buildRequestSigner(seat),
3052
+ localKnowledgePath: process.env.HOLOSCRIPT_AGENT_LOCAL_KNOWLEDGE_PATH
2993
3053
  });
2994
3054
  const commitHook = buildCommitHook(identity, mesh);
2995
3055
  const auditLog = buildAuditLog();
@@ -3224,11 +3284,11 @@ async function cmdAblate(rest) {
3224
3284
  timeoutPerCellMs: spec.timeoutPerCellMs
3225
3285
  });
3226
3286
  if (outJson) {
3227
- mkdirSync5(dirname5(resolve3(outJson)), { recursive: true });
3287
+ mkdirSync5(dirname6(resolve3(outJson)), { recursive: true });
3228
3288
  writeFileSync4(outJson, JSON.stringify(matrix, null, 2), "utf8");
3229
3289
  }
3230
3290
  if (outMd) {
3231
- mkdirSync5(dirname5(resolve3(outMd)), { recursive: true });
3291
+ mkdirSync5(dirname6(resolve3(outMd)), { recursive: true });
3232
3292
  writeFileSync4(outMd, renderAblationMarkdown(matrix), "utf8");
3233
3293
  }
3234
3294
  if (!outMd && !outJson) {
@@ -3262,7 +3322,8 @@ async function cmdWhoami() {
3262
3322
  apiBase: identity.meshApiBase,
3263
3323
  bearer,
3264
3324
  teamId: identity.teamId,
3265
- signer: buildRequestSigner(seat)
3325
+ signer: buildRequestSigner(seat),
3326
+ localKnowledgePath: process.env.HOLOSCRIPT_AGENT_LOCAL_KNOWLEDGE_PATH
3266
3327
  });
3267
3328
  const me = await mesh.whoAmI();
3268
3329
  console.log(JSON.stringify({ identity: identityForLog(identity), me }, null, 2));
@@ -3437,6 +3498,7 @@ OPTIONAL ENV
3437
3498
  HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL local-llm provider base URL (default http://localhost:8080)
3438
3499
  HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL local-llm model id (e.g. "qwen3:4b-instruct"); overrides HOLOSCRIPT_AGENT_MODEL for the local provider
3439
3500
  HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS local-llm request timeout in ms (default 300000 \u2014 edge devices like Jetson need >120s)
3501
+ HOLOSCRIPT_AGENT_LOCAL_KNOWLEDGE_PATH local JSONL path for sovereign private knowledge store (bypasses mcp-orchestrator /knowledge/sync)
3440
3502
  `);
3441
3503
  }
3442
3504
  main().catch((err) => {