@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 +6 -6
- package/dist/contract-hash.d.ts +2 -2
- package/dist/contract-hash.js +2 -2
- package/dist/decision-input.d.ts +5 -2
- package/dist/decision-input.d.ts.map +1 -1
- package/dist/decision-input.js +3 -0
- package/dist/decision.d.ts +7 -3
- package/dist/decision.d.ts.map +1 -1
- package/dist/decision.js +25 -24
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/intent.d.ts +7 -7
- package/dist/intent.d.ts.map +1 -1
- package/dist/intent.js +8 -8
- package/dist/negotiate.d.ts +11 -10
- package/dist/negotiate.d.ts.map +1 -1
- package/dist/negotiate.js +17 -15
- package/dist/render.d.ts +57 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +25 -0
- package/dist/suggestion.d.ts +5 -5
- package/dist/suggestion.d.ts.map +1 -1
- package/dist/suggestion.js +6 -6
- package/dist/synthesize-contract.js +1 -1
- package/package.json +4 -4
- package/src/contract-hash.ts +2 -2
- package/src/decision-input.ts +5 -2
- package/src/decision.ts +27 -27
- package/src/index.ts +3 -3
- package/src/intent.ts +8 -8
- package/src/negotiate.ts +23 -21
- package/src/render.ts +56 -0
- package/src/suggestion.ts +8 -8
- package/src/synthesize-contract.ts +1 -1
- package/dist/session.d.ts +0 -42
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -21
- package/src/session.ts +0 -41
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
|
-
|
|
7
|
-
interface, update an existing one,
|
|
8
|
-
|
|
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
|
|
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` / `
|
|
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" | "
|
|
43
|
+
// result.action — "create" | "update" | "replace"
|
|
44
44
|
// result.blueprint — the picked blueprint, if any
|
|
45
45
|
```
|
|
46
46
|
|
package/dist/contract-hash.d.ts
CHANGED
|
@@ -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 `
|
|
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
|
-
*
|
|
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;
|
package/dist/contract-hash.js
CHANGED
|
@@ -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 `
|
|
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
|
-
*
|
|
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) {
|
package/dist/decision-input.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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"}
|
package/dist/decision-input.js
CHANGED
|
@@ -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 {};
|
package/dist/decision.d.ts
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` / `
|
|
6
|
-
*
|
|
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
|
|
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<{
|
package/dist/decision.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"AAAA
|
|
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` / `
|
|
6
|
-
*
|
|
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
|
|
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" | "
|
|
54
|
+
"action": "create" | "update" | "replace",
|
|
51
55
|
"reasoning": "1-2 sentences explaining why",
|
|
52
56
|
"blueprintId": "matched blueprint ID or null",
|
|
53
|
-
"
|
|
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":
|
|
88
|
-
- "
|
|
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
|
-
//
|
|
149
|
-
const {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
158
|
+
parts.push('Current render: none (cold start)');
|
|
158
159
|
}
|
|
159
|
-
if (
|
|
160
|
-
const recent =
|
|
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', '
|
|
240
|
-
description: 'What to do: create (new UI), update (swap data
|
|
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
|
-
|
|
301
|
+
targetRenderId: {
|
|
301
302
|
type: 'string',
|
|
302
|
-
description: '
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
5
|
-
* signal (data/prompt/context/agentTools) and current
|
|
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 {
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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/
|
|
5
|
-
* signal (data/prompt/context/agentTools) and current
|
|
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
|
|
6
|
-
* collapse to a single suggestion. SHA-256 truncated to 16
|
|
7
|
-
* (64 bits) gives collision-resistant ids without being
|
|
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
|
|
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(
|
|
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
|
|
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.
|
package/dist/intent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,
|
|
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
|
|
7
|
-
* collapse to a single suggestion. SHA-256 truncated to 16
|
|
8
|
-
* (64 bits) gives collision-resistant ids without being
|
|
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
|
|
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(
|
|
15
|
+
export function computeIntentId(renderId, data, action) {
|
|
16
16
|
const dataShape = data ? Object.keys(data).sort().join(',') : 'no-data';
|
|
17
|
-
const raw = `${
|
|
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
|
|
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.
|
package/dist/negotiate.d.ts
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
|
|
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
|
-
*
|
|
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
|
|
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 {
|
|
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). `
|
|
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
|
-
|
|
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
|
|
116
|
-
|
|
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,
|
|
138
|
-
* path, decision LLM. See module docstring for the pipeline
|
|
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
|
package/dist/negotiate.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
46
|
-
const
|
|
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,
|
|
52
|
-
* path, decision LLM. See module docstring for the pipeline
|
|
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
|
|
92
|
-
const
|
|
93
|
-
? ((await deps.
|
|
94
|
-
:
|
|
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
|
|
99
|
-
|
|
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
|
-
|
|
135
|
+
renderState,
|
|
134
136
|
blueprintCandidates: ragResult.options.map((opt) => ({
|
|
135
137
|
blueprintId: opt.blueprintId ?? opt.id,
|
|
136
138
|
description: opt.description,
|
package/dist/render.d.ts
ADDED
|
@@ -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"}
|