@uptimizr/agent-core 1.1.0 → 1.2.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 (63) hide show
  1. package/AGENTS.md +238 -25
  2. package/README.md +57 -21
  3. package/dist/client.d.ts +22 -5
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +39 -10
  6. package/dist/client.js.map +1 -1
  7. package/dist/context.d.ts +93 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +137 -0
  10. package/dist/context.js.map +1 -0
  11. package/dist/index.d.ts +10 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +20 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/nonRegistryTools.d.ts +34 -0
  16. package/dist/nonRegistryTools.d.ts.map +1 -0
  17. package/dist/nonRegistryTools.js +70 -0
  18. package/dist/nonRegistryTools.js.map +1 -0
  19. package/dist/prompt.d.ts +33 -0
  20. package/dist/prompt.d.ts.map +1 -0
  21. package/dist/prompt.js +49 -0
  22. package/dist/prompt.js.map +1 -0
  23. package/dist/provider.d.ts +18 -0
  24. package/dist/provider.d.ts.map +1 -1
  25. package/dist/providers/anthropic.d.ts +18 -1
  26. package/dist/providers/anthropic.d.ts.map +1 -1
  27. package/dist/providers/anthropic.js +35 -3
  28. package/dist/providers/anthropic.js.map +1 -1
  29. package/dist/providers/openai.d.ts +14 -1
  30. package/dist/providers/openai.d.ts.map +1 -1
  31. package/dist/providers/openai.js +23 -1
  32. package/dist/providers/openai.js.map +1 -1
  33. package/dist/queryTool.d.ts +36 -0
  34. package/dist/queryTool.d.ts.map +1 -0
  35. package/dist/queryTool.js +123 -0
  36. package/dist/queryTool.js.map +1 -0
  37. package/dist/registryTools.d.ts +23 -1
  38. package/dist/registryTools.d.ts.map +1 -1
  39. package/dist/registryTools.js +185 -31
  40. package/dist/registryTools.js.map +1 -1
  41. package/dist/skills.d.ts +105 -0
  42. package/dist/skills.d.ts.map +1 -0
  43. package/dist/skills.generated.d.ts +44 -0
  44. package/dist/skills.generated.d.ts.map +1 -0
  45. package/dist/skills.generated.js +511 -0
  46. package/dist/skills.generated.js.map +1 -0
  47. package/dist/skills.js +144 -0
  48. package/dist/skills.js.map +1 -0
  49. package/dist/tools.d.ts +55 -8
  50. package/dist/tools.d.ts.map +1 -1
  51. package/dist/tools.js +41 -1
  52. package/dist/tools.js.map +1 -1
  53. package/dist/writeTools.d.ts +68 -0
  54. package/dist/writeTools.d.ts.map +1 -0
  55. package/dist/writeTools.js +256 -0
  56. package/dist/writeTools.js.map +1 -0
  57. package/llms.txt +164 -15
  58. package/package.json +7 -5
  59. package/skills/attention-hotspots/SKILL.md +88 -0
  60. package/skills/conversion-investigation/SKILL.md +97 -0
  61. package/skills/performance-regression-triage/SKILL.md +106 -0
  62. package/skills/weekly-scene-health/SKILL.md +105 -0
  63. package/skills/xr-comfort-audit/SKILL.md +95 -0
package/dist/skills.js ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * **Packaged agent skills** — the canned investigations a client can run against
3
+ * a project's analytics (ADR 0050 §7, ADR 0051 §6–§7, design sketch §G.4).
4
+ *
5
+ * A skill is a named methodology: a short title, the tools its method relies on,
6
+ * the capabilities an API key needs to follow it, the arguments it accepts, and
7
+ * a `render` that turns those arguments into the single user turn that steers an
8
+ * agent. No data is fetched here and no model is called — a skill is text plus
9
+ * metadata, so the same definition serves every consumer:
10
+ *
11
+ * - `@uptimizr/mcp` registers each one as an MCP **prompt template**
12
+ * (`prompts/list` → `prompts/get`) and lists them on `uptimizr://skills`.
13
+ * - `uptimizr agent report --skill <name>` in the collector CLI seeds its
14
+ * headless `runAgent` transcript with the rendered text (ADR 0051 §6, design
15
+ * sketch §F.4).
16
+ * - `@uptimizr/agent-eval` asks the bank the very questions a real client sends.
17
+ * - The in-browser assistant offers them as starter prompts.
18
+ *
19
+ * ### Where the text lives
20
+ *
21
+ * Not here. Each skill is an **Agent Skills file** —
22
+ * `oss/packages/agent-core/skills/<name>/SKILL.md` — with YAML frontmatter
23
+ * (`name`, `title`, `description`, `tools`, `capabilities`, `args`) and a
24
+ * Markdown body holding the methodology. Those files are the source of truth:
25
+ * they ship in the `@uptimizr/agent-core` and `@uptimizr/mcp` tarballs, and a
26
+ * user can lift one straight into their own agent.
27
+ *
28
+ * They cannot be read at runtime, because this package is browser-safe and must
29
+ * not touch `node:fs`. So `scripts/gen-agent-skills.mjs` compiles them into
30
+ * {@link GENERATED_AGENT_SKILLS} (`skills.generated.ts`) and this module turns
31
+ * that data into {@link AgentSkill} values. Reword a SKILL.md, run
32
+ * `pnpm gen:skills`, and every consumer's wording changes with it, with nothing
33
+ * to keep in step. `pnpm gen:skills:check` is the CI gate that fails on a
34
+ * hand-edited generated file.
35
+ *
36
+ * ### Body placeholders
37
+ *
38
+ * A body is a template with a deliberately tiny vocabulary — enough to scope a
39
+ * methodology, not a second templating language:
40
+ *
41
+ * - `{{scene}}` — the scene id, or the empty string.
42
+ * - `{{scope}}` — `scene "lobby"`, or `the project (all scenes)`.
43
+ * - `{{range}}` — the window, in words, defaulting to the argument's `default`.
44
+ * - `{{#name}}…{{/name}}` / `{{^name}}…{{/name}}` — a section kept only when the
45
+ * argument is, or is not, present. Sections may span lines.
46
+ */
47
+ import { GENERATED_AGENT_SKILLS, } from "./skills.generated.js";
48
+ /** `{{#name}}…{{/name}}` and `{{^name}}…{{/name}}`, possibly spanning lines. */
49
+ const SECTION = /\{\{([#^])([a-z][a-z0-9_]*)\}\}([\s\S]*?)\{\{\/\2\}\}/g;
50
+ /** `{{name}}`. */
51
+ const VARIABLE = /\{\{([a-z][a-z0-9_]*)\}\}/g;
52
+ /** `scene "lobby"` when a scene was given, otherwise the whole project. */
53
+ function scopeFor(scene) {
54
+ return scene ? `scene "${scene}"` : "the project (all scenes)";
55
+ }
56
+ /**
57
+ * Substitute a skill's arguments into its body.
58
+ *
59
+ * Sections are resolved before variables so a `{{scene}}` inside a dropped
60
+ * `{{#scene}}` block never renders, and the trailing whitespace a dropped
61
+ * section can leave behind is stripped per line — otherwise a scene-less render
62
+ * would carry invisible spaces the authored file never had.
63
+ */
64
+ function renderBody(body, values) {
65
+ const resolved = body
66
+ .replace(SECTION, (_match, kind, name, inner) => (kind === "#") === Boolean(values[name]) ? inner : "")
67
+ .replace(VARIABLE, (_match, name) => values[name] ?? "");
68
+ return resolved.replace(/[ \t]+$/gm, "");
69
+ }
70
+ /** Turn one compiled SKILL.md into the renderable skill consumers use. */
71
+ function toAgentSkill(source) {
72
+ return {
73
+ id: source.id,
74
+ name: source.name,
75
+ title: source.title,
76
+ description: source.description,
77
+ tools: source.tools,
78
+ capabilities: source.capabilities,
79
+ args: source.args,
80
+ render(args = {}) {
81
+ const values = {};
82
+ for (const arg of source.args) {
83
+ values[arg.name] = args[arg.name]?.trim() || arg.default || "";
84
+ }
85
+ // `scope` is derived rather than declared: a skill that takes a scene
86
+ // always wants to open with "scene X" or "the whole project", and spelling
87
+ // that out in five frontmatter blocks would invite five phrasings of it.
88
+ values.scope = scopeFor(values.scene);
89
+ return renderBody(source.body, values);
90
+ },
91
+ };
92
+ }
93
+ /**
94
+ * The packaged skills, in catalog order. The text of each is the contract: it is
95
+ * what an MCP client shows its user and what the report CLI sends as the user
96
+ * turn, so changing a SKILL.md changes every consumer at once (which is the
97
+ * point).
98
+ */
99
+ export const AGENT_SKILLS = GENERATED_AGENT_SKILLS.map(toAgentSkill);
100
+ /** The packaged skill names, in catalog order. */
101
+ export const AGENT_SKILL_NAMES = AGENT_SKILLS.map((skill) => skill.name);
102
+ /**
103
+ * Names that used to identify a skill and still resolve to one.
104
+ *
105
+ * `xr_comfort_review` shipped as an MCP prompt and a `--skill` value before the
106
+ * methodology was packaged and widened into `xr_comfort_audit` (#316). An MCP
107
+ * client's saved prompt reference and an operator's cron line both name a skill
108
+ * by string, so the old name keeps working rather than failing on the next
109
+ * scheduled run.
110
+ */
111
+ const SKILL_ALIASES = {
112
+ xr_comfort_review: "xr_comfort_audit",
113
+ };
114
+ /**
115
+ * Resolve a skill by name, or `undefined` when nothing answers to it.
116
+ *
117
+ * The one lookup every consumer goes through, and deliberately forgiving about
118
+ * spelling: a skill's directory is kebab-case (`xr-comfort-audit`) and its id is
119
+ * snake_case (`xr_comfort_audit`), so both resolve, as do the historical names
120
+ * in {@link SKILL_ALIASES}.
121
+ */
122
+ export function getAgentSkill(name) {
123
+ const normalized = name.trim().toLowerCase().replace(/-/g, "_");
124
+ const resolved = SKILL_ALIASES[normalized] ?? normalized;
125
+ return AGENT_SKILLS.find((skill) => skill.name === resolved);
126
+ }
127
+ /**
128
+ * Render one skill's user turn, throwing on an unknown name (a typo should fail
129
+ * loudly rather than silently produce an empty prompt) or on a missing required
130
+ * argument.
131
+ */
132
+ export function renderAgentSkill(name, args = {}) {
133
+ const skill = getAgentSkill(name);
134
+ if (!skill) {
135
+ throw new Error(`Unknown skill "${name}". Known skills: ${AGENT_SKILL_NAMES.join(", ")}.`);
136
+ }
137
+ for (const arg of skill.args) {
138
+ if (arg.required && !args[arg.name]) {
139
+ throw new Error(`Skill "${name}" requires the "${arg.name}" argument.`);
140
+ }
141
+ }
142
+ return skill.render(args);
143
+ }
144
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
package/dist/tools.d.ts CHANGED
@@ -17,15 +17,33 @@ export interface ReadTool {
17
17
  description: string;
18
18
  inputSchema: z.ZodRawShape;
19
19
  /**
20
- * Zod raw shape describing what the tool **returns**, derived from the metric
21
- * registry's `row` schema (ADR 0051 §1). It is always the single-key envelope
22
- * `{ rows: Row[] }`: a single-object result (a session descriptor, a one-row
23
- * summary) is reported as a one-element array so every tool has the same
24
- * shape. Consumers that speak MCP register it as the tool's `outputSchema`
25
- * and return matching `structuredContent`; consumers that do not can ignore
26
- * it. Optional so a hand-built tool stays valid.
20
+ * Zod schema describing what the tool **returns**, derived from the metric
21
+ * registry's `row` schema (ADR 0051 §1) and the `format` result envelope
22
+ * (ADR 0051 §2).
23
+ *
24
+ * It is a single **object** schema, because that is what an MCP
25
+ * `outputSchema` must be. For a metric that honours `format` it is
26
+ * `structuredEnvelopeSchema(row)`: the merged, every-key-optional object form
27
+ * of the three envelopes — `{ rows }` for `full`, `{ meta, rows }` for
28
+ * `table`, the `kind`-tagged digest for `summary` — so a client validating
29
+ * `structuredContent` accepts whichever one the collector returns (#350). The
30
+ * two resource metrics, which declare no `format`, keep the single-key
31
+ * `{ rows: Row[] }` envelope: a single-object result (a session descriptor) is
32
+ * reported as a one-element array so every tool has the same shape.
33
+ *
34
+ * Consumers that speak MCP register it as the tool's `outputSchema`;
35
+ * consumers that do not can ignore it. Optional so a hand-built tool stays
36
+ * valid.
27
37
  */
28
- outputSchema?: z.ZodRawShape;
38
+ outputSchema?: z.ZodType;
39
+ /**
40
+ * Turn the collector's response into the object {@link outputSchema}
41
+ * describes. Omitted by every generated per-metric tool, whose result is
42
+ * always a list of rows and is therefore wrapped as `{ rows }` by the
43
+ * consumer. The `query` tool sets it because its shape is chosen by the
44
+ * request's `format`, so only the tool knows how its answer is keyed.
45
+ */
46
+ structuredContent?: (data: unknown) => Record<string, unknown>;
29
47
  buildRequest: (args: Record<string, unknown>) => ReadToolRequest;
30
48
  }
31
49
  /**
@@ -43,12 +61,41 @@ export interface ReadTool {
43
61
  * resource entries (`session_meta`, `scene_representation`) are coarse
44
62
  * descriptors, never an event stream.
45
63
  *
64
+ * This is the **`query`** surface: every metric whose endpoint needs nothing
65
+ * more than the ordinary read capability. Metrics that require more are not
66
+ * silently mixed in — see {@link rawTools}.
67
+ *
46
68
  * The 20 tool names the hand-written catalog shipped are registry ids verbatim
47
69
  * and their argument schemas are unchanged — `__tests__/shippedToolCompat.test.ts`
48
70
  * pins that against a frozen fixture, so an MCP client written against the old
49
71
  * catalog keeps working.
72
+ *
73
+ * One tool is **not** per-metric: `query` (ADR 0051 §3), appended last. Its
74
+ * input is the query DSL, so it can run any metric with any filter that metric
75
+ * declares — what the per-metric tools are for discovery, `query` is for
76
+ * anything that needs a filter the canned tool does not expose. See
77
+ * `queryTool.ts`.
50
78
  */
51
79
  export declare const readTools: readonly ReadTool[];
80
+ /**
81
+ * The **`query:raw`** tools: generated from exactly the registry metrics whose
82
+ * endpoint declares that capability (ADR 0051 §7, design sketch §G.2). Today
83
+ * that is `session_narrative`, the compacted account of one session.
84
+ *
85
+ * Kept as a separate catalog rather than folded into {@link readTools} because
86
+ * the capability is not a property of the *agent*, it is a property of the **key
87
+ * the agent was handed**. A host registers these tools only after confirming the
88
+ * key holds `query:raw` — `@uptimizr/mcp`'s `createMcpServer(client, {
89
+ * capabilities })` does exactly that, and `GET /api/v1/whoami` is where the
90
+ * capability set comes from. Registering them unconditionally would advertise a
91
+ * tool that answers 403, which is worse for a model than not having it: it burns
92
+ * a turn and invites a retry.
93
+ *
94
+ * The collector refuses these endpoints unless it *also* has
95
+ * `ENABLE_RAW_SESSION_RETENTION` enabled, so holding the capability is necessary
96
+ * but never sufficient (ADR 0003).
97
+ */
98
+ export declare const rawTools: readonly ReadTool[];
52
99
  /**
53
100
  * Names of the **core** read tools — a small, single-step-friendly subset of
54
101
  * {@link readTools} for small local models (ADR 0050). A 4-bit 7–8B model folds
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC;IAC7B,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,eAAe,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,SAAS,EAAE,SAAS,QAAQ,EAAsB,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAQjD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,QAAQ,EAE5C,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,QAAQ,EAAE,CAE1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,QAAQ,EAAE,CAG7E"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/D,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,eAAe,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,SAAS,EAAE,SAAS,QAAQ,EAQxC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,QAAQ,EAEvC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAQjD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,QAAQ,EAE5C,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,QAAQ,EAAE,CAE1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,QAAQ,EAAE,CAG7E"}
package/dist/tools.js CHANGED
@@ -1,4 +1,7 @@
1
+ import { allMetrics, metricCapability } from "@uptimizr/metrics";
1
2
  import { registryToTools } from "./registryTools.js";
3
+ import { queryTool } from "./queryTool.js";
4
+ import { NON_REGISTRY_READ_TOOLS } from "./nonRegistryTools.js";
2
5
  /**
3
6
  * The catalog of read-only tools — one per metric in the `@uptimizr/db`
4
7
  * **semantic metric registry** that the collector serves on an endpoint
@@ -14,12 +17,49 @@ import { registryToTools } from "./registryTools.js";
14
17
  * resource entries (`session_meta`, `scene_representation`) are coarse
15
18
  * descriptors, never an event stream.
16
19
  *
20
+ * This is the **`query`** surface: every metric whose endpoint needs nothing
21
+ * more than the ordinary read capability. Metrics that require more are not
22
+ * silently mixed in — see {@link rawTools}.
23
+ *
17
24
  * The 20 tool names the hand-written catalog shipped are registry ids verbatim
18
25
  * and their argument schemas are unchanged — `__tests__/shippedToolCompat.test.ts`
19
26
  * pins that against a frozen fixture, so an MCP client written against the old
20
27
  * catalog keeps working.
28
+ *
29
+ * One tool is **not** per-metric: `query` (ADR 0051 §3), appended last. Its
30
+ * input is the query DSL, so it can run any metric with any filter that metric
31
+ * declares — what the per-metric tools are for discovery, `query` is for
32
+ * anything that needs a filter the canned tool does not expose. See
33
+ * `queryTool.ts`.
34
+ */
35
+ export const readTools = [
36
+ ...registryToTools(allMetrics().filter((metric) => metricCapability(metric) === "query")),
37
+ // The one deliberate exception to "generated, not hand-written": collector
38
+ // reads that are configuration rather than measurements and so have no
39
+ // registry entry to generate from (#311). See `nonRegistryTools.ts` for why
40
+ // inventing a registry entry for them would be worse.
41
+ ...NON_REGISTRY_READ_TOOLS,
42
+ queryTool,
43
+ ];
44
+ /**
45
+ * The **`query:raw`** tools: generated from exactly the registry metrics whose
46
+ * endpoint declares that capability (ADR 0051 §7, design sketch §G.2). Today
47
+ * that is `session_narrative`, the compacted account of one session.
48
+ *
49
+ * Kept as a separate catalog rather than folded into {@link readTools} because
50
+ * the capability is not a property of the *agent*, it is a property of the **key
51
+ * the agent was handed**. A host registers these tools only after confirming the
52
+ * key holds `query:raw` — `@uptimizr/mcp`'s `createMcpServer(client, {
53
+ * capabilities })` does exactly that, and `GET /api/v1/whoami` is where the
54
+ * capability set comes from. Registering them unconditionally would advertise a
55
+ * tool that answers 403, which is worse for a model than not having it: it burns
56
+ * a turn and invites a retry.
57
+ *
58
+ * The collector refuses these endpoints unless it *also* has
59
+ * `ENABLE_RAW_SESSION_RETENTION` enabled, so holding the capability is necessary
60
+ * but never sufficient (ADR 0003).
21
61
  */
22
- export const readTools = registryToTools();
62
+ export const rawTools = registryToTools(allMetrics().filter((metric) => metricCapability(metric) === "query:raw"));
23
63
  /**
24
64
  * Names of the **core** read tools — a small, single-step-friendly subset of
25
65
  * {@link readTools} for small local models (ADR 0050). A 4-bit 7–8B model folds
package/dist/tools.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAgCrD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAwB,eAAe,EAAE,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsB;IACrD,eAAe;IACf,aAAa;IACb,YAAY;IACZ,cAAc;IACd,cAAc;IACd,YAAY;IACZ,gBAAgB;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAwB,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1E,oBAAoB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CACzC,CAAC;AAKF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAqB;IACnD,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,KAAwB;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAkDhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,SAAS,GAAwB;IAC5C,GAAG,eAAe,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,CAAC;IACzF,2EAA2E;IAC3E,uEAAuE;IACvE,4EAA4E;IAC5E,sDAAsD;IACtD,GAAG,uBAAuB;IAC1B,SAAS;CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAwB,eAAe,CAC1D,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,WAAW,CAAC,CAC1E,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsB;IACrD,eAAe;IACf,aAAa;IACb,YAAY;IACZ,cAAc;IACd,cAAc;IACd,YAAY;IACZ,gBAAgB;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAwB,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1E,oBAAoB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CACzC,CAAC;AAKF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAqB;IACnD,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,KAAwB;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import type { CollectorClient } from "./client.js";
3
+ /**
4
+ * The **metadata write tools** (#310, ADR 0051 §5; #315, §7): `annotate`,
5
+ * `define_term`, `save_analysis`, `pin_panel` and `unpin_panel`, plus the four
6
+ * reads that make them usable (`list_annotations`, `list_glossary`,
7
+ * `list_analyses`, `list_panels`).
8
+ *
9
+ * They are a deliberately separate export from `readTools`, not an addition to
10
+ * it, for one reason: ADR 0017's read-only stance must stay **inspectable**. A
11
+ * reader who wants to know whether an integration can change anything looks at
12
+ * which of the two catalogs it registers, and `readTools` remains exactly what
13
+ * its name says.
14
+ *
15
+ * What they can and cannot touch:
16
+ *
17
+ * - They write **metadata only** — notes, definitions, saved questions. No tool
18
+ * here can write, alter or delete an analytics event; events stay read-only
19
+ * (ADR 0051 §9) and no ingestion path exists in this package at all.
20
+ * - Every one of them needs an API key holding the `annotate` capability. The
21
+ * collector enforces that (`403` without it); the MCP server checks it once at
22
+ * start-up so an agent is never offered a tool its key cannot use.
23
+ * - Every call is recorded in the project's agent audit log, and the stored row
24
+ * records that an agent — not a person — wrote it.
25
+ *
26
+ * Each definition is pure data plus an `execute` that calls one collector
27
+ * endpoint, so they are unit-testable without a live collector, exactly like the
28
+ * generated read tools.
29
+ */
30
+ /** One metadata write (or metadata read) exposed as an agent tool. */
31
+ export interface WriteTool {
32
+ name: string;
33
+ title: string;
34
+ description: string;
35
+ /** Zod raw shape the MCP runtime validates arguments against. */
36
+ inputSchema: z.ZodRawShape;
37
+ /** Whether this tool changes stored state (all three writers do; the lists do not). */
38
+ mutates: boolean;
39
+ /** Call the collector with validated arguments and return its JSON. */
40
+ execute: (client: CollectorClient, args: Record<string, unknown>) => Promise<unknown>;
41
+ }
42
+ /**
43
+ * Thrown when a write tool is handed a client with no write transport — a
44
+ * read-only `CollectorClient` built by hand, for instance. A clear error beats
45
+ * `client.post is not a function`.
46
+ */
47
+ export declare class WriteNotSupportedError extends Error {
48
+ constructor(method: string);
49
+ }
50
+ export declare const annotateTool: WriteTool;
51
+ export declare const defineTermTool: WriteTool;
52
+ export declare const saveAnalysisTool: WriteTool;
53
+ export declare const listAnnotationsTool: WriteTool;
54
+ export declare const listGlossaryTool: WriteTool;
55
+ export declare const listAnalysesTool: WriteTool;
56
+ export declare const pinPanelTool: WriteTool;
57
+ export declare const listPanelsTool: WriteTool;
58
+ export declare const unpinPanelTool: WriteTool;
59
+ /**
60
+ * The metadata tool catalog. Registered **only** when the calling key holds the
61
+ * `annotate` capability — including the read tools, which are listed here
62
+ * rather than in `readTools` so the whole metadata surface appears and
63
+ * disappears as one coherent feature.
64
+ */
65
+ export declare const writeTools: readonly WriteTool[];
66
+ /** The tools that actually change stored state. */
67
+ export declare const mutatingWriteTools: readonly WriteTool[];
68
+ //# sourceMappingURL=writeTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeTools.d.ts","sourceRoot":"","sources":["../src/writeTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,OAAO,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,MAAM,EAAE,MAAM;CAM3B;AAuBD,eAAO,MAAM,YAAY,EAAE,SA2C1B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,SAsB5B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SA4B9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,SAqBjC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SAS9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SAS9B,CAAC;AAWF,eAAO,MAAM,YAAY,EAAE,SA6D1B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,SAY5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,SAe5B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAU1C,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,EAAE,SAAS,SAAS,EAA8C,CAAC"}
@@ -0,0 +1,256 @@
1
+ import { z } from "zod";
2
+ import { LIMITS } from "@uptimizr/schema";
3
+ /**
4
+ * Thrown when a write tool is handed a client with no write transport — a
5
+ * read-only `CollectorClient` built by hand, for instance. A clear error beats
6
+ * `client.post is not a function`.
7
+ */
8
+ export class WriteNotSupportedError extends Error {
9
+ constructor(method) {
10
+ super(`This collector client is read-only: it has no \`${method}\` method, so metadata write tools cannot be used with it.`);
11
+ this.name = "WriteNotSupportedError";
12
+ }
13
+ }
14
+ function requirePost(client) {
15
+ if (!client.post)
16
+ throw new WriteNotSupportedError("post");
17
+ return client.post.bind(client);
18
+ }
19
+ function requirePut(client) {
20
+ if (!client.put)
21
+ throw new WriteNotSupportedError("put");
22
+ return client.put.bind(client);
23
+ }
24
+ /** Drop `undefined` entries so an optional argument is simply absent on the wire. */
25
+ function compact(body) {
26
+ return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined));
27
+ }
28
+ const targetKind = z
29
+ .enum(["project", "scene", "mesh", "region", "metric", "window"])
30
+ .describe("What the note is about. `scene`/`mesh`/`region`/`metric` need a `targetId`; `window` needs `since`.");
31
+ export const annotateTool = {
32
+ name: "annotate",
33
+ title: "Annotate something",
34
+ description: "Leave a note on the project, a scene, a mesh, a region, a metric, or a period of time — what changed, what you concluded, what a future reader should know before trusting a number. Notes are shown on dashboard time axes and read back by agents. Use it when you have found something worth remembering, not for scratch state.",
35
+ inputSchema: {
36
+ targetKind,
37
+ targetId: z
38
+ .string()
39
+ .min(1)
40
+ .max(LIMITS.maxAnnotationTargetIdLength)
41
+ .optional()
42
+ .describe("The scene id, mesh name, region id or metric id the note is about."),
43
+ since: z
44
+ .number()
45
+ .int()
46
+ .nonnegative()
47
+ .optional()
48
+ .describe("Start of the period the note applies to, epoch milliseconds."),
49
+ until: z
50
+ .number()
51
+ .int()
52
+ .nonnegative()
53
+ .optional()
54
+ .describe("End of the period the note applies to, epoch milliseconds."),
55
+ text: z
56
+ .string()
57
+ .min(1)
58
+ .max(LIMITS.maxAnnotationTextLength)
59
+ .describe("The note itself, in plain words."),
60
+ },
61
+ mutates: true,
62
+ execute: async (client, args) => requirePost(client)("/api/v1/annotations", compact({
63
+ targetKind: args.targetKind,
64
+ targetId: args.targetId,
65
+ since: args.since,
66
+ until: args.until,
67
+ text: args.text,
68
+ })),
69
+ };
70
+ export const defineTermTool = {
71
+ name: "define_term",
72
+ title: "Define a term",
73
+ description: "Record what a name means in this project — a mesh name, a scene id, a custom event, an in-house abbreviation. Writing it down once saves every later reader (and every later agent) from guessing. Defining a term again replaces its meaning.",
74
+ inputSchema: {
75
+ term: z
76
+ .string()
77
+ .min(1)
78
+ .max(LIMITS.maxGlossaryTermLength)
79
+ .describe("The name being defined, exactly as it appears in the data."),
80
+ meaning: z
81
+ .string()
82
+ .min(1)
83
+ .max(LIMITS.maxGlossaryMeaningLength)
84
+ .describe("What it means in this project."),
85
+ },
86
+ mutates: true,
87
+ execute: async (client, args) => requirePut(client)(`/api/v1/glossary/${encodeURIComponent(String(args.term))}`, {
88
+ meaning: args.meaning,
89
+ }),
90
+ };
91
+ export const saveAnalysisTool = {
92
+ name: "save_analysis",
93
+ title: "Save an analysis",
94
+ description: "Store a question worth re-asking together with what you concluded from it: a title someone will recognise in a list, the query that produced the answer, and the finding in words. Use it at the end of an investigation, not for every intermediate query.",
95
+ inputSchema: {
96
+ title: z
97
+ .string()
98
+ .min(1)
99
+ .max(LIMITS.maxSavedAnalysisTitleLength)
100
+ .describe("Short human title, e.g. 'Lobby FPS after the lighting change'."),
101
+ query: z
102
+ .record(z.string(), z.unknown())
103
+ .describe("The question as a JSON object — the endpoint and filters you used. Stored as-is; the collector does not interpret it."),
104
+ conclusion: z
105
+ .string()
106
+ .max(LIMITS.maxSavedAnalysisConclusionLength)
107
+ .optional()
108
+ .describe("What the numbers showed, and how far to trust it."),
109
+ },
110
+ mutates: true,
111
+ execute: async (client, args) => requirePost(client)("/api/v1/analyses", compact({ title: args.title, query: args.query, conclusion: args.conclusion })),
112
+ };
113
+ export const listAnnotationsTool = {
114
+ name: "list_annotations",
115
+ title: "Read the project's annotations",
116
+ description: "The notes people and agents have already left, newest first. Read them before answering a question about a change or a spike — someone may already have explained it. `since`/`until` select notes whose period overlaps the window.",
117
+ inputSchema: {
118
+ targetKind: targetKind.optional(),
119
+ targetId: z.string().min(1).max(LIMITS.maxAnnotationTargetIdLength).optional(),
120
+ since: z.number().int().nonnegative().optional(),
121
+ until: z.number().int().nonnegative().optional(),
122
+ limit: z.number().int().positive().max(500).optional(),
123
+ },
124
+ mutates: false,
125
+ execute: async (client, args) => client.get("/api/v1/annotations", {
126
+ targetKind: args.targetKind,
127
+ targetId: args.targetId,
128
+ since: args.since,
129
+ until: args.until,
130
+ limit: args.limit,
131
+ }),
132
+ };
133
+ export const listGlossaryTool = {
134
+ name: "list_glossary",
135
+ title: "Read the project's glossary",
136
+ description: "What this project's names mean. Read it before interpreting mesh names, scene ids or custom events — the team's vocabulary is rarely self-explanatory.",
137
+ inputSchema: { limit: z.number().int().positive().max(500).optional() },
138
+ mutates: false,
139
+ execute: async (client, args) => client.get("/api/v1/glossary", { limit: args.limit }),
140
+ };
141
+ export const listAnalysesTool = {
142
+ name: "list_analyses",
143
+ title: "Read the project's saved analyses",
144
+ description: "Questions this project has asked before and what was concluded, newest first. Check here before re-deriving an answer from scratch.",
145
+ inputSchema: { limit: z.number().int().positive().max(500).optional() },
146
+ mutates: false,
147
+ execute: async (client, args) => client.get("/api/v1/analyses", { limit: args.limit }),
148
+ };
149
+ // --- Declarative panel specs (#315, ADR 0051 §7 / sketch §G.3) -------------
150
+ //
151
+ // The most durable thing an agent can leave behind: not a note *about* an
152
+ // answer, but the question itself, on the dashboard, re-asked every time
153
+ // somebody opens it. And still metadata — the spec is a closed document (a
154
+ // metric id, a chart name, some column names) that the dashboard renders with
155
+ // panels it already ships. No module is loaded and nothing is evaluated, so
156
+ // pinning a panel does not widen the dashboard's trust boundary (ADR 0041).
157
+ export const pinPanelTool = {
158
+ name: "pin_panel",
159
+ title: "Pin an answer to the dashboard as a panel",
160
+ description: "Keep a question on the project's dashboard, where it will be re-asked and redrawn every " +
161
+ "time somebody opens it. Pass the same `query` document you ran with the `query` tool, but " +
162
+ 'with `range` set to "inherit" so the panel follows the dashboard\'s own time filter instead ' +
163
+ "of freezing the window you happened to ask in. Pick a `chart` the metric's grain can " +
164
+ "actually support — `line`/`area` need a time-bucketed metric, `heatmap2d` a binned one, " +
165
+ "`world3d` a voxelised one, `stat` a single-record one, `bar` a ranking, `table` anything — " +
166
+ "or the collector refuses the spec and names the charts that would have worked. Put your " +
167
+ "one-line reading in `note`: it becomes the panel's subtitle, and it is the part a person " +
168
+ "reads a week later. Use this when an answer is worth watching, not for a one-off lookup.",
169
+ inputSchema: {
170
+ title: z
171
+ .string()
172
+ .min(1)
173
+ .max(LIMITS.maxPanelSpecTitleLength)
174
+ .describe("What the panel is called in the grid, e.g. 'Meshes people actually touch'."),
175
+ query: z
176
+ .record(z.string(), z.unknown())
177
+ .describe('A `queryV1` document — the same shape the `query` tool takes — with `range` set to "inherit", or to an explicit `{since, until}` to pin one period.'),
178
+ chart: z
179
+ .enum(["stat", "table", "bar", "line", "area", "heatmap2d", "world3d"])
180
+ .describe("How to draw the result. Must suit the metric's grain."),
181
+ encoding: z
182
+ .object({
183
+ x: z.string().max(LIMITS.maxPanelEncodingColumnLength).optional(),
184
+ y: z.string().max(LIMITS.maxPanelEncodingColumnLength).optional(),
185
+ series: z.string().max(LIMITS.maxPanelEncodingColumnLength).optional(),
186
+ })
187
+ .optional()
188
+ .describe("Which result column feeds which channel. Omit to use the metric's own label/axis and measure columns."),
189
+ span: z
190
+ .union([z.literal(1), z.literal(2)])
191
+ .optional()
192
+ .describe("Grid width: 1 (half) or 2 (full). Defaults to 1."),
193
+ note: z
194
+ .string()
195
+ .max(LIMITS.maxPanelSpecNoteLength)
196
+ .optional()
197
+ .describe("Your one-line reading of the result. Shown as the panel's subtitle."),
198
+ },
199
+ mutates: true,
200
+ execute: async (client, args) => requirePost(client)("/api/v1/panels", compact({
201
+ v: 1,
202
+ title: args.title,
203
+ query: args.query,
204
+ chart: args.chart,
205
+ encoding: args.encoding,
206
+ span: args.span,
207
+ note: args.note,
208
+ })),
209
+ };
210
+ export const listPanelsTool = {
211
+ name: "list_panels",
212
+ title: "Read the project's pinned panels",
213
+ description: "The panels already pinned to this project's dashboard, oldest first — what somebody decided " +
214
+ "was worth watching. Read it before pinning, so you extend the dashboard instead of " +
215
+ "duplicating a panel that is already there, and to learn which questions this team treats as " +
216
+ "important. Each row carries the spec's `id`, which is what `unpin_panel` takes.",
217
+ inputSchema: { limit: z.number().int().positive().max(500).optional() },
218
+ mutates: false,
219
+ execute: async (client, args) => client.get("/api/v1/panels", { limit: args.limit }),
220
+ };
221
+ export const unpinPanelTool = {
222
+ name: "unpin_panel",
223
+ title: "Remove a pinned panel",
224
+ description: "Remove one panel from the project's dashboard by its `id` (from `list_panels`). This " +
225
+ "removes it for **everyone** on the project rather than just the current viewer, so unpin a " +
226
+ "panel that has stopped being useful — never one you have not read first.",
227
+ inputSchema: {
228
+ id: z.string().min(1).max(128).describe("The panel spec's id, as `list_panels` reports it."),
229
+ },
230
+ mutates: true,
231
+ execute: async (client, args) => {
232
+ if (!client.delete)
233
+ throw new WriteNotSupportedError("delete");
234
+ return client.delete(`/api/v1/panels/${encodeURIComponent(String(args.id))}`);
235
+ },
236
+ };
237
+ /**
238
+ * The metadata tool catalog. Registered **only** when the calling key holds the
239
+ * `annotate` capability — including the read tools, which are listed here
240
+ * rather than in `readTools` so the whole metadata surface appears and
241
+ * disappears as one coherent feature.
242
+ */
243
+ export const writeTools = [
244
+ annotateTool,
245
+ defineTermTool,
246
+ saveAnalysisTool,
247
+ pinPanelTool,
248
+ unpinPanelTool,
249
+ listAnnotationsTool,
250
+ listGlossaryTool,
251
+ listAnalysesTool,
252
+ listPanelsTool,
253
+ ];
254
+ /** The tools that actually change stored state. */
255
+ export const mutatingWriteTools = writeTools.filter((tool) => tool.mutates);
256
+ //# sourceMappingURL=writeTools.js.map