@alfe.ai/openclaw-knowledge 0.0.6 → 0.0.8

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/plugin2.js CHANGED
@@ -8,16 +8,17 @@ function snippet(text) {
8
8
  return clean.length > SNIPPET_MAX ? `${clean.slice(0, SNIPPET_MAX)}…` : clean;
9
9
  }
10
10
  /**
11
- * Format RAG search hits for a tool return. Each hit carries provenance so
12
- * the agent can follow up: docs point at a `filePath` (already mirrored to
13
- * `shared/<scope>/`), facts at a `factId`.
11
+ * Format RAG search hits for a tool return. Every live hit is a document and
12
+ * points at a `filePath` (already mirrored to `shared/<scope>/`) the agent can
13
+ * read or edit. (Legacy fact vectors may still carry no filePath — they render
14
+ * without a path rather than being special-cased.)
14
15
  */
15
16
  function formatSearchResults(result) {
16
17
  if (result.results.length === 0) return "No matching knowledge found in your scopes.";
17
18
  const lines = [];
18
19
  for (const hit of result.results) {
19
20
  const where = `${hit.scopeType}:${hit.scopeId}`;
20
- const ref = hit.source === "doc" ? `doc ${hit.filePath ?? "?"}` : `fact ${hit.factId ?? "?"}`;
21
+ const ref = `doc ${hit.filePath ?? "?"}`;
21
22
  lines.push(`- [${where}] (${ref}, score ${hit.score.toFixed(2)})\n ${snippet(hit.text)}`);
22
23
  }
23
24
  if (result.truncatedScopes) lines.push("(Note: you belong to more scopes than the search fan-out cap; narrow with scopeId to reach the rest.)");
@@ -101,15 +102,27 @@ var AutoContext = class {
101
102
  * attributable, deliberate-publish knowledge that humans and other agents
102
103
  * read and contribute to.
103
104
  *
105
+ * Shared knowledge is curated DOCUMENTS (+ scope profiles). There is no
106
+ * free-floating "fact" primitive: an agent that learns something durable
107
+ * finds the right document and edits it in (search → read_doc → write_doc /
108
+ * propose_change). Quick or uncertain capture belongs in the agent's private
109
+ * per-agent memory, not shared knowledge.
110
+ *
104
111
  * Backed by:
105
- * - services/knowledge — RAG search (vector index over docs + facts)
112
+ * - services/knowledge — RAG search (vector index over docs)
106
113
  * - services/org — system of record + per-agent membership gate
107
- * (docs, facts, profile)
114
+ * (docs, profile)
108
115
  *
109
116
  * Registers:
110
117
  * - Tools: resource_search, resource_read_doc, resource_write_doc,
111
- * resource_write_fact, resource_get_profile, resource_list_scopes
118
+ * resource_get_profile, resource_list_scopes,
119
+ * resource_propose_change, resource_list_change_requests
112
120
  * - Lifecycle hook: before_agent_start (bounded org-profile + scope-list inject)
121
+ *
122
+ * NOTE: every tool name here MUST also appear in `openclaw.plugin.json`'s
123
+ * `contracts.tools` allowlist — OpenClaw (2026.5+) gates the model's tool
124
+ * payload on that strict-literal list, so a registered-but-unlisted tool is
125
+ * invisible to the LLM.
113
126
  */
114
127
  const pkg = createRequire(import.meta.url)("../package.json");
115
128
  const DEFAULT_CONFIG = {
@@ -137,7 +150,7 @@ function asScopeType(value) {
137
150
  const plugin = {
138
151
  id: "@alfe.ai/openclaw-knowledge",
139
152
  name: "Knowledge Resources",
140
- description: "Scoped, searchable org/team/project knowledge — docs, facts, and profiles",
153
+ description: "Scoped, searchable org/team/project knowledge — curated docs and profiles",
141
154
  version: pkg.version,
142
155
  register(api) {
143
156
  const config = resolvePluginConfig(api.pluginConfig);
@@ -151,7 +164,7 @@ const plugin = {
151
164
  api.registerTool(() => ({
152
165
  name: "resource_search",
153
166
  label: "Resource Search",
154
- description: "Semantically search shared knowledge (docs + facts) across the org, teams, and projects you belong to. Returns provenance: doc hits point at a filePath, fact hits at a factId. Docs are ALSO already synced to your workspace under shared/<scope>/, so for a small corpus just read those files directly — search earns its keep on LARGE doc corpora and on published facts. Pass scopeType+scopeId to narrow to one scope; omit to search all your scopes.",
167
+ description: "Semantically search shared knowledge documents across the org, teams, and projects you belong to. Each hit points at the doc's filePath so you can read or edit it. Docs are ALSO already synced to your workspace under shared/<scope>/, so for a small corpus just read those files directly — search earns its keep on LARGE doc corpora. Pass scopeType+scopeId to narrow to one scope; omit to search all your scopes.",
155
168
  parameters: {
156
169
  type: "object",
157
170
  properties: {
@@ -220,7 +233,7 @@ const plugin = {
220
233
  api.registerTool(() => ({
221
234
  name: "resource_write_doc",
222
235
  label: "Resource Write Doc",
223
- description: "Create or overwrite a knowledge DOC in a scope. Use a DOC for durable, reshaped, human-readable prose that grows and is curated over time — runbooks, designs, onboarding notes, an evolving description of a system. Use resource_write_fact instead for a single crisp atomic statement. Writing is a DELIBERATE publish into shared, attributable knowledge (not your private memory). Overwriting an existing path creates a new revision (history is kept).",
236
+ description: "Create or overwrite a knowledge DOC in a scope. Docs are the ONLY unit of shared knowledge: when you learn something durable, find the right existing doc (resource_search → resource_read_doc) and edit it in, or create a new one if none fits — do NOT scatter free-floating notes. Use a DOC for durable, reshaped, human-readable prose that grows and is curated over time — runbooks, designs, onboarding notes, an evolving description of a system. Writing is a DELIBERATE publish into shared, attributable knowledge; quick or uncertain capture belongs in your private memory instead. Overwriting an existing path creates a new revision (history is kept).",
224
237
  parameters: {
225
238
  type: "object",
226
239
  properties: {
@@ -260,39 +273,6 @@ const plugin = {
260
273
  }
261
274
  }
262
275
  }), { names: ["resource_write_doc"] });
263
- api.registerTool(() => ({
264
- name: "resource_write_fact",
265
- label: "Resource Write Fact",
266
- description: "Publish a single crisp FACT into a scope's shared knowledge. Use a FACT for one atomic, durable statement worth remembering across agents (e.g. 'The prod data center is in Hetzner FSN1', 'The billing owner is Kevin'). Use resource_write_doc instead for longer curated prose. This is a DELIBERATE, attributable cross-agent publish — it is NOT your private memory and is NOT an automatic merge. Keep it short and self-contained.",
267
- parameters: {
268
- type: "object",
269
- properties: {
270
- scopeType: SCOPE_TYPE_PROP,
271
- scopeId: {
272
- type: "string",
273
- description: "The scope's id (from resource_list_scopes)"
274
- },
275
- text: {
276
- type: "string",
277
- description: "The fact — one atomic, self-contained statement"
278
- }
279
- },
280
- required: [
281
- "scopeType",
282
- "scopeId",
283
- "text"
284
- ]
285
- },
286
- execute: async (_toolCallId, params) => {
287
- const scopeType = asScopeType(params.scopeType);
288
- if (!scopeType) return "Error: scopeType must be one of org, team, project.";
289
- try {
290
- return `Published fact ${(await client.createScopeFact(scopeType, params.scopeId, params.text)).factId} to ${scopeType}:${String(params.scopeId)}.`;
291
- } catch (err) {
292
- return `Error publishing fact: ${err instanceof Error ? err.message : String(err)}`;
293
- }
294
- }
295
- }), { names: ["resource_write_fact"] });
296
276
  api.registerTool(() => ({
297
277
  name: "resource_get_profile",
298
278
  label: "Resource Get Profile",
@@ -342,6 +322,148 @@ const plugin = {
342
322
  }
343
323
  }
344
324
  }), { names: ["resource_list_scopes"] });
325
+ api.registerTool(() => ({
326
+ name: "resource_propose_change",
327
+ label: "Resource Propose Change",
328
+ description: "Propose a change to a scope's shared knowledge (doc / profile) WITHOUT writing it directly. Use this ONLY as the contribution path to a scope you are NOT a member of — resource_write_doc will be refused there, but a proposal is always allowed. If you ARE a member of the scope (it appears in resource_list_scopes), just write directly instead; proposing would be redundant. A proposal is INERT: nothing changes until a reviewer with authority over the scope approves it, so this does not grant you any read access to the scope. Always give a clear `rationale` — the reviewer sees it.\nPayload by resourceType:\n • doc — set operation (create|update|delete), targetPath (e.g. designs/data-center.md); for create/update also pass the full markdown `content`.\n • profile — operation is always 'update'; pass any of about / description / links.",
329
+ parameters: {
330
+ type: "object",
331
+ properties: {
332
+ scopeType: SCOPE_TYPE_PROP,
333
+ scopeId: {
334
+ type: "string",
335
+ description: "The target scope's id (for 'org' this is the tenantId)"
336
+ },
337
+ resourceType: {
338
+ type: "string",
339
+ enum: ["doc", "profile"],
340
+ description: "Which knowledge resource the proposal targets"
341
+ },
342
+ operation: {
343
+ type: "string",
344
+ enum: [
345
+ "create",
346
+ "update",
347
+ "delete"
348
+ ],
349
+ description: "The proposed operation (profile supports 'update' only)"
350
+ },
351
+ rationale: {
352
+ type: "string",
353
+ description: "Why this change should be made — shown to the reviewer"
354
+ },
355
+ targetPath: {
356
+ type: "string",
357
+ description: "doc only: path within the scope, e.g. notes/runbook.md"
358
+ },
359
+ content: {
360
+ type: "string",
361
+ description: "doc create/update only: full markdown content of the proposed doc"
362
+ },
363
+ about: {
364
+ type: "string",
365
+ description: "profile update only: the scope's 'about' line"
366
+ },
367
+ description: {
368
+ type: "string",
369
+ description: "profile update only: the scope's longer description"
370
+ },
371
+ links: {
372
+ type: "array",
373
+ description: "profile update only: list of { label, url } links",
374
+ items: {
375
+ type: "object",
376
+ properties: {
377
+ label: { type: "string" },
378
+ url: { type: "string" }
379
+ },
380
+ required: ["label", "url"]
381
+ }
382
+ }
383
+ },
384
+ required: [
385
+ "scopeType",
386
+ "scopeId",
387
+ "resourceType",
388
+ "operation",
389
+ "rationale"
390
+ ]
391
+ },
392
+ execute: async (_toolCallId, params) => {
393
+ const scopeType = asScopeType(params.scopeType);
394
+ if (!scopeType) return "Error: scopeType must be one of org, team, project.";
395
+ const resourceType = params.resourceType;
396
+ if (resourceType !== "doc" && resourceType !== "profile") return "Error: resourceType must be one of doc, profile.";
397
+ const operation = params.operation;
398
+ if (operation !== "create" && operation !== "update" && operation !== "delete") return "Error: operation must be one of create, update, delete.";
399
+ const rationale = typeof params.rationale === "string" ? params.rationale : "";
400
+ if (!rationale) return "Error: rationale is required.";
401
+ let proposedValue;
402
+ if (resourceType === "profile") {
403
+ const links = Array.isArray(params.links) ? params.links : void 0;
404
+ proposedValue = {
405
+ about: typeof params.about === "string" ? params.about : void 0,
406
+ description: typeof params.description === "string" ? params.description : void 0,
407
+ links
408
+ };
409
+ }
410
+ try {
411
+ const cr = await client.proposeScopeChange(scopeType, params.scopeId, {
412
+ resourceType,
413
+ operation,
414
+ rationale,
415
+ targetPath: typeof params.targetPath === "string" ? params.targetPath : void 0,
416
+ content: typeof params.content === "string" ? params.content : void 0,
417
+ proposedValue
418
+ });
419
+ return `Proposed ${cr.resourceType} ${cr.operation} to ${scopeType}:${String(params.scopeId)} (change request ${cr.changeRequestId}, status ${cr.status}). It is inert until a scope reviewer approves it.`;
420
+ } catch (err) {
421
+ return `Error proposing change: ${err instanceof Error ? err.message : String(err)}`;
422
+ }
423
+ }
424
+ }), { names: ["resource_propose_change"] });
425
+ api.registerTool(() => ({
426
+ name: "resource_list_change_requests",
427
+ label: "Resource List Change Requests",
428
+ description: "List the change requests YOU have proposed in a scope, to track whether they are still open, approved, rejected, or withdrawn. Optionally filter by status. Only your own proposals are returned.",
429
+ parameters: {
430
+ type: "object",
431
+ properties: {
432
+ scopeType: SCOPE_TYPE_PROP,
433
+ scopeId: {
434
+ type: "string",
435
+ description: "The scope's id (for 'org' this is the tenantId)"
436
+ },
437
+ status: {
438
+ type: "string",
439
+ enum: [
440
+ "open",
441
+ "approved",
442
+ "rejected",
443
+ "withdrawn",
444
+ "superseded"
445
+ ],
446
+ description: "Optional: only return proposals in this status"
447
+ }
448
+ },
449
+ required: ["scopeType", "scopeId"]
450
+ },
451
+ execute: async (_toolCallId, params) => {
452
+ const scopeType = asScopeType(params.scopeType);
453
+ if (!scopeType) return "Error: scopeType must be one of org, team, project.";
454
+ const status = params.status;
455
+ try {
456
+ const { changeRequests } = await client.listScopeChangeRequests(scopeType, params.scopeId, { status: status === "open" || status === "approved" || status === "rejected" || status === "withdrawn" || status === "superseded" ? status : void 0 });
457
+ if (changeRequests.length === 0) return "You have no change requests in this scope.";
458
+ return changeRequests.map((cr) => {
459
+ const target = cr.targetPath ?? cr.resourceType;
460
+ return `- ${cr.changeRequestId} [${cr.status}] ${cr.resourceType} ${cr.operation} → ${target}`;
461
+ }).join("\n");
462
+ } catch (err) {
463
+ return `Error listing change requests: ${err instanceof Error ? err.message : String(err)}`;
464
+ }
465
+ }
466
+ }), { names: ["resource_list_change_requests"] });
345
467
  api.on("before_agent_start", async () => {
346
468
  const block = await autoContext.loadForPrompt();
347
469
  if (block) return { prependContext: block };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin2.js","names":[],"sources":["../src/formatter.ts","../src/context.ts","../src/plugin.ts"],"sourcesContent":["import type { KnowledgeScope, KnowledgeProfile, KnowledgeSearchResult } from \"./types.js\";\n\nconst SNIPPET_MAX = 240;\n\nfunction snippet(text: string): string {\n const clean = text.replace(/\\s+/g, \" \").trim();\n return clean.length > SNIPPET_MAX ? `${clean.slice(0, SNIPPET_MAX)}…` : clean;\n}\n\n/**\n * Format RAG search hits for a tool return. Each hit carries provenance so\n * the agent can follow up: docs point at a `filePath` (already mirrored to\n * `shared/<scope>/`), facts at a `factId`.\n */\nexport function formatSearchResults(result: KnowledgeSearchResult): string {\n if (result.results.length === 0) {\n return \"No matching knowledge found in your scopes.\";\n }\n\n const lines: string[] = [];\n for (const hit of result.results) {\n const where = `${hit.scopeType}:${hit.scopeId}`;\n const ref = hit.source === \"doc\" ? `doc ${hit.filePath ?? \"?\"}` : `fact ${hit.factId ?? \"?\"}`;\n lines.push(`- [${where}] (${ref}, score ${hit.score.toFixed(2)})\\n ${snippet(hit.text)}`);\n }\n\n if (result.truncatedScopes) {\n lines.push(\n \"(Note: you belong to more scopes than the search fan-out cap; narrow with scopeId to reach the rest.)\",\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Bounded session-start context: the org profile (the headline \"what is this\n * org\" surface) plus a NAME-ONLY list of every scope the agent belongs to.\n * Full per-scope profiles are intentionally NOT fetched here — the agent\n * pulls those on demand via `resource_get_profile`. Mirrors the memory\n * plugin's tiered/capped auto-recall.\n */\nexport function formatContextBlock(\n orgProfile: KnowledgeProfile | undefined,\n scopes: KnowledgeScope[],\n maxScopes: number,\n): string | undefined {\n const parts: string[] = [];\n\n if (orgProfile && (orgProfile.about || orgProfile.description)) {\n parts.push(\"Organization profile:\");\n if (orgProfile.about) parts.push(`- About: ${snippet(orgProfile.about)}`);\n if (orgProfile.description) parts.push(`- ${snippet(orgProfile.description)}`);\n if (orgProfile.links.length > 0) {\n parts.push(`- Links: ${orgProfile.links.map((l) => `${l.label} (${l.url})`).join(\", \")}`);\n }\n }\n\n if (scopes.length > 0) {\n if (parts.length > 0) parts.push(\"\");\n parts.push(\n \"Your knowledge scopes (use resource_search / resource_get_profile with the scopeId):\",\n );\n for (const s of scopes.slice(0, maxScopes)) {\n parts.push(`- ${s.scopeType}: ${s.name} [scopeId: ${s.scopeId}]`);\n }\n if (scopes.length > maxScopes) {\n parts.push(`- …and ${String(scopes.length - maxScopes)} more (call resource_list_scopes).`);\n }\n }\n\n if (parts.length === 0) return undefined;\n return `<knowledge-resources>\\n${parts.join(\"\\n\")}\\n</knowledge-resources>`;\n}\n","import { formatContextBlock } from \"./formatter.js\";\nimport type { KnowledgeApi, KnowledgeConfig, KnowledgeScope } from \"./types.js\";\n\n/**\n * Loads bounded knowledge context at session start: the org profile plus a\n * name-only list of the agent's scopes. Called from `before_agent_start`.\n *\n * Deliberately cheap and bounded — one `listScopes` call plus (at most) one\n * org-profile fetch. Full per-scope profiles are NOT loaded; the agent pulls\n * those on demand. Membership is fixed at connect time, so this is sent once\n * per session and is stale until reconnect (mirrors SHARED_SCOPES).\n */\nexport class AutoContext {\n constructor(\n private readonly client: KnowledgeApi,\n private readonly config: KnowledgeConfig,\n private readonly logger: {\n debug: (msg: string, ctx?: Record<string, unknown>) => void;\n warn: (msg: string, ctx?: Record<string, unknown>) => void;\n },\n ) {}\n\n async loadForPrompt(): Promise<string | undefined> {\n if (!this.config.injectContext) return undefined;\n\n try {\n const { scopes } = await this.client.listScopes();\n if (scopes.length === 0) {\n this.logger.debug(\"No knowledge scopes for agent; skipping context inject\");\n return undefined;\n }\n\n const orgScope: KnowledgeScope | undefined = scopes.find((s) => s.scopeType === \"org\");\n let orgProfile;\n if (orgScope) {\n try {\n orgProfile = await this.client.getScopeProfile(\"org\", orgScope.scopeId);\n } catch (err) {\n this.logger.debug(\"Org profile fetch failed; injecting scopes only\", { err: String(err) });\n }\n }\n\n const block = formatContextBlock(orgProfile, scopes, this.config.maxScopes);\n if (block) {\n this.logger.debug(\"Loaded knowledge context for prompt\", {\n scopeCount: scopes.length,\n hasOrgProfile: orgProfile !== undefined,\n });\n }\n return block;\n } catch (err) {\n this.logger.warn(\"Failed to load knowledge context\", { err: String(err) });\n return undefined;\n }\n }\n}\n","/**\n * knowledge — OpenClaw knowledge-resources extension.\n *\n * Scoped, searchable knowledge ABOUT a thing being worked on (org / team /\n * project). Distinct from private per-agent memory: this is SHARED,\n * attributable, deliberate-publish knowledge that humans and other agents\n * read and contribute to.\n *\n * Backed by:\n * - services/knowledge — RAG search (vector index over docs + facts)\n * - services/org — system of record + per-agent membership gate\n * (docs, facts, profile)\n *\n * Registers:\n * - Tools: resource_search, resource_read_doc, resource_write_doc,\n * resource_write_fact, resource_get_profile, resource_list_scopes\n * - Lifecycle hook: before_agent_start (bounded org-profile + scope-list inject)\n */\n\nimport { resolveConfig } from \"@alfe.ai/config\";\nimport { AgentApiClient } from \"@alfe.ai/agent-api-client\";\nimport { AutoContext } from \"./context.js\";\nimport { formatSearchResults } from \"./formatter.js\";\nimport type { KnowledgeConfig, KnowledgeScopeType } from \"./types.js\";\nimport { createRequire } from \"node:module\";\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\n// OpenClaw plugin types — match the OpenClaw plugin API contract.\ninterface PluginApi {\n pluginConfig?: Record<string, unknown>;\n config: Record<string, unknown>;\n logger: {\n info: (msg: string, ctx?: Record<string, unknown>) => void;\n debug: (msg: string, ctx?: Record<string, unknown>) => void;\n warn: (msg: string, ctx?: Record<string, unknown>) => void;\n error: (msg: string, ctx?: Record<string, unknown>) => void;\n };\n registerTool: (factory: (ctx: ToolContext) => Tool, opts?: { names?: string[] }) => void;\n on: (hookName: string, handler: (...args: unknown[]) => unknown, opts?: { priority?: number }) => void;\n}\n\ninterface ToolContext {\n agentId?: string;\n sessionKey?: string;\n sessionId?: string;\n messageChannel?: string;\n}\n\ninterface Tool {\n name: string;\n label: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;\n}\n\nconst DEFAULT_CONFIG: KnowledgeConfig = {\n injectContext: true,\n maxScopes: 25,\n};\n\nfunction resolvePluginConfig(pluginConfig?: Record<string, unknown>): KnowledgeConfig {\n return {\n injectContext:\n typeof pluginConfig?.injectContext === \"boolean\"\n ? pluginConfig.injectContext\n : DEFAULT_CONFIG.injectContext,\n maxScopes:\n typeof pluginConfig?.maxScopes === \"number\" ? pluginConfig.maxScopes : DEFAULT_CONFIG.maxScopes,\n };\n}\n\nconst SCOPE_TYPE_PROP = {\n type: \"string\",\n enum: [\"org\", \"team\", \"project\"],\n description:\n \"Scope kind. For 'org' the scopeId is the tenantId. Get exact scopeIds from resource_list_scopes.\",\n};\n\nfunction asScopeType(value: unknown): KnowledgeScopeType | undefined {\n return value === \"org\" || value === \"team\" || value === \"project\" ? value : undefined;\n}\n\nconst plugin = {\n id: \"@alfe.ai/openclaw-knowledge\",\n name: \"Knowledge Resources\",\n description: \"Scoped, searchable org/team/project knowledge — docs, facts, and profiles\",\n version: pkg.version,\n\n register(api: PluginApi): void {\n const config = resolvePluginConfig(api.pluginConfig);\n const logger = api.logger;\n\n const alfeConfig = resolveConfig();\n const client = new AgentApiClient({\n apiKey: alfeConfig.apiKey,\n apiUrl: alfeConfig.apiUrl,\n });\n\n const autoContext = new AutoContext(client, config, logger);\n\n // ─── Tools ──────────────────────────────────────────────────\n // Registered SYNCHRONOUSLY inside register() so they reach the LLM on\n // the startup pass (deferring behind a promise makes them startup-only,\n // invisible to the model).\n\n api.registerTool(() => ({\n name: \"resource_search\",\n label: \"Resource Search\",\n description:\n \"Semantically search shared knowledge (docs + facts) across the org, teams, and projects you belong to. \" +\n \"Returns provenance: doc hits point at a filePath, fact hits at a factId. \" +\n \"Docs are ALSO already synced to your workspace under shared/<scope>/, so for a small corpus just read those files directly — \" +\n \"search earns its keep on LARGE doc corpora and on published facts. \" +\n \"Pass scopeType+scopeId to narrow to one scope; omit to search all your scopes.\",\n parameters: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"What to search for\" },\n limit: { type: \"number\", description: \"Maximum results (default 10, max 50)\" },\n scopeType: SCOPE_TYPE_PROP,\n scopeId: {\n type: \"string\",\n description: \"Narrow to one scope (from resource_list_scopes). Omit to search all.\",\n },\n },\n required: [\"query\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n try {\n const result = await client.knowledgeSearch(params.query as string, {\n limit: params.limit as number | undefined,\n scopeType: asScopeType(params.scopeType),\n scopeId: typeof params.scopeId === \"string\" ? params.scopeId : undefined,\n });\n return formatSearchResults(result);\n } catch (err) {\n return `Error searching knowledge: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_search\"] });\n\n api.registerTool(() => ({\n name: \"resource_read_doc\",\n label: \"Resource Read Doc\",\n description:\n \"Read the full text of a knowledge doc in a scope. Docs are also mirrored to shared/<scope>/<filePath> in your \" +\n \"workspace — prefer reading that local file when it exists; use this when you only have a search hit's filePath.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n filePath: { type: \"string\", description: \"The doc's path within the scope (e.g. notes/datacenter.md)\" },\n },\n required: [\"scopeType\", \"scopeId\", \"filePath\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const { text } = await client.readScopeDoc(\n scopeType,\n params.scopeId as string,\n params.filePath as string,\n );\n return text;\n } catch (err) {\n return `Error reading doc: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_read_doc\"] });\n\n api.registerTool(() => ({\n name: \"resource_write_doc\",\n label: \"Resource Write Doc\",\n description:\n \"Create or overwrite a knowledge DOC in a scope. Use a DOC for durable, reshaped, human-readable prose that grows \" +\n \"and is curated over time — runbooks, designs, onboarding notes, an evolving description of a system. \" +\n \"Use resource_write_fact instead for a single crisp atomic statement. Writing is a DELIBERATE publish into shared, \" +\n \"attributable knowledge (not your private memory). Overwriting an existing path creates a new revision (history is kept).\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n filePath: { type: \"string\", description: \"Path within the scope, e.g. designs/data-center.md\" },\n content: { type: \"string\", description: \"Full markdown content of the doc\" },\n message: { type: \"string\", description: \"Optional revision note describing the change\" },\n },\n required: [\"scopeType\", \"scopeId\", \"filePath\", \"content\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const { filePath } = await client.writeScopeDoc(\n scopeType,\n params.scopeId as string,\n params.filePath as string,\n params.content as string,\n { message: typeof params.message === \"string\" ? params.message : undefined },\n );\n return `Wrote doc ${filePath} to ${scopeType}:${String(params.scopeId)}.`;\n } catch (err) {\n return `Error writing doc: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_write_doc\"] });\n\n api.registerTool(() => ({\n name: \"resource_write_fact\",\n label: \"Resource Write Fact\",\n description:\n \"Publish a single crisp FACT into a scope's shared knowledge. Use a FACT for one atomic, durable statement worth \" +\n \"remembering across agents (e.g. 'The prod data center is in Hetzner FSN1', 'The billing owner is Kevin'). \" +\n \"Use resource_write_doc instead for longer curated prose. This is a DELIBERATE, attributable cross-agent publish — \" +\n \"it is NOT your private memory and is NOT an automatic merge. Keep it short and self-contained.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n text: { type: \"string\", description: \"The fact — one atomic, self-contained statement\" },\n },\n required: [\"scopeType\", \"scopeId\", \"text\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const fact = await client.createScopeFact(\n scopeType,\n params.scopeId as string,\n params.text as string,\n );\n return `Published fact ${fact.factId} to ${scopeType}:${String(params.scopeId)}.`;\n } catch (err) {\n return `Error publishing fact: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_write_fact\"] });\n\n api.registerTool(() => ({\n name: \"resource_get_profile\",\n label: \"Resource Get Profile\",\n description:\n \"Fetch the structured profile (about / description / links) for one scope. Use this to learn what a specific team or \" +\n \"project is, on demand — the session-start context only lists scope names, not their full profiles.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n },\n required: [\"scopeType\", \"scopeId\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const p = await client.getScopeProfile(scopeType, params.scopeId as string);\n const lines: string[] = [`Profile for ${scopeType}:${String(params.scopeId)}`];\n if (p.about) lines.push(`About: ${p.about}`);\n if (p.description) lines.push(p.description);\n if (p.links.length > 0) {\n lines.push(`Links: ${p.links.map((l) => `${l.label} (${l.url})`).join(\", \")}`);\n }\n if (lines.length === 1) lines.push(\"(no profile set)\");\n return lines.join(\"\\n\");\n } catch (err) {\n return `Error fetching profile: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_get_profile\"] });\n\n api.registerTool(() => ({\n name: \"resource_list_scopes\",\n label: \"Resource List Scopes\",\n description:\n \"List every knowledge scope you belong to (org + teams + projects) with each scope's exact scopeId. \" +\n \"Call this to discover the scopeId to pass to the other resource_* tools (for 'org', scopeId is the tenantId).\",\n parameters: { type: \"object\", properties: {} },\n execute: async () => {\n try {\n const { scopes } = await client.listScopes();\n if (scopes.length === 0) return \"You do not belong to any knowledge scopes.\";\n return scopes\n .map((s) => `- ${s.scopeType}: ${s.name} [scopeId: ${s.scopeId}]`)\n .join(\"\\n\");\n } catch (err) {\n return `Error listing scopes: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_list_scopes\"] });\n\n // ─── Lifecycle hook ─────────────────────────────────────────\n // Inject bounded knowledge context (org profile + name-only scope list)\n // at session start. Full per-scope profiles are fetched on demand.\n api.on(\"before_agent_start\", async () => {\n const block = await autoContext.loadForPrompt();\n if (block) {\n return { prependContext: block };\n }\n return undefined;\n }, { priority: 10 });\n\n logger.info(\"knowledge extension registered\", { injectContext: config.injectContext });\n },\n};\n\nexport default plugin;\n"],"mappings":";;;;AAEA,MAAM,cAAc;AAEpB,SAAS,QAAQ,MAAsB;CACrC,MAAM,QAAQ,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAC9C,QAAO,MAAM,SAAS,cAAc,GAAG,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK;;;;;;;AAQ1E,SAAgB,oBAAoB,QAAuC;AACzE,KAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO;CAGT,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,OAAO,OAAO,SAAS;EAChC,MAAM,QAAQ,GAAG,IAAI,UAAU,GAAG,IAAI;EACtC,MAAM,MAAM,IAAI,WAAW,QAAQ,OAAO,IAAI,YAAY,QAAQ,QAAQ,IAAI,UAAU;AACxF,QAAM,KAAK,MAAM,MAAM,KAAK,IAAI,UAAU,IAAI,MAAM,QAAQ,EAAE,CAAC,OAAO,QAAQ,IAAI,KAAK,GAAG;;AAG5F,KAAI,OAAO,gBACT,OAAM,KACJ,wGACD;AAEH,QAAO,MAAM,KAAK,KAAK;;;;;;;;;AAUzB,SAAgB,mBACd,YACA,QACA,WACoB;CACpB,MAAM,QAAkB,EAAE;AAE1B,KAAI,eAAe,WAAW,SAAS,WAAW,cAAc;AAC9D,QAAM,KAAK,wBAAwB;AACnC,MAAI,WAAW,MAAO,OAAM,KAAK,YAAY,QAAQ,WAAW,MAAM,GAAG;AACzE,MAAI,WAAW,YAAa,OAAM,KAAK,KAAK,QAAQ,WAAW,YAAY,GAAG;AAC9E,MAAI,WAAW,MAAM,SAAS,EAC5B,OAAM,KAAK,YAAY,WAAW,MAAM,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,KAAK,KAAK,GAAG;;AAI7F,KAAI,OAAO,SAAS,GAAG;AACrB,MAAI,MAAM,SAAS,EAAG,OAAM,KAAK,GAAG;AACpC,QAAM,KACJ,uFACD;AACD,OAAK,MAAM,KAAK,OAAO,MAAM,GAAG,UAAU,CACxC,OAAM,KAAK,KAAK,EAAE,UAAU,IAAI,EAAE,KAAK,aAAa,EAAE,QAAQ,GAAG;AAEnE,MAAI,OAAO,SAAS,UAClB,OAAM,KAAK,UAAU,OAAO,OAAO,SAAS,UAAU,CAAC,oCAAoC;;AAI/F,KAAI,MAAM,WAAW,EAAG,QAAO,KAAA;AAC/B,QAAO,0BAA0B,MAAM,KAAK,KAAK,CAAC;;;;;;;;;;;;;AC3DpD,IAAa,cAAb,MAAyB;CACvB,YACE,QACA,QACA,QAIA;AANiB,OAAA,SAAA;AACA,OAAA,SAAA;AACA,OAAA,SAAA;;CAMnB,MAAM,gBAA6C;AACjD,MAAI,CAAC,KAAK,OAAO,cAAe,QAAO,KAAA;AAEvC,MAAI;GACF,MAAM,EAAE,WAAW,MAAM,KAAK,OAAO,YAAY;AACjD,OAAI,OAAO,WAAW,GAAG;AACvB,SAAK,OAAO,MAAM,yDAAyD;AAC3E;;GAGF,MAAM,WAAuC,OAAO,MAAM,MAAM,EAAE,cAAc,MAAM;GACtF,IAAI;AACJ,OAAI,SACF,KAAI;AACF,iBAAa,MAAM,KAAK,OAAO,gBAAgB,OAAO,SAAS,QAAQ;YAChE,KAAK;AACZ,SAAK,OAAO,MAAM,mDAAmD,EAAE,KAAK,OAAO,IAAI,EAAE,CAAC;;GAI9F,MAAM,QAAQ,mBAAmB,YAAY,QAAQ,KAAK,OAAO,UAAU;AAC3E,OAAI,MACF,MAAK,OAAO,MAAM,uCAAuC;IACvD,YAAY,OAAO;IACnB,eAAe,eAAe,KAAA;IAC/B,CAAC;AAEJ,UAAO;WACA,KAAK;AACZ,QAAK,OAAO,KAAK,oCAAoC,EAAE,KAAK,OAAO,IAAI,EAAE,CAAC;AAC1E;;;;;;;;;;;;;;;;;;;;;;;;AC1BN,MAAM,MADU,cAAc,OAAO,KAAK,IAAI,CAC1B,kBAAkB;AA+BtC,MAAM,iBAAkC;CACtC,eAAe;CACf,WAAW;CACZ;AAED,SAAS,oBAAoB,cAAyD;AACpF,QAAO;EACL,eACE,OAAO,cAAc,kBAAkB,YACnC,aAAa,gBACb,eAAe;EACrB,WACE,OAAO,cAAc,cAAc,WAAW,aAAa,YAAY,eAAe;EACzF;;AAGH,MAAM,kBAAkB;CACtB,MAAM;CACN,MAAM;EAAC;EAAO;EAAQ;EAAU;CAChC,aACE;CACH;AAED,SAAS,YAAY,OAAgD;AACnE,QAAO,UAAU,SAAS,UAAU,UAAU,UAAU,YAAY,QAAQ,KAAA;;AAG9E,MAAM,SAAS;CACb,IAAI;CACJ,MAAM;CACN,aAAa;CACb,SAAS,IAAI;CAEb,SAAS,KAAsB;EAC7B,MAAM,SAAS,oBAAoB,IAAI,aAAa;EACpD,MAAM,SAAS,IAAI;EAEnB,MAAM,aAAa,eAAe;EAClC,MAAM,SAAS,IAAI,eAAe;GAChC,QAAQ,WAAW;GACnB,QAAQ,WAAW;GACpB,CAAC;EAEF,MAAM,cAAc,IAAI,YAAY,QAAQ,QAAQ,OAAO;AAO3D,MAAI,oBAAoB;GACtB,MAAM;GACN,OAAO;GACP,aACE;GAKF,YAAY;IACV,MAAM;IACN,YAAY;KACV,OAAO;MAAE,MAAM;MAAU,aAAa;MAAsB;KAC5D,OAAO;MAAE,MAAM;MAAU,aAAa;MAAwC;KAC9E,WAAW;KACX,SAAS;MACP,MAAM;MACN,aAAa;MACd;KACF;IACD,UAAU,CAAC,QAAQ;IACpB;GACD,SAAS,OAAO,aAAqB,WAAoC;AACvE,QAAI;AAMF,YAAO,oBALQ,MAAM,OAAO,gBAAgB,OAAO,OAAiB;MAClE,OAAO,OAAO;MACd,WAAW,YAAY,OAAO,UAAU;MACxC,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,KAAA;MAChE,CAAC,CACgC;aAC3B,KAAK;AACZ,YAAO,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;GAG1F,GAAG,EAAE,OAAO,CAAC,kBAAkB,EAAE,CAAC;AAEnC,MAAI,oBAAoB;GACtB,MAAM;GACN,OAAO;GACP,aACE;GAEF,YAAY;IACV,MAAM;IACN,YAAY;KACV,WAAW;KACX,SAAS;MAAE,MAAM;MAAU,aAAa;MAA8C;KACtF,UAAU;MAAE,MAAM;MAAU,aAAa;MAA8D;KACxG;IACD,UAAU;KAAC;KAAa;KAAW;KAAW;IAC/C;GACD,SAAS,OAAO,aAAqB,WAAoC;IACvE,MAAM,YAAY,YAAY,OAAO,UAAU;AAC/C,QAAI,CAAC,UAAW,QAAO;AACvB,QAAI;KACF,MAAM,EAAE,SAAS,MAAM,OAAO,aAC5B,WACA,OAAO,SACP,OAAO,SACR;AACD,YAAO;aACA,KAAK;AACZ,YAAO,sBAAsB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;GAGlF,GAAG,EAAE,OAAO,CAAC,oBAAoB,EAAE,CAAC;AAErC,MAAI,oBAAoB;GACtB,MAAM;GACN,OAAO;GACP,aACE;GAIF,YAAY;IACV,MAAM;IACN,YAAY;KACV,WAAW;KACX,SAAS;MAAE,MAAM;MAAU,aAAa;MAA8C;KACtF,UAAU;MAAE,MAAM;MAAU,aAAa;MAAsD;KAC/F,SAAS;MAAE,MAAM;MAAU,aAAa;MAAoC;KAC5E,SAAS;MAAE,MAAM;MAAU,aAAa;MAAgD;KACzF;IACD,UAAU;KAAC;KAAa;KAAW;KAAY;KAAU;IAC1D;GACD,SAAS,OAAO,aAAqB,WAAoC;IACvE,MAAM,YAAY,YAAY,OAAO,UAAU;AAC/C,QAAI,CAAC,UAAW,QAAO;AACvB,QAAI;KACF,MAAM,EAAE,aAAa,MAAM,OAAO,cAChC,WACA,OAAO,SACP,OAAO,UACP,OAAO,SACP,EAAE,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,KAAA,GAAW,CAC7E;AACD,YAAO,aAAa,SAAS,MAAM,UAAU,GAAG,OAAO,OAAO,QAAQ,CAAC;aAChE,KAAK;AACZ,YAAO,sBAAsB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;GAGlF,GAAG,EAAE,OAAO,CAAC,qBAAqB,EAAE,CAAC;AAEtC,MAAI,oBAAoB;GACtB,MAAM;GACN,OAAO;GACP,aACE;GAIF,YAAY;IACV,MAAM;IACN,YAAY;KACV,WAAW;KACX,SAAS;MAAE,MAAM;MAAU,aAAa;MAA8C;KACtF,MAAM;MAAE,MAAM;MAAU,aAAa;MAAmD;KACzF;IACD,UAAU;KAAC;KAAa;KAAW;KAAO;IAC3C;GACD,SAAS,OAAO,aAAqB,WAAoC;IACvE,MAAM,YAAY,YAAY,OAAO,UAAU;AAC/C,QAAI,CAAC,UAAW,QAAO;AACvB,QAAI;AAMF,YAAO,mBALM,MAAM,OAAO,gBACxB,WACA,OAAO,SACP,OAAO,KACR,EAC6B,OAAO,MAAM,UAAU,GAAG,OAAO,OAAO,QAAQ,CAAC;aACxE,KAAK;AACZ,YAAO,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;GAGtF,GAAG,EAAE,OAAO,CAAC,sBAAsB,EAAE,CAAC;AAEvC,MAAI,oBAAoB;GACtB,MAAM;GACN,OAAO;GACP,aACE;GAEF,YAAY;IACV,MAAM;IACN,YAAY;KACV,WAAW;KACX,SAAS;MAAE,MAAM;MAAU,aAAa;MAA8C;KACvF;IACD,UAAU,CAAC,aAAa,UAAU;IACnC;GACD,SAAS,OAAO,aAAqB,WAAoC;IACvE,MAAM,YAAY,YAAY,OAAO,UAAU;AAC/C,QAAI,CAAC,UAAW,QAAO;AACvB,QAAI;KACF,MAAM,IAAI,MAAM,OAAO,gBAAgB,WAAW,OAAO,QAAkB;KAC3E,MAAM,QAAkB,CAAC,eAAe,UAAU,GAAG,OAAO,OAAO,QAAQ,GAAG;AAC9E,SAAI,EAAE,MAAO,OAAM,KAAK,UAAU,EAAE,QAAQ;AAC5C,SAAI,EAAE,YAAa,OAAM,KAAK,EAAE,YAAY;AAC5C,SAAI,EAAE,MAAM,SAAS,EACnB,OAAM,KAAK,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,KAAK,KAAK,GAAG;AAEhF,SAAI,MAAM,WAAW,EAAG,OAAM,KAAK,mBAAmB;AACtD,YAAO,MAAM,KAAK,KAAK;aAChB,KAAK;AACZ,YAAO,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;GAGvF,GAAG,EAAE,OAAO,CAAC,uBAAuB,EAAE,CAAC;AAExC,MAAI,oBAAoB;GACtB,MAAM;GACN,OAAO;GACP,aACE;GAEF,YAAY;IAAE,MAAM;IAAU,YAAY,EAAE;IAAE;GAC9C,SAAS,YAAY;AACnB,QAAI;KACF,MAAM,EAAE,WAAW,MAAM,OAAO,YAAY;AAC5C,SAAI,OAAO,WAAW,EAAG,QAAO;AAChC,YAAO,OACJ,KAAK,MAAM,KAAK,EAAE,UAAU,IAAI,EAAE,KAAK,aAAa,EAAE,QAAQ,GAAG,CACjE,KAAK,KAAK;aACN,KAAK;AACZ,YAAO,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;GAGrF,GAAG,EAAE,OAAO,CAAC,uBAAuB,EAAE,CAAC;AAKxC,MAAI,GAAG,sBAAsB,YAAY;GACvC,MAAM,QAAQ,MAAM,YAAY,eAAe;AAC/C,OAAI,MACF,QAAO,EAAE,gBAAgB,OAAO;KAGjC,EAAE,UAAU,IAAI,CAAC;AAEpB,SAAO,KAAK,kCAAkC,EAAE,eAAe,OAAO,eAAe,CAAC;;CAEzF"}
1
+ {"version":3,"file":"plugin2.js","names":[],"sources":["../src/formatter.ts","../src/context.ts","../src/plugin.ts"],"sourcesContent":["import type { KnowledgeScope, KnowledgeProfile, KnowledgeSearchResult } from \"./types.js\";\n\nconst SNIPPET_MAX = 240;\n\nfunction snippet(text: string): string {\n const clean = text.replace(/\\s+/g, \" \").trim();\n return clean.length > SNIPPET_MAX ? `${clean.slice(0, SNIPPET_MAX)}…` : clean;\n}\n\n/**\n * Format RAG search hits for a tool return. Every live hit is a document and\n * points at a `filePath` (already mirrored to `shared/<scope>/`) the agent can\n * read or edit. (Legacy fact vectors may still carry no filePath — they render\n * without a path rather than being special-cased.)\n */\nexport function formatSearchResults(result: KnowledgeSearchResult): string {\n if (result.results.length === 0) {\n return \"No matching knowledge found in your scopes.\";\n }\n\n const lines: string[] = [];\n for (const hit of result.results) {\n const where = `${hit.scopeType}:${hit.scopeId}`;\n const ref = `doc ${hit.filePath ?? \"?\"}`;\n lines.push(`- [${where}] (${ref}, score ${hit.score.toFixed(2)})\\n ${snippet(hit.text)}`);\n }\n\n if (result.truncatedScopes) {\n lines.push(\n \"(Note: you belong to more scopes than the search fan-out cap; narrow with scopeId to reach the rest.)\",\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Bounded session-start context: the org profile (the headline \"what is this\n * org\" surface) plus a NAME-ONLY list of every scope the agent belongs to.\n * Full per-scope profiles are intentionally NOT fetched here — the agent\n * pulls those on demand via `resource_get_profile`. Mirrors the memory\n * plugin's tiered/capped auto-recall.\n */\nexport function formatContextBlock(\n orgProfile: KnowledgeProfile | undefined,\n scopes: KnowledgeScope[],\n maxScopes: number,\n): string | undefined {\n const parts: string[] = [];\n\n if (orgProfile && (orgProfile.about || orgProfile.description)) {\n parts.push(\"Organization profile:\");\n if (orgProfile.about) parts.push(`- About: ${snippet(orgProfile.about)}`);\n if (orgProfile.description) parts.push(`- ${snippet(orgProfile.description)}`);\n if (orgProfile.links.length > 0) {\n parts.push(`- Links: ${orgProfile.links.map((l) => `${l.label} (${l.url})`).join(\", \")}`);\n }\n }\n\n if (scopes.length > 0) {\n if (parts.length > 0) parts.push(\"\");\n parts.push(\n \"Your knowledge scopes (use resource_search / resource_get_profile with the scopeId):\",\n );\n for (const s of scopes.slice(0, maxScopes)) {\n parts.push(`- ${s.scopeType}: ${s.name} [scopeId: ${s.scopeId}]`);\n }\n if (scopes.length > maxScopes) {\n parts.push(`- …and ${String(scopes.length - maxScopes)} more (call resource_list_scopes).`);\n }\n }\n\n if (parts.length === 0) return undefined;\n return `<knowledge-resources>\\n${parts.join(\"\\n\")}\\n</knowledge-resources>`;\n}\n","import { formatContextBlock } from \"./formatter.js\";\nimport type { KnowledgeApi, KnowledgeConfig, KnowledgeScope } from \"./types.js\";\n\n/**\n * Loads bounded knowledge context at session start: the org profile plus a\n * name-only list of the agent's scopes. Called from `before_agent_start`.\n *\n * Deliberately cheap and bounded — one `listScopes` call plus (at most) one\n * org-profile fetch. Full per-scope profiles are NOT loaded; the agent pulls\n * those on demand. Membership is fixed at connect time, so this is sent once\n * per session and is stale until reconnect (mirrors SHARED_SCOPES).\n */\nexport class AutoContext {\n constructor(\n private readonly client: KnowledgeApi,\n private readonly config: KnowledgeConfig,\n private readonly logger: {\n debug: (msg: string, ctx?: Record<string, unknown>) => void;\n warn: (msg: string, ctx?: Record<string, unknown>) => void;\n },\n ) {}\n\n async loadForPrompt(): Promise<string | undefined> {\n if (!this.config.injectContext) return undefined;\n\n try {\n const { scopes } = await this.client.listScopes();\n if (scopes.length === 0) {\n this.logger.debug(\"No knowledge scopes for agent; skipping context inject\");\n return undefined;\n }\n\n const orgScope: KnowledgeScope | undefined = scopes.find((s) => s.scopeType === \"org\");\n let orgProfile;\n if (orgScope) {\n try {\n orgProfile = await this.client.getScopeProfile(\"org\", orgScope.scopeId);\n } catch (err) {\n this.logger.debug(\"Org profile fetch failed; injecting scopes only\", { err: String(err) });\n }\n }\n\n const block = formatContextBlock(orgProfile, scopes, this.config.maxScopes);\n if (block) {\n this.logger.debug(\"Loaded knowledge context for prompt\", {\n scopeCount: scopes.length,\n hasOrgProfile: orgProfile !== undefined,\n });\n }\n return block;\n } catch (err) {\n this.logger.warn(\"Failed to load knowledge context\", { err: String(err) });\n return undefined;\n }\n }\n}\n","/**\n * knowledge — OpenClaw knowledge-resources extension.\n *\n * Scoped, searchable knowledge ABOUT a thing being worked on (org / team /\n * project). Distinct from private per-agent memory: this is SHARED,\n * attributable, deliberate-publish knowledge that humans and other agents\n * read and contribute to.\n *\n * Shared knowledge is curated DOCUMENTS (+ scope profiles). There is no\n * free-floating \"fact\" primitive: an agent that learns something durable\n * finds the right document and edits it in (search → read_doc → write_doc /\n * propose_change). Quick or uncertain capture belongs in the agent's private\n * per-agent memory, not shared knowledge.\n *\n * Backed by:\n * - services/knowledge — RAG search (vector index over docs)\n * - services/org — system of record + per-agent membership gate\n * (docs, profile)\n *\n * Registers:\n * - Tools: resource_search, resource_read_doc, resource_write_doc,\n * resource_get_profile, resource_list_scopes,\n * resource_propose_change, resource_list_change_requests\n * - Lifecycle hook: before_agent_start (bounded org-profile + scope-list inject)\n *\n * NOTE: every tool name here MUST also appear in `openclaw.plugin.json`'s\n * `contracts.tools` allowlist — OpenClaw (2026.5+) gates the model's tool\n * payload on that strict-literal list, so a registered-but-unlisted tool is\n * invisible to the LLM.\n */\n\nimport { resolveConfig } from \"@alfe.ai/config\";\nimport { AgentApiClient } from \"@alfe.ai/agent-api-client\";\nimport { AutoContext } from \"./context.js\";\nimport { formatSearchResults } from \"./formatter.js\";\nimport type { KnowledgeConfig, KnowledgeScopeType } from \"./types.js\";\nimport { createRequire } from \"node:module\";\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\n// OpenClaw plugin types — match the OpenClaw plugin API contract.\ninterface PluginApi {\n pluginConfig?: Record<string, unknown>;\n config: Record<string, unknown>;\n logger: {\n info: (msg: string, ctx?: Record<string, unknown>) => void;\n debug: (msg: string, ctx?: Record<string, unknown>) => void;\n warn: (msg: string, ctx?: Record<string, unknown>) => void;\n error: (msg: string, ctx?: Record<string, unknown>) => void;\n };\n registerTool: (factory: (ctx: ToolContext) => Tool, opts?: { names?: string[] }) => void;\n on: (hookName: string, handler: (...args: unknown[]) => unknown, opts?: { priority?: number }) => void;\n}\n\ninterface ToolContext {\n agentId?: string;\n sessionKey?: string;\n sessionId?: string;\n messageChannel?: string;\n}\n\ninterface Tool {\n name: string;\n label: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;\n}\n\nconst DEFAULT_CONFIG: KnowledgeConfig = {\n injectContext: true,\n maxScopes: 25,\n};\n\nfunction resolvePluginConfig(pluginConfig?: Record<string, unknown>): KnowledgeConfig {\n return {\n injectContext:\n typeof pluginConfig?.injectContext === \"boolean\"\n ? pluginConfig.injectContext\n : DEFAULT_CONFIG.injectContext,\n maxScopes:\n typeof pluginConfig?.maxScopes === \"number\" ? pluginConfig.maxScopes : DEFAULT_CONFIG.maxScopes,\n };\n}\n\nconst SCOPE_TYPE_PROP = {\n type: \"string\",\n enum: [\"org\", \"team\", \"project\"],\n description:\n \"Scope kind. For 'org' the scopeId is the tenantId. Get exact scopeIds from resource_list_scopes.\",\n};\n\nfunction asScopeType(value: unknown): KnowledgeScopeType | undefined {\n return value === \"org\" || value === \"team\" || value === \"project\" ? value : undefined;\n}\n\nconst plugin = {\n id: \"@alfe.ai/openclaw-knowledge\",\n name: \"Knowledge Resources\",\n description: \"Scoped, searchable org/team/project knowledge — curated docs and profiles\",\n version: pkg.version,\n\n register(api: PluginApi): void {\n const config = resolvePluginConfig(api.pluginConfig);\n const logger = api.logger;\n\n const alfeConfig = resolveConfig();\n const client = new AgentApiClient({\n apiKey: alfeConfig.apiKey,\n apiUrl: alfeConfig.apiUrl,\n });\n\n const autoContext = new AutoContext(client, config, logger);\n\n // ─── Tools ──────────────────────────────────────────────────\n // Registered SYNCHRONOUSLY inside register() so they reach the LLM on\n // the startup pass (deferring behind a promise makes them startup-only,\n // invisible to the model).\n\n api.registerTool(() => ({\n name: \"resource_search\",\n label: \"Resource Search\",\n description:\n \"Semantically search shared knowledge documents across the org, teams, and projects you belong to. \" +\n \"Each hit points at the doc's filePath so you can read or edit it. \" +\n \"Docs are ALSO already synced to your workspace under shared/<scope>/, so for a small corpus just read those files directly — \" +\n \"search earns its keep on LARGE doc corpora. \" +\n \"Pass scopeType+scopeId to narrow to one scope; omit to search all your scopes.\",\n parameters: {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"What to search for\" },\n limit: { type: \"number\", description: \"Maximum results (default 10, max 50)\" },\n scopeType: SCOPE_TYPE_PROP,\n scopeId: {\n type: \"string\",\n description: \"Narrow to one scope (from resource_list_scopes). Omit to search all.\",\n },\n },\n required: [\"query\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n try {\n const result = await client.knowledgeSearch(params.query as string, {\n limit: params.limit as number | undefined,\n scopeType: asScopeType(params.scopeType),\n scopeId: typeof params.scopeId === \"string\" ? params.scopeId : undefined,\n });\n return formatSearchResults(result);\n } catch (err) {\n return `Error searching knowledge: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_search\"] });\n\n api.registerTool(() => ({\n name: \"resource_read_doc\",\n label: \"Resource Read Doc\",\n description:\n \"Read the full text of a knowledge doc in a scope. Docs are also mirrored to shared/<scope>/<filePath> in your \" +\n \"workspace — prefer reading that local file when it exists; use this when you only have a search hit's filePath.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n filePath: { type: \"string\", description: \"The doc's path within the scope (e.g. notes/datacenter.md)\" },\n },\n required: [\"scopeType\", \"scopeId\", \"filePath\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const { text } = await client.readScopeDoc(\n scopeType,\n params.scopeId as string,\n params.filePath as string,\n );\n return text;\n } catch (err) {\n return `Error reading doc: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_read_doc\"] });\n\n api.registerTool(() => ({\n name: \"resource_write_doc\",\n label: \"Resource Write Doc\",\n description:\n \"Create or overwrite a knowledge DOC in a scope. Docs are the ONLY unit of shared knowledge: when you learn something \" +\n \"durable, find the right existing doc (resource_search → resource_read_doc) and edit it in, or create a new one if none \" +\n \"fits — do NOT scatter free-floating notes. Use a DOC for durable, reshaped, human-readable prose that grows and is \" +\n \"curated over time — runbooks, designs, onboarding notes, an evolving description of a system. Writing is a DELIBERATE \" +\n \"publish into shared, attributable knowledge; quick or uncertain capture belongs in your private memory instead. \" +\n \"Overwriting an existing path creates a new revision (history is kept).\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n filePath: { type: \"string\", description: \"Path within the scope, e.g. designs/data-center.md\" },\n content: { type: \"string\", description: \"Full markdown content of the doc\" },\n message: { type: \"string\", description: \"Optional revision note describing the change\" },\n },\n required: [\"scopeType\", \"scopeId\", \"filePath\", \"content\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const { filePath } = await client.writeScopeDoc(\n scopeType,\n params.scopeId as string,\n params.filePath as string,\n params.content as string,\n { message: typeof params.message === \"string\" ? params.message : undefined },\n );\n return `Wrote doc ${filePath} to ${scopeType}:${String(params.scopeId)}.`;\n } catch (err) {\n return `Error writing doc: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_write_doc\"] });\n\n api.registerTool(() => ({\n name: \"resource_get_profile\",\n label: \"Resource Get Profile\",\n description:\n \"Fetch the structured profile (about / description / links) for one scope. Use this to learn what a specific team or \" +\n \"project is, on demand — the session-start context only lists scope names, not their full profiles.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (from resource_list_scopes)\" },\n },\n required: [\"scopeType\", \"scopeId\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n try {\n const p = await client.getScopeProfile(scopeType, params.scopeId as string);\n const lines: string[] = [`Profile for ${scopeType}:${String(params.scopeId)}`];\n if (p.about) lines.push(`About: ${p.about}`);\n if (p.description) lines.push(p.description);\n if (p.links.length > 0) {\n lines.push(`Links: ${p.links.map((l) => `${l.label} (${l.url})`).join(\", \")}`);\n }\n if (lines.length === 1) lines.push(\"(no profile set)\");\n return lines.join(\"\\n\");\n } catch (err) {\n return `Error fetching profile: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_get_profile\"] });\n\n api.registerTool(() => ({\n name: \"resource_list_scopes\",\n label: \"Resource List Scopes\",\n description:\n \"List every knowledge scope you belong to (org + teams + projects) with each scope's exact scopeId. \" +\n \"Call this to discover the scopeId to pass to the other resource_* tools (for 'org', scopeId is the tenantId).\",\n parameters: { type: \"object\", properties: {} },\n execute: async () => {\n try {\n const { scopes } = await client.listScopes();\n if (scopes.length === 0) return \"You do not belong to any knowledge scopes.\";\n return scopes\n .map((s) => `- ${s.scopeType}: ${s.name} [scopeId: ${s.scopeId}]`)\n .join(\"\\n\");\n } catch (err) {\n return `Error listing scopes: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_list_scopes\"] });\n\n api.registerTool(() => ({\n name: \"resource_propose_change\",\n label: \"Resource Propose Change\",\n description:\n \"Propose a change to a scope's shared knowledge (doc / profile) WITHOUT writing it directly. \" +\n \"Use this ONLY as the contribution path to a scope you are NOT a member of — resource_write_doc \" +\n \"will be refused there, but a proposal is always allowed. If you ARE a member of the \" +\n \"scope (it appears in resource_list_scopes), just write directly instead; proposing would be redundant. \" +\n \"A proposal is INERT: nothing changes until a reviewer with authority over the scope approves it, so this \" +\n \"does not grant you any read access to the scope. Always give a clear `rationale` — the reviewer sees it.\\n\" +\n \"Payload by resourceType:\\n\" +\n \" • doc — set operation (create|update|delete), targetPath (e.g. designs/data-center.md); for create/update \" +\n \"also pass the full markdown `content`.\\n\" +\n \" • profile — operation is always 'update'; pass any of about / description / links.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The target scope's id (for 'org' this is the tenantId)\" },\n resourceType: {\n type: \"string\",\n enum: [\"doc\", \"profile\"],\n description: \"Which knowledge resource the proposal targets\",\n },\n operation: {\n type: \"string\",\n enum: [\"create\", \"update\", \"delete\"],\n description: \"The proposed operation (profile supports 'update' only)\",\n },\n rationale: {\n type: \"string\",\n description: \"Why this change should be made — shown to the reviewer\",\n },\n targetPath: { type: \"string\", description: \"doc only: path within the scope, e.g. notes/runbook.md\" },\n content: { type: \"string\", description: \"doc create/update only: full markdown content of the proposed doc\" },\n about: { type: \"string\", description: \"profile update only: the scope's 'about' line\" },\n description: { type: \"string\", description: \"profile update only: the scope's longer description\" },\n links: {\n type: \"array\",\n description: \"profile update only: list of { label, url } links\",\n items: {\n type: \"object\",\n properties: {\n label: { type: \"string\" },\n url: { type: \"string\" },\n },\n required: [\"label\", \"url\"],\n },\n },\n },\n required: [\"scopeType\", \"scopeId\", \"resourceType\", \"operation\", \"rationale\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n const resourceType = params.resourceType;\n if (resourceType !== \"doc\" && resourceType !== \"profile\") {\n return \"Error: resourceType must be one of doc, profile.\";\n }\n const operation = params.operation;\n if (operation !== \"create\" && operation !== \"update\" && operation !== \"delete\") {\n return \"Error: operation must be one of create, update, delete.\";\n }\n const rationale = typeof params.rationale === \"string\" ? params.rationale : \"\";\n if (!rationale) return \"Error: rationale is required.\";\n\n // Build the per-type proposed value.\n let proposedValue: unknown;\n if (resourceType === \"profile\") {\n const links = Array.isArray(params.links) ? params.links : undefined;\n proposedValue = {\n about: typeof params.about === \"string\" ? params.about : undefined,\n description: typeof params.description === \"string\" ? params.description : undefined,\n links,\n };\n }\n\n try {\n const cr = await client.proposeScopeChange(scopeType, params.scopeId as string, {\n resourceType,\n operation,\n rationale,\n targetPath: typeof params.targetPath === \"string\" ? params.targetPath : undefined,\n content: typeof params.content === \"string\" ? params.content : undefined,\n proposedValue,\n });\n return (\n `Proposed ${cr.resourceType} ${cr.operation} to ${scopeType}:${String(params.scopeId)} ` +\n `(change request ${cr.changeRequestId}, status ${cr.status}). ` +\n `It is inert until a scope reviewer approves it.`\n );\n } catch (err) {\n return `Error proposing change: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_propose_change\"] });\n\n api.registerTool(() => ({\n name: \"resource_list_change_requests\",\n label: \"Resource List Change Requests\",\n description:\n \"List the change requests YOU have proposed in a scope, to track whether they are still open, approved, \" +\n \"rejected, or withdrawn. Optionally filter by status. Only your own proposals are returned.\",\n parameters: {\n type: \"object\",\n properties: {\n scopeType: SCOPE_TYPE_PROP,\n scopeId: { type: \"string\", description: \"The scope's id (for 'org' this is the tenantId)\" },\n status: {\n type: \"string\",\n enum: [\"open\", \"approved\", \"rejected\", \"withdrawn\", \"superseded\"],\n description: \"Optional: only return proposals in this status\",\n },\n },\n required: [\"scopeType\", \"scopeId\"],\n },\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n const scopeType = asScopeType(params.scopeType);\n if (!scopeType) return \"Error: scopeType must be one of org, team, project.\";\n const status = params.status;\n try {\n const { changeRequests } = await client.listScopeChangeRequests(\n scopeType,\n params.scopeId as string,\n {\n status:\n status === \"open\" ||\n status === \"approved\" ||\n status === \"rejected\" ||\n status === \"withdrawn\" ||\n status === \"superseded\"\n ? status\n : undefined,\n },\n );\n if (changeRequests.length === 0) return \"You have no change requests in this scope.\";\n return changeRequests\n .map((cr) => {\n const target = cr.targetPath ?? cr.resourceType;\n return `- ${cr.changeRequestId} [${cr.status}] ${cr.resourceType} ${cr.operation} → ${target}`;\n })\n .join(\"\\n\");\n } catch (err) {\n return `Error listing change requests: ${err instanceof Error ? err.message : String(err)}`;\n }\n },\n }), { names: [\"resource_list_change_requests\"] });\n\n // ─── Lifecycle hook ─────────────────────────────────────────\n // Inject bounded knowledge context (org profile + name-only scope list)\n // at session start. Full per-scope profiles are fetched on demand.\n api.on(\"before_agent_start\", async () => {\n const block = await autoContext.loadForPrompt();\n if (block) {\n return { prependContext: block };\n }\n return undefined;\n }, { priority: 10 });\n\n logger.info(\"knowledge extension registered\", { injectContext: config.injectContext });\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "@alfe.ai/openclaw-knowledge",
3
3
  "name": "Knowledge Resources",
4
- "description": "Scoped, searchable knowledge about your org, teams, and projects — read/search/contribute docs, facts, and profiles. Distinct from private per-agent memory: this is shared, attributable, deliberate-publish knowledge.",
4
+ "description": "Scoped, searchable knowledge about your org, teams, and projects — read/search/contribute curated docs and profiles. Distinct from private per-agent memory: this is shared, attributable, deliberate-publish knowledge.",
5
5
  "entry": "./dist/plugin.js",
6
6
  "activation": { "onStartup": true },
7
7
  "contracts": {
@@ -9,9 +9,10 @@
9
9
  "resource_search",
10
10
  "resource_read_doc",
11
11
  "resource_write_doc",
12
- "resource_write_fact",
13
12
  "resource_get_profile",
14
- "resource_list_scopes"
13
+ "resource_list_scopes",
14
+ "resource_propose_change",
15
+ "resource_list_change_requests"
15
16
  ]
16
17
  },
17
18
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-knowledge",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "OpenClaw knowledge-resources plugin — scoped, searchable org/team/project knowledge (docs, facts, profile)",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -27,7 +27,7 @@
27
27
  "openclaw.plugin.json"
28
28
  ],
29
29
  "dependencies": {
30
- "@alfe.ai/agent-api-client": "0.5.0",
30
+ "@alfe.ai/agent-api-client": "0.6.1",
31
31
  "@alfe.ai/config": "0.3.0"
32
32
  },
33
33
  "license": "UNLICENSED",