@ggui-ai/negotiator 0.1.0-rc.3 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  UI decision engine for [ggui](https://github.com/ggui-ai/ggui).
4
4
 
5
5
  Given an agent's signal (data, prompt, context, agent tools) and the current
6
- session state, the negotiator decides **which UI to render** — create a new
7
- interface, update an existing one, compose with what's on screen, or replace
8
- it — and, on the cold path, synthesizes the data contract that drives it.
6
+ render state, the negotiator decides **which UI to render** — create a new
7
+ interface, update an existing one, or replace it — and, on the cold path,
8
+ synthesizes the data contract that drives it.
9
9
 
10
10
  The package is deployment-agnostic. It composes the storage interfaces
11
11
  defined in `@ggui-ai/mcp-server-core` (`EmbeddingProvider`, `VectorStore`),
@@ -21,10 +21,10 @@ pnpm add @ggui-ai/negotiator
21
21
  ## What's in the box
22
22
 
23
23
  - **`negotiate(deps, input)`** — top-level orchestrator. Runs RAG search over
24
- registered blueprints, reads session state, fast-paths exact blueprint
24
+ registered blueprints, reads render state, fast-paths exact blueprint
25
25
  hits, and otherwise calls the decision LLM.
26
26
  - **`makeDecision(...)`** — the decision step in isolation: pick an action
27
- (`create` / `update` / `compose` / `replace`) and a blueprint from the
27
+ (`create` / `update` / `replace`) and a blueprint from the
28
28
  candidate set.
29
29
  - **`synthesizeContract(...)`** — cold-path contract synthesizer. Turns an
30
30
  agent intent into a `DataContract` (props / context / action / stream
@@ -40,7 +40,7 @@ pnpm add @ggui-ai/negotiator
40
40
  import { negotiate } from "@ggui-ai/negotiator";
41
41
 
42
42
  const result = await negotiate(deps, input);
43
- // result.action — "create" | "update" | "compose" | "replace"
43
+ // result.action — "create" | "update" | "replace"
44
44
  // result.blueprint — the picked blueprint, if any
45
45
  ```
46
46
 
@@ -26,7 +26,7 @@ import type { DataContract } from '@ggui-ai/protocol';
26
26
  *
27
27
  * `intent` is passed separately because `DataContract` itself does
28
28
  * not carry an `intent` field — the outer pipeline owns intent
29
- * (`story.intent` on `ggui_push`, the operator prompt for harness
29
+ * (`story.intent` on `ggui_render`, the operator prompt for harness
30
30
  * benchmarks). Threading intent into the hash keeps cache identity
31
31
  * stable across negotiations: different intents produce different
32
32
  * generated code (labels, copy, layout) even when the wire surface is
@@ -39,7 +39,7 @@ import type { DataContract } from '@ggui-ai/protocol';
39
39
  *
40
40
  * @param contract - The data contract from negotiation (unused today)
41
41
  * @param intent - The outer pipeline's intent (story.intent on
42
- * ggui_push). Empty/falsy values are dropped from the hash input.
42
+ * ggui_render). Empty/falsy values are dropped from the hash input.
43
43
  * @returns Contract hash prefixed with `ch_` (e.g., `ch_a3f8b2c1e9d04567`)
44
44
  */
45
45
  export declare function hashContract(_contract: DataContract, intent: string): string;
@@ -60,7 +60,7 @@ function canonicalize(value) {
60
60
  *
61
61
  * `intent` is passed separately because `DataContract` itself does
62
62
  * not carry an `intent` field — the outer pipeline owns intent
63
- * (`story.intent` on `ggui_push`, the operator prompt for harness
63
+ * (`story.intent` on `ggui_render`, the operator prompt for harness
64
64
  * benchmarks). Threading intent into the hash keeps cache identity
65
65
  * stable across negotiations: different intents produce different
66
66
  * generated code (labels, copy, layout) even when the wire surface is
@@ -73,7 +73,7 @@ function canonicalize(value) {
73
73
  *
74
74
  * @param contract - The data contract from negotiation (unused today)
75
75
  * @param intent - The outer pipeline's intent (story.intent on
76
- * ggui_push). Empty/falsy values are dropped from the hash input.
76
+ * ggui_render). Empty/falsy values are dropped from the hash input.
77
77
  * @returns Contract hash prefixed with `ch_` (e.g., `ch_a3f8b2c1e9d04567`)
78
78
  */
79
79
  export function hashContract(_contract, intent) {
@@ -10,9 +10,12 @@
10
10
  * those five fields are the only projection `makeDecision` reads
11
11
  * from a `NegotiatorOption`; extracting a named type here would
12
12
  * grow public surface for no consumer.
13
+ *
14
+ * `renderState` is the decision engine's view of the live render
15
+ * (at most ONE current render — see {@link RenderState.currentRender}).
13
16
  */
14
17
  import type { GadgetDescriptor, DataContract } from '@ggui-ai/protocol';
15
- import type { SessionState } from './session.js';
18
+ import type { RenderState } from './render.js';
16
19
  /** Input to the decision engine. */
17
20
  export interface NegotiatorDecisionInput {
18
21
  agentData?: Record<string, unknown>;
@@ -36,7 +39,7 @@ export interface NegotiatorDecisionInput {
36
39
  * partial LLM output with canonical entries from the catalog).
37
40
  */
38
41
  gadgets?: readonly GadgetDescriptor[];
39
- sessionState: SessionState;
42
+ renderState: RenderState;
40
43
  blueprintCandidates: Array<{
41
44
  blueprintId: string;
42
45
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"decision-input.d.ts","sourceRoot":"","sources":["../src/decision-input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,oCAAoC;AACpC,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,YAAY,EAAE,YAAY,CAAC;IAC3B,mBAAmB,EAAE,KAAK,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;KAC9B,CAAC,CAAC;CACJ"}
1
+ {"version":3,"file":"decision-input.d.ts","sourceRoot":"","sources":["../src/decision-input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,oCAAoC;AACpC,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,WAAW,EAAE,WAAW,CAAC;IACzB,mBAAmB,EAAE,KAAK,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;KAC9B,CAAC,CAAC;CACJ"}
@@ -10,5 +10,8 @@
10
10
  * those five fields are the only projection `makeDecision` reads
11
11
  * from a `NegotiatorOption`; extracting a named type here would
12
12
  * grow public surface for no consumer.
13
+ *
14
+ * `renderState` is the decision engine's view of the live render
15
+ * (at most ONE current render — see {@link RenderState.currentRender}).
13
16
  */
14
17
  export {};
@@ -2,11 +2,15 @@
2
2
  * Decision Engine — one LLM call, one UI decision.
3
3
  *
4
4
  * Replaces the V2 brainstorm/option-picker pattern with a single
5
- * opinionated decision: `create` / `update` / `compose` / `replace`.
6
- * The LLM sees the agent's data, the current session stack, any
5
+ * opinionated decision: `create` / `update` / `replace`. The LLM
6
+ * sees the agent's data, the current render (if any), any
7
7
  * `blueprintCandidates` from `ragSearch`, and returns a
8
8
  * {@link NegotiatorDecision} with a full {@link DataContract} payload.
9
9
  *
10
+ * Decision space is exactly `create | replace | update` — there is
11
+ * at most ONE current render per scope (flatten-render identity), so
12
+ * composition collapses into `replace` / `update`.
13
+ *
10
14
  * Contract shape — the returned `contract` always includes an
11
15
  * `intent` (semantic identity — same intent = cached component).
12
16
  * Other fields are populated opportunistically; `agentCapabilities` is
@@ -44,7 +48,7 @@
44
48
  import type { NegotiatorAlternative, NegotiatorDecision } from '@ggui-ai/protocol';
45
49
  import type { NegotiatorDecisionInput } from './decision-input.js';
46
50
  import type { LLMCaller } from './llm-caller.js';
47
- export declare const DECISION_SYSTEM_PROMPT = "You are a UI strategist for ggui, a generative UI platform. Given the agent's data, current session state, and blueprint candidates, decide the best way to show this information.\n\nRespond with a JSON object:\n{\n \"action\": \"create\" | \"update\" | \"compose\" | \"replace\",\n \"reasoning\": \"1-2 sentences explaining why\",\n \"blueprintId\": \"matched blueprint ID or null\",\n \"targetStackItemId\": \"existing page to update/compose/replace, or null\",\n \"contract\": {\n \"intent\": \"Concise purpose \u2014 e.g. 'Display current weather conditions for a quick daily check'\",\n \"propsSpec\": {\n \"properties\": {\n \"fieldName\": {\n \"description\": \"what this field is\",\n \"schema\": { \"type\": \"string\" },\n \"required\": true,\n \"example\": \"sample value\"\n }\n }\n }\n },\n \"adaptations\": {\n \"fontSize\": \"compact\" | \"default\" | \"large\",\n \"density\": \"dense\" | \"default\" | \"spacious\",\n \"complexity\": \"simplified\" | \"default\" | \"detailed\"\n }\n}\n\nINTENT RULES (most important):\n- The \"intent\" field captures WHY this UI exists in one sentence.\n- Include: the user's goal (why), what data is shown (what), and how they interact (how).\n- Be abstract enough to match reusable patterns \u2014 \"Display current weather conditions\" not \"Display Tokyo weather at 3pm\".\n- Same intent = same component can be reused with different data.\n- Examples:\n - \"Display current weather conditions for a quick daily check\"\n - \"Collect user feedback via a multi-field survey form\"\n - \"Show real-time stock prices with live updates\"\n - \"Compare two products side by side for purchase decision\"\n\nDECISION RULES:\n- \"create\": No existing UI or blueprint matches this intent. Show something new.\n- \"update\": An existing UI on the stack has the same intent. Update its props.\n- \"compose\": An existing UI could incorporate this data as a section.\n- \"replace\": Two or more related UIs would be better as a single unified view.\n\nREUSE BIAS (critical for performance):\n- Reusing a blueprint = INSTANT render (cached code, <1 second).\n- Creating new = 20+ seconds of generation. The user waits.\n- Default to reuse. Only \"create\" when NO candidate can reasonably serve the request.\n- A candidate that shows the SAME KIND of data (e.g., weather, stock prices, user profiles) is a match \u2014 even if the specific data differs (Tokyo vs Seoul, AAPL vs GOOG).\n- Ask yourself: \"Can this candidate display the agent's data with different prop values?\" If yes \u2192 reuse it.\n- When reusing a blueprint, copy its contract EXACTLY as-is (including its intent). Do NOT rephrase the intent.\n\nCONTRACT RULES:\n- Always include an \"intent\" field \u2014 it's required.\n- If reusing a blueprint: use that blueprint's contract verbatim. Do not modify intent or propsSpec.\n- If no blueprint match: infer propsSpec.properties from the agent's data shape. Each key becomes a prop.\n- Use the data values as examples.\n\nACTION SPEC \u2014 declare interactive affordances WHENEVER you see them in the data:\n- The discrimination is local-state vs persistent-state, NOT \"did the agent declare agentTools\".\n- LOCAL STATE (counter value, theme toggle, slider position, picker selection, search-as-you-type, form draft fields): contextSpec only. NO actionSpec. The slot mirror IS the wire \u2014 the agent observes context via its next ggui_consume.\n- PERSISTENT STATE (items with identity / IDs + mutable fields, draft submissions awaiting save, deletions of agent-owned rows): actionSpec required. Each gesture is a discrete event the agent must witness.\n- Inference signals for persistent state:\n \u2022 Items with `id`/`itemId`/`uuid` fields + boolean toggle fields like `done`/`completed`/`checked`/`pinned`/`enabled` \u2192 toggle action (e.g. `toggleTodo`, `togglePin`).\n \u2022 Lists where the data shape implies the agent maintains identity \u2192 add/delete actions.\n \u2022 Form data with mutable fields + a submit gesture \u2192 submit action.\n \u2022 A click that would cause a server-side side-effect (publish, archive, send, delete-from-database) \u2192 action.\n- nextStep is OPTIONAL on actionSpec entries:\n \u2022 Bind `nextStep: \"tool_name\"` ONLY when input.agentTools contains a matching tool (exact or close name match \u2014 `todo_toggle` matches the `toggleTodo` action).\n \u2022 If no agentTools match, OMIT nextStep. Events drain via ggui_consume on the agent's next turn \u2014 the agent's reasoning loop sees the event and decides what to do.\n- Examples:\n \u2022 agentTools=[\"todo_toggle\",\"todo_delete\"] + todo data \u2192 actionSpec: { \"toggleTodo\": { label: \"Toggle\", schema: {type:\"object\",properties:{id:{type:\"string\"}},required:[\"id\"]}, nextStep: \"todo_toggle\" }, \"deleteTodo\": {...nextStep: \"todo_delete\"} }\n \u2022 agentTools=[] + todo data \u2192 SAME actionSpec entries WITHOUT nextStep. The agent's next turn reads the event and reacts.\n \u2022 agentTools=[] + counter prompt \u2192 contextSpec.count only. NO actionSpec.\n\nAGENT CAPABILITIES (catalog):\n- You do NOT need to emit contract.agentCapabilities \u2014 it is populated deterministically from input.agentTools after you return.\n- Focus on actionSpec entries + their optional nextStep bindings; the catalog auto-populates.\n\nANTI-PATTERNS \u2014 DO NOT EMIT (cross-ref linter rejects at push):\n- \"props\" / \"props.properties\" as a CONTRACT field (retired contract-side spelling \u2014 the contract field is propsSpec; the wire field on push/update is still \"props\" but carries VALUES, not the spec)\n- \"wiredTools\" / \"agentTools\" / \"clientTools\" catalog names (retired; use agentCapabilities.tools / clientCapabilities.gadgets)\n- clientCapabilities.capabilities (retired inner key; use clientCapabilities.gadgets)\n- ActionEntry.tool / ActionEntry.dispatch.kind (retired discriminated union; use the flat nextStep field)\n- mode: 'host-routed' / mode: 'agent-routed' (retired; all actions are agent-routed)\n- broadcast: { ... } as a top-level field (retired; use streamSpec[X].source instead)";
51
+ export declare const DECISION_SYSTEM_PROMPT = "You are a UI strategist for ggui, a generative UI platform. Given the agent's data, current render state, and blueprint candidates, decide the best way to show this information.\n\nRespond with a JSON object:\n{\n \"action\": \"create\" | \"update\" | \"replace\",\n \"reasoning\": \"1-2 sentences explaining why\",\n \"blueprintId\": \"matched blueprint ID or null\",\n \"targetRenderId\": \"existing render to update/replace, or null\",\n \"contract\": {\n \"intent\": \"Concise purpose \u2014 e.g. 'Display current weather conditions for a quick daily check'\",\n \"propsSpec\": {\n \"properties\": {\n \"fieldName\": {\n \"description\": \"what this field is\",\n \"schema\": { \"type\": \"string\" },\n \"required\": true,\n \"example\": \"sample value\"\n }\n }\n }\n },\n \"adaptations\": {\n \"fontSize\": \"compact\" | \"default\" | \"large\",\n \"density\": \"dense\" | \"default\" | \"spacious\",\n \"complexity\": \"simplified\" | \"default\" | \"detailed\"\n }\n}\n\nINTENT RULES (most important):\n- The \"intent\" field captures WHY this UI exists in one sentence.\n- Include: the user's goal (why), what data is shown (what), and how they interact (how).\n- Be abstract enough to match reusable patterns \u2014 \"Display current weather conditions\" not \"Display Tokyo weather at 3pm\".\n- Same intent = same component can be reused with different data.\n- Examples:\n - \"Display current weather conditions for a quick daily check\"\n - \"Collect user feedback via a multi-field survey form\"\n - \"Show real-time stock prices with live updates\"\n - \"Compare two products side by side for purchase decision\"\n\nDECISION RULES:\n- \"create\": No existing UI or blueprint matches this intent. Show something new.\n- \"update\": The current render has the same intent. Update its props in place.\n- \"replace\": The current render does NOT match this intent \u2014 swap it for a different UI.\n\nREUSE BIAS (critical for performance):\n- Reusing a blueprint = INSTANT render (cached code, <1 second).\n- Creating new = 20+ seconds of generation. The user waits.\n- Default to reuse. Only \"create\" when NO candidate can reasonably serve the request.\n- A candidate that shows the SAME KIND of data (e.g., weather, stock prices, user profiles) is a match \u2014 even if the specific data differs (Tokyo vs Seoul, AAPL vs GOOG).\n- Ask yourself: \"Can this candidate display the agent's data with different prop values?\" If yes \u2192 reuse it.\n- When reusing a blueprint, copy its contract EXACTLY as-is (including its intent). Do NOT rephrase the intent.\n\nCONTRACT RULES:\n- Always include an \"intent\" field \u2014 it's required.\n- If reusing a blueprint: use that blueprint's contract verbatim. Do not modify intent or propsSpec.\n- If no blueprint match: infer propsSpec.properties from the agent's data shape. Each key becomes a prop.\n- Use the data values as examples.\n\nACTION SPEC \u2014 declare interactive affordances WHENEVER you see them in the data:\n- The discrimination is local-state vs persistent-state, NOT \"did the agent declare agentTools\".\n- LOCAL STATE (counter value, theme toggle, slider position, picker selection, search-as-you-type, form draft fields): contextSpec only. NO actionSpec. The slot mirror IS the wire \u2014 the agent observes context via its next ggui_consume.\n- PERSISTENT STATE (items with identity / IDs + mutable fields, draft submissions awaiting save, deletions of agent-owned rows): actionSpec required. Each gesture is a discrete event the agent must witness.\n- Inference signals for persistent state:\n \u2022 Items with `id`/`itemId`/`uuid` fields + boolean toggle fields like `done`/`completed`/`checked`/`pinned`/`enabled` \u2192 toggle action (e.g. `toggleTodo`, `togglePin`).\n \u2022 Lists where the data shape implies the agent maintains identity \u2192 add/delete actions.\n \u2022 Form data with mutable fields + a submit gesture \u2192 submit action.\n \u2022 A click that would cause a server-side side-effect (publish, archive, send, delete-from-database) \u2192 action.\n- nextStep is OPTIONAL on actionSpec entries:\n \u2022 Bind `nextStep: \"tool_name\"` ONLY when input.agentTools contains a matching tool (exact or close name match \u2014 `todo_toggle` matches the `toggleTodo` action).\n \u2022 If no agentTools match, OMIT nextStep. Events drain via ggui_consume on the agent's next turn \u2014 the agent's reasoning loop sees the event and decides what to do.\n- Examples:\n \u2022 agentTools=[\"todo_toggle\",\"todo_delete\"] + todo data \u2192 actionSpec: { \"toggleTodo\": { label: \"Toggle\", schema: {type:\"object\",properties:{id:{type:\"string\"}},required:[\"id\"]}, nextStep: \"todo_toggle\" }, \"deleteTodo\": {...nextStep: \"todo_delete\"} }\n \u2022 agentTools=[] + todo data \u2192 SAME actionSpec entries WITHOUT nextStep. The agent's next turn reads the event and reacts.\n \u2022 agentTools=[] + counter prompt \u2192 contextSpec.count only. NO actionSpec.\n\nAGENT CAPABILITIES (catalog):\n- You do NOT need to emit contract.agentCapabilities \u2014 it is populated deterministically from input.agentTools after you return.\n- Focus on actionSpec entries + their optional nextStep bindings; the catalog auto-populates.\n\nANTI-PATTERNS \u2014 DO NOT EMIT (cross-ref linter rejects at push):\n- \"props\" / \"props.properties\" as a CONTRACT field (retired contract-side spelling \u2014 the contract field is propsSpec; the wire field on push/update is still \"props\" but carries VALUES, not the spec)\n- \"wiredTools\" / \"agentTools\" / \"clientTools\" catalog names (retired; use agentCapabilities.tools / clientCapabilities.gadgets)\n- clientCapabilities.capabilities (retired inner key; use clientCapabilities.gadgets)\n- ActionEntry.tool / ActionEntry.dispatch.kind (retired discriminated union; use the flat nextStep field)\n- mode: 'host-routed' / mode: 'agent-routed' (retired; all actions are agent-routed)\n- broadcast: { ... } as a top-level field (retired; use streamSpec[X].source instead)";
48
52
  export declare function buildDecisionUserMessage(input: NegotiatorDecisionInput): string;
49
53
  /** Make the UI decision via one LLM call with all context. */
50
54
  export declare function makeDecision(input: NegotiatorDecisionInput, llmCaller: LLMCaller): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAQV,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,eAAO,MAAM,sBAAsB,8nMAsFiE,CAAC;AAErG,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAwG/E;AAuGD,8DAA8D;AAC9D,wBAAsB,YAAY,CAChC,KAAK,EAAE,uBAAuB,EAC9B,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,kBAAkB,CAAC;IAAC,YAAY,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC,CAkElF"}
1
+ {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAQV,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,eAAO,MAAM,sBAAsB,4iMAqFiE,CAAC;AAErG,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAqG/E;AAuGD,8DAA8D;AAC9D,wBAAsB,YAAY,CAChC,KAAK,EAAE,uBAAuB,EAC9B,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,kBAAkB,CAAC;IAAC,YAAY,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC,CAkElF"}
package/dist/decision.js CHANGED
@@ -2,11 +2,15 @@
2
2
  * Decision Engine — one LLM call, one UI decision.
3
3
  *
4
4
  * Replaces the V2 brainstorm/option-picker pattern with a single
5
- * opinionated decision: `create` / `update` / `compose` / `replace`.
6
- * The LLM sees the agent's data, the current session stack, any
5
+ * opinionated decision: `create` / `update` / `replace`. The LLM
6
+ * sees the agent's data, the current render (if any), any
7
7
  * `blueprintCandidates` from `ragSearch`, and returns a
8
8
  * {@link NegotiatorDecision} with a full {@link DataContract} payload.
9
9
  *
10
+ * Decision space is exactly `create | replace | update` — there is
11
+ * at most ONE current render per scope (flatten-render identity), so
12
+ * composition collapses into `replace` / `update`.
13
+ *
10
14
  * Contract shape — the returned `contract` always includes an
11
15
  * `intent` (semantic identity — same intent = cached component).
12
16
  * Other fields are populated opportunistically; `agentCapabilities` is
@@ -43,14 +47,14 @@
43
47
  */
44
48
  import { gadgetExportName, gadgetIdentityKey } from '@ggui-ai/protocol';
45
49
  import { composeAvailableGadgetsSection } from './synthesize-contract.js';
46
- export const DECISION_SYSTEM_PROMPT = `You are a UI strategist for ggui, a generative UI platform. Given the agent's data, current session state, and blueprint candidates, decide the best way to show this information.
50
+ export const DECISION_SYSTEM_PROMPT = `You are a UI strategist for ggui, a generative UI platform. Given the agent's data, current render state, and blueprint candidates, decide the best way to show this information.
47
51
 
48
52
  Respond with a JSON object:
49
53
  {
50
- "action": "create" | "update" | "compose" | "replace",
54
+ "action": "create" | "update" | "replace",
51
55
  "reasoning": "1-2 sentences explaining why",
52
56
  "blueprintId": "matched blueprint ID or null",
53
- "targetStackItemId": "existing page to update/compose/replace, or null",
57
+ "targetRenderId": "existing render to update/replace, or null",
54
58
  "contract": {
55
59
  "intent": "Concise purpose — e.g. 'Display current weather conditions for a quick daily check'",
56
60
  "propsSpec": {
@@ -84,9 +88,8 @@ INTENT RULES (most important):
84
88
 
85
89
  DECISION RULES:
86
90
  - "create": No existing UI or blueprint matches this intent. Show something new.
87
- - "update": An existing UI on the stack has the same intent. Update its props.
88
- - "compose": An existing UI could incorporate this data as a section.
89
- - "replace": Two or more related UIs would be better as a single unified view.
91
+ - "update": The current render has the same intent. Update its props in place.
92
+ - "replace": The current render does NOT match this intent — swap it for a different UI.
90
93
 
91
94
  REUSE BIAS (critical for performance):
92
95
  - Reusing a blueprint = INSTANT render (cached code, <1 second).
@@ -145,19 +148,17 @@ export function buildDecisionUserMessage(input) {
145
148
  : JSON.stringify(input.agentContext);
146
149
  parts.push(`Agent's context: ${ctx}`);
147
150
  }
148
- // Session state
149
- const { sessionState } = input;
150
- if (sessionState.stack.length > 0) {
151
- const stackSummary = sessionState.stack
152
- .map((item) => ` [${item.id}] ${item.prompt ?? 'no prompt'}`)
153
- .join('\n');
154
- parts.push(`Current UI stack (${sessionState.stack.length} items):\n${stackSummary}`);
151
+ // Render state
152
+ const { renderState } = input;
153
+ const current = renderState.currentRender;
154
+ if (current) {
155
+ parts.push(`Current render: [${current.id}] ${current.prompt ?? 'no prompt'}`);
155
156
  }
156
157
  else {
157
- parts.push('Current UI stack: empty');
158
+ parts.push('Current render: none (cold start)');
158
159
  }
159
- if (sessionState.conversationHistory.length > 0) {
160
- const recent = sessionState.conversationHistory.slice(-5);
160
+ if (renderState.conversationHistory.length > 0) {
161
+ const recent = renderState.conversationHistory.slice(-5);
161
162
  parts.push(`Recent conversation:\n${recent.map((t) => ` ${t.role}: ${t.content}`).join('\n')}`);
162
163
  }
163
164
  // Agent tools — MCP tools the agent invokes; component never calls these.
@@ -236,8 +237,8 @@ const DECISION_TOOL = {
236
237
  properties: {
237
238
  action: {
238
239
  type: 'string',
239
- enum: ['create', 'update', 'compose', 'replace'],
240
- description: 'What to do: create (new UI), update (swap data), compose (add to stack), replace (swap UI type)',
240
+ enum: ['create', 'update', 'replace'],
241
+ description: 'What to do: create (new UI when nothing on screen), update (swap data on the current render), replace (swap the current render for a different UI)',
241
242
  },
242
243
  reasoning: { type: 'string', description: 'Brief explanation' },
243
244
  blueprintId: {
@@ -297,9 +298,9 @@ const DECISION_TOOL = {
297
298
  },
298
299
  required: ['intent'],
299
300
  },
300
- targetStackItemId: {
301
+ targetRenderId: {
301
302
  type: 'string',
302
- description: 'Page to target (update/replace actions)',
303
+ description: 'Render to target (update/replace actions)',
303
304
  },
304
305
  adaptations: {
305
306
  type: 'object',
@@ -327,7 +328,7 @@ export async function makeDecision(input, llmCaller) {
327
328
  reasoning: parsed.reasoning ?? 'Structured output',
328
329
  blueprintId: parsed.blueprintId ?? undefined,
329
330
  contract: mergeGadgets(mergeAgentCapabilities({ ...parsed.contract }, input.agentTools), input.gadgets),
330
- targetStackItemId: parsed.targetStackItemId ?? undefined,
331
+ targetRenderId: parsed.targetRenderId ?? undefined,
331
332
  adaptations: parsed.adaptations ?? undefined,
332
333
  },
333
334
  alternatives: [],
@@ -351,7 +352,7 @@ export async function makeDecision(input, llmCaller) {
351
352
  reasoning: parsed.reasoning ?? 'No reasoning provided',
352
353
  blueprintId: parsed.blueprintId ?? undefined,
353
354
  contract: mergeGadgets(mergeAgentCapabilities({ ...parsed.contract }, input.agentTools), input.gadgets),
354
- targetStackItemId: parsed.targetStackItemId ?? undefined,
355
+ targetRenderId: parsed.targetRenderId ?? undefined,
355
356
  adaptations: parsed.adaptations ?? undefined,
356
357
  },
357
358
  alternatives: [],
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @ggui-ai/negotiator — open-source UI decision engine for ggui.
3
3
  *
4
- * Decides which UI to render (create/update/compose/replace) given agent
5
- * signal (data/prompt/context/agentTools) and current session state.
4
+ * Decides which UI to render (create/update/replace) given agent
5
+ * signal (data/prompt/context/agentTools) and current render state.
6
6
  *
7
7
  * Composes the storage seams defined in `@ggui-ai/mcp-server-core`
8
8
  * (`EmbeddingProvider`, `VectorStore`, `Negotiator`). The decision
@@ -22,7 +22,7 @@ export { ragSearch } from './rag-search.js';
22
22
  export type { RagSearchDeps, RagSearchInput, RagSearchResult, } from './rag-search.js';
23
23
  export type { NegotiatorOption } from './types.js';
24
24
  export type { LLMCaller, LLMCallerConfig, ToolSchema } from './llm-caller.js';
25
- export type { SessionState, SessionStackEntry } from './session.js';
25
+ export type { RenderState, RenderEntry } from './render.js';
26
26
  export type { NegotiatorDecisionInput } from './decision-input.js';
27
27
  export { DECISION_SYSTEM_PROMPT, buildDecisionUserMessage, makeDecision, } from './decision.js';
28
28
  export { negotiate } from './negotiate.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtE,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACpE,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EACV,eAAe,EACf,cAAc,EACd,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,yBAAyB,EACzB,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtE,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EACV,eAAe,EACf,cAAc,EACd,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,yBAAyB,EACzB,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,0BAA0B,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @ggui-ai/negotiator — open-source UI decision engine for ggui.
3
3
  *
4
- * Decides which UI to render (create/update/compose/replace) given agent
5
- * signal (data/prompt/context/agentTools) and current session state.
4
+ * Decides which UI to render (create/update/replace) given agent
5
+ * signal (data/prompt/context/agentTools) and current render state.
6
6
  *
7
7
  * Composes the storage seams defined in `@ggui-ai/mcp-server-core`
8
8
  * (`EmbeddingProvider`, `VectorStore`, `Negotiator`). The decision
package/dist/intent.d.ts CHANGED
@@ -2,18 +2,18 @@
2
2
  * Deterministic identifier for a negotiation intent.
3
3
  *
4
4
  * Used by the suggestion engine to deduplicate auto-suggested UIs
5
- * within a session — two prompts that would produce the same intent
6
- * collapse to a single suggestion. SHA-256 truncated to 16 hex chars
7
- * (64 bits) gives collision-resistant ids without being wastefully
8
- * large in logs.
5
+ * within a render scope — two prompts that would produce the same
6
+ * intent collapse to a single suggestion. SHA-256 truncated to 16
7
+ * hex chars (64 bits) gives collision-resistant ids without being
8
+ * wastefully large in logs.
9
9
  *
10
- * @param sessionId Scope. Intent ids are session-local.
10
+ * @param renderId Scope. Intent ids are render-local.
11
11
  * @param data Data shape (keys only — values ignored). Undefined → 'no-data'.
12
12
  * @param action Optional action verb. Defaults to 'create'.
13
13
  */
14
- export declare function computeIntentId(sessionId: string, data: Record<string, unknown> | undefined, action?: string): string;
14
+ export declare function computeIntentId(renderId: string, data: Record<string, unknown> | undefined, action?: string): string;
15
15
  /**
16
- * True if the given intent id is already being handled in this session.
16
+ * True if the given intent id is already being handled in this scope.
17
17
  *
18
18
  * The suggestion engine uses this to avoid re-suggesting the same UI
19
19
  * while the agent is already preparing one.
@@ -1 +1 @@
1
- {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACzC,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAC3B,OAAO,CAET"}
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACzC,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAC3B,OAAO,CAET"}
package/dist/intent.js CHANGED
@@ -3,22 +3,22 @@ import { createHash } from 'node:crypto';
3
3
  * Deterministic identifier for a negotiation intent.
4
4
  *
5
5
  * Used by the suggestion engine to deduplicate auto-suggested UIs
6
- * within a session — two prompts that would produce the same intent
7
- * collapse to a single suggestion. SHA-256 truncated to 16 hex chars
8
- * (64 bits) gives collision-resistant ids without being wastefully
9
- * large in logs.
6
+ * within a render scope — two prompts that would produce the same
7
+ * intent collapse to a single suggestion. SHA-256 truncated to 16
8
+ * hex chars (64 bits) gives collision-resistant ids without being
9
+ * wastefully large in logs.
10
10
  *
11
- * @param sessionId Scope. Intent ids are session-local.
11
+ * @param renderId Scope. Intent ids are render-local.
12
12
  * @param data Data shape (keys only — values ignored). Undefined → 'no-data'.
13
13
  * @param action Optional action verb. Defaults to 'create'.
14
14
  */
15
- export function computeIntentId(sessionId, data, action) {
15
+ export function computeIntentId(renderId, data, action) {
16
16
  const dataShape = data ? Object.keys(data).sort().join(',') : 'no-data';
17
- const raw = `${sessionId}:${dataShape}:${action ?? 'create'}`;
17
+ const raw = `${renderId}:${dataShape}:${action ?? 'create'}`;
18
18
  return createHash('sha256').update(raw).digest('hex').slice(0, 16);
19
19
  }
20
20
  /**
21
- * True if the given intent id is already being handled in this session.
21
+ * True if the given intent id is already being handled in this scope.
22
22
  *
23
23
  * The suggestion engine uses this to avoid re-suggesting the same UI
24
24
  * while the agent is already preparing one.
@@ -6,10 +6,10 @@
6
6
  * 1. RAG search (per-scope + optional shared pool) via
7
7
  * {@link ragSearch} — composes `EmbeddingProvider.embed` +
8
8
  * `VectorStore.query` from `@ggui-ai/mcp-server-core`.
9
- * 2. Read session state (optional injectable).
9
+ * 2. Read render state (optional injectable).
10
10
  * 3. Fast-path for exact blueprint hits — skip the decision LLM.
11
11
  * 4. Otherwise call {@link makeDecision} with the RAG candidates and
12
- * session stack; fold the picked blueprint's pool provenance into
12
+ * current render; fold the picked blueprint's pool provenance into
13
13
  * the return value.
14
14
  *
15
15
  * Timing logs (stable format — consumed by benchmarks):
@@ -23,7 +23,7 @@
23
23
  * Exported:
24
24
  * - `negotiate(deps, input)` — runtime orchestrator.
25
25
  * - `NegotiateDeps` — injection shape (embedding / vectors / llm +
26
- * optional session-state reader + optional progress callback).
26
+ * optional render-state reader + optional progress callback).
27
27
  * - `NegotiateInput` — agent signal + config.
28
28
  * - `NegotiateConfig` — minimum fields the orchestrator actually
29
29
  * reads. Pool selection is expressed as `includeSharedPool:
@@ -43,19 +43,19 @@
43
43
  import type { NegotiatorAlternative, NegotiatorDecision } from '@ggui-ai/protocol';
44
44
  import type { EmbeddingProvider, VectorStore } from '@ggui-ai/mcp-server-core';
45
45
  import type { LLMCaller } from './llm-caller.js';
46
- import type { SessionState } from './session.js';
46
+ import type { RenderState } from './render.js';
47
47
  /**
48
48
  * Minimum config the orchestrator reads.
49
49
  *
50
50
  * `appId` is the primary RAG scope (per-app registered UIs live
51
- * here). `sessionId` keys the optional `readSessionState` callback.
51
+ * here). `renderId` keys the optional `readRenderState` callback.
52
52
  * `includeSharedPool` (default `false`) gates whether to also search
53
53
  * the global `"shared"` pool in parallel and fold its hits into the
54
54
  * candidate set.
55
55
  */
56
56
  export interface NegotiateConfig {
57
57
  appId: string;
58
- sessionId: string;
58
+ renderId: string;
59
59
  /** Also search the shared (global) pool in parallel. Default `false`. */
60
60
  includeSharedPool?: boolean;
61
61
  }
@@ -112,8 +112,8 @@ export interface NegotiateDeps {
112
112
  embedding?: EmbeddingProvider;
113
113
  vectors?: VectorStore;
114
114
  llm: LLMCaller;
115
- /** Optional: read current session state for stack-aware decisions. */
116
- readSessionState?: (sessionId: string) => Promise<SessionState | null>;
115
+ /** Optional: read current render state for reuse-aware decisions. */
116
+ readRenderState?: (renderId: string) => Promise<RenderState | null>;
117
117
  /** Optional: surface pipeline progress to consumers. */
118
118
  onProgress?: (phase: string, summary: string) => void;
119
119
  }
@@ -134,8 +134,9 @@ export interface NegotiateResult {
134
134
  decisionLatencyMs: number;
135
135
  }
136
136
  /**
137
- * Orchestrate one negotiation call — RAG search, session read, fast
138
- * path, decision LLM. See module docstring for the pipeline outline.
137
+ * Orchestrate one negotiation call — RAG search, render-state read,
138
+ * fast path, decision LLM. See module docstring for the pipeline
139
+ * outline.
139
140
  */
140
141
  export declare function negotiate(deps: NegotiateDeps, input: NegotiateInput): Promise<NegotiateResult>;
141
142
  //# sourceMappingURL=negotiate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"negotiate.d.ts","sourceRoot":"","sources":["../src/negotiate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAEV,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAWjD;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE;QACL,0CAA0C;QAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,+DAA+D;QAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,iFAAiF;QACjF,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3C;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB;;;;;;WAMG;QACH,OAAO,CAAC,EAAE,SAAS,OAAO,mBAAmB,EAAE,gBAAgB,EAAE,CAAC;KACnE,CAAC;IACF,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,GAAG,EAAE,SAAS,CAAC;IACf,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACvE,wDAAwD;IACxD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACxC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC,CAmJ1B"}
1
+ {"version":3,"file":"negotiate.d.ts","sourceRoot":"","sources":["../src/negotiate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAEV,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAU/C;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE;QACL,0CAA0C;QAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,+DAA+D;QAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,iFAAiF;QACjF,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3C;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB;;;;;;WAMG;QACH,OAAO,CAAC,EAAE,SAAS,OAAO,mBAAmB,EAAE,gBAAgB,EAAE,CAAC;KACnE,CAAC;IACF,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,GAAG,EAAE,SAAS,CAAC;IACf,qEAAqE;IACrE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACpE,wDAAwD;IACxD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACxC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC,CAqJ1B"}
package/dist/negotiate.js CHANGED
@@ -6,10 +6,10 @@
6
6
  * 1. RAG search (per-scope + optional shared pool) via
7
7
  * {@link ragSearch} — composes `EmbeddingProvider.embed` +
8
8
  * `VectorStore.query` from `@ggui-ai/mcp-server-core`.
9
- * 2. Read session state (optional injectable).
9
+ * 2. Read render state (optional injectable).
10
10
  * 3. Fast-path for exact blueprint hits — skip the decision LLM.
11
11
  * 4. Otherwise call {@link makeDecision} with the RAG candidates and
12
- * session stack; fold the picked blueprint's pool provenance into
12
+ * current render; fold the picked blueprint's pool provenance into
13
13
  * the return value.
14
14
  *
15
15
  * Timing logs (stable format — consumed by benchmarks):
@@ -23,7 +23,7 @@
23
23
  * Exported:
24
24
  * - `negotiate(deps, input)` — runtime orchestrator.
25
25
  * - `NegotiateDeps` — injection shape (embedding / vectors / llm +
26
- * optional session-state reader + optional progress callback).
26
+ * optional render-state reader + optional progress callback).
27
27
  * - `NegotiateInput` — agent signal + config.
28
28
  * - `NegotiateConfig` — minimum fields the orchestrator actually
29
29
  * reads. Pool selection is expressed as `includeSharedPool:
@@ -42,14 +42,14 @@
42
42
  */
43
43
  import { ragSearch } from './rag-search.js';
44
44
  import { makeDecision } from './decision.js';
45
- /** Empty session state for cold starts and benchmarks. */
46
- const EMPTY_SESSION = {
47
- stack: [],
45
+ /** Empty render state for cold starts and benchmarks. */
46
+ const EMPTY_RENDER_STATE = {
48
47
  conversationHistory: [],
49
48
  };
50
49
  /**
51
- * Orchestrate one negotiation call — RAG search, session read, fast
52
- * path, decision LLM. See module docstring for the pipeline outline.
50
+ * Orchestrate one negotiation call — RAG search, render-state read,
51
+ * fast path, decision LLM. See module docstring for the pipeline
52
+ * outline.
53
53
  */
54
54
  export async function negotiate(deps, input) {
55
55
  const { agent, config } = input;
@@ -88,15 +88,17 @@ export async function negotiate(deps, input) {
88
88
  deps.onProgress?.('blueprint_search', ragResult.options.length > 0
89
89
  ? `Found ${ragResult.options.length} blueprint candidate${ragResult.options.length > 1 ? 's' : ''}`
90
90
  : 'No blueprints found');
91
- // Step 2: Read session state (falls back to EMPTY_SESSION).
92
- const sessionState = deps.readSessionState
93
- ? ((await deps.readSessionState(config.sessionId)) ?? EMPTY_SESSION)
94
- : EMPTY_SESSION;
91
+ // Step 2: Read render state (falls back to EMPTY_RENDER_STATE).
92
+ const renderState = deps.readRenderState
93
+ ? ((await deps.readRenderState(config.renderId)) ?? EMPTY_RENDER_STATE)
94
+ : EMPTY_RENDER_STATE;
95
95
  // Step 3: Fast-path for high-confidence exact matches — skip decision LLM.
96
96
  const exactOpt = ragResult.options.find((opt) => opt.description.includes('exact'));
97
97
  if (exactOpt) {
98
- const stackHasSameType = sessionState.stack.some((item) => item.prompt && agent.prompt && item.prompt === agent.prompt);
99
- const action = stackHasSameType ? 'update' : 'create';
98
+ const currentMatches = Boolean(renderState.currentRender?.prompt &&
99
+ agent.prompt &&
100
+ renderState.currentRender.prompt === agent.prompt);
101
+ const action = currentMatches ? 'update' : 'create';
100
102
  const totalMs = Date.now() - negotiateStart;
101
103
  // eslint-disable-next-line no-console
102
104
  console.log(`[negotiate] FAST PATH: blueprint=${exactOpt.blueprintId?.slice(-8)} hash=${exactOpt.contractHash?.slice(0, 12) ?? 'none'} | LLM decision: 0ms | total: ${totalMs}ms`);
@@ -130,7 +132,7 @@ export async function negotiate(deps, input) {
130
132
  ...(agent.gadgets
131
133
  ? { gadgets: agent.gadgets }
132
134
  : {}),
133
- sessionState,
135
+ renderState,
134
136
  blueprintCandidates: ragResult.options.map((opt) => ({
135
137
  blueprintId: opt.blueprintId ?? opt.id,
136
138
  description: opt.description,
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `RenderState` — what the decision engine sees about the live
3
+ * render at the moment of a negotiation call.
4
+ *
5
+ * Captures the current render (if any), recent conversation, and
6
+ * optional interface context (viewport, device class). Consumed by
7
+ * `NegotiatorDecisionInput` to drive `create / update / replace`
8
+ * decisions — the current render tells the engine whether the
9
+ * already-on-screen UI can absorb this push as an `update`, and the
10
+ * conversation history feeds the decision LLM.
11
+ *
12
+ * The decision space is `create | replace | update`: there is at
13
+ * most ONE current render per negotiation call (flatten-render
14
+ * identity has no multi-entry stack), and `currentRender` captures
15
+ * that single live render when present.
16
+ *
17
+ * Kept in `@ggui-ai/negotiator` (not `mcp-server-core`): this is
18
+ * the decision engine's input shape, not a storage seam. An MCP
19
+ * server implementer binding against the public `Negotiator`
20
+ * interface sees `NegotiatorInput` / `NegotiatorResult` — never
21
+ * this internal input shape. Community adapters that want to
22
+ * call `makeDecision` directly (bypassing the `Negotiator` wrapper)
23
+ * import this type.
24
+ */
25
+ import type { DataContract, InterfaceContext } from '@ggui-ai/protocol';
26
+ /**
27
+ * One entry describing the render currently on screen — minimal
28
+ * shape: every field the decision engine actually reads to decide
29
+ * reuse vs. create.
30
+ *
31
+ * Mirrors the protocol's `ComponentRender` projection but stays
32
+ * intentionally narrower — the engine only consumes identity,
33
+ * authoring prompt, contract, and component code.
34
+ */
35
+ export interface RenderEntry {
36
+ id: string;
37
+ prompt?: string;
38
+ contract?: DataContract;
39
+ componentCode: string;
40
+ }
41
+ /**
42
+ * Current state of the active render — at most ONE current render
43
+ * plus conversation history.
44
+ */
45
+ export interface RenderState {
46
+ /**
47
+ * The render currently on screen for this scope, when any. Absent
48
+ * ⇒ no live render exists (cold start or post-clear).
49
+ */
50
+ currentRender?: RenderEntry;
51
+ conversationHistory: Array<{
52
+ role: 'user' | 'agent';
53
+ content: string;
54
+ }>;
55
+ interfaceContext?: InterfaceContext;
56
+ }
57
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,mBAAmB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC"}