hcifootprint 0.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 (87) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +524 -0
  3. package/dist/atom/types.d.ts +618 -0
  4. package/dist/atom/types.d.ts.map +1 -0
  5. package/dist/atom/types.js +2 -0
  6. package/dist/atom/types.js.map +1 -0
  7. package/dist/graph/builder.d.ts +31 -0
  8. package/dist/graph/builder.d.ts.map +1 -0
  9. package/dist/graph/builder.js +165 -0
  10. package/dist/graph/builder.js.map +1 -0
  11. package/dist/graph/guards.d.ts +31 -0
  12. package/dist/graph/guards.d.ts.map +1 -0
  13. package/dist/graph/guards.js +94 -0
  14. package/dist/graph/guards.js.map +1 -0
  15. package/dist/graph/skill-deps.d.ts +20 -0
  16. package/dist/graph/skill-deps.d.ts.map +1 -0
  17. package/dist/graph/skill-deps.js +15 -0
  18. package/dist/graph/skill-deps.js.map +1 -0
  19. package/dist/index.d.ts +49 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +42 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/mcp.d.ts +16 -0
  24. package/dist/mcp.d.ts.map +1 -0
  25. package/dist/mcp.js +15 -0
  26. package/dist/mcp.js.map +1 -0
  27. package/dist/presence/presence.d.ts +50 -0
  28. package/dist/presence/presence.d.ts.map +1 -0
  29. package/dist/presence/presence.js +126 -0
  30. package/dist/presence/presence.js.map +1 -0
  31. package/dist/registry/registry.d.ts +53 -0
  32. package/dist/registry/registry.d.ts.map +1 -0
  33. package/dist/registry/registry.js +76 -0
  34. package/dist/registry/registry.js.map +1 -0
  35. package/dist/serve/mcp-server.d.ts +40 -0
  36. package/dist/serve/mcp-server.d.ts.map +1 -0
  37. package/dist/serve/mcp-server.js +72 -0
  38. package/dist/serve/mcp-server.js.map +1 -0
  39. package/dist/serve/mcp.d.ts +12 -0
  40. package/dist/serve/mcp.d.ts.map +1 -0
  41. package/dist/serve/mcp.js +62 -0
  42. package/dist/serve/mcp.js.map +1 -0
  43. package/dist/serve/modes.d.ts +66 -0
  44. package/dist/serve/modes.d.ts.map +1 -0
  45. package/dist/serve/modes.js +347 -0
  46. package/dist/serve/modes.js.map +1 -0
  47. package/dist/testing/harness.d.ts +156 -0
  48. package/dist/testing/harness.d.ts.map +1 -0
  49. package/dist/testing/harness.js +416 -0
  50. package/dist/testing/harness.js.map +1 -0
  51. package/dist/testing/index.d.ts +35 -0
  52. package/dist/testing/index.d.ts.map +1 -0
  53. package/dist/testing/index.js +32 -0
  54. package/dist/testing/index.js.map +1 -0
  55. package/dist/testing/lint.d.ts +20 -0
  56. package/dist/testing/lint.d.ts.map +1 -0
  57. package/dist/testing/lint.js +18 -0
  58. package/dist/testing/lint.js.map +1 -0
  59. package/dist/testing/model/check.d.ts +30 -0
  60. package/dist/testing/model/check.d.ts.map +1 -0
  61. package/dist/testing/model/check.js +89 -0
  62. package/dist/testing/model/check.js.map +1 -0
  63. package/dist/testing/model/lint.d.ts +54 -0
  64. package/dist/testing/model/lint.d.ts.map +1 -0
  65. package/dist/testing/model/lint.js +235 -0
  66. package/dist/testing/model/lint.js.map +1 -0
  67. package/dist/testing/model/satisfiable.d.ts +25 -0
  68. package/dist/testing/model/satisfiable.d.ts.map +1 -0
  69. package/dist/testing/model/satisfiable.js +101 -0
  70. package/dist/testing/model/satisfiable.js.map +1 -0
  71. package/dist/traverse/nav-session.d.ts +108 -0
  72. package/dist/traverse/nav-session.d.ts.map +1 -0
  73. package/dist/traverse/nav-session.js +637 -0
  74. package/dist/traverse/nav-session.js.map +1 -0
  75. package/dist/traverse/session.d.ts +239 -0
  76. package/dist/traverse/session.d.ts.map +1 -0
  77. package/dist/traverse/session.js +1305 -0
  78. package/dist/traverse/session.js.map +1 -0
  79. package/dist/tree/appmap.d.ts +8 -0
  80. package/dist/tree/appmap.d.ts.map +1 -0
  81. package/dist/tree/appmap.js +259 -0
  82. package/dist/tree/appmap.js.map +1 -0
  83. package/dist/tree/types.d.ts +151 -0
  84. package/dist/tree/types.d.ts.map +1 -0
  85. package/dist/tree/types.js +2 -0
  86. package/dist/tree/types.js.map +1 -0
  87. package/package.json +90 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * PresenceIndex — the pure presence sensor (D18).
3
+ *
4
+ * Registration observes MOUNTED, nothing more. This index is deliberately a
5
+ * plain refcounted data structure that knows nothing about sessions, trees,
6
+ * routers, or footprint — the meaning of presence (dormancy below the router,
7
+ * overlay masking, tab exclusivity, assumed-active defaults) lives one layer
8
+ * up in NavSession, which COMPOSES this with the authored tree.
9
+ *
10
+ * Contract points that make React StrictMode/HMR safe by construction:
11
+ * - Handles are identities: open() returns a token, close(token) is
12
+ * idempotent per token. setup→cleanup→setup nets to one open handle.
13
+ * - Instance handles (repeats containers) are tracked separately and are
14
+ * EXCLUDED from the fingerprint — a scrolling virtualized list must never
15
+ * look like world motion (that scoping rule is enforced here, at the
16
+ * lowest layer that can).
17
+ * - Visibility is an EXPLICIT signal store (set by show()/setVisible()/the
18
+ * `visible:` mount option). No amount of mount-counting can see CSS; when
19
+ * no signal exists the layer above serves honesty markers instead of
20
+ * guessing.
21
+ */
22
+ export class PresenceIndex {
23
+ #nextToken = 1;
24
+ /** token → what it holds open. */
25
+ #open = new Map();
26
+ /** node → count of open NODE handles (instance handles excluded). */
27
+ #nodeCounts = new Map();
28
+ /** node → instance key → count of open instance handles. */
29
+ #instanceCounts = new Map();
30
+ /** node → last explicit visibility signal. */
31
+ #visibility = new Map();
32
+ open(node, instance) {
33
+ const token = this.#nextToken++;
34
+ this.#open.set(token, { node, instance });
35
+ if (instance === undefined) {
36
+ this.#nodeCounts.set(node, (this.#nodeCounts.get(node) ?? 0) + 1);
37
+ }
38
+ else {
39
+ const byInstance = this.#instanceCounts.get(node) ?? new Map();
40
+ byInstance.set(instance, (byInstance.get(instance) ?? 0) + 1);
41
+ this.#instanceCounts.set(node, byInstance);
42
+ }
43
+ const index = this;
44
+ return {
45
+ node,
46
+ instance,
47
+ release() {
48
+ index.#release(token);
49
+ },
50
+ };
51
+ }
52
+ #release(token) {
53
+ const held = this.#open.get(token);
54
+ if (!held)
55
+ return; // idempotent per handle
56
+ this.#open.delete(token);
57
+ if (held.instance === undefined) {
58
+ const next = (this.#nodeCounts.get(held.node) ?? 1) - 1;
59
+ if (next <= 0) {
60
+ this.#nodeCounts.delete(held.node);
61
+ // A visibility signal describes a mounted thing. When the last handle
62
+ // leaves, the signal leaves with it — otherwise a released modal whose
63
+ // app once said visible:true would mask its page FOREVER (ghost mask).
64
+ // Signals set on never-mounted nodes (the pure-L0 wire) are untouched.
65
+ this.#visibility.delete(held.node);
66
+ }
67
+ else {
68
+ this.#nodeCounts.set(held.node, next);
69
+ }
70
+ }
71
+ else {
72
+ const byInstance = this.#instanceCounts.get(held.node);
73
+ if (!byInstance)
74
+ return;
75
+ const next = (byInstance.get(held.instance) ?? 1) - 1;
76
+ if (next <= 0)
77
+ byInstance.delete(held.instance);
78
+ else
79
+ byInstance.set(held.instance, next);
80
+ if (byInstance.size === 0)
81
+ this.#instanceCounts.delete(held.node);
82
+ }
83
+ }
84
+ /** A node is present when at least one NODE handle is open on it. */
85
+ isPresent(node) {
86
+ return (this.#nodeCounts.get(node) ?? 0) > 0;
87
+ }
88
+ presentNodes() {
89
+ return [...this.#nodeCounts.keys()];
90
+ }
91
+ /** Mounted instance keys of a repeats node (the mounted WINDOW, not existence). */
92
+ instancesOf(node) {
93
+ return [...(this.#instanceCounts.get(node)?.keys() ?? [])];
94
+ }
95
+ hasInstance(node, instance) {
96
+ return (this.#instanceCounts.get(node)?.get(instance) ?? 0) > 0;
97
+ }
98
+ /** True when ANY node handle is open — the signal that presence is in use at all. */
99
+ hasAnyHandles() {
100
+ return this.#nodeCounts.size > 0;
101
+ }
102
+ /** True when ANY handle (node OR instance) is open — "the mount layer is in use". */
103
+ hasAny() {
104
+ return this.#open.size > 0;
105
+ }
106
+ setVisible(node, visible) {
107
+ this.#visibility.set(node, visible);
108
+ }
109
+ /** The explicit signal, or undefined when none was ever given (→ honesty markers above). */
110
+ visibility(node) {
111
+ return this.#visibility.get(node);
112
+ }
113
+ /**
114
+ * The served-structure identity used for coalesced world-motion detection:
115
+ * node presence + visibility signals. Instance churn is EXCLUDED by design.
116
+ */
117
+ fingerprint() {
118
+ const nodes = [...this.#nodeCounts.keys()].sort().join('|');
119
+ const vis = [...this.#visibility.entries()]
120
+ .map(([node, visible]) => `${node}=${visible ? 1 : 0}`)
121
+ .sort()
122
+ .join('|');
123
+ return `${nodes}::${vis}`;
124
+ }
125
+ }
126
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/presence/presence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * ToolRegistry — the LIVE-BINDING layer (D13: declare statically, bind dynamically).
3
+ *
4
+ * The declared graph is the map; this registry is what's actually wired right
5
+ * now: affordanceId → the app's real handler function, registered in GROUPS
6
+ * (one per component/section) so unmount cleanup is a single call.
7
+ *
8
+ * Deliberately knows nothing about sessions, guards, or footprint — a plain
9
+ * data structure so this layer tests in isolation.
10
+ *
11
+ * Semantics:
12
+ * - Last registration wins per affordance (React StrictMode double-mounts;
13
+ * a dev warning fires so real duplicates are visible).
14
+ * - unregisterGroup(g) removes only registrations whose CURRENT owner is g —
15
+ * if group B re-registered an id after group A, A's unmount cannot tear
16
+ * down B's live binding.
17
+ * - Registration carries NO planner-facing strings (descriptions/guards live
18
+ * in the declared spec — the prompt-injection firewall).
19
+ */
20
+ export type ToolHandler = (payload?: unknown) => unknown | Promise<unknown>;
21
+ export interface Registration {
22
+ affordanceId: string;
23
+ group: string;
24
+ handler: ToolHandler;
25
+ registeredAt: number;
26
+ /**
27
+ * False when the control is on screen but not currently clickable (a greyed
28
+ * button). The tool is still SERVED to the agent — with an honesty marker —
29
+ * but firing it is refused as TOOL_DISABLED. Default true.
30
+ */
31
+ enabled: boolean;
32
+ }
33
+ export declare class ToolRegistry {
34
+ #private;
35
+ constructor(warn?: (message: string) => void);
36
+ register(group: string, affordanceId: string, handler: ToolHandler, enabled?: boolean): void;
37
+ /**
38
+ * Flip a registered tool between clickable and greyed-out. Returns true if
39
+ * the state actually changed (so the caller can bump the version / emit only
40
+ * on a real change). No-op + false if the id isn't registered.
41
+ */
42
+ setEnabled(affordanceId: string, enabled: boolean): boolean;
43
+ /** Whether a registered tool is currently clickable. Undefined if not registered. */
44
+ isEnabled(affordanceId: string): boolean | undefined;
45
+ /** Remove every registration currently owned by `group`. Returns the removed ids. */
46
+ unregisterGroup(group: string): string[];
47
+ handlerFor(affordanceId: string): ToolHandler | undefined;
48
+ isRegistered(affordanceId: string): boolean;
49
+ /** True when anything is registered — the signal that materialization is meaningful. */
50
+ hasAny(): boolean;
51
+ registrations(): Registration[];
52
+ }
53
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,YAAY;;gBAIX,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAI5C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,UAAO,GAAG,IAAI;IAYzF;;;;OAIG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;IAO3D,qFAAqF;IACrF,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIpD,qFAAqF;IACrF,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAWxC,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIzD,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI3C,wFAAwF;IACxF,MAAM,IAAI,OAAO;IAIjB,aAAa,IAAI,YAAY,EAAE;CAGhC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ToolRegistry — the LIVE-BINDING layer (D13: declare statically, bind dynamically).
3
+ *
4
+ * The declared graph is the map; this registry is what's actually wired right
5
+ * now: affordanceId → the app's real handler function, registered in GROUPS
6
+ * (one per component/section) so unmount cleanup is a single call.
7
+ *
8
+ * Deliberately knows nothing about sessions, guards, or footprint — a plain
9
+ * data structure so this layer tests in isolation.
10
+ *
11
+ * Semantics:
12
+ * - Last registration wins per affordance (React StrictMode double-mounts;
13
+ * a dev warning fires so real duplicates are visible).
14
+ * - unregisterGroup(g) removes only registrations whose CURRENT owner is g —
15
+ * if group B re-registered an id after group A, A's unmount cannot tear
16
+ * down B's live binding.
17
+ * - Registration carries NO planner-facing strings (descriptions/guards live
18
+ * in the declared spec — the prompt-injection firewall).
19
+ */
20
+ export class ToolRegistry {
21
+ #byAffordance = new Map();
22
+ #warn;
23
+ constructor(warn) {
24
+ this.#warn = warn ?? ((message) => console.warn(message));
25
+ }
26
+ register(group, affordanceId, handler, enabled = true) {
27
+ const existing = this.#byAffordance.get(affordanceId);
28
+ if (existing) {
29
+ this.#warn(`hcifootprint: '${affordanceId}' re-registered by group '${group}' (previously '${existing.group}') — ` +
30
+ `last registration wins. Common causes: a component mounted twice without unregistering, or two ` +
31
+ `components claiming the same action.`);
32
+ }
33
+ this.#byAffordance.set(affordanceId, { affordanceId, group, handler, registeredAt: Date.now(), enabled });
34
+ }
35
+ /**
36
+ * Flip a registered tool between clickable and greyed-out. Returns true if
37
+ * the state actually changed (so the caller can bump the version / emit only
38
+ * on a real change). No-op + false if the id isn't registered.
39
+ */
40
+ setEnabled(affordanceId, enabled) {
41
+ const reg = this.#byAffordance.get(affordanceId);
42
+ if (!reg || reg.enabled === enabled)
43
+ return false;
44
+ reg.enabled = enabled;
45
+ return true;
46
+ }
47
+ /** Whether a registered tool is currently clickable. Undefined if not registered. */
48
+ isEnabled(affordanceId) {
49
+ return this.#byAffordance.get(affordanceId)?.enabled;
50
+ }
51
+ /** Remove every registration currently owned by `group`. Returns the removed ids. */
52
+ unregisterGroup(group) {
53
+ const removed = [];
54
+ for (const [id, reg] of this.#byAffordance) {
55
+ if (reg.group === group) {
56
+ this.#byAffordance.delete(id);
57
+ removed.push(id);
58
+ }
59
+ }
60
+ return removed;
61
+ }
62
+ handlerFor(affordanceId) {
63
+ return this.#byAffordance.get(affordanceId)?.handler;
64
+ }
65
+ isRegistered(affordanceId) {
66
+ return this.#byAffordance.has(affordanceId);
67
+ }
68
+ /** True when anything is registered — the signal that materialization is meaningful. */
69
+ hasAny() {
70
+ return this.#byAffordance.size > 0;
71
+ }
72
+ registrations() {
73
+ return [...this.#byAffordance.values()].map((r) => ({ ...r }));
74
+ }
75
+ }
76
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH,MAAM,OAAO,YAAY;IACd,aAAa,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,KAAK,CAA4B;IAE1C,YAAY,IAAgC;QAC1C,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,QAAQ,CAAC,KAAa,EAAE,YAAoB,EAAE,OAAoB,EAAE,OAAO,GAAG,IAAI;QAChF,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACtD,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,CACR,kBAAkB,YAAY,6BAA6B,KAAK,kBAAkB,QAAQ,CAAC,KAAK,OAAO;gBACrG,iGAAiG;gBACjG,sCAAsC,CACzC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5G,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,YAAoB,EAAE,OAAgB;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACjD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QAClD,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qFAAqF;IACrF,SAAS,CAAC,YAAoB;QAC5B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvD,CAAC;IAED,qFAAqF;IACrF,eAAe,CAAC,KAAa;QAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YAC3C,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;gBACxB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC9B,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU,CAAC,YAAoB;QAC7B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvD,CAAC;IAED,YAAY,CAAC,YAAoB;QAC/B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC;IAED,wFAAwF;IACxF,MAAM;QACJ,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,aAAa;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;CACF"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * mcpServer(session) — expose the session as a real MODEL CONTEXT PROTOCOL
3
+ * server, so ANY MCP host (Claude Desktop, a LangGraph MCP client, Cursor, …)
4
+ * can drive your app without a line of framework-specific glue.
5
+ *
6
+ * It wraps {@link skillsAsTools} (Mode B): `tools/list` returns the FIXED tool
7
+ * array (one per skill + whats_here / do_action / why), and `tools/call` routes to
8
+ * the port. Because the tool set never changes, a plain MCP server works with
9
+ * no `tools/list_changed` churn — that is the whole point of the fixed-tool
10
+ * design. High-effect steps come back as `judgment: 'needs-confirm'` in the
11
+ * result; the HOST decides how to get human approval, then calls again with
12
+ * `confirm: true` — a portable, framework-agnostic human-in-the-loop.
13
+ *
14
+ * This module lives behind the `hcifootprint/mcp` subpath and is the ONLY
15
+ * place `@modelcontextprotocol/sdk` (an OPTIONAL peer dependency) is imported —
16
+ * the core entry stays zero-dependency. You pick the transport (stdio, SSE,
17
+ * streamable HTTP) and connect it yourself; the topology (where the live
18
+ * session runs) is yours.
19
+ *
20
+ * @example
21
+ * import { mcpServer } from 'hcifootprint/mcp';
22
+ * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
23
+ * const server = mcpServer(session);
24
+ * await server.connect(new StdioServerTransport());
25
+ */
26
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
27
+ import type { SkillToolsOptions } from './modes.js';
28
+ import type { Session } from '../traverse/session.js';
29
+ export interface McpServerOptions extends SkillToolsOptions {
30
+ /** Server name advertised over MCP. Default: the graph id. */
31
+ name?: string;
32
+ /** Server version advertised over MCP. Default '0.1.0'. */
33
+ version?: string;
34
+ }
35
+ /**
36
+ * Build an MCP `Server` backed by a live InteractionSession. Attach any
37
+ * transport with `server.connect(transport)`.
38
+ */
39
+ export declare function mcpServer(session: Session, opts?: McpServerOptions): Server;
40
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/serve/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,MAAM,CA0C3E"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * mcpServer(session) — expose the session as a real MODEL CONTEXT PROTOCOL
3
+ * server, so ANY MCP host (Claude Desktop, a LangGraph MCP client, Cursor, …)
4
+ * can drive your app without a line of framework-specific glue.
5
+ *
6
+ * It wraps {@link skillsAsTools} (Mode B): `tools/list` returns the FIXED tool
7
+ * array (one per skill + whats_here / do_action / why), and `tools/call` routes to
8
+ * the port. Because the tool set never changes, a plain MCP server works with
9
+ * no `tools/list_changed` churn — that is the whole point of the fixed-tool
10
+ * design. High-effect steps come back as `judgment: 'needs-confirm'` in the
11
+ * result; the HOST decides how to get human approval, then calls again with
12
+ * `confirm: true` — a portable, framework-agnostic human-in-the-loop.
13
+ *
14
+ * This module lives behind the `hcifootprint/mcp` subpath and is the ONLY
15
+ * place `@modelcontextprotocol/sdk` (an OPTIONAL peer dependency) is imported —
16
+ * the core entry stays zero-dependency. You pick the transport (stdio, SSE,
17
+ * streamable HTTP) and connect it yourself; the topology (where the live
18
+ * session runs) is yours.
19
+ *
20
+ * @example
21
+ * import { mcpServer } from 'hcifootprint/mcp';
22
+ * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
23
+ * const server = mcpServer(session);
24
+ * await server.connect(new StdioServerTransport());
25
+ */
26
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
27
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
28
+ import { skillsAsTools } from './modes.js';
29
+ /**
30
+ * Build an MCP `Server` backed by a live InteractionSession. Attach any
31
+ * transport with `server.connect(transport)`.
32
+ */
33
+ export function mcpServer(session, opts) {
34
+ const port = skillsAsTools(session, opts);
35
+ const server = new Server({ name: opts?.name ?? session.graphId, version: opts?.version ?? '0.1.0' }, { capabilities: { tools: {} } });
36
+ // Fixed tool list — identical every request (Mode B), so no list_changed.
37
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
38
+ tools: port.tools(),
39
+ }));
40
+ // Route a call to the port; a domain rejection is a normal result the model
41
+ // reads (needs-confirm, GUARD_FAILED, …). Only a genuinely unknown tool — or
42
+ // an unexpected throw — is surfaced as isError.
43
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
44
+ const { name, arguments: args } = request.params;
45
+ try {
46
+ const result = port.call(name, args ?? {});
47
+ // Act → data back, over the wire: if the call fired something, let the
48
+ // handler settle and fold any produced data (search results, a looked-up
49
+ // record) INTO the result — so a remote MCP client sees it, not just an
50
+ // in-process caller.
51
+ if (typeof result['transitionId'] === 'string') {
52
+ await new Promise((resolve) => setTimeout(resolve, 0));
53
+ const produced = session.producedFor(result['transitionId']);
54
+ if (produced !== undefined)
55
+ result['data'] = produced;
56
+ }
57
+ const misused = result['reason'] === 'UNKNOWN_TOOL';
58
+ return {
59
+ content: [{ type: 'text', text: JSON.stringify(result) }],
60
+ ...(misused ? { isError: true } : {}),
61
+ };
62
+ }
63
+ catch (error) {
64
+ return {
65
+ content: [{ type: 'text', text: `hcifootprint: tool '${name}' failed: ${String(error)}` }],
66
+ isError: true,
67
+ };
68
+ }
69
+ });
70
+ return server;
71
+ }
72
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../../src/serve/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAEnG,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAW3C;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,IAAuB;IACjE,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,OAAO,EAAE,EAC1E,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,0EAA0E;IAC1E,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,KAAK,EAAE,IAAI,CAAC,KAAK,EAAuB;KACzC,CAAC,CAAC,CAAC;IAEJ,4EAA4E;IAC5E,6EAA6E;IAC7E,gDAAgD;IAChD,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAA4B,CAAC;YACtE,uEAAuE;YACvE,yEAAyE;YACzE,wEAAwE;YACxE,qBAAqB;YACrB,IAAI,OAAO,MAAM,CAAC,cAAc,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC/C,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;gBAC7D,IAAI,QAAQ,KAAK,SAAS;oBAAE,MAAM,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC;YACxD,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,cAAc,CAAC;YACpD,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,uBAAuB,IAAI,aAAa,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBACnG,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { MCPToolDescription } from 'footprintjs';
2
+ import type { AvailableEdge, SkillGraphSpec } from '../atom/types.js';
3
+ /**
4
+ * Synthetic escape tool served whenever a skill frame is open — the acting
5
+ * agent must always be able to collapse back to skill-level planning. The
6
+ * description is an authored-class constant (two-string-class safe).
7
+ */
8
+ export declare function leaveSkillTool(spec: SkillGraphSpec, skillId: string): MCPToolDescription;
9
+ export declare function edgesToMCPTools(spec: SkillGraphSpec, edges: AvailableEdge[], opts?: {
10
+ lossySchemas?: boolean;
11
+ }): MCPToolDescription[];
12
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/serve/mcp.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOtE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAMxF;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,aAAa,EAAE,EACtB,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,kBAAkB,EAAE,CAStB"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Per-edge MCP tool emission.
3
+ *
4
+ * footprint's toMCPTool() is one-tool-per-CHART with a permanent cache — the
5
+ * wrong grain for an action space that changes on every available() call. This
6
+ * follows the repo's own multi-tool precedent (the MCP triage socket example):
7
+ * hand-shaped {name, description, inputSchema} descriptors, reusing only the
8
+ * pure schema layer (detectSchema + normalizeSchema).
9
+ *
10
+ * Two-string-class invariant: `description` is ALWAYS the authored affordance
11
+ * description. Runtime-resolved strings (DOM labels, user content) are data —
12
+ * they never reach a descriptor, so attacker-controlled page text cannot
13
+ * inject instructions into the planner's action space.
14
+ */
15
+ import { detectSchema } from 'footprintjs';
16
+ import { normalizeSchema } from 'footprintjs/advanced';
17
+ const NO_PARAMS = { type: 'object', properties: {}, additionalProperties: false };
18
+ /** Appended (authored-class constant) so MCP-only planners see the step-up marker. */
19
+ const HIGH_EFFECT_SUFFIX = ' [high-effect: requires explicit confirmation]';
20
+ /**
21
+ * Synthetic escape tool served whenever a skill frame is open — the acting
22
+ * agent must always be able to collapse back to skill-level planning. The
23
+ * description is an authored-class constant (two-string-class safe).
24
+ */
25
+ export function leaveSkillTool(spec, skillId) {
26
+ return {
27
+ name: sanitizeMCPName(`${spec.id}.leave-skill`),
28
+ description: `Leave the current skill (${skillId}) without completing it and return to skill-level planning.`,
29
+ inputSchema: structuredClone(NO_PARAMS),
30
+ };
31
+ }
32
+ export function edgesToMCPTools(spec, edges, opts) {
33
+ return edges.map((edge) => ({
34
+ name: sanitizeMCPName(`${spec.id}.${edge.affordanceId}`),
35
+ description: edge.highEffect ? edge.description + HIGH_EFFECT_SUFFIX : edge.description,
36
+ inputSchema: edge.schema === undefined
37
+ ? structuredClone(NO_PARAMS)
38
+ : gateSchema(edge.affordanceId, edge.schema, opts?.lossySchemas ?? false),
39
+ }));
40
+ }
41
+ function gateSchema(affordanceId, schema, lossy) {
42
+ const kind = detectSchema(schema);
43
+ if (kind === 'zod')
44
+ return normalizeSchema(schema);
45
+ // Clone: descriptors must never hand out live references into the spec —
46
+ // an MCP host normalizing inputSchema in place would corrupt the graph.
47
+ if (kind === 'json-schema')
48
+ return structuredClone(schema);
49
+ // 'parseable' (yup/superstruct/...): validates payloads at fire() but cannot
50
+ // be serialized to JSON Schema — emitting the raw validator would hand an
51
+ // MCP client garbage. Fail loudly unless the caller opts into lossy output.
52
+ if (!lossy) {
53
+ throw new Error(`hcifootprint: affordance '${affordanceId}' has a 'parseable' schema (non-Zod validator). It validates ` +
54
+ `payloads at fire() but cannot be serialized to JSON Schema for MCP. Declare a Zod or JSON-Schema ` +
55
+ `schema, or pass { lossySchemas: true } to emit a permissive object schema.`);
56
+ }
57
+ return { type: 'object' };
58
+ }
59
+ function sanitizeMCPName(name) {
60
+ return name.replace(/[^A-Za-z0-9_.-]/g, '_');
61
+ }
62
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/serve/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,MAAM,SAAS,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC;AAElF,sFAAsF;AACtF,MAAM,kBAAkB,GAAG,gDAAgD,CAAC;AAE5E;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAoB,EAAE,OAAe;IAClE,OAAO;QACL,IAAI,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC,EAAE,cAAc,CAAC;QAC/C,WAAW,EAAE,4BAA4B,OAAO,6DAA6D;QAC7G,WAAW,EAAE,eAAe,CAAC,SAAS,CAAC;KAClB,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,IAAoB,EACpB,KAAsB,EACtB,IAAiC;IAEjC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QACxD,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,GAAG,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;QACvF,WAAW,EACT,IAAI,CAAC,MAAM,KAAK,SAAS;YACvB,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC;YAC5B,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,IAAI,KAAK,CAAC;KAC9E,CAAC,CAAyB,CAAC;AAC9B,CAAC;AAED,SAAS,UAAU,CAAC,YAAoB,EAAE,MAAe,EAAE,KAAc;IACvE,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,eAAe,CAAC,MAAe,CAAW,CAAC;IACtE,yEAAyE;IACzE,wEAAwE;IACxE,IAAI,IAAI,KAAK,aAAa;QAAE,OAAO,eAAe,CAAC,MAAM,CAAW,CAAC;IACrE,6EAA6E;IAC7E,0EAA0E;IAC1E,4EAA4E;IAC5E,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,6BAA6B,YAAY,+DAA+D;YACtG,mGAAmG;YACnG,4EAA4E,CAC/E,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Mode B — skills as FIXED tools (the default serving mode, D18 §7).
3
+ *
4
+ * The tool array an LLM sees contains ONE tool per skill plus three fixed
5
+ * generics (whats_here, do_action, why) and NEVER changes for the life of a
6
+ * conversation. Disclosure rides the RESULT channel: every call returns
7
+ * readySteps — what is fireable at the current navigation cursor, right now —
8
+ * and the model acts by calling the SAME skill tool again with {step}.
9
+ * Between-turn grounding rides the same channel: whats_here accepts
10
+ * {sinceVersion} and narrates only the delta (who did what since the model's
11
+ * last look), and `why` serves the causal backward slice for a state key —
12
+ * the mixed-initiative attribution query.
13
+ *
14
+ * Why: tools render first in the prompt; any tool-set change busts every
15
+ * prompt-cache tier. Result payloads are ordinary messages — cache-stable.
16
+ * ("JIT disclosure moved from the tool channel to the result channel.")
17
+ * It also makes the library a PLAIN MCP server for any host: no
18
+ * tools/list_changed, no dynamic-tool support required.
19
+ *
20
+ * The stated trade-off: the static schema cannot enforce each step's exact
21
+ * input shape at the API layer — `input` is free-form and fire() validates.
22
+ * A wrong input returns a structured error RESULT carrying what was expected,
23
+ * and the model corrects on the next call.
24
+ *
25
+ * Layering: this file consumes ONLY the public Session surface (available /
26
+ * availableSkills / skillPlan / frames / fire / contextBrief) — it is a pure
27
+ * projection, independently testable, swappable per conversation. One
28
+ * conversation = one mode (a mid-conversation mode flip is a tool-set change
29
+ * = a full cache bust).
30
+ *
31
+ * Two-string-class invariant: every text field in tools AND results is either
32
+ * an authored description or a fixed authored-constant sentence. Runtime
33
+ * values (state, payloads, instance keys, evidence) are structured DATA fields.
34
+ */
35
+ import type { MCPToolDescription } from 'footprintjs';
36
+ import type { Principal } from '../atom/types.js';
37
+ import type { Session } from '../traverse/session.js';
38
+ export interface SkillToolsOptions {
39
+ /** Require confirm:true before firing high-effect steps/actions. Default true. */
40
+ confirmHighEffect?: boolean;
41
+ /** Principal stamped on fires made through this port. Default 'agent'. */
42
+ source?: Principal;
43
+ }
44
+ export interface SkillCallArgs {
45
+ step?: string;
46
+ input?: unknown;
47
+ confirm?: boolean;
48
+ /** Instance key for steps on repeats containers (from `instances` in results). */
49
+ instance?: string;
50
+ }
51
+ export interface DoActionArgs {
52
+ action: string;
53
+ input?: unknown;
54
+ confirm?: boolean;
55
+ instance?: string;
56
+ }
57
+ /** Results are plain data objects — serialize one as the tool_result body. */
58
+ export type ServeResult = Record<string, unknown>;
59
+ export interface SkillToolsPort {
60
+ /** The STATIC tool array — identical bytes for the life of the conversation. */
61
+ tools(): MCPToolDescription[];
62
+ /** Route a tool_use by name. Unknown names return a structured error result. */
63
+ call(name: string, args?: unknown): ServeResult;
64
+ }
65
+ export declare function skillsAsTools(session: Session, opts?: SkillToolsOptions): SkillToolsPort;
66
+ //# sourceMappingURL=modes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modes.d.ts","sourceRoot":"","sources":["../../src/serve/modes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,KAAK,IAAI,kBAAkB,EAAE,CAAC;IAC9B,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;CACjD;AAmCD,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,CA2UxF"}