@graview/tools 0.1.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 (111) hide show
  1. package/LICENSE +96 -0
  2. package/README.md +47 -0
  3. package/dist/agent/adapters.d.ts +43 -0
  4. package/dist/agent/adapters.d.ts.map +1 -0
  5. package/dist/agent/adapters.js +52 -0
  6. package/dist/agent/adapters.js.map +1 -0
  7. package/dist/agent/tools.d.ts +105 -0
  8. package/dist/agent/tools.d.ts.map +1 -0
  9. package/dist/agent/tools.js +311 -0
  10. package/dist/agent/tools.js.map +1 -0
  11. package/dist/cli.d.ts +37 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +306 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/conversation.d.ts +108 -0
  16. package/dist/conversation.d.ts.map +1 -0
  17. package/dist/conversation.js +698 -0
  18. package/dist/conversation.js.map +1 -0
  19. package/dist/decide.d.ts +43 -0
  20. package/dist/decide.d.ts.map +1 -0
  21. package/dist/decide.js +125 -0
  22. package/dist/decide.js.map +1 -0
  23. package/dist/derive.d.ts +84 -0
  24. package/dist/derive.d.ts.map +1 -0
  25. package/dist/derive.js +302 -0
  26. package/dist/derive.js.map +1 -0
  27. package/dist/edit.d.ts +70 -0
  28. package/dist/edit.d.ts.map +1 -0
  29. package/dist/edit.js +97 -0
  30. package/dist/edit.js.map +1 -0
  31. package/dist/figure.d.ts +53 -0
  32. package/dist/figure.d.ts.map +1 -0
  33. package/dist/figure.js +86 -0
  34. package/dist/figure.js.map +1 -0
  35. package/dist/index.d.ts +44 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +31 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/intelligence.d.ts +132 -0
  40. package/dist/intelligence.d.ts.map +1 -0
  41. package/dist/intelligence.js +463 -0
  42. package/dist/intelligence.js.map +1 -0
  43. package/dist/local.d.ts +192 -0
  44. package/dist/local.d.ts.map +1 -0
  45. package/dist/local.js +365 -0
  46. package/dist/local.js.map +1 -0
  47. package/dist/loop.d.ts +84 -0
  48. package/dist/loop.d.ts.map +1 -0
  49. package/dist/loop.js +173 -0
  50. package/dist/loop.js.map +1 -0
  51. package/dist/mcp-stdio.d.ts +38 -0
  52. package/dist/mcp-stdio.d.ts.map +1 -0
  53. package/dist/mcp-stdio.js +83 -0
  54. package/dist/mcp-stdio.js.map +1 -0
  55. package/dist/pins.d.ts +29 -0
  56. package/dist/pins.d.ts.map +1 -0
  57. package/dist/pins.js +60 -0
  58. package/dist/pins.js.map +1 -0
  59. package/dist/plan.d.ts +150 -0
  60. package/dist/plan.d.ts.map +1 -0
  61. package/dist/plan.js +303 -0
  62. package/dist/plan.js.map +1 -0
  63. package/dist/providers/insight.d.ts +14 -0
  64. package/dist/providers/insight.d.ts.map +1 -0
  65. package/dist/providers/insight.js +81 -0
  66. package/dist/providers/insight.js.map +1 -0
  67. package/dist/providers/invariant.d.ts +14 -0
  68. package/dist/providers/invariant.d.ts.map +1 -0
  69. package/dist/providers/invariant.js +123 -0
  70. package/dist/providers/invariant.js.map +1 -0
  71. package/dist/providers/jev.d.ts +115 -0
  72. package/dist/providers/jev.d.ts.map +1 -0
  73. package/dist/providers/jev.js +148 -0
  74. package/dist/providers/jev.js.map +1 -0
  75. package/dist/providers/lens.d.ts +24 -0
  76. package/dist/providers/lens.d.ts.map +1 -0
  77. package/dist/providers/lens.js +36 -0
  78. package/dist/providers/lens.js.map +1 -0
  79. package/dist/providers/llm.d.ts +25 -0
  80. package/dist/providers/llm.d.ts.map +1 -0
  81. package/dist/providers/llm.js +44 -0
  82. package/dist/providers/llm.js.map +1 -0
  83. package/dist/providers/schema.d.ts +12 -0
  84. package/dist/providers/schema.d.ts.map +1 -0
  85. package/dist/providers/schema.js +508 -0
  86. package/dist/providers/schema.js.map +1 -0
  87. package/dist/providers/structure.d.ts +14 -0
  88. package/dist/providers/structure.d.ts.map +1 -0
  89. package/dist/providers/structure.js +211 -0
  90. package/dist/providers/structure.js.map +1 -0
  91. package/dist/questions.d.ts +162 -0
  92. package/dist/questions.d.ts.map +1 -0
  93. package/dist/questions.js +316 -0
  94. package/dist/questions.js.map +1 -0
  95. package/dist/run.d.ts +202 -0
  96. package/dist/run.d.ts.map +1 -0
  97. package/dist/run.js +453 -0
  98. package/dist/run.js.map +1 -0
  99. package/dist/space.d.ts +50 -0
  100. package/dist/space.d.ts.map +1 -0
  101. package/dist/space.js +55 -0
  102. package/dist/space.js.map +1 -0
  103. package/dist/types.d.ts +122 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +2 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/usage.d.ts +10 -0
  108. package/dist/usage.d.ts.map +1 -0
  109. package/dist/usage.js +59 -0
  110. package/dist/usage.js.map +1 -0
  111. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,96 @@
1
+ Elastic License 2.0
2
+
3
+ ## Acceptance
4
+
5
+ By using the software, you agree to all of the terms and conditions below.
6
+
7
+ ## Copyright License
8
+
9
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
10
+ non-sublicensable, non-transferable license to use, copy, distribute, make
11
+ available, and prepare derivative works of the software, in each case subject
12
+ to the limitations and conditions below.
13
+
14
+ ## Limitations
15
+
16
+ You may not provide the software to third parties as a hosted or managed
17
+ service, where the service provides users with access to any substantial set
18
+ of the features or functionality of the software.
19
+
20
+ You may not move, change, disable, or circumvent the license key
21
+ functionality in the software, and you may not remove or obscure any
22
+ functionality in the software that is protected by the license key.
23
+
24
+ You may not alter, remove, or obscure any licensing, copyright, or other
25
+ notices of the licensor in the software. Any use of the licensor's trademarks
26
+ is subject to applicable law.
27
+
28
+ ## Patents
29
+
30
+ The licensor grants you a license, under any patent claims the licensor can
31
+ license, or becomes able to license, to make, have made, use, sell, offer for
32
+ sale, import and have imported the software, in each case subject to the
33
+ limitations and conditions in this license. This license does not cover any
34
+ patent claims that you cause to be infringed by modifications or additions to
35
+ the software. If you or your company make any written claim that the software
36
+ infringes or contributes to infringement of any patent, your patent license
37
+ for the software granted under these terms ends immediately. If your company
38
+ makes such a claim, your patent license ends immediately for work on behalf
39
+ of your company.
40
+
41
+ ## Notices
42
+
43
+ You must ensure that anyone who gets a copy of any part of the software from
44
+ you also gets a copy of these terms.
45
+
46
+ If you modify the software, you must include in any modified copies of the
47
+ software prominent notices stating that you have modified the software.
48
+
49
+ ## No Other Rights
50
+
51
+ These terms do not imply any licenses other than those expressly granted in
52
+ these terms.
53
+
54
+ ## Termination
55
+
56
+ If you use the software in violation of these terms, such use is not
57
+ licensed, and your licenses will automatically terminate. If the licensor
58
+ provides you with a notice of your violation, and you cease all violation of
59
+ this license no later than 30 days after you receive that notice, your
60
+ licenses will be reinstated retroactively. However, if you violate these
61
+ terms after such reinstatement, any additional violation of these terms will
62
+ cause your licenses to terminate automatically and permanently.
63
+
64
+ ## No Liability
65
+
66
+ *As far as the law allows, the software comes as is, without any warranty or
67
+ condition, and the licensor will not be liable to you for any damages arising
68
+ out of these terms or the use or nature of the software, under any kind of
69
+ legal claim.*
70
+
71
+ ## Definitions
72
+
73
+ The **licensor** is the entity offering these terms, and the **software** is
74
+ the software the licensor makes available under these terms, including any
75
+ portion of it.
76
+
77
+ **you** refers to the individual or entity agreeing to these terms.
78
+
79
+ **your company** is any legal entity, sole proprietorship, or other kind of
80
+ organization that you work for, plus all organizations that have control
81
+ over, are under the control of, or are under common control with that
82
+ organization. **control** means ownership of substantially all the assets of
83
+ an entity, or the power to direct its management and policies by vote,
84
+ contract, or otherwise. Control can be direct or indirect.
85
+
86
+ **your licenses** are all the licenses granted to you for the software under
87
+ these terms.
88
+
89
+ **use** means anything you do with the software requiring one of your
90
+ licenses.
91
+
92
+ **trademark** means trademarks, service marks, and similar rights.
93
+
94
+ ---
95
+
96
+ Copyright 2025-2026 En Dash Consulting
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @graview/tools
2
+
3
+ What can legally be done with a selection, and the agent surface that shares it.
4
+
5
+ Nobody authors an affordance. Providers notice things — a violation and the
6
+ repairs it names, a mutation whose subject accepts every selected kind, a
7
+ neighbour all but one of them share — and the results merge into one ranked
8
+ set. An LLM is one optional provider among these rather than the mechanism.
9
+
10
+ `createToolRuntime` generates a tool per mutation from the same declarations,
11
+ so an external agent over MCP and a seat inside the interface use literally the
12
+ same actions and produce literally the same diffs. That is why watching an
13
+ agent work needs no bespoke observability layer.
14
+
15
+ Read-only calls report the nodes they looked at, which is the half a diff
16
+ cannot show. A seat holding a principal gets tools for what that principal may
17
+ run, and is told plainly about the ones it may not.
18
+
19
+ ## The host: `graview mcp` and `graview apply`
20
+
21
+ An external agent — an editor's assistant, a worker on a schedule — used to
22
+ edit the seed file, because the seed was the only thing it could reach. These
23
+ two commands put the runtime where the data is, so it evolves the live graph
24
+ the way a person does: through `store.apply`, under its own seat, judged by the
25
+ same policy, logged with its name.
26
+
27
+ ```sh
28
+ graview mcp ./dist/domain/app.js --data ./data --as cursor --roles keeper
29
+ graview mcp ./dist/domain/app.js --remote-url https://host.example/app --header "authorization: Bearer …"
30
+ graview mcp ./dist/domain/app.js --list --roles keeper # the seat's tools as tools/list JSON
31
+
32
+ graview apply ./dist/domain/app.js --data ./data --roles keeper \
33
+ --call add-task --args '{"listId":"today","label":"Book the van","id":"t-van"}'
34
+ graview apply ./dist/domain/app.js --data ./data --roles keeper --plan ./plan.json --preview
35
+ graview apply ./dist/domain/app.js --data ./data --roles keeper --undo batch:7
36
+ ```
37
+
38
+ `mcp` speaks MCP over stdio — JSON-RPC, one message per line, no SDK — around
39
+ `createToolRuntime` and `createMcpAdapter`; a reply waits for the write to
40
+ land, or for the server's verdict against a remote, so "done" is never said
41
+ before it is true. `apply` is one act, a plan of many as one batch (`[{
42
+ mutation, args, as? }]`, a later call naming an earlier one's node as
43
+ `{ "$plan": "<as>" }`), or a take-back; `--preview` writes nothing. Both take
44
+ the store backends `graview serve` takes — `--data`, `--sqlite`,
45
+ `--remote-url` — and the seat flags `--as` and `--roles`. Every act that
46
+ creates a kind accepts an optional `id` for the node it makes; every kind has
47
+ a derived `remove-<kind>`, permitted through the acts that create it.
@@ -0,0 +1,43 @@
1
+ import type { AnySchema, GraphDiff, NodeOfSchema } from "@graview/core";
2
+ import type { ToolRuntime } from "./tools.js";
3
+ /** The shape an MCP server expects from `tools/list`. */
4
+ export interface McpTool {
5
+ readonly name: string;
6
+ readonly title?: string;
7
+ readonly description: string;
8
+ readonly inputSchema: Record<string, unknown>;
9
+ }
10
+ export interface McpContent {
11
+ readonly type: "text";
12
+ readonly text: string;
13
+ }
14
+ export interface McpToolResult {
15
+ readonly content: readonly McpContent[];
16
+ readonly isError?: boolean;
17
+ }
18
+ /**
19
+ * Exposes the runtime to an external agent (Claude Code, Cursor) over MCP.
20
+ *
21
+ * This is a TRANSPORT, not a second implementation: it renames fields and
22
+ * stringifies results, and that is all it does. The moment it starts making
23
+ * decisions the two agent surfaces have begun to diverge.
24
+ */
25
+ export declare function createMcpAdapter<S extends AnySchema>(runtime: ToolRuntime<S>): {
26
+ listTools(): McpTool[];
27
+ callTool(name: string, args?: Record<string, unknown>): Promise<McpToolResult>;
28
+ };
29
+ export interface InAppAgent<S extends AnySchema> {
30
+ readonly tools: ToolRuntime<S>["definitions"];
31
+ run(name: string, args?: Record<string, unknown>): Promise<unknown>;
32
+ /** Watch every change, whoever caused it. */
33
+ watch(listener: (diff: GraphDiff<NodeOfSchema<S>>) => void): () => void;
34
+ }
35
+ /**
36
+ * The same runtime, driven from a surface inside the interface.
37
+ *
38
+ * Both adapters emit the same diff stream, which is the whole point: a human
39
+ * edit and an agent edit are indistinguishable downstream, so the renderer
40
+ * highlights an agent's work with exactly the code that highlights yours.
41
+ */
42
+ export declare function createInAppAdapter<S extends AnySchema>(runtime: ToolRuntime<S>): InAppAgent<S>;
43
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../src/agent/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,yDAAyD;AACzD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;iBAE5D,OAAO,EAAE;mBAUd,MAAM,SACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,aAAa,CAAC;EAY5B;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,SAAS;IAC7C,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAC9C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,6CAA6C;IAC7C,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACzE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,SAAS,EACpD,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,UAAU,CAAC,CAAC,CAAC,CAYf"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Exposes the runtime to an external agent (Claude Code, Cursor) over MCP.
3
+ *
4
+ * This is a TRANSPORT, not a second implementation: it renames fields and
5
+ * stringifies results, and that is all it does. The moment it starts making
6
+ * decisions the two agent surfaces have begun to diverge.
7
+ */
8
+ export function createMcpAdapter(runtime) {
9
+ return {
10
+ listTools() {
11
+ return runtime.definitions.map((tool) => ({
12
+ name: tool.name,
13
+ ...(tool.title === undefined ? {} : { title: tool.title }),
14
+ description: tool.description,
15
+ inputSchema: tool.inputSchema,
16
+ }));
17
+ },
18
+ async callTool(name, args = {}) {
19
+ const result = await runtime.call(name, args);
20
+ if (!result.ok) {
21
+ return { content: [{ type: "text", text: result.error }], isError: true };
22
+ }
23
+ return {
24
+ content: [
25
+ { type: "text", text: JSON.stringify({ data: result.data, diff: result.diff }, null, 2) },
26
+ ],
27
+ };
28
+ },
29
+ };
30
+ }
31
+ /**
32
+ * The same runtime, driven from a surface inside the interface.
33
+ *
34
+ * Both adapters emit the same diff stream, which is the whole point: a human
35
+ * edit and an agent edit are indistinguishable downstream, so the renderer
36
+ * highlights an agent's work with exactly the code that highlights yours.
37
+ */
38
+ export function createInAppAdapter(runtime) {
39
+ return {
40
+ tools: runtime.definitions,
41
+ async run(name, args = {}) {
42
+ const result = await runtime.call(name, args);
43
+ if (!result.ok)
44
+ throw new Error(result.error);
45
+ return result.data;
46
+ },
47
+ watch(listener) {
48
+ return runtime.onDiff(listener);
49
+ },
50
+ };
51
+ }
52
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/agent/adapters.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAsB,OAAuB;IAC3E,OAAO;QACL,SAAS;YACP,OAAO,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACxC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC1D,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;aAC9B,CAAC,CAAC,CAAC;QACN,CAAC;QAED,KAAK,CAAC,QAAQ,CACZ,IAAY,EACZ,OAAgC,EAAE;YAElC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC5E,CAAC;YACD,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;iBAC1F;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAuB;IAEvB,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,WAAW;QAC1B,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE;YACvB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9C,OAAO,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,QAAQ;YACZ,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { type AnySchema, type GraphDiff, type JsonSchema, type NodeOfSchema, type Place, type Principal, type Store } from "@graview/core";
2
+ import { applyAffordance, type DeriveOptions } from "../derive.js";
3
+ export interface ToolDefinition {
4
+ readonly name: string;
5
+ readonly title?: string;
6
+ readonly description: string;
7
+ readonly inputSchema: JsonSchema;
8
+ /** False for the read tools, true for anything that changes the graph. */
9
+ readonly mutating: boolean;
10
+ }
11
+ export type ToolResult<S extends AnySchema> = {
12
+ readonly ok: true;
13
+ readonly data: unknown;
14
+ readonly diff?: GraphDiff<NodeOfSchema<S>>;
15
+ /**
16
+ * The nodes this call LOOKED AT.
17
+ *
18
+ * A diff can only ever show what changed, and an agent that reassigns
19
+ * one run after reading the whole week is doing something different
20
+ * from one that reassigns it after reading nothing. The runtime is the
21
+ * only place that knows, so it says so — and an interface can then draw
22
+ * attention as well as change.
23
+ */
24
+ readonly reads?: readonly string[];
25
+ } | {
26
+ readonly ok: false;
27
+ readonly error: string;
28
+ };
29
+ export interface ToolRuntimeOptions<S extends AnySchema> {
30
+ /**
31
+ * Who this seat acts as. A principal is an author with roles, so the seat's
32
+ * attribution and its authorisation are the same fact — there is no way to
33
+ * write as one participant and be permitted as another.
34
+ */
35
+ readonly author?: Principal;
36
+ /**
37
+ * Options for the seat's own deriveAffordances calls. A FUNCTION is read
38
+ * fresh on every call — which is how a person's pins, toggled in the
39
+ * menu after this runtime was built, still reach the agent's tool list.
40
+ * The strip, the pointer menu and the seat must never disagree about
41
+ * the same acts.
42
+ */
43
+ readonly derive?: DeriveOptions<S> | (() => DeriveOptions<S>);
44
+ /** Refuse every mutating tool. Useful for a read-only agent seat. */
45
+ readonly readOnly?: boolean;
46
+ /**
47
+ * The places the app's pictures name, so `search_graph` can answer "where
48
+ * do I go for X" as well as "where is X". The store cannot see pictures;
49
+ * whoever built the runtime beside a view registry can.
50
+ */
51
+ readonly places?: readonly Place[] | (() => readonly Place[]);
52
+ }
53
+ /**
54
+ * One tool call, as it happens.
55
+ *
56
+ * The claim was that watching an agent needs no bespoke observability
57
+ * because its edits produce the same diffs a human's do. True, and not
58
+ * enough: a diff says what changed, never what was CONSIDERED. An agent that
59
+ * reads six nodes and then reassigns one run appears, through diffs alone,
60
+ * as a single unexplained write — the interface can only offer a spinner and
61
+ * a toast. Emitting the calls themselves is what turns that into something a
62
+ * person can follow, and read-only calls are the interesting half.
63
+ */
64
+ export interface ToolCall {
65
+ readonly name: string;
66
+ readonly args: Readonly<Record<string, unknown>>;
67
+ readonly mutating: boolean;
68
+ /** `running` on the way in; `ok` or `failed` on the way out. */
69
+ readonly phase: "running" | "ok" | "failed";
70
+ readonly error?: string;
71
+ readonly at: string;
72
+ /**
73
+ * The nodes a settled read-only call looked at. Absent while running, and
74
+ * absent for a mutating call — a change already reports its own reads
75
+ * through the op log, and reporting them twice would double-count.
76
+ */
77
+ readonly reads?: readonly string[];
78
+ }
79
+ export interface ToolRuntime<S extends AnySchema> {
80
+ readonly definitions: readonly ToolDefinition[];
81
+ /**
82
+ * Who this seat writes as.
83
+ *
84
+ * Exposed because a read has to be attributed to the SAME participant the
85
+ * writes are, or one agent looking at the graph and then changing it reads
86
+ * as two people editing at once.
87
+ */
88
+ readonly author?: ToolRuntimeOptions<S>["author"];
89
+ call(name: string, args: Record<string, unknown>): Promise<ToolResult<S>>;
90
+ /** Every applied change, whoever caused it. */
91
+ onDiff(listener: (diff: GraphDiff<NodeOfSchema<S>>) => void): () => void;
92
+ /** Every call, mutating or not, as it starts and as it settles. */
93
+ onCall(listener: (call: ToolCall) => void): () => void;
94
+ }
95
+ /**
96
+ * Tool definitions generate ONCE from the schema and are transport-agnostic.
97
+ *
98
+ * The MCP adapter and the in-app adapter are two thin wrappers over the same
99
+ * runtime, so an external agent and a surface inside the interface are using
100
+ * literally the same actions and emitting literally the same diffs. That is
101
+ * why watching an agent work needs no bespoke observability layer.
102
+ */
103
+ export declare function createToolRuntime<S extends AnySchema>(store: Store<S>, options?: ToolRuntimeOptions<S>): ToolRuntime<S>;
104
+ export { applyAffordance };
105
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/agent/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,311 @@
1
+ import { mutationToolSchema, search, } from "@graview/core";
2
+ import { deriveAffordances, applyAffordance, } from "../derive.js";
3
+ const READ_TOOLS = [
4
+ {
5
+ name: "search_graph",
6
+ description: "Find things by name: records whose name or readable fields carry the words, and the kinds, places and rules the words name — each with why it matched. Reach for this before get_graph when you know what something is called. Words match the start of words, case and accents aside; key:value tokens narrow as a list's filter does (done:false, is:any for past records, kind:<kind>). Pass subject (a node id) to get the acts you may run on it as hits too.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ query: { type: "string", description: "Words, with optional key:value conditions." },
11
+ limit: { type: "number", description: "Most hits to return; 20 when unsaid." },
12
+ subject: { type: "string", description: "A node id whose acts to include." },
13
+ },
14
+ required: ["query"],
15
+ additionalProperties: false,
16
+ },
17
+ mutating: false,
18
+ },
19
+ {
20
+ name: "get_graph",
21
+ description: "Read the whole graph: every node with its fields, and every edge. Start here when you need the shape of the domain rather than one thing in it.",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {},
25
+ additionalProperties: false,
26
+ },
27
+ mutating: false,
28
+ },
29
+ {
30
+ name: "get_node",
31
+ description: "Read one node, its edges, and the violations that implicate it. Use this to check a thing before you change it.",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: { id: { type: "string", description: "Node id." } },
35
+ required: ["id"],
36
+ additionalProperties: false,
37
+ },
38
+ mutating: false,
39
+ },
40
+ {
41
+ name: "get_violations",
42
+ description: "List every invariant violation the graph currently has, with the repairs that would resolve each one. Read this before proposing work: a rule that is already broken is more urgent than anything you could add.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ context: {
47
+ type: "object",
48
+ description: "Evaluation context, e.g. { weekStart }.",
49
+ },
50
+ },
51
+ additionalProperties: false,
52
+ },
53
+ mutating: false,
54
+ },
55
+ {
56
+ name: "get_affordances",
57
+ description: "Ask what can legally be done with a selection, ranked. Prefer these over composing a mutation by hand: they are derived from the schema, the invariants and the shape of the graph, so they cannot name an action that does not exist.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ selection: {
62
+ type: "array",
63
+ items: { type: "string" },
64
+ description: "Node ids.",
65
+ },
66
+ context: { type: "object" },
67
+ },
68
+ required: ["selection"],
69
+ additionalProperties: false,
70
+ },
71
+ mutating: false,
72
+ },
73
+ {
74
+ name: "preview_mutation",
75
+ description: "Try a mutation without applying it: get back the diff it would produce and any invariant it would break. Do this when you are unsure, rather than applying and undoing.",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ mutation: { type: "string" },
80
+ args: { type: "object" },
81
+ },
82
+ required: ["mutation", "args"],
83
+ additionalProperties: false,
84
+ },
85
+ mutating: false,
86
+ },
87
+ {
88
+ name: "undo_batch",
89
+ description: "Undo one batch of operations. Fails, naming the blocking operation, when a later operation read what it wrote.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ batch: { type: "string" },
94
+ include: { type: "array", items: { type: "string" } },
95
+ },
96
+ required: ["batch"],
97
+ additionalProperties: false,
98
+ },
99
+ mutating: true,
100
+ },
101
+ ];
102
+ /**
103
+ * Tool definitions generate ONCE from the schema and are transport-agnostic.
104
+ *
105
+ * The MCP adapter and the in-app adapter are two thin wrappers over the same
106
+ * runtime, so an external agent and a surface inside the interface are using
107
+ * literally the same actions and emitting literally the same diffs. That is
108
+ * why watching an agent work needs no bespoke observability layer.
109
+ */
110
+ export function createToolRuntime(store, options = {}) {
111
+ /*
112
+ * Only what this seat MAY do.
113
+ *
114
+ * The narrowing is the store's, not the runtime's: a seat holding a
115
+ * principal gets tools for what that principal can run, and there is no
116
+ * second list to keep in step with the policy. A seat that then calls a
117
+ * mutation it was not given still hits the store's refusal, because the
118
+ * schema is a convenience and the enforcement is elsewhere.
119
+ */
120
+ const mutationTools = store
121
+ .permittedMutations(options.author ?? { kind: "agent" })
122
+ .map((mutation) => {
123
+ const tool = mutationToolSchema(mutation);
124
+ return {
125
+ name: mutation.name,
126
+ ...(tool.title === undefined ? {} : { title: tool.title }),
127
+ description: tool.description,
128
+ inputSchema: tool.inputSchema,
129
+ mutating: true,
130
+ };
131
+ });
132
+ const definitions = options.readOnly
133
+ ? [...READ_TOOLS.filter((tool) => !tool.mutating)]
134
+ : [...READ_TOOLS, ...mutationTools];
135
+ /**
136
+ * The call itself, separated from the announcing so that every exit —
137
+ * including an early return for an unknown tool — is reported exactly once.
138
+ */
139
+ const run = async (name, args) => {
140
+ try {
141
+ const definition = definitions.find((tool) => tool.name === name);
142
+ if (!definition) {
143
+ /*
144
+ * A tool this seat may not use EXISTS, and saying "unknown" would be
145
+ * a lie an agent then reasons from — it would conclude the capability
146
+ * is missing and go looking for a workaround. The same honesty the
147
+ * interface owes a person: it is there, you may not use it, here is
148
+ * who can.
149
+ */
150
+ const withheld = store.allMutations().find((mutation) => mutation.name === name);
151
+ if (withheld) {
152
+ const verdict = store.permits({ name, args }, options.author ?? { kind: "agent" });
153
+ if (!verdict.ok)
154
+ return { ok: false, error: verdict.refusal.message };
155
+ }
156
+ return {
157
+ ok: false,
158
+ error: `Unknown tool "${name}". Available: ${definitions.map((t) => t.name).join(", ")}`,
159
+ };
160
+ }
161
+ if (definition.mutating && options.readOnly) {
162
+ return {
163
+ ok: false,
164
+ error: `"${name}" changes the graph, and this seat is read-only.`,
165
+ };
166
+ }
167
+ switch (name) {
168
+ case "search_graph": {
169
+ const places = typeof options.places === "function" ? options.places() : options.places;
170
+ const subject = typeof args["subject"] === "string" ? args["subject"] : undefined;
171
+ const found = search(store, String(args["query"] ?? ""), {
172
+ principal: options.author ?? { kind: "agent" },
173
+ limit: typeof args["limit"] === "number" ? args["limit"] : 20,
174
+ ...(places ? { places } : {}),
175
+ ...(subject ? { subject, from: [subject] } : {}),
176
+ });
177
+ return {
178
+ ok: true,
179
+ data: found,
180
+ // What came back was looked at: the records named, and nothing else.
181
+ reads: found.hits.flatMap((hit) => (hit.about === "node" ? [hit.id] : [])),
182
+ };
183
+ }
184
+ case "get_graph": {
185
+ const snapshot = store.graph.snapshot();
186
+ return {
187
+ ok: true,
188
+ data: snapshot,
189
+ reads: snapshot.nodes.map((node) => node.id),
190
+ };
191
+ }
192
+ case "get_node": {
193
+ const id = String(args["id"] ?? "");
194
+ const node = store.graph.getNode(id);
195
+ if (!node)
196
+ return { ok: false, error: `No node "${id}".` };
197
+ const out = store.graph.outEdges(id);
198
+ const inbound = store.graph.inEdges(id);
199
+ return {
200
+ ok: true,
201
+ data: {
202
+ node,
203
+ out,
204
+ in: inbound,
205
+ violations: store
206
+ .violations()
207
+ .filter((violation) => violation.nodeIds.includes(id)),
208
+ },
209
+ // Asking about a node is asking about its neighbourhood: the
210
+ // answer names them, so looking at it looked at them.
211
+ reads: [id, ...out.map((edge) => edge.to), ...inbound.map((edge) => edge.from)],
212
+ };
213
+ }
214
+ case "get_violations":
215
+ {
216
+ const violations = store.violations(args["context"]);
217
+ return {
218
+ ok: true,
219
+ data: violations,
220
+ reads: [...new Set(violations.flatMap((violation) => violation.nodeIds))],
221
+ };
222
+ }
223
+ case "get_affordances": {
224
+ const selection = args["selection"] ?? [];
225
+ const derived = deriveAffordances(store, selection, {
226
+ ...(typeof options.derive === "function" ? options.derive() : options.derive),
227
+ // The seat asks as ITSELF, so what it is offered is what it may
228
+ // do — and what it may not is stated rather than hidden, which
229
+ // is how an agent learns a capability exists that it lacks.
230
+ ...(options.author ? { principal: options.author } : {}),
231
+ ...(args["context"]
232
+ ? { context: args["context"] }
233
+ : {}),
234
+ });
235
+ return { ok: true, data: derived, reads: selection };
236
+ }
237
+ case "preview_mutation": {
238
+ const preview = store.preview({
239
+ name: String(args["mutation"]),
240
+ args: args["args"] ?? {},
241
+ });
242
+ return { ok: true, data: preview };
243
+ }
244
+ case "undo_batch": {
245
+ const batch = String(args["batch"]);
246
+ const include = args["include"] ?? [];
247
+ const check = store.canUndo([batch, ...include]);
248
+ if (!check.ok)
249
+ return { ok: false, error: check.message };
250
+ const result = store.undo([batch, ...include], {
251
+ ...(options.author ? { author: options.author } : {}),
252
+ });
253
+ return { ok: true, data: result, diff: result.diff };
254
+ }
255
+ default: {
256
+ const result = store.apply({ name, args }, { ...(options.author ? { author: options.author } : {}) });
257
+ return {
258
+ ok: true,
259
+ data: {
260
+ batch: result.batch,
261
+ intent: result.intent,
262
+ introduces: result.introduces,
263
+ resolves: result.resolves,
264
+ },
265
+ diff: result.diff,
266
+ };
267
+ }
268
+ }
269
+ }
270
+ catch (error) {
271
+ return {
272
+ ok: false,
273
+ error: error instanceof Error ? error.message : String(error),
274
+ };
275
+ }
276
+ };
277
+ const watchers = new Set();
278
+ const announce = (call) => {
279
+ for (const watcher of watchers)
280
+ watcher(call);
281
+ };
282
+ return {
283
+ definitions,
284
+ ...(options.author ? { author: options.author } : {}),
285
+ onDiff(listener) {
286
+ return store.subscribe(listener);
287
+ },
288
+ onCall(listener) {
289
+ watchers.add(listener);
290
+ return () => watchers.delete(listener);
291
+ },
292
+ async call(name, args) {
293
+ const mutating = definitions.find((tool) => tool.name === name)?.mutating ?? false;
294
+ const started = { name, args, mutating, at: new Date().toISOString() };
295
+ announce({ ...started, phase: "running" });
296
+ const settle = (result) => {
297
+ announce(result.ok
298
+ ? {
299
+ ...started,
300
+ phase: "ok",
301
+ ...(!mutating && result.reads ? { reads: result.reads } : {}),
302
+ }
303
+ : { ...started, phase: "failed", error: result.error });
304
+ return result;
305
+ };
306
+ return settle(await run(name, args));
307
+ },
308
+ };
309
+ }
310
+ export { applyAffordance };
311
+ //# sourceMappingURL=tools.js.map