llm_meta_widget 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/README.md +27 -6
- data/app/assets/javascripts/llm_meta_widget/element.js +78 -9
- data/app/assets/javascripts/llm_meta_widget/llm-meta-widget.js +57 -11
- data/app/assets/javascripts/llm_meta_widget/orchestrator.js +6 -1
- data/lib/llm_meta_widget/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5bd30ac6cb4c389fbc600db77a91fd6cc9b363290c970043917706f26288041a
|
|
4
|
+
data.tar.gz: 44696139fdb278e97b039971e27284de9bb4a082a3b2fe12c1341a88b4e94b96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02277ce080547d0f35deb01b761abfa2a40face578f012c607594c08b80152c700c30b72d13652339b044d10ecf0d2e5eb305026ee816d5928290e39f28147d0
|
|
7
|
+
data.tar.gz: 4f349cddfa82766fd1a203d9c29ebd8f56b428ff0d241ec7399e19c29cf1d59c5783ca8122e225c33aa9c578abe4fa51db27d4d9dbeb8bb7894fd1a5803bdca5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file starts at 0.8.0, the first release with a breaking change worth
|
|
4
|
+
calling out. Earlier releases are in the git history (`git log --oneline v0.7.6`).
|
|
5
|
+
|
|
6
|
+
## 0.8.0
|
|
7
|
+
|
|
8
|
+
### Breaking: state readers must declare a description
|
|
9
|
+
|
|
10
|
+
`window.aiState` entries are now objects, not bare functions:
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
window.aiState = {
|
|
14
|
+
text: {
|
|
15
|
+
description: "the text the user wants to annotate",
|
|
16
|
+
read: function() { return $("#text").val() || ""; }
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The bare-function form (`text: function() {…}`) is **removed**, with no fallback.
|
|
22
|
+
A key still using it is reported to the console by name, with the shape it should
|
|
23
|
+
have, and skipped — the page's other readers keep working.
|
|
24
|
+
|
|
25
|
+
**Why.** Every other channel the widget declares to the model already carries a
|
|
26
|
+
developer-authored description: Class 3 action schemas, Class 2 `.well-known`
|
|
27
|
+
tools, Class 1 hub-registered tools, static-primitives resources. State readers
|
|
28
|
+
were the exception — the model saw a bare key and inferred meaning from its name.
|
|
29
|
+
That holds for `text` and fails for `annotation_mode` or `pending_merges`.
|
|
30
|
+
|
|
31
|
+
**What to change.** Wrap each reader in `{ description, read }`. The description
|
|
32
|
+
is the place to tell the model what it must know about the value, not merely what
|
|
33
|
+
the value is — an enumeration it must choose from, a unit, a null convention.
|
|
34
|
+
|
|
35
|
+
The system prompt now renders one line per reader with the description inline:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Current page state:
|
|
39
|
+
- text (the text the user wants to annotate): "The stomach was examined."
|
|
40
|
+
- selected_dictionaries (the dictionaries the user has chosen): ["uberon"]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Prompt-argument pre-filling (`promptArgFromState`) reads the same shape, so a
|
|
44
|
+
page left on the old form also stops pre-filling prompt arguments.
|
data/README.md
CHANGED
|
@@ -211,10 +211,17 @@ Declared inline on the same view as the widget. Runs as JavaScript in the browse
|
|
|
211
211
|
|
|
212
212
|
<!-- (b) State readers folded into the LLM's system prompt EVERY turn, -->
|
|
213
213
|
<!-- so the LLM can answer from page state without a tool_call. -->
|
|
214
|
+
<!-- Each reader declares what it returns, like every other channel. -->
|
|
214
215
|
<script>
|
|
215
216
|
window.aiState = {
|
|
216
|
-
text:
|
|
217
|
-
|
|
217
|
+
text: {
|
|
218
|
+
description: "the text the user wants to annotate",
|
|
219
|
+
read: function() { return $("#text").val() || ""; }
|
|
220
|
+
},
|
|
221
|
+
selected_dictionaries: {
|
|
222
|
+
description: "the dictionaries the user has chosen for annotation",
|
|
223
|
+
read: function() { return getSelected(); }
|
|
224
|
+
}
|
|
218
225
|
};
|
|
219
226
|
</script>
|
|
220
227
|
|
|
@@ -226,7 +233,17 @@ Declared inline on the same view as the widget. Runs as JavaScript in the browse
|
|
|
226
233
|
</script>
|
|
227
234
|
```
|
|
228
235
|
|
|
229
|
-
**What the LLM sees.** The `ai-actions` JSON is passed to the meta-server as `local_tools` for every turn. Alongside it, every reader in `window.aiState` is invoked (each turn) and
|
|
236
|
+
**What the LLM sees.** The `ai-actions` JSON is passed to the meta-server as `local_tools` for every turn. Alongside it, every reader in `window.aiState` is invoked (each turn) and rendered into a `Current page state:` block appended to the system prompt, one line per reader, with its description beside its value:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
Current page state:
|
|
240
|
+
- text (the text the user wants to annotate): "The stomach was examined."
|
|
241
|
+
- selected_dictionaries (the dictionaries the user has chosen for annotation): ["uberon"]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
So the LLM can answer from state directly instead of tool-calling for lookups, and knows what each value *means* rather than guessing from the key.
|
|
245
|
+
|
|
246
|
+
**Breaking change in 0.8.0.** A reader must be `{ description, read }`. The bare-function form — `text: function() {…}` — is removed, with no fallback. A key that still uses it is reported to the console by name, with the shape it should have, and skipped; the page's other readers keep working. Descriptions are not decoration: every other channel the widget declares to the model already carries one (Class 3 tool schemas, Class 2 `.well-known` tools, Class 1 hub tools, resources), and state readers were the exception.
|
|
230
247
|
|
|
231
248
|
**When the tool_call fires.** During the turn, as soon as the LLM emits the
|
|
232
249
|
call. Since 0.4.0 the action's outcome — `{ok: true, applied: "<name>"}`, or
|
|
@@ -279,7 +296,7 @@ Declared out-of-band on the meta-server (via the hub's admin UI or `/user/:id/mc
|
|
|
279
296
|
|
|
280
297
|
**Declaration.** No widget-side change. Register the server on the hub → flip `public: true` (visible to signed-in users) and optionally `public_to_anonymous: true` (visible to widget visitors without a login). See the meta-server's `Api::McpServersController`.
|
|
281
298
|
|
|
282
|
-
**What the LLM sees.** Only tools
|
|
299
|
+
**What the LLM sees.** Only tools the visitor has **enabled via the tool picker** (see "Level-1 pickers" below) — nothing is auto-selected, the visitor opts in per session. A page can set the starting selection instead, with `remote_tools_schema_id:`: those tools arrive already ticked, and the visitor can still untick them.
|
|
283
300
|
|
|
284
301
|
**When the tool_call fires.** Synchronously during the turn — widget POSTs to the hub's `/api/llm_api_keys/:uuid/models/:name/single_llm_calls` endpoint with `tool_ids: [...]`; the hub proxies to each MCP server and streams results back through SSE.
|
|
285
302
|
|
|
@@ -332,9 +349,9 @@ translate directly.
|
|
|
332
349
|
| `api_key_uuid:` | `"ollama-local"` | Hub API-key uuid to invoke |
|
|
333
350
|
| `element_path:` | `"/llm_meta_widget_assets/llm-meta-widget.js"` | The bundled element the page loads. Served by the gem's engine; override to load it from elsewhere |
|
|
334
351
|
| `actions_schema_id:` | `"ai-actions"` | DOM id of the Class-3 schema block |
|
|
335
|
-
| `state_global:` | `"aiState"` | Global window object holding Class-3 state readers |
|
|
352
|
+
| `state_global:` | `"aiState"` | Global window object holding Class-3 state readers, each `{ description, read }` (see the breaking change in 0.8.0) |
|
|
336
353
|
| `actions_global:` | `"aiActions"` | Global window object holding Class-3 implementations |
|
|
337
|
-
| `remote_tools_schema_id:` | `"remote-mcp-tools"` |
|
|
354
|
+
| `remote_tools_schema_id:` | `"remote-mcp-tools"` | DOM id of a JSON block listing Class-1 tools (`{id, name, description, input_schema}`) to start with. They seed the picker rather than bypassing it — shown ticked, and the visitor may untick them. Read once, at boot |
|
|
338
355
|
| `well_known_urls:` | `nil` | `nil` = auto-discover same-origin; explicit array = fetch those; `[]` = disable |
|
|
339
356
|
| `greeting:` | `nil` | First thing a visitor sees when the panel opens, above the offered prompt templates. `nil` = a generic line |
|
|
340
357
|
| `max_rounds:` | `3` | Cap on tool-call rounds per LLM turn. Page actions cost a round each since 0.4.0 — raise it for multi-step flows |
|
|
@@ -457,6 +474,10 @@ it most, since they must already know your form to press it.
|
|
|
457
474
|
behind the custom-element move: the npm package and its CDN URL, why `main`,
|
|
458
475
|
`exports` and `sideEffects` are set the way they are, and why the gem and the npm
|
|
459
476
|
package must never drift in version.
|
|
477
|
+
- **A page you can run** — `examples/connection-test.html` checks every call the
|
|
478
|
+
widget makes to a hub, one at a time, then mounts the widget with a tool
|
|
479
|
+
already selected. Serve it from an origin the hub allows and open it: if
|
|
480
|
+
something in the chain is wrong, it names which step.
|
|
460
481
|
- **Issues and questions** — <https://github.com/jdkim/llm_meta_widget/issues>
|
|
461
482
|
|
|
462
483
|
## License
|
|
@@ -299,7 +299,11 @@ function boot(cfg) {
|
|
|
299
299
|
// baseline (if any), then the level-1 picker adds/removes entries as
|
|
300
300
|
// the visitor toggles server checkboxes. Passed to runChatLoop on
|
|
301
301
|
// every submit so the current selection is respected turn-by-turn.
|
|
302
|
-
|
|
302
|
+
// Keep the baseline separately: refreshRemoteToolsFromPicker() rebuilds
|
|
303
|
+
// remoteTools wholesale, so without a pristine copy the pre-configured
|
|
304
|
+
// tools vanish the moment the picker first renders.
|
|
305
|
+
var baselineRemoteTools = remoteToolsEl ? JSON.parse(remoteToolsEl.textContent) : [];
|
|
306
|
+
var remoteTools = baselineRemoteTools.slice();
|
|
303
307
|
|
|
304
308
|
// Level-1 picker caches. hubMcpServers[] is the list of anon-visible
|
|
305
309
|
// servers fetched from GET /api/mcp_servers; each server has an
|
|
@@ -310,6 +314,10 @@ function boot(cfg) {
|
|
|
310
314
|
// state (all-on / indeterminate / all-off).
|
|
311
315
|
var hubMcpServers = [];
|
|
312
316
|
var selectedToolIds = new Set();
|
|
317
|
+
// A pre-configured tool IS a selection: seed the set so the picker renders
|
|
318
|
+
// it checked and the visitor can turn it off, rather than the picker
|
|
319
|
+
// silently overriding the page.
|
|
320
|
+
baselineRemoteTools.forEach(function(t) { selectedToolIds.add(t.id); });
|
|
313
321
|
|
|
314
322
|
function anyAllowedByAllowlist(name, allowlist) {
|
|
315
323
|
return allowlist === null || allowlist.indexOf(name) >= 0;
|
|
@@ -319,9 +327,11 @@ function boot(cfg) {
|
|
|
319
327
|
// Flatten every SELECTED tool (across all servers) into remoteTools,
|
|
320
328
|
// adapted to runChatLoop's expected { id, name, description, input_schema }.
|
|
321
329
|
var flat = [];
|
|
330
|
+
var seen = {};
|
|
322
331
|
hubMcpServers.forEach(function(s) {
|
|
323
332
|
(s.tools || []).forEach(function(t) {
|
|
324
333
|
if (!selectedToolIds.has(t.id)) return;
|
|
334
|
+
seen[t.id] = true;
|
|
325
335
|
flat.push({
|
|
326
336
|
id: t.id,
|
|
327
337
|
name: t.name,
|
|
@@ -330,6 +340,12 @@ function boot(cfg) {
|
|
|
330
340
|
});
|
|
331
341
|
});
|
|
332
342
|
});
|
|
343
|
+
// A baseline tool whose server the picker never listed (not anon-visible,
|
|
344
|
+
// or the fetch failed) has no checkbox to be found through — keep it
|
|
345
|
+
// unless the visitor explicitly turned it off.
|
|
346
|
+
baselineRemoteTools.forEach(function(t) {
|
|
347
|
+
if (!seen[t.id] && selectedToolIds.has(t.id)) { flat.push(t); seen[t.id] = true; }
|
|
348
|
+
});
|
|
333
349
|
remoteTools = flat;
|
|
334
350
|
if (toolsCountEl) {
|
|
335
351
|
toolsCountEl.textContent = flat.length;
|
|
@@ -938,15 +954,69 @@ function boot(cfg) {
|
|
|
938
954
|
currentThinkingBody = null;
|
|
939
955
|
}
|
|
940
956
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
957
|
+
// Every declaration channel the widget hands the model carries a
|
|
958
|
+
// developer-authored description — Class 3 action schemas, Class 2
|
|
959
|
+
// .well-known tools, Class 1 hub tools, static-primitives resources. State
|
|
960
|
+
// readers used to be the exception: the model saw a bare key and had to
|
|
961
|
+
// infer meaning from its name, which holds for `text` and falls apart for
|
|
962
|
+
// `annotation_mode` or `pending_merges`. Each reader now declares what it
|
|
963
|
+
// returns, in the same shape as everything else.
|
|
964
|
+
//
|
|
965
|
+
// The bare-function form is GONE as of 0.8.0 — no fallback branch. A page
|
|
966
|
+
// that still uses it is told exactly what to change, per key, and that key
|
|
967
|
+
// is skipped; the rest of the page still works.
|
|
968
|
+
function stateReaders() {
|
|
969
|
+
var declared = window[STATE_GLOBAL] || {};
|
|
970
|
+
var out = [];
|
|
971
|
+
Object.keys(declared).forEach(function(key) {
|
|
972
|
+
var entry = declared[key];
|
|
973
|
+
var hasDescription = entry && typeof entry.description === "string" && entry.description.trim() !== "";
|
|
974
|
+
var hasRead = entry && typeof entry.read === "function";
|
|
975
|
+
if (!hasDescription || !hasRead) {
|
|
976
|
+
console.error(
|
|
977
|
+
"[llm-meta-widget] " + STATE_GLOBAL + "." + key + " is not a valid state reader. " +
|
|
978
|
+
"Expected { description: \"what this value is\", read: function () { … } }, got " +
|
|
979
|
+
(typeof entry === "function" ? "a bare function (the 0.7 form, removed in 0.8)" : describeValue(entry)) +
|
|
980
|
+
". Skipping " + key + "."
|
|
981
|
+
);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
out.push({ key: key, description: entry.description, read: entry.read });
|
|
946
985
|
});
|
|
947
986
|
return out;
|
|
948
987
|
}
|
|
949
988
|
|
|
989
|
+
function describeValue(v) {
|
|
990
|
+
if (v === null) return "null";
|
|
991
|
+
if (Array.isArray(v)) return "an array";
|
|
992
|
+
if (typeof v === "object") {
|
|
993
|
+
var missing = [];
|
|
994
|
+
if (typeof v.description !== "string" || v.description.trim() === "") missing.push("description");
|
|
995
|
+
if (typeof v.read !== "function") missing.push("read");
|
|
996
|
+
return "an object missing " + missing.join(" and ");
|
|
997
|
+
}
|
|
998
|
+
return typeof v;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function currentPageState() {
|
|
1002
|
+
return stateReaders().map(function(r) {
|
|
1003
|
+
var value;
|
|
1004
|
+
try { value = r.read(); } catch (e) { value = "<error: " + e.message + ">"; }
|
|
1005
|
+
return { key: r.key, description: r.description, value: value };
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// One line per reader, with its description inline. A separate glossary
|
|
1010
|
+
// above the values would make the model match names across two lists; the
|
|
1011
|
+
// point is that it reads the meaning and the value together.
|
|
1012
|
+
function pageStateLines() {
|
|
1013
|
+
var readers = currentPageState();
|
|
1014
|
+
if (readers.length === 0) return [ "(this page declares no state)" ];
|
|
1015
|
+
return readers.map(function(r) {
|
|
1016
|
+
return "- " + r.key + " (" + r.description + "): " + JSON.stringify(r.value);
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
950
1020
|
function currentSystemPrompt(resourceLines) {
|
|
951
1021
|
return [
|
|
952
1022
|
"You are integrated into a web page as an AI assistant. You have tools available to change page state or fetch information.",
|
|
@@ -957,9 +1027,8 @@ function boot(cfg) {
|
|
|
957
1027
|
"3. After a tool returns a result, use it: either take the next step the task needs, or — if the task is done — answer in plain text. Do not stop silently after a tool call.",
|
|
958
1028
|
"4. NEVER repeat a call you have already made with the same or similar arguments — its result is already in the conversation history.",
|
|
959
1029
|
"",
|
|
960
|
-
"Current page state:"
|
|
961
|
-
|
|
962
|
-
].concat(resourceLines || []).join("\n");
|
|
1030
|
+
"Current page state:"
|
|
1031
|
+
].concat(pageStateLines()).concat(resourceLines || []).join("\n");
|
|
963
1032
|
}
|
|
964
1033
|
|
|
965
1034
|
// Runs once per send, before the system prompt is built: a volatile
|
|
@@ -642,7 +642,8 @@ async function resourceLinesForTurn({ plan, cached, endpoint, read, budgetBytes
|
|
|
642
642
|
}
|
|
643
643
|
var PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" };
|
|
644
644
|
function promptArgFromState(argName, state) {
|
|
645
|
-
const
|
|
645
|
+
const entry = state && state[argName] || state && state[PROMPT_ARG_ALIASES[argName]];
|
|
646
|
+
const reader = entry && typeof entry.read === "function" ? entry.read : null;
|
|
646
647
|
if (typeof reader !== "function") return "";
|
|
647
648
|
let value;
|
|
648
649
|
try {
|
|
@@ -3765,17 +3766,23 @@ function boot(cfg) {
|
|
|
3765
3766
|
var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
|
|
3766
3767
|
var localTools = actionsSchemaEl ? JSON.parse(actionsSchemaEl.textContent) : [];
|
|
3767
3768
|
var remoteToolsEl = document.getElementById(REMOTE_TOOLS_SCHEMA_ID);
|
|
3768
|
-
var
|
|
3769
|
+
var baselineRemoteTools = remoteToolsEl ? JSON.parse(remoteToolsEl.textContent) : [];
|
|
3770
|
+
var remoteTools = baselineRemoteTools.slice();
|
|
3769
3771
|
var hubMcpServers = [];
|
|
3770
3772
|
var selectedToolIds = /* @__PURE__ */ new Set();
|
|
3773
|
+
baselineRemoteTools.forEach(function(t) {
|
|
3774
|
+
selectedToolIds.add(t.id);
|
|
3775
|
+
});
|
|
3771
3776
|
function anyAllowedByAllowlist(name, allowlist) {
|
|
3772
3777
|
return allowlist === null || allowlist.indexOf(name) >= 0;
|
|
3773
3778
|
}
|
|
3774
3779
|
function refreshRemoteToolsFromPicker() {
|
|
3775
3780
|
var flat = [];
|
|
3781
|
+
var seen = {};
|
|
3776
3782
|
hubMcpServers.forEach(function(s) {
|
|
3777
3783
|
(s.tools || []).forEach(function(t) {
|
|
3778
3784
|
if (!selectedToolIds.has(t.id)) return;
|
|
3785
|
+
seen[t.id] = true;
|
|
3779
3786
|
flat.push({
|
|
3780
3787
|
id: t.id,
|
|
3781
3788
|
name: t.name,
|
|
@@ -3784,6 +3791,12 @@ function boot(cfg) {
|
|
|
3784
3791
|
});
|
|
3785
3792
|
});
|
|
3786
3793
|
});
|
|
3794
|
+
baselineRemoteTools.forEach(function(t) {
|
|
3795
|
+
if (!seen[t.id] && selectedToolIds.has(t.id)) {
|
|
3796
|
+
flat.push(t);
|
|
3797
|
+
seen[t.id] = true;
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3787
3800
|
remoteTools = flat;
|
|
3788
3801
|
if (toolsCountEl) {
|
|
3789
3802
|
toolsCountEl.textContent = flat.length;
|
|
@@ -4254,17 +4267,51 @@ function boot(cfg) {
|
|
|
4254
4267
|
currentThinkingBlock = null;
|
|
4255
4268
|
currentThinkingBody = null;
|
|
4256
4269
|
}
|
|
4270
|
+
function stateReaders() {
|
|
4271
|
+
var declared = window[STATE_GLOBAL] || {};
|
|
4272
|
+
var out = [];
|
|
4273
|
+
Object.keys(declared).forEach(function(key) {
|
|
4274
|
+
var entry = declared[key];
|
|
4275
|
+
var hasDescription = entry && typeof entry.description === "string" && entry.description.trim() !== "";
|
|
4276
|
+
var hasRead = entry && typeof entry.read === "function";
|
|
4277
|
+
if (!hasDescription || !hasRead) {
|
|
4278
|
+
console.error(
|
|
4279
|
+
"[llm-meta-widget] " + STATE_GLOBAL + "." + key + ' is not a valid state reader. Expected { description: "what this value is", read: function () { \u2026 } }, got ' + (typeof entry === "function" ? "a bare function (the 0.7 form, removed in 0.8)" : describeValue(entry)) + ". Skipping " + key + "."
|
|
4280
|
+
);
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
out.push({ key, description: entry.description, read: entry.read });
|
|
4284
|
+
});
|
|
4285
|
+
return out;
|
|
4286
|
+
}
|
|
4287
|
+
function describeValue(v) {
|
|
4288
|
+
if (v === null) return "null";
|
|
4289
|
+
if (Array.isArray(v)) return "an array";
|
|
4290
|
+
if (typeof v === "object") {
|
|
4291
|
+
var missing = [];
|
|
4292
|
+
if (typeof v.description !== "string" || v.description.trim() === "") missing.push("description");
|
|
4293
|
+
if (typeof v.read !== "function") missing.push("read");
|
|
4294
|
+
return "an object missing " + missing.join(" and ");
|
|
4295
|
+
}
|
|
4296
|
+
return typeof v;
|
|
4297
|
+
}
|
|
4257
4298
|
function currentPageState() {
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
Object.keys(reader).forEach(function(k) {
|
|
4299
|
+
return stateReaders().map(function(r) {
|
|
4300
|
+
var value;
|
|
4261
4301
|
try {
|
|
4262
|
-
|
|
4302
|
+
value = r.read();
|
|
4263
4303
|
} catch (e) {
|
|
4264
|
-
|
|
4304
|
+
value = "<error: " + e.message + ">";
|
|
4265
4305
|
}
|
|
4306
|
+
return { key: r.key, description: r.description, value };
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
function pageStateLines() {
|
|
4310
|
+
var readers = currentPageState();
|
|
4311
|
+
if (readers.length === 0) return ["(this page declares no state)"];
|
|
4312
|
+
return readers.map(function(r) {
|
|
4313
|
+
return "- " + r.key + " (" + r.description + "): " + JSON.stringify(r.value);
|
|
4266
4314
|
});
|
|
4267
|
-
return out;
|
|
4268
4315
|
}
|
|
4269
4316
|
function currentSystemPrompt(resourceLines) {
|
|
4270
4317
|
return [
|
|
@@ -4276,9 +4323,8 @@ function boot(cfg) {
|
|
|
4276
4323
|
"3. After a tool returns a result, use it: either take the next step the task needs, or \u2014 if the task is done \u2014 answer in plain text. Do not stop silently after a tool call.",
|
|
4277
4324
|
"4. NEVER repeat a call you have already made with the same or similar arguments \u2014 its result is already in the conversation history.",
|
|
4278
4325
|
"",
|
|
4279
|
-
"Current page state:"
|
|
4280
|
-
|
|
4281
|
-
].concat(resourceLines || []).join("\n");
|
|
4326
|
+
"Current page state:"
|
|
4327
|
+
].concat(pageStateLines()).concat(resourceLines || []).join("\n");
|
|
4282
4328
|
}
|
|
4283
4329
|
async function resourceLinesForThisTurn() {
|
|
4284
4330
|
var turn = await resourceLinesForTurn({
|
|
@@ -971,7 +971,12 @@ export async function resourceLinesForTurn({ plan, cached, endpoint, read, budge
|
|
|
971
971
|
const PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" }
|
|
972
972
|
|
|
973
973
|
export function promptArgFromState(argName, state) {
|
|
974
|
-
|
|
974
|
+
// Same object shape the system-prompt annex consumes: { description, read }.
|
|
975
|
+
// The bare-function form was removed in 0.8.0, so an entry without `read` is
|
|
976
|
+
// simply not a reader — the prompt argument stays empty and the visitor fills
|
|
977
|
+
// it, which is the documented behaviour for state the page cannot supply.
|
|
978
|
+
const entry = (state && state[argName]) || (state && state[PROMPT_ARG_ALIASES[argName]])
|
|
979
|
+
const reader = entry && typeof entry.read === "function" ? entry.read : null
|
|
975
980
|
if (typeof reader !== "function") return ""
|
|
976
981
|
let value
|
|
977
982
|
try {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm_meta_widget
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- jdkim
|
|
@@ -35,6 +35,7 @@ executables: []
|
|
|
35
35
|
extensions: []
|
|
36
36
|
extra_rdoc_files: []
|
|
37
37
|
files:
|
|
38
|
+
- CHANGELOG.md
|
|
38
39
|
- LICENSE
|
|
39
40
|
- README.md
|
|
40
41
|
- Rakefile
|