@mercury-fw/core 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +38 -0
  3. package/dist/index.d.ts +23 -0
  4. package/dist/src/admin/cli-routes.d.ts +22 -0
  5. package/dist/src/admin/env-file.d.ts +1 -0
  6. package/dist/src/admin/model-routes.d.ts +26 -0
  7. package/dist/src/admin/qdrant-scroll.d.ts +34 -0
  8. package/dist/src/admin/server.d.ts +40 -0
  9. package/dist/src/admin/wiki-routes.d.ts +31 -0
  10. package/dist/src/compose.d.ts +42 -0
  11. package/dist/src/config/define-config.d.ts +31 -0
  12. package/dist/src/cron/idle-session-cron.d.ts +80 -0
  13. package/dist/src/cron/idle-session-scanner.d.ts +16 -0
  14. package/dist/src/cron/self-review-cron.d.ts +55 -0
  15. package/dist/src/cron/semantic-consolidation.d.ts +71 -0
  16. package/dist/src/memory/embedder.d.ts +9 -0
  17. package/dist/src/memory/episodic-store.d.ts +121 -0
  18. package/dist/src/memory/memory-provider.d.ts +51 -0
  19. package/dist/src/memory/semantic-facts-store.d.ts +37 -0
  20. package/dist/src/memory/tool-corrections-store.d.ts +26 -0
  21. package/dist/src/memory/verbatim-archive-store.d.ts +86 -0
  22. package/dist/src/model/client.d.ts +24 -0
  23. package/dist/src/model/context-size.d.ts +30 -0
  24. package/dist/src/plugins/manifest.d.ts +29 -0
  25. package/dist/src/plugins/plugin-loader.d.ts +85 -0
  26. package/dist/src/router/channel-loader.d.ts +30 -0
  27. package/dist/src/router/provider.d.ts +7 -0
  28. package/dist/src/router/terminal-provider.d.ts +37 -0
  29. package/dist/src/router/terminal.d.ts +41 -0
  30. package/dist/src/router/tool-log.d.ts +65 -0
  31. package/dist/src/router/turn-runner.d.ts +86 -0
  32. package/dist/src/session/agent-turn.d.ts +266 -0
  33. package/dist/src/session/context-primer.d.ts +16 -0
  34. package/dist/src/session/episodic-summarizer.d.ts +25 -0
  35. package/dist/src/session/history.d.ts +95 -0
  36. package/dist/src/session/pending-confirmation.d.ts +8 -0
  37. package/dist/src/session/read-skill-tool.d.ts +4 -0
  38. package/dist/src/session/semantic-fact-extractor.d.ts +45 -0
  39. package/dist/src/session/step-info.d.ts +24 -0
  40. package/dist/src/session/summarizer.d.ts +23 -0
  41. package/dist/src/session/system-prompt.d.ts +38 -0
  42. package/dist/src/session/tool-correction-extractor.d.ts +43 -0
  43. package/dist/src/session/tool-log-buffer.d.ts +24 -0
  44. package/dist/src/session/tool-log-recall-tool.d.ts +18 -0
  45. package/dist/src/session/tool-start-hook.d.ts +57 -0
  46. package/dist/src/tools/display-store.d.ts +36 -0
  47. package/dist/src/tools/present-tool.d.ts +23 -0
  48. package/dist/src/wiki/frontmatter-schema.d.ts +53 -0
  49. package/dist/src/wiki/index-entry.d.ts +15 -0
  50. package/dist/src/wiki/orphan-detector.d.ts +1 -0
  51. package/dist/src/wiki/self-review-runner.d.ts +48 -0
  52. package/dist/src/wiki/self-review-tools.d.ts +22 -0
  53. package/dist/src/wiki/vault-cli.d.ts +2 -0
  54. package/dist/src/wiki/vault-init.d.ts +7 -0
  55. package/dist/src/wiki/wiki-note.d.ts +62 -0
  56. package/dist/src/wiki/wiki-read.d.ts +27 -0
  57. package/dist/src/wiki/wiki-tools.d.ts +7 -0
  58. package/index.ts +23 -0
  59. package/package.json +49 -0
  60. package/src/admin/cli-routes.ts +48 -0
  61. package/src/admin/env-file.ts +29 -0
  62. package/src/admin/model-routes.ts +71 -0
  63. package/src/admin/public/index.html +416 -0
  64. package/src/admin/qdrant-scroll.ts +45 -0
  65. package/src/admin/server.ts +188 -0
  66. package/src/admin/wiki-routes.ts +93 -0
  67. package/src/compose.ts +599 -0
  68. package/src/config/define-config.ts +35 -0
  69. package/src/cron/.gitkeep +0 -0
  70. package/src/cron/idle-session-cron.ts +144 -0
  71. package/src/cron/idle-session-scanner.ts +37 -0
  72. package/src/cron/self-review-cron.ts +103 -0
  73. package/src/cron/semantic-consolidation.ts +228 -0
  74. package/src/memory/.gitkeep +0 -0
  75. package/src/memory/embedder.ts +15 -0
  76. package/src/memory/episodic-store.ts +183 -0
  77. package/src/memory/memory-provider.ts +98 -0
  78. package/src/memory/semantic-facts-store.ts +89 -0
  79. package/src/memory/tool-corrections-store.ts +72 -0
  80. package/src/memory/verbatim-archive-store.ts +202 -0
  81. package/src/model/client.ts +33 -0
  82. package/src/model/context-size.ts +42 -0
  83. package/src/plugins/manifest.ts +47 -0
  84. package/src/plugins/plugin-loader.ts +205 -0
  85. package/src/router/channel-loader.ts +56 -0
  86. package/src/router/provider.ts +7 -0
  87. package/src/router/terminal-provider.ts +155 -0
  88. package/src/router/terminal.ts +151 -0
  89. package/src/router/tool-log.ts +116 -0
  90. package/src/router/turn-runner.ts +205 -0
  91. package/src/session/agent-turn.ts +391 -0
  92. package/src/session/context-primer.ts +134 -0
  93. package/src/session/episodic-summarizer.ts +38 -0
  94. package/src/session/history.ts +168 -0
  95. package/src/session/pending-confirmation.ts +8 -0
  96. package/src/session/read-skill-tool.ts +38 -0
  97. package/src/session/semantic-fact-extractor.ts +69 -0
  98. package/src/session/step-info.ts +27 -0
  99. package/src/session/summarizer.ts +36 -0
  100. package/src/session/system-prompt.ts +142 -0
  101. package/src/session/tool-correction-extractor.ts +133 -0
  102. package/src/session/tool-log-buffer.ts +73 -0
  103. package/src/session/tool-log-recall-tool.ts +38 -0
  104. package/src/session/tool-start-hook.ts +164 -0
  105. package/src/tools/display-store.ts +89 -0
  106. package/src/tools/present-tool.ts +41 -0
  107. package/src/wiki/.gitkeep +0 -0
  108. package/src/wiki/frontmatter-schema.ts +49 -0
  109. package/src/wiki/index-entry.ts +59 -0
  110. package/src/wiki/orphan-detector.ts +61 -0
  111. package/src/wiki/self-review-runner.ts +133 -0
  112. package/src/wiki/self-review-tools.ts +162 -0
  113. package/src/wiki/vault-cli.ts +143 -0
  114. package/src/wiki/vault-init.ts +43 -0
  115. package/src/wiki/wiki-note.ts +326 -0
  116. package/src/wiki/wiki-read.ts +122 -0
  117. package/src/wiki/wiki-tools.ts +112 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Thin glue that turns `createSessionHistory`'s injected `summarize`
3
+ * dependency into a real LLM call.
4
+ *
5
+ * Kept separate from `history.ts` on purpose: `history.ts`'s threshold/
6
+ * merge/replace logic is the substantial part and is fully unit-tested
7
+ * with a fake summarizer; this file's only job is producing the prompt
8
+ * and calling `generateText`, which isn't worth mocking deeply for one
9
+ * line of glue.
10
+ *
11
+ * Used by: `src/index.ts` (wiring), which passes the result into
12
+ * `createSessionHistory` (see `src/session/history.ts`).
13
+ */
14
+ import { type LanguageModel } from "ai";
15
+ import type { Message } from "./history.ts";
16
+ /**
17
+ * Returns a function matching `createSessionHistory`'s `summarize`
18
+ * signature, backed by `model`. The returned function asks the model to
19
+ * condense the given messages into a short summary that preserves
20
+ * names, ticket keys, and decisions — the things a future turn would
21
+ * otherwise lose once the raw messages are cleared.
22
+ */
23
+ export declare function createSummarizer(model: LanguageModel): (messages: Message[]) => Promise<string>;
@@ -0,0 +1,38 @@
1
+ import type { Skill } from "@mercury-fw/plugin-types";
2
+ /** The assistant's persona, set by the instance: `identity` opens the system
3
+ * prompt, `tone` closes it. Either one left out falls back to its default. */
4
+ export type Persona = {
5
+ identity?: string;
6
+ tone?: string;
7
+ };
8
+ /** The identity an instance gets when its config sets none. */
9
+ export declare const DEFAULT_PERSONA_IDENTITY = "You are Mercury, an internal assistant.";
10
+ /** The tone an instance gets when its config sets none. */
11
+ export declare const DEFAULT_PERSONA_TONE: string;
12
+ /**
13
+ * Builds a system prompt that only describes tools actually present in
14
+ * `tools` (see `src/session/agent-turn.ts` for why a prompt mentioning
15
+ * an absent tool is a real bug, not a harmless no-op). The persona fills the
16
+ * first and last slots (see `personaSlot`).
17
+ */
18
+ export declare function buildSystemPrompt(opts: {
19
+ pluginFragments: string[];
20
+ skills: Skill[];
21
+ multiUserChannel: boolean;
22
+ persona?: Persona;
23
+ }): string;
24
+ /**
25
+ * Builds the instance's two system prompts from the same plugins and persona:
26
+ * `system` for 1:1 channels (the terminal, the HTTP surface) and `chatSystem`
27
+ * for shared spaces, the only one that carries the multi-user clause — an
28
+ * operator typing normally must never get a NO_REPLY meant for a shared space.
29
+ */
30
+ export declare function buildSystemPrompts(opts: {
31
+ pluginFragments: string[];
32
+ skills: Skill[];
33
+ /** Required even when undefined, so a caller can't silently drop the instance's persona. */
34
+ persona: Persona | undefined;
35
+ }): {
36
+ system: string;
37
+ chatSystem: string;
38
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Detects, within a single turn's steps, a CLI command call that failed
3
+ * followed later by one for the same binary that succeeded — a candidate
4
+ * procedural correction, distinct from `semantic-fact-extractor.ts` (which
5
+ * is about the user, from `session.messages`) — this is about a *tool*,
6
+ * from the tool-call trace (`StepInfo`), true for whoever uses that
7
+ * command next, not just this user. Pairing is deterministic (pure
8
+ * string/status matching); describing *what* the correction actually was
9
+ * is delegated to the model, same `generateObject`-with-fixed-schema style
10
+ * as `semantic-fact-extractor.ts`.
11
+ *
12
+ * Wired per-turn (`onStepFinish`, see `index.ts`), not from
13
+ * `idle-session-cron.ts`'s idle sweep: the tool-call trace for a turn only
14
+ * exists in memory for the duration of that turn (`tool-log-buffer.ts` is a
15
+ * 200-entry ring buffer shared across every session — not a reliable place
16
+ * to reconstruct one turn's trace minutes or hours later).
17
+ */
18
+ import { type LanguageModel } from "ai";
19
+ import { z } from "zod";
20
+ import type { StepInfo } from "./step-info.ts";
21
+ export declare const ProceduralCorrectionCandidateSchema: z.ZodObject<{
22
+ topic: z.ZodString;
23
+ value: z.ZodString;
24
+ }, z.core.$strip>;
25
+ export type ProceduralCorrection = {
26
+ tool: string;
27
+ topic: string;
28
+ value: string;
29
+ };
30
+ type GenerateObjectFn = (params: {
31
+ model: LanguageModel;
32
+ output: "array";
33
+ schema: typeof ProceduralCorrectionCandidateSchema;
34
+ instructions: string;
35
+ prompt: string;
36
+ }) => Promise<{
37
+ object: z.infer<typeof ProceduralCorrectionCandidateSchema>[];
38
+ }>;
39
+ /** Returns a function that extracts candidate procedural corrections from a single turn's steps. */
40
+ export declare function createToolCorrectionExtractor(model: LanguageModel, generateObjectFn?: GenerateObjectFn, deps?: {
41
+ log?: (msg: string) => void;
42
+ }): (steps: StepInfo[]) => Promise<ProceduralCorrection[]>;
43
+ export {};
@@ -0,0 +1,24 @@
1
+ import type { StepInfo } from "./step-info.ts";
2
+ /**
3
+ * Whatever the provider that ran the turn calls itself — see
4
+ * `InboundTurn.channel` in `src/router/provider.ts`. Was a closed union
5
+ * ("terminal" | "google-chat") back when the set of providers was fixed in
6
+ * this file; widened so a new provider is a new `Provider` implementation,
7
+ * not an edit here.
8
+ */
9
+ export type ToolLogChannel = string;
10
+ export type ToolLogEntry = {
11
+ timestamp: string;
12
+ channel: ToolLogChannel;
13
+ sessionKey: string;
14
+ toolName: string;
15
+ input: string;
16
+ output: string;
17
+ };
18
+ export declare function recordStep(channel: ToolLogChannel, sessionKey: string, step: StepInfo): void;
19
+ /** Most recent entries first, optionally restricted to one session. */
20
+ export declare function getToolLog(filter?: {
21
+ sessionKey?: string;
22
+ }): ToolLogEntry[];
23
+ /** Test-only: clears the module-level buffer so tests don't leak into each other. */
24
+ export declare function resetToolLogForTest(): void;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Model-invocable recall of this conversation's own past tool calls —
3
+ * same split as `wiki-tools.ts` wrapping plain functions in `tool()`.
4
+ * Exists because `SessionHistory` (`history.ts`) only ever persists the
5
+ * final assistant text of a turn, never the tool-call trace: asked to
6
+ * recall exactly what it ran, the model otherwise has no ground truth in
7
+ * its own context and reconstructs a plausible-looking (and sometimes
8
+ * wrong) answer instead of quoting the real one. Backed by
9
+ * `tool-log-buffer.ts`, filtered to the calling session only — never
10
+ * another conversation's history.
11
+ */
12
+ import type { ExecutableTool } from "@mercury-fw/plugin-types";
13
+ export type ToolLogRecallDeps = {
14
+ sessionKey: string;
15
+ };
16
+ export declare function createToolLogRecallTool(deps: ToolLogRecallDeps): {
17
+ recall_tool_calls: ExecutableTool;
18
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Wraps every tool's `execute` so `onToolStart` fires the moment a call
3
+ * actually begins (before its result is ready), instead of the existing
4
+ * `onStepFinish` visibility (`tool-log.ts`) which only reports after a step
5
+ * already finished, and `onToolFinish` fires once it settles. Used by
6
+ * `src/index.ts`'s `buildTools` for every channel — both terminal and
7
+ * Google Chat always supply a real `onToolStart` (`TurnSink` in
8
+ * `router/provider.ts`), so both are always wrapped.
9
+ *
10
+ * Every wrapped tool's `execute` calls share one `chain` (below), so at
11
+ * most one tool call runs at a time turn-wide — the AI SDK otherwise runs
12
+ * a step's tool calls concurrently (`Promise.all`), which this codebase
13
+ * has never actually relied on; serializing keeps Google Chat's status
14
+ * card for a given `toolCallId` (see `google-chat-provider.ts`) from ever
15
+ * having to handle an overlapping in-flight card.
16
+ */
17
+ import type { Tool } from "ai";
18
+ /**
19
+ * Describes, in one short user-facing sentence, what a tool call is about to do
20
+ * — no arguments/queries shown. A tool a plugin contributed carries its own
21
+ * label via `toolStatusDescribers` (keyed by tool name), so the core never
22
+ * decides how a plugin's tool reads — it just looks the describer up. The other
23
+ * tools are core functionality (wiki, grep, memory) and keep their core-coined
24
+ * labels until they too become internal plugins.
25
+ */
26
+ export declare function describeToolStart(toolName: string, input: unknown, toolStatusDescribers: Record<string, (input: unknown) => string>): string;
27
+ /**
28
+ * The actual command/input behind a tool call, for display in a status
29
+ * card — `describeToolStart`'s label alone doesn't say *what* ran, only
30
+ * which service. No markdown decoration: Google Chat cards don't render
31
+ * backticks as monospace, so a plain string reads better. Named per-tool
32
+ * (path for a file, pattern for a search, ...) rather than dumping the raw
33
+ * input, since that's what a human actually wants to see; only a genuinely
34
+ * unmapped/future tool falls back to a bounded JSON dump.
35
+ */
36
+ export declare function describeToolDetail(toolName: string, input: unknown): string;
37
+ export type { ToolOutcome } from "@mercury-fw/channel-types";
38
+ import type { ToolOutcome } from "@mercury-fw/channel-types";
39
+ /**
40
+ * Every tool in this codebase returns a uniform `{ok: true, ...}` /
41
+ * `{ok: false, error, ...}` shape (confirmed across `wiki-tools.ts`,
42
+ * `tool-log-recall-tool.ts`, `cli-tool.ts`), with `cli-tool.ts`'s
43
+ * confirm-required staging additionally setting `pendingConfirmation: true`
44
+ * without having actually run the command yet — so this classifier works
45
+ * for any tool without per-tool special-casing. Defaults to `"success"` for
46
+ * an unrecognized result shape; nothing returned by a tool today hits that
47
+ * branch.
48
+ */
49
+ export declare function classifyToolResult(result: unknown): ToolOutcome;
50
+ /**
51
+ * Wraps every tool's `execute` to call `onToolStart` first, then run the
52
+ * original, then `onToolFinish` once it settles. `chain` serializes every
53
+ * wrapped tool sharing this one `withToolStartHook` call (i.e. one turn's
54
+ * worth of tools, since `buildTools` builds fresh tools per turn) — see
55
+ * this file's header comment for why.
56
+ */
57
+ export declare function withToolStartHook(tools: Record<string, Tool>, onToolStart: (label: string, detail?: string, toolCallId?: string) => void, toolStatusDescribers: Record<string, (input: unknown) => string>, onToolFinish?: (toolCallId: string, outcome: ToolOutcome) => void): Record<string, Tool>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * In-memory staging area for the user-facing `display` artifacts a tool
3
+ * produces (see `ToolDisplay` and the formatter decorator), decoupling
4
+ * *producing* an artifact from *showing* it. A tool `stash`es its already-
5
+ * rendered artifact here and gets back a short `ref`; the model, and only
6
+ * the model, decides whether to actually surface it by calling the
7
+ * `present` tool (see `present-tool.ts`), which marks the ref via
8
+ * `surface`. At finalize the turn runner appends only what
9
+ * `takeSurfaced` returns — the artifacts the model explicitly asked to
10
+ * show — never the whole set unconditionally. The worst case becomes
11
+ * omission (the user says "show me"), never a garbled or force-appended
12
+ * list.
13
+ *
14
+ * Scoped by `sessionKey`, exactly like `confirmation-store.ts`: a ref
15
+ * minted for one session (terminal, or a given Google Chat space+sender)
16
+ * can't be surfaced by another. The store is created once and shared
17
+ * across turns, so a ref stays valid (until its TTL) for a later
18
+ * "show me that list again" follow-up — session-scoped, not turn-scoped.
19
+ */
20
+ export type DisplayStore = {
21
+ /** Stashes `artifact` for `sessionKey` and returns a fresh ref. */
22
+ stash(sessionKey: string, artifact: string): string;
23
+ /** Marks `ref` to be shown at finalize. Returns `false` if it doesn't
24
+ * exist, belongs to a different session, or has expired. */
25
+ surface(sessionKey: string, ref: string): boolean;
26
+ /** The artifacts surfaced for `sessionKey`, in stash order. Consumes the
27
+ * surfaced flag (so the same artifact isn't re-appended on a later turn
28
+ * unless `surface` is called again) but keeps the entry until its TTL,
29
+ * so a still-valid ref can be surfaced again across turns. */
30
+ takeSurfaced(sessionKey: string): string[];
31
+ };
32
+ export declare function createDisplayStore(opts?: {
33
+ now?: () => number;
34
+ ttlMs?: number;
35
+ refFn?: () => string;
36
+ }): DisplayStore;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The `present` tool: the model's explicit "show this artifact to the user"
3
+ * action. A tool that produced a user-facing `display` stashed it in the
4
+ * `DisplayStore` (see `display-store.ts`) and returned a `ref` on the model
5
+ * channel; the model never sees the rendered content, only the ref. Calling
6
+ * `present(ref)` marks that ref to be appended to the reply at finalize.
7
+ *
8
+ * This is what shrinks the model's job from "format this list correctly"
9
+ * (which small models garble) to "show this list? yes/no": the artifact is
10
+ * always the deterministic one the formatter produced, and the model only
11
+ * decides whether it appears. If the model never calls `present`, nothing is
12
+ * shown — the right outcome for a "how many are open?" question answered in
13
+ * prose. Scoped to the calling session, so it can only surface its own refs.
14
+ */
15
+ import type { ExecutableTool } from "@mercury-fw/plugin-types";
16
+ import type { DisplayStore } from "./display-store.ts";
17
+ export type PresentToolDeps = {
18
+ sessionKey: string;
19
+ store: DisplayStore;
20
+ };
21
+ export declare function createPresentTool(deps: PresentToolDeps): {
22
+ present: ExecutableTool;
23
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Frontmatter schema for wiki notes, based on Open Knowledge
3
+ * Format (OKF — https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing):
4
+ * `type` is the only field OKF mandates; `source`/`confidence`/
5
+ * `derived_from`/`last_reviewed` are Mercury-specific extensions, the
6
+ * kind OKF explicitly leaves to the producer. `curated` and `inferred`
7
+ * are validated as separate shapes (discriminated on `type`) because
8
+ * only `inferred` notes carry provenance — a curated doc has no
9
+ * meaningful `confidence` or `derived_from`.
10
+ */
11
+ import { z } from "zod";
12
+ export declare const CuratedFrontmatterSchema: z.ZodObject<{
13
+ type: z.ZodLiteral<"curated">;
14
+ author: z.ZodOptional<z.ZodString>;
15
+ last_updated: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ export declare const InferredFrontmatterSchema: z.ZodObject<{
18
+ type: z.ZodLiteral<"inferred">;
19
+ source: z.ZodLiteral<"agent">;
20
+ confidence: z.ZodEnum<{
21
+ low: "low";
22
+ medium: "medium";
23
+ high: "high";
24
+ }>;
25
+ derived_from: z.ZodArray<z.ZodString>;
26
+ last_reviewed: z.ZodNullable<z.ZodString>;
27
+ }, z.core.$strip>;
28
+ /**
29
+ * A fourth category: the lifecycle of one confirm-required action, from
30
+ * staging through its eventual resolution — a deterministic instruction
31
+ * the user explicitly approved via the confirmation-token mechanism,
32
+ * never an autonomous LLM judgment call. Tracks a specific CLI action's
33
+ * own token, written once when staged (`status: "pending"`) and
34
+ * overwritten in place once resolved (`"confirmed"`/`"failed"`). Lives
35
+ * outside `inferred/users/<userId>/` (see `wiki-read.ts`'s
36
+ * `allowedRoots`) so it's structurally invisible to the model's own
37
+ * `list_files`/`grep` — reachable only via the narrow `resolve_reference`
38
+ * tool given the exact token (see `wiki-tools.ts`), never by browsing.
39
+ */
40
+ export declare const ConfirmationFrontmatterSchema: z.ZodObject<{
41
+ type: z.ZodLiteral<"confirmation">;
42
+ status: z.ZodEnum<{
43
+ pending: "pending";
44
+ failed: "failed";
45
+ confirmed: "confirmed";
46
+ }>;
47
+ requested_at: z.ZodString;
48
+ resolved_at: z.ZodNullable<z.ZodString>;
49
+ command: z.ZodString;
50
+ }, z.core.$strip>;
51
+ export type CuratedFrontmatter = z.infer<typeof CuratedFrontmatterSchema>;
52
+ export type InferredFrontmatter = z.infer<typeof InferredFrontmatterSchema>;
53
+ export type ConfirmationFrontmatter = z.infer<typeof ConfirmationFrontmatterSchema>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Deterministic index.md line management. Turns "this curated doc, with
3
+ * this description" into the exact `[[wikilink]]` line format
4
+ * `orphan-detector.ts`'s wikilink check recognizes — instead of trusting
5
+ * the model to freely author (and correctly reproduce, on every edit) the
6
+ * whole file's syntax by hand, which is what `write_index` used to do and
7
+ * how a doc could end up with an index.md line that still isn't
8
+ * recognized as a reference to it.
9
+ */
10
+ /** Accepts any of "curated/x/y.md", "x/y.md", or "x/y" and normalizes to "x/y" — the form `[[wikilink]]`s use. */
11
+ export declare function normalizeIndexKey(path: string): string;
12
+ /** Adds a line for `key`, or replaces its existing one in place — never duplicates an entry. */
13
+ export declare function upsertIndexEntry(content: string, key: string, description: string): string;
14
+ /** Removes `key`'s line if present; no-op otherwise. */
15
+ export declare function removeIndexEntry(content: string, key: string): string;
@@ -0,0 +1 @@
1
+ export declare function findOrphanCuratedDocs(vaultPath: string): Promise<string[]>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The nightly self-review job's three LLM passes. Each is a fresh,
3
+ * stateless `generateText` call — never a persisted conversation history
4
+ * like `agent-turn.ts`'s `runTurn` — so nothing carries over between
5
+ * passes or between nights except what actually landed on disk. Running
6
+ * all four checks as one giant multi-step call would let tool-call
7
+ * context accumulate across unrelated jobs at once; splitting into three
8
+ * independent calls keeps each one's context bounded to its own job.
9
+ *
10
+ * `generateText` comes from `ai-sdk-ollama`, matching `agent-turn.ts` —
11
+ * plain `ai`'s version is documented there to return empty text after a
12
+ * tool call on this codebase's Ollama setup, which applies here too
13
+ * since every pass uses tools.
14
+ */
15
+ import { type LanguageModel, type Tool } from "ai";
16
+ import type { Message } from "../session/history.ts";
17
+ /** Empirical, tuned later — generous headroom for reading several docs and
18
+ * writing/deleting within one pass, well above a conversational turn's. */
19
+ export declare const SELF_REVIEW_STEP_COUNT = 15;
20
+ type GenerateTextFn = (params: {
21
+ model: LanguageModel;
22
+ messages: Message[];
23
+ tools: Record<string, Tool>;
24
+ instructions: string;
25
+ }) => Promise<{
26
+ text: string;
27
+ }>;
28
+ export type RawTriagePassDeps = {
29
+ vaultPath: string;
30
+ model: LanguageModel;
31
+ rawEntries: string[];
32
+ generateTextFn?: GenerateTextFn;
33
+ };
34
+ export declare function runRawTriagePass(deps: RawTriagePassDeps): Promise<void>;
35
+ export type IndexAndOrphanPassDeps = {
36
+ vaultPath: string;
37
+ model: LanguageModel;
38
+ orphans: string[];
39
+ generateTextFn?: GenerateTextFn;
40
+ };
41
+ export declare function runIndexAndOrphanPass(deps: IndexAndOrphanPassDeps): Promise<void>;
42
+ export type ContradictionCheckPassDeps = {
43
+ vaultPath: string;
44
+ model: LanguageModel;
45
+ generateTextFn?: GenerateTextFn;
46
+ };
47
+ export declare function runContradictionCheckPass(deps: ContradictionCheckPassDeps): Promise<void>;
48
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The nightly self-review job's own tool set — distinct from
3
+ * `wiki-tools.ts` (the model-invocable tools a normal conversation gets),
4
+ * same reasoning already applied to `writeInferredNote` never being
5
+ * wired into those: this is a separate trust context (a stateless admin
6
+ * batch job, not a live conversation), scoped via `selfReviewRoots`
7
+ * (curated/ + raw/, never inferred/ — reserved for deterministic,
8
+ * mechanically-written notes, not an LLM's own judgment call) and with
9
+ * capabilities no conversational tool
10
+ * has (deleting an entry, updating/removing an index.md entry — the
11
+ * model supplies a doc path and a description, never the file's raw text,
12
+ * so an update can't get the `[[wikilink]]` format wrong).
13
+ *
14
+ * All three nightly sub-passes (`self-review-runner.ts`) share this
15
+ * exact tool set — they differ only in system prompt and pre-computed
16
+ * input data, not in which tools they can call.
17
+ */
18
+ import type { ExecutableTool } from "@mercury-fw/plugin-types";
19
+ export type SelfReviewToolsDeps = {
20
+ vaultPath: string;
21
+ };
22
+ export declare function createSelfReviewTools(deps: SelfReviewToolsDeps): Record<"list_files" | "read_file" | "grep" | "write_curated" | "update_index_entry" | "remove_index_entry" | "delete_raw" | "delete_curated", ExecutableTool>;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Creates the vault's curated/inferred subdirectories and git-inits the
3
+ * vault if it isn't already a git repo. Safe to call on every startup:
4
+ * pre-existing directories/content are left untouched, and re-running
5
+ * `git init` on an already-initialized repo is a no-op.
6
+ */
7
+ export declare function initVault(vaultPath: string): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /** Writes a curated doc at `curated/<relativePath>` (e.g. "standards/jira-fields.md"). */
2
+ export declare function writeCuratedNote(vaultPath: string, relativePath: string, fields: {
3
+ author?: string;
4
+ last_updated?: string;
5
+ }, body: string): Promise<void>;
6
+ /** Writes a semantic note at `inferred/users/<userId>/<topic>.md`. */
7
+ export declare function writeInferredNote(vaultPath: string, userId: string, topic: string, fields: {
8
+ confidence: "low" | "medium" | "high";
9
+ derived_from: string[];
10
+ last_reviewed: string | null;
11
+ }, body: string): Promise<void>;
12
+ /**
13
+ * Writes a deterministically-promoted procedural correction at
14
+ * `curated/standards/<tool>-<topic>.md` — one file per correction, not
15
+ * merged into a single per-tool doc (that would need safe section-level
16
+ * merging into whatever a human already wrote by hand there, e.g.
17
+ * `curated/standards/jira-cli.md`, deliberately out of scope here).
18
+ * Frontmatter is still `type: inferred, source: agent` (same provenance
19
+ * shape as `writeInferredNote` — probabilistic, consolidation-derived, not
20
+ * human-authored) even though the file lives under `curated/`: the path
21
+ * controls read visibility (every user's wiki tools expose `curated/`,
22
+ * only their own `inferred/users/<userId>/`), not authorship.
23
+ */
24
+ export declare function writeToolCorrectionNote(vaultPath: string, tool: string, topic: string, fields: {
25
+ confidence: "low" | "medium" | "high";
26
+ derived_from: string[];
27
+ last_reviewed: string | null;
28
+ }, body: string): Promise<void>;
29
+ /**
30
+ * Writes (or overwrites) the deterministic lifecycle record for one
31
+ * confirm-required action at `inferred/confirmations/<encoded userId>/<token>.md`
32
+ * — see `ConfirmationFrontmatterSchema`'s own doc comment for why this
33
+ * subtree, not `inferred/users/<userId>/`. Called twice per action: once
34
+ * at staging (`status: "pending"`, `resolvedAt: null`), once at resolution
35
+ * (`"confirmed"`/`"failed"`, `resolvedAt` set) — the whole-file replace
36
+ * every writer here already does, not a partial update.
37
+ */
38
+ export declare function writeConfirmationNote(vaultPath: string, userId: string, token: string, fields: {
39
+ status: "pending" | "confirmed" | "failed";
40
+ requestedAt: string;
41
+ resolvedAt: string | null;
42
+ command: string;
43
+ }): Promise<void>;
44
+ /** Writes a raw/ inbox entry verbatim at `raw/<relativePath>` — no
45
+ * frontmatter, content is whatever a human pasted as-is (see
46
+ * `vault-cli.ts`'s `write-raw`). Never called during a normal
47
+ * conversation; only the self-review job (`self-review-tools.ts`) reads
48
+ * this back to triage it into `curated/`. */
49
+ export declare function writeRawEntry(vaultPath: string, relativePath: string, body: string): Promise<void>;
50
+ /** Overwrites `index.md` at the vault root with `content` verbatim — no
51
+ * frontmatter, it's a generated Karpathy-pattern index, not a note.
52
+ * Whole-file replace: the caller (self-review) computes the full new
53
+ * text and passes the complete replacement, same as every writer here. */
54
+ export declare function writeIndexFile(vaultPath: string, content: string): Promise<void>;
55
+ /** Deletes a raw/ entry once self-review has resolved it (merged,
56
+ * promoted, or discarded). */
57
+ export declare function deleteRawEntry(vaultPath: string, relativePath: string): Promise<void>;
58
+ /** Deletes a curated/ doc — used only by the self-review job to retire a
59
+ * redundant/superseded doc (never during a normal conversation). Callers
60
+ * should also remove the doc's `index.md` line in the same pass, so a
61
+ * deletion doesn't leave a dangling index reference. */
62
+ export declare function deleteCuratedEntry(vaultPath: string, relativePath: string): Promise<void>;
@@ -0,0 +1,27 @@
1
+ /** curated/ + raw/ only — never inferred/, for the nightly self-review job.
2
+ * A distinct trust boundary from a per-user conversation's `allowedRoots`
3
+ * (which trades curated/ for one user's own inferred/ instead of raw/):
4
+ * inferred/ is meant to hold only deterministic, mechanically-written
5
+ * notes (never an LLM's own judgment call about what to remember) —
6
+ * off-limits here for the same reason it's off-limits to regular
7
+ * conversations, not a special exception for self-review. */
8
+ export declare function selfReviewRoots(vaultPath: string): string[];
9
+ /** Lists every `.md` file under `roots`. */
10
+ export declare function listWikiFilesInRoots(vaultPath: string, roots: string[]): Promise<string[]>;
11
+ /** Lists every `.md` file visible to `userId`: all of curated/, plus only their own inferred/users/<userId>/. */
12
+ export declare function listWikiFiles(vaultPath: string, userId: string): Promise<string[]>;
13
+ /** Reads a single wiki file. Throws if `relativePath` falls outside `roots`. */
14
+ export declare function readWikiFileInRoots(vaultPath: string, roots: string[], relativePath: string): Promise<string>;
15
+ /** Reads a single wiki file. Throws if `relativePath` falls outside the caller's allowed scope. */
16
+ export declare function readWikiFile(vaultPath: string, userId: string, relativePath: string): Promise<string>;
17
+ export type WikiGrepMatch = {
18
+ path: string;
19
+ line: number;
20
+ text: string;
21
+ };
22
+ /** Searches every file under `roots` for `pattern` (a regular expression), line by line. */
23
+ export declare function grepWikiInRoots(vaultPath: string, roots: string[], pattern: string): Promise<WikiGrepMatch[]>;
24
+ /** Searches every file visible to `userId` for `pattern` (a regular expression), line by line. */
25
+ export declare function grepWiki(vaultPath: string, userId: string, pattern: string): Promise<WikiGrepMatch[]>;
26
+ /** Reads index.md at the vault root; empty string if it doesn't exist yet (a brand-new vault, or one where self-review hasn't created it). */
27
+ export declare function readIndexFile(vaultPath: string): Promise<string>;
@@ -0,0 +1,7 @@
1
+ import type { ExecutableTool } from "@mercury-fw/plugin-types";
2
+ export type WikiToolsDeps = {
3
+ vaultPath: string;
4
+ userId: string;
5
+ };
6
+ /** Builds the four wiki tools scoped to `deps.userId` (curated/ fully, only their own inferred/users/<userId>/). */
7
+ export declare function createWikiTools(deps: WikiToolsDeps): Record<"list_files" | "read_file" | "write_file" | "grep" | "resolve_reference", ExecutableTool>;
package/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The public surface of `@mercury-fw/core` — the framework runtime a Mercury
3
+ * instance consumes. Everything else under `src/` is internal: an app depends on
4
+ * this barrel, never on a deep path.
5
+ *
6
+ * What's exported, and who uses it:
7
+ * - `composeMercury` builds the instance from a config it is *given* (the
8
+ * config is never imported by the core — that's what keeps it app-agnostic).
9
+ * `ComposedApp`/`ConfirmDeps` are its result and confirm-binding types.
10
+ * - `loadChannels`/`LoadedChannel` — the service entrypoint starts the declared
11
+ * channels with these.
12
+ * - `createTerminalProvider` — the dev REPL entrypoint opens the terminal with it.
13
+ * - `defineMercuryConfig`/`MercuryConfig` — the app's `mercury.config.ts` declares
14
+ * its composition through these; `Persona` types its `persona` field.
15
+ * - `DEFAULT_PERSONA_IDENTITY`/`DEFAULT_PERSONA_TONE` — the persona an instance
16
+ * gets when its config sets none; the scaffolder writes them as a new app's
17
+ * starting persona.
18
+ */
19
+ export { composeMercury, type ComposedApp, type ConfirmDeps } from "./src/compose.ts";
20
+ export { loadChannels, type LoadedChannel } from "./src/router/channel-loader.ts";
21
+ export { createTerminalProvider } from "./src/router/terminal-provider.ts";
22
+ export { defineMercuryConfig, type MercuryConfig } from "./src/config/define-config.ts";
23
+ export { DEFAULT_PERSONA_IDENTITY, DEFAULT_PERSONA_TONE, type Persona } from "./src/session/system-prompt.ts";
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@mercury-fw/core",
3
+ "version": "0.25.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/lucabro81/mercury-fw.git",
9
+ "directory": "packages/libs/core"
10
+ },
11
+ "files": [
12
+ "*.ts",
13
+ "dist",
14
+ "src",
15
+ "CHANGELOG.md",
16
+ "!**/*.test.ts",
17
+ "!**/__fixtures__"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "mercury-fw-source": "./index.ts",
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./index.ts"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "bun test"
32
+ },
33
+ "dependencies": {
34
+ "@mercury-fw/channel-types": "0.25.0",
35
+ "@mercury-fw/cli-engine": "0.25.0",
36
+ "@mercury-fw/confirm-engine": "0.25.0",
37
+ "@mercury-fw/plugin-types": "0.25.0",
38
+ "@qdrant/js-client-rest": "^1.19.0",
39
+ "ai": "^7.0.77",
40
+ "ai-sdk-ollama": "^4.2.0",
41
+ "yaml": "^2.9.0",
42
+ "zod": "^4.4.3"
43
+ },
44
+ "devDependencies": {
45
+ "@mercury-fw/typescript-config": "*",
46
+ "@types/bun": "^1.4.0",
47
+ "typescript": "^6.0.3"
48
+ }
49
+ }