@jxsuite/studio 5.6.0 → 6.0.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.
- package/dist/chunks/{2020-zvax9fmb.js → 2020-cs2sb9t1.js} +3 -3
- package/dist/chunks/adapter-y5gdq19c.js +4 -0
- package/dist/chunks/{cf-account-picker-zfycs4k2.js → cf-account-picker-bb2yq946.js} +3 -3
- package/dist/chunks/cf-settings-20dznc3j.js +4 -0
- package/dist/chunks/components-q037dya7.js +4 -0
- package/dist/chunks/content-source-t0q184js.js +4 -0
- package/dist/chunks/{convert-to-repeater-x19jr6c7.js → convert-to-repeater-vqkt7s3s.js} +3 -3
- package/dist/chunks/editor-j5sq149q.js +4 -0
- package/dist/chunks/entry-commands-fxzgcc4k.js +4 -0
- package/dist/chunks/{extension-sections-y90sa0n4.js → extension-sections-8beq9ac3.js} +3 -3
- package/dist/chunks/file-ops-2n77759h.js +4 -0
- package/dist/chunks/files-fjy7zpxt.js +4 -0
- package/dist/chunks/format-host-wj3mf3n8.js +4 -0
- package/dist/chunks/fs-events-7kcgn4e9.js +4 -0
- package/dist/chunks/git-panel-7z2mpt2d.js +4 -0
- package/dist/chunks/{grid-view-d9mybhn0.js → grid-view-ca10def6.js} +10 -10
- package/dist/chunks/iframe-host-a0zjd6xp.js +4 -0
- package/dist/chunks/{index-vnwy9d44.js → index-9ydfe04y.js} +3 -3
- package/dist/chunks/index-m0x5ztax.js +4 -0
- package/dist/chunks/{javascript-f0nrbdjb.js → javascript-d9pqgc2p.js} +2 -2
- package/dist/chunks/{jsonMode-nfa90dtf.js → jsonMode-800yvbem.js} +4 -4
- package/dist/chunks/jx-validate-zc9zp77s.js +4 -0
- package/dist/chunks/layers-p677nrh5.js +4 -0
- package/dist/chunks/layers-panel-y4w1dq90.js +4 -0
- package/dist/chunks/lit-html-cfn61dn7.js +4 -0
- package/dist/chunks/media-picker-xg7yjpz9.js +4 -0
- package/dist/chunks/menu-fpekzj6w.js +4 -0
- package/dist/chunks/{monaco-binding-antn79jf.js → monaco-binding-62dtmgbf.js} +3 -3
- package/dist/chunks/monaco-buffer-nn4dtx27.js +4 -0
- package/dist/chunks/{monaco-setup-k8s40vwg.js → monaco-setup-tj13ndtm.js} +3 -3
- package/dist/chunks/notify-ahs57a2z.js +4 -0
- package/dist/chunks/platform-v9xyfkbx.js +4 -0
- package/dist/chunks/publish-panel-xtbfgg2r.js +4 -0
- package/dist/chunks/quick-search-d3a6m7j3.js +4 -0
- package/dist/chunks/references-99ws0b0m.js +4 -0
- package/dist/chunks/right-panel-31vbk5xp.js +4 -0
- package/dist/chunks/section-registry-b2yzp9sw.js +4 -0
- package/dist/chunks/{studio-y17505ge.js → studio-01kbbqg3.js} +207 -207
- package/dist/chunks/{studio-gxna6yta.js → studio-01qza1n3.js} +4 -4
- package/dist/chunks/{studio-n2e133d6.js → studio-03wwfvyt.js} +4 -4
- package/dist/chunks/{studio-09n3fyy0.js → studio-0bbxhhce.js} +4 -4
- package/dist/chunks/studio-1a99w39s.js +5 -0
- package/dist/chunks/studio-1q6amtph.js +5 -0
- package/dist/chunks/{studio-eegyf5vg.js → studio-1wvfxkyy.js} +4 -4
- package/dist/chunks/{studio-z47mm077.js → studio-3ck2a5c4.js} +6 -6
- package/dist/chunks/{studio-ymsjnvp9.js → studio-4fde4a0x.js} +4 -4
- package/dist/chunks/{studio-je15p5v8.js → studio-4jj9gme2.js} +5 -5
- package/dist/chunks/{studio-w8w9frjy.js → studio-4m50vh74.js} +12 -12
- package/dist/chunks/{studio-891t1tv9.js → studio-4r0sjtx8.js} +4 -4
- package/dist/chunks/{studio-6exg5z4x.js → studio-4xppwncr.js} +3 -3
- package/dist/chunks/{studio-011em5cf.js → studio-5d7wfmnp.js} +4 -4
- package/dist/chunks/{studio-zkkdbbvd.js → studio-5e9zsamh.js} +6 -6
- package/dist/chunks/{studio-pvj970st.js → studio-5gs9th4b.js} +4 -4
- package/dist/chunks/{studio-dtmdjmqb.js → studio-5vy2wfm5.js} +4 -4
- package/dist/chunks/{studio-654fxcr0.js → studio-7pgy3xaf.js} +4 -4
- package/dist/chunks/{studio-z1vdxq0e.js → studio-7qvs7v0m.js} +4 -4
- package/dist/chunks/studio-84rtc120.js +5 -0
- package/dist/chunks/{studio-ks0ecgjx.js → studio-8xcwewjq.js} +4 -4
- package/dist/chunks/{studio-sadckcpp.js → studio-aekhcdzw.js} +5 -5
- package/dist/chunks/{studio-mf41717j.js → studio-aje966s9.js} +4 -4
- package/dist/chunks/studio-aqmph6k4.js +5 -0
- package/dist/chunks/{studio-28pz4s1k.js → studio-bftp4n4b.js} +4 -4
- package/dist/chunks/{studio-tzs6njnb.js → studio-c6zdm25z.js} +7 -7
- package/dist/chunks/{studio-sgzss898.js → studio-capk0583.js} +7 -7
- package/dist/chunks/{studio-805fwp9k.js → studio-cbr9czkn.js} +4 -4
- package/dist/chunks/{studio-1pxvgx1t.js → studio-chdmwwzw.js} +4 -4
- package/dist/chunks/{studio-dknr8m7b.js → studio-cp1gbff9.js} +7 -7
- package/dist/chunks/{studio-ft8pfpp0.js → studio-cqmtbg2p.js} +4 -4
- package/dist/chunks/studio-e47215hh.js +5 -0
- package/dist/chunks/{studio-9n2j9nqk.js → studio-edbtvcd1.js} +4 -4
- package/dist/chunks/{studio-mm8x32gg.js → studio-evwgp1ym.js} +4 -4
- package/dist/chunks/{studio-02y9cb4h.js → studio-fbx6twps.js} +5 -5
- package/dist/chunks/{studio-0nm2skex.js → studio-fqq890ap.js} +3 -3
- package/dist/chunks/studio-fthwn4kf.js +5 -0
- package/dist/chunks/{studio-s85vah66.js → studio-g8dsj3d2.js} +5 -5
- package/dist/chunks/{studio-0c3fpdqj.js → studio-ge5fqjth.js} +5 -5
- package/dist/chunks/{studio-ebt49j3c.js → studio-gjknvzam.js} +5 -5
- package/dist/chunks/{studio-zkvyd3ev.js → studio-gp02nqnw.js} +4 -4
- package/dist/chunks/{studio-5fc0728f.js → studio-hjbg8d5y.js} +7 -7
- package/dist/chunks/{studio-d007j0z3.js → studio-hnvnfbdv.js} +6 -6
- package/dist/chunks/{studio-b5rxwkj2.js → studio-mk00zd5d.js} +4 -4
- package/dist/chunks/{studio-36s6qjgq.js → studio-n78gjfy8.js} +4 -4
- package/dist/chunks/studio-nbxqcgvf.js +5 -0
- package/dist/chunks/{studio-y164brht.js → studio-pwqfmt4p.js} +4 -4
- package/dist/chunks/studio-qa73881a.js +5 -0
- package/dist/chunks/{studio-6zyt56ek.js → studio-r3kaka4d.js} +4 -4
- package/dist/chunks/{studio-njmg0e73.js → studio-rmn2dz59.js} +5 -5
- package/dist/chunks/{studio-wr0pq67n.js → studio-rr4xkv81.js} +4 -4
- package/dist/chunks/{studio-tj28pxa1.js → studio-semd1s8s.js} +4 -4
- package/dist/chunks/{studio-7w04zw8h.js → studio-st75kn99.js} +5 -5
- package/dist/chunks/{studio-dmymvk2j.js → studio-sxc0zvpt.js} +3 -3
- package/dist/chunks/studio-t1eedsyx.js +4 -0
- package/dist/chunks/{studio-9tkaykt5.js → studio-tmeq6mjb.js} +4 -4
- package/dist/chunks/{studio-153mwkyq.js → studio-tp642n5g.js} +4 -4
- package/dist/chunks/{studio-t0v47dpc.js → studio-tzegmy4w.js} +4 -4
- package/dist/chunks/studio-vavmwshr.js +5 -0
- package/dist/chunks/studio-x5g1xk77.js +5 -0
- package/dist/chunks/studio-x92tgnee.js +64 -0
- package/dist/chunks/{studio-4yztve2t.js → studio-xg1942as.js} +4 -4
- package/dist/chunks/{studio-k9pwvmz8.js → studio-xkfs0dp2.js} +4 -4
- package/dist/chunks/{studio-6k1eswp9.js → studio-y7jehjrp.js} +4 -4
- package/dist/chunks/{studio-81twpmzj.js → studio-yr04mv2j.js} +4 -4
- package/dist/chunks/{studio-gj4j9e7k.js → studio-zd4kayn2.js} +4 -4
- package/dist/chunks/{studio-jsr14n1s.js → studio-zev8ssp1.js} +4 -4
- package/dist/chunks/{studio-r7yeeqc4.js → studio-ztvcxm3w.js} +4 -4
- package/dist/chunks/{tsMode-eq26t9gs.js → tsMode-k9s9fcag.js} +4 -4
- package/dist/chunks/workspace-5vmgsagj.js +4 -0
- package/dist/chunks/{ws-client-3gxqp06s.js → ws-client-0ja08haa.js} +3 -3
- package/dist/iframe-entry.js +3 -3
- package/dist/manifest.json +62 -62
- package/dist/studio.js +95 -95
- package/package.json +12 -12
- package/src/canvas/stage-scrollbar.ts +7 -7
- package/src/files/file-ops.ts +5 -8
- package/src/panels/jump-bar.ts +132 -158
- package/src/panels/pane-context.ts +8 -8
- package/src/panels/pane-grid.ts +18 -26
- package/src/panels/style-panel.ts +2 -2
- package/src/services/context-manager.ts +41 -70
- package/src/services/document-assistant.ts +12 -3
- package/src/services/harness/chat-reducer.ts +115 -0
- package/src/services/harness/turn-hooks.ts +50 -0
- package/src/services/tool-executor.ts +69 -288
- package/src/services/tool-outcomes.ts +12 -10
- package/src/studio.ts +2 -5
- package/src/surfaces/block-action-bar.json +6 -1
- package/src/surfaces/jump-bar.json +9 -12
- package/src/surfaces/jump-bar.ts +15 -16
- package/src/surfaces/pane-context.json +2 -2
- package/src/surfaces/pane-grid.json +8 -25
- package/src/surfaces/pane-grid.ts +7 -8
- package/src/surfaces/statusbar.json +71 -3
- package/src/surfaces/statusbar.ts +66 -139
- package/src/surfaces/style-panel.json +3 -3
- package/src/surfaces/style-panel.ts +11 -1
- package/src/ui/schema-form.ts +6 -2
- package/dist/chunks/adapter-6zhjbpmd.js +0 -4
- package/dist/chunks/cf-settings-fpa485q3.js +0 -4
- package/dist/chunks/components-b0vh4sq8.js +0 -4
- package/dist/chunks/content-source-z5t0d0h9.js +0 -4
- package/dist/chunks/editor-7e23n4a8.js +0 -4
- package/dist/chunks/entry-commands-5cm6aba2.js +0 -4
- package/dist/chunks/file-ops-hv7gf757.js +0 -4
- package/dist/chunks/files-kb0epdxf.js +0 -4
- package/dist/chunks/format-host-eg1wy2bp.js +0 -4
- package/dist/chunks/fs-events-ky3vxfmh.js +0 -4
- package/dist/chunks/git-panel-yyg3bzp2.js +0 -4
- package/dist/chunks/iframe-host-9wdf0hes.js +0 -4
- package/dist/chunks/index-2tf0m2ne.js +0 -4
- package/dist/chunks/jx-validate-7w11db9v.js +0 -4
- package/dist/chunks/layers-7zasrpse.js +0 -4
- package/dist/chunks/layers-panel-9nzc4fdq.js +0 -4
- package/dist/chunks/lit-html-5qr7nbng.js +0 -4
- package/dist/chunks/media-picker-maevn9d4.js +0 -4
- package/dist/chunks/menu-wq6q0v3y.js +0 -4
- package/dist/chunks/monaco-buffer-9ry2rn5x.js +0 -4
- package/dist/chunks/notify-g91gjd6w.js +0 -4
- package/dist/chunks/platform-jdwjp2vt.js +0 -4
- package/dist/chunks/publish-panel-8yym447p.js +0 -4
- package/dist/chunks/quick-search-9bzm9kh3.js +0 -4
- package/dist/chunks/references-znssfqvq.js +0 -4
- package/dist/chunks/right-panel-czbzktm8.js +0 -4
- package/dist/chunks/section-registry-0rtq73qs.js +0 -4
- package/dist/chunks/studio-0mm38y62.js +0 -5
- package/dist/chunks/studio-27tc8q0h.js +0 -5
- package/dist/chunks/studio-7djymzt2.js +0 -5
- package/dist/chunks/studio-ant1zbp3.js +0 -5
- package/dist/chunks/studio-b29h7g7s.js +0 -5
- package/dist/chunks/studio-eb5sty2k.js +0 -5
- package/dist/chunks/studio-frv7vjhw.js +0 -5
- package/dist/chunks/studio-mb8jhtr4.js +0 -64
- package/dist/chunks/studio-nvykrgbj.js +0 -4
- package/dist/chunks/studio-nw4q9sdn.js +0 -5
- package/dist/chunks/studio-p6avneaq.js +0 -5
- package/dist/chunks/studio-vz7b43ng.js +0 -5
- package/dist/chunks/workspace-emt8454d.js +0 -4
|
@@ -1,60 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tool-executor.js —
|
|
2
|
+
* Tool-executor.js — the agent loop, as Studio runs it.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The loop itself is the turn engine in `@jxsuite/ai/harness` (specs/ai.md §3.8): it streams each
|
|
5
|
+
* round, runs the calls the model made, feeds their results back, and decides how the turn ends.
|
|
6
|
+
* What stays here is Studio's side of a turn, which no other host shares:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* summary, because a read returns a summary too, and a turn that only looked around was reported
|
|
18
|
-
* as having changed things.
|
|
19
|
-
* - **A turn that drew nothing says so.** A model that answered with neither text nor a tool call
|
|
20
|
-
* left the author's message sitting there with no reply at all. That turn ends on an error row,
|
|
21
|
-
* with Retry, instead.
|
|
22
|
-
* - **A batch belongs to the tab it edits.** `beginBatch(getTab())` ran once, against whichever tab
|
|
23
|
-
* happened to be active when the loop started. A turn that then moved to a second document closed
|
|
24
|
-
* its batch against the FIRST tab, so the second document's edits got neither a history snapshot
|
|
25
|
-
* nor a collab publish. The loop re-anchors the batch whenever the active tab changes under it.
|
|
8
|
+
* - **The chat store is the transcript of record.** The engine's events are applied to it as they
|
|
9
|
+
* happen (`services/harness/chat-reducer.ts`), and each round's request is projected from it, as
|
|
10
|
+
* it always was, so the provider sees the conversation the panel shows, with whatever the send
|
|
11
|
+
* path trimmed or sealed before the turn began.
|
|
12
|
+
* - **A batch belongs to the tab it edits** (§3.3). The undo batch opens on the active tab and is
|
|
13
|
+
* re-anchored after every call whose tools moved to another document, so a turn that edits two
|
|
14
|
+
* documents gives each its own history step and collab publish.
|
|
15
|
+
* - **The ledger is filed under the last message the turn drew** (§3.2), in this module's `finally`,
|
|
16
|
+
* so the transcript draws the turn's changed-files summary under it.
|
|
26
17
|
*
|
|
27
18
|
* @license MIT
|
|
28
19
|
*/
|
|
29
20
|
|
|
30
21
|
import type { createChatState } from "@jxsuite/ai/chat-state";
|
|
22
|
+
import { DEFAULT_TURN_POLICY, runTurn } from "@jxsuite/ai/harness";
|
|
23
|
+
import type { TurnRun } from "@jxsuite/ai/harness";
|
|
24
|
+
import { toChatMessages, toOpenAITools } from "@jxsuite/ai/messages";
|
|
31
25
|
import type { StreamingClient } from "@jxsuite/ai/streaming-client";
|
|
32
|
-
import { createSessionFacts
|
|
33
|
-
import type {
|
|
26
|
+
import { createSessionFacts } from "@jxsuite/ai/tools";
|
|
27
|
+
import type { SessionFacts, ToolRegistry } from "@jxsuite/ai/tools";
|
|
34
28
|
|
|
35
29
|
import type { Tab } from "../tabs/tab";
|
|
36
|
-
import type { ImportProgressEvent } from "../types";
|
|
37
30
|
import { batchTab, beginBatch, endBatch } from "../tabs/transact";
|
|
38
|
-
import { ensureProxyProbe, resetModelCache } from "./ai-models";
|
|
39
31
|
import { estimatePromptTokens } from "./context-manager";
|
|
40
32
|
import { fileTurn, openTurnLedger, turnAnchor } from "./ai-writes";
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
const MAX_ROUNDS = 5;
|
|
33
|
+
import { applyHarnessEvent } from "./harness/chat-reducer";
|
|
34
|
+
import { studioTurnHooks } from "./harness/turn-hooks";
|
|
44
35
|
|
|
45
36
|
/** What a turn that drew nothing says: the model answered with neither text nor a tool call. */
|
|
46
|
-
export const EMPTY_TURN_TEXT =
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Hard ceiling on rounds of every kind, so the loop terminates whatever the model does.
|
|
50
|
-
*
|
|
51
|
-
* {@link MAX_ROUNDS} bounds AUTONOMOUS work, and a round that ends by blocking on a person is the
|
|
52
|
-
* opposite of runaway — it cannot advance without them, which is the property the cap was ever a
|
|
53
|
-
* proxy for. So an interactive round does not spend that budget. This one is the backstop that
|
|
54
|
-
* keeps `runAgentLoop` provably terminating anyway; a human answering ten questions will have
|
|
55
|
-
* stopped it long before.
|
|
56
|
-
*/
|
|
57
|
-
const MAX_TOTAL_ROUNDS = 25;
|
|
37
|
+
export const EMPTY_TURN_TEXT = DEFAULT_TURN_POLICY.emptyText;
|
|
58
38
|
|
|
59
39
|
interface RunAgentLoopOptions {
|
|
60
40
|
chatState: ReturnType<typeof createChatState>;
|
|
@@ -80,35 +60,15 @@ export async function runAgentLoop({
|
|
|
80
60
|
getTab,
|
|
81
61
|
session = createSessionFacts(),
|
|
82
62
|
}: RunAgentLoopOptions): Promise<void> {
|
|
83
|
-
const allErrors: string[] = [];
|
|
84
|
-
const appliedSummaries: string[] = [];
|
|
85
|
-
|
|
86
63
|
/* The ledger is filed under the turn's last DRAWN assistant message (see services/ai-writes), so
|
|
87
64
|
the scan starts at the message this turn answers. */
|
|
88
65
|
const turnUserId = chatState.messages.findLast((m) => m.role === "user")?.id;
|
|
89
|
-
/* What every call's context carries (specs/ai.md §3.7): this turn's ledger, the conversation's
|
|
90
|
-
facts, and who the calls act as. The signal and the call id are the call's own, below. */
|
|
91
66
|
const ledger = openTurnLedger(`turn:${chatState.messages.length}`);
|
|
92
|
-
const actor: Actor = {
|
|
93
|
-
id: `assistant:${session.sessionId ?? "local"}:${ledger.turnId}`,
|
|
94
|
-
kind: "assistant",
|
|
95
|
-
model: chatState.model,
|
|
96
|
-
sessionId: session.sessionId,
|
|
97
|
-
turnId: ledger.turnId,
|
|
98
|
-
};
|
|
99
67
|
|
|
100
68
|
// Batch all tool-call mutations into a single undo step, anchored on the tab being edited.
|
|
101
69
|
if (getTab) {
|
|
102
70
|
beginBatch(getTab());
|
|
103
71
|
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Close the batch and re-open it when the tools have moved to a different document.
|
|
107
|
-
*
|
|
108
|
-
* Runs after every tool execution rather than only inside `open_document`, because a tool is not
|
|
109
|
-
* the only thing that can change the active tab: project adoption replaces every tab in the
|
|
110
|
-
* workspace, and the user is free to click another tab while the model is still streaming.
|
|
111
|
-
*/
|
|
112
72
|
const reanchorBatch = () => {
|
|
113
73
|
if (!getTab) {
|
|
114
74
|
return;
|
|
@@ -120,237 +80,58 @@ export async function runAgentLoop({
|
|
|
120
80
|
}
|
|
121
81
|
};
|
|
122
82
|
|
|
83
|
+
/* The placeholder `sendMessage` pushed is round one's message, so the turn answers into it; the
|
|
84
|
+
conversation the turn continues is everything before it. */
|
|
85
|
+
const last = chatState.messages.at(-1);
|
|
86
|
+
const placeholder =
|
|
87
|
+
chatState.status === "streaming" && last?.role === "assistant" ? last : undefined;
|
|
88
|
+
const history = toChatMessages(
|
|
89
|
+
placeholder ? chatState.messages.slice(0, -1) : chatState.messages,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/* Once another chat has replaced the one this turn answers, nothing more of the turn is written
|
|
93
|
+
into the transcript: its events belong to a conversation that is no longer on screen. */
|
|
94
|
+
let superseded = false;
|
|
95
|
+
let run: TurnRun | undefined;
|
|
123
96
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
case "tool_call_delta": {
|
|
158
|
-
chatState.appendToolCallDelta(event.id, event.args);
|
|
159
|
-
const tc = toolCalls.get(event.id);
|
|
160
|
-
if (tc) {
|
|
161
|
-
tc.arguments += event.args;
|
|
162
|
-
}
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
case "tool_call_end": {
|
|
166
|
-
chatState.appendToolCallEnd(event.id);
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
case "usage": {
|
|
170
|
-
/* The provider's own count replaces the four-characters-per-token estimate. The prompt
|
|
171
|
-
it covered is recorded with it, because the next send rebuilds the prompt. */
|
|
172
|
-
chatState.recordUsage(event, { systemTokens: estimatePromptTokens(systemPrompt) });
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
case "done": {
|
|
176
|
-
({ stopReason } = event);
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
case "error": {
|
|
180
|
-
streamError = event.message;
|
|
181
|
-
streamErrorCode = event.code;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
default: {
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (streamError) {
|
|
191
|
-
/* A lapsed hosted grant is the one stream error that makes the app's OWN reading wrong.
|
|
192
|
-
The probe settles once at boot, so without this every gate keeps offering an assistant
|
|
193
|
-
that cannot answer, and the send that just failed is the only evidence anyone has. Drop
|
|
194
|
-
the reading and re-probe, so the next paint carries the Reconnect CTA instead. */
|
|
195
|
-
if (streamErrorCode === "cf_reconnect_required") {
|
|
196
|
-
resetModelCache();
|
|
197
|
-
ensureProxyProbe();
|
|
198
|
-
}
|
|
199
|
-
/* `setError` alone, never after `finishStream`: it removes the round's partial message,
|
|
200
|
-
and only while that message is still the streaming one. Finishing first let go of it, so
|
|
201
|
-
the partial (a tool call cut off mid-arguments, as often as not) stayed in the
|
|
202
|
-
transcript and went out on the next send. */
|
|
203
|
-
chatState.setError(streamError);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/* A turn that has drawn nothing by the end of a round with no calls ends here, as an error
|
|
208
|
-
the author can see and retry, rather than as silence under their message. `setError`
|
|
209
|
-
before `finishStream`, so it removes the empty reply. A stopped round is not empty: the
|
|
210
|
-
author ended it. */
|
|
211
|
-
const stopped = stopReason === "cancelled" || signal?.aborted === true;
|
|
212
|
-
if (toolCalls.size === 0 && !stopped && turnAnchor(chatState.messages, turnUserId) === null) {
|
|
213
|
-
chatState.setError(EMPTY_TURN_TEXT);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
chatState.finishStream(stopReason);
|
|
218
|
-
|
|
219
|
-
/* The calls the model streamed decide whether tools run, not the finish reason the provider
|
|
220
|
-
reported beside them. Some OpenAI-compatible backends (Workers AI among them) end a
|
|
221
|
-
tool-calling turn with `finish_reason: "stop"`, and gating on `tool_calls` dropped those
|
|
222
|
-
calls unrun — the turn ended looking finished, and the next send sealed them as failures.
|
|
223
|
-
A call cut off by `length` still runs: its arguments fail to parse, and the model reads
|
|
224
|
-
that as a tool error it can correct.
|
|
225
|
-
|
|
226
|
-
Cancellation is the exception, and it is not a finish reason at all: it is the author
|
|
227
|
-
pressing Stop. A stopped round runs nothing it streamed, including calls whose arguments
|
|
228
|
-
were already complete, because Stop is the one control that must not be outrun. */
|
|
229
|
-
if (toolCalls.size === 0 || stopReason === "cancelled" || signal?.aborted) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
let didWork = false;
|
|
234
|
-
for (const [id, call] of toolCalls) {
|
|
235
|
-
/* A Stop that lands between calls (while `ask_user` waits, or during a long import) stops
|
|
236
|
-
the calls after it too. They stay unanswered, and the send path seals them. */
|
|
237
|
-
if (signal?.aborted) {
|
|
238
|
-
return;
|
|
97
|
+
run = runTurn({
|
|
98
|
+
history,
|
|
99
|
+
system: [{ text: systemPrompt }],
|
|
100
|
+
/* Projected from the chat store, the transcript of record. The streaming client is read on
|
|
101
|
+
every round, and the turn's own signal is the host's, so a request is the one it was. */
|
|
102
|
+
model: (request) =>
|
|
103
|
+
streamingClient.streamChat(
|
|
104
|
+
chatState.toMessagesArray(),
|
|
105
|
+
toOpenAITools(request.tools),
|
|
106
|
+
systemPrompt,
|
|
107
|
+
signal as AbortSignal,
|
|
108
|
+
),
|
|
109
|
+
modelInfo: { family: "openai-compat", model: chatState.model },
|
|
110
|
+
tools: toolRegistry,
|
|
111
|
+
...(signal ? { signal } : {}),
|
|
112
|
+
turnId: ledger.turnId,
|
|
113
|
+
...(placeholder ? { firstMessageId: placeholder.id } : {}),
|
|
114
|
+
policy: { estimateTokens: estimatePromptTokens },
|
|
115
|
+
hooks: studioTurnHooks({
|
|
116
|
+
superseded: () =>
|
|
117
|
+
turnUserId !== undefined && !chatState.messages.some((m) => m.id === turnUserId),
|
|
118
|
+
onSuperseded: () => {
|
|
119
|
+
superseded = true;
|
|
120
|
+
run?.cancel("superseded");
|
|
121
|
+
},
|
|
122
|
+
reanchor: reanchorBatch,
|
|
123
|
+
}),
|
|
124
|
+
session,
|
|
125
|
+
ledger,
|
|
126
|
+
onEvent: (event) => {
|
|
127
|
+
if (!superseded) {
|
|
128
|
+
applyHarnessEvent(chatState, event);
|
|
239
129
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
/* The call's own signal: it aborts with the turn, so a tool waiting on a person or a crawl
|
|
244
|
-
learns of a Stop, and it is unlinked once the call settles, so a Stop later in the turn
|
|
245
|
-
reaches nothing that has already finished. */
|
|
246
|
-
const link = signal ? linkCallSignal(signal) : null;
|
|
247
|
-
const ctx = createToolContext({
|
|
248
|
-
actor,
|
|
249
|
-
callId: id,
|
|
250
|
-
ledger,
|
|
251
|
-
progress: (event) => {
|
|
252
|
-
recordImportProgress(id, event as unknown as ImportProgressEvent);
|
|
253
|
-
},
|
|
254
|
-
session,
|
|
255
|
-
...(link ? { signal: link.signal } : {}),
|
|
256
|
-
});
|
|
257
|
-
try {
|
|
258
|
-
result = await toolRegistry.execute(call.name, parseToolArguments(call.arguments), ctx);
|
|
259
|
-
} catch (error) {
|
|
260
|
-
result = {
|
|
261
|
-
success: false,
|
|
262
|
-
error: `Failed to parse arguments: ${(error as Error).message}`,
|
|
263
|
-
};
|
|
264
|
-
} finally {
|
|
265
|
-
link?.release();
|
|
266
|
-
}
|
|
267
|
-
/* The transcript was replaced while the call ran: another chat was opened from Chat
|
|
268
|
-
History, which stops the turn. The reply belongs to a request that is no longer there,
|
|
269
|
-
so writing it would land a stray `tool` message in the other conversation, and could
|
|
270
|
-
overwrite a restored record's result where a provider reuses call ids. */
|
|
271
|
-
if (turnUserId !== undefined && !chatState.messages.some((m) => m.id === turnUserId)) {
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
// A call that suspends the turn on a person does not spend its work budget.
|
|
275
|
-
if (toolRegistry.getDefinition(call.name)?.interactive !== true) {
|
|
276
|
-
didWork = true;
|
|
277
|
-
}
|
|
278
|
-
reanchorBatch();
|
|
279
|
-
if (!result.success && result.error) {
|
|
280
|
-
allErrors.push(result.error);
|
|
281
|
-
}
|
|
282
|
-
const wrote = ledger.writes.slice(writesBefore).some((write) => write.ok);
|
|
283
|
-
if (result.success && result.summary && wrote) {
|
|
284
|
-
appliedSummaries.push(result.summary);
|
|
285
|
-
}
|
|
286
|
-
chatState.appendToolResult(id, result);
|
|
287
|
-
chatState.pushToolResultMessage(id, JSON.stringify(result));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (didWork) {
|
|
291
|
-
workRounds += 1;
|
|
292
|
-
}
|
|
293
|
-
/* A Stop that landed during the round's last call. Nothing is left to stop in this round, so
|
|
294
|
-
without this the loop opened the next one: a placeholder pushed after the author stopped,
|
|
295
|
-
and a request streamed on the aborted signal. */
|
|
296
|
-
if (signal?.aborted) {
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (workRounds < MAX_ROUNDS && round < MAX_TOTAL_ROUNDS) {
|
|
300
|
-
chatState.beginAssistantTurn();
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/*
|
|
305
|
-
* The round cap. Surface the actual errors so the user knows what went wrong, not just a
|
|
306
|
-
* generic "I couldn't do it" — and, when anything was applied, say it as the ASSISTANT rather
|
|
307
|
-
* than as a failure (`ai.md` §3.2). A partial success is not a failure.
|
|
308
|
-
*/
|
|
309
|
-
const uniqueErrors = [...new Set(allErrors)];
|
|
310
|
-
const applied =
|
|
311
|
-
appliedSummaries.length > 0
|
|
312
|
-
? `\n\nChanges applied so far:\n${appliedSummaries.map((s) => `- ${s}`).join("\n")}`
|
|
313
|
-
: "";
|
|
314
|
-
const errors =
|
|
315
|
-
uniqueErrors.length > 0
|
|
316
|
-
? `\n\nErrors encountered:\n${uniqueErrors.map((e) => `- ${e}`).join("\n")}`
|
|
317
|
-
: "";
|
|
318
|
-
const tail =
|
|
319
|
-
`I ran out of tool-call rounds (${MAX_ROUNDS}) before finishing.${applied}${errors}` +
|
|
320
|
-
`\n\nYou can continue by sending another message, or try a more specific request.`;
|
|
321
|
-
if (appliedSummaries.length > 0) {
|
|
322
|
-
chatState.beginAssistantTurn();
|
|
323
|
-
chatState.appendDelta(tail);
|
|
324
|
-
chatState.finishStream("length");
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
chatState.setError(tail);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
await run.outcome;
|
|
328
133
|
} finally {
|
|
329
134
|
endBatch();
|
|
330
135
|
fileTurn(turnAnchor(chatState.messages, turnUserId) ?? "", ledger.writes);
|
|
331
136
|
}
|
|
332
137
|
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* A call's arguments as the object a tool takes.
|
|
336
|
-
*
|
|
337
|
-
* An empty string is no arguments. JSON that parses to something other than an object (`null`, an
|
|
338
|
-
* array, a number) is refused here with its own sentence, rather than reaching the registry, where
|
|
339
|
-
* a validator reading a property of `null` threw a message about the validator. The prefix is the
|
|
340
|
-
* one a JSON syntax error already carries, so the model reads both as one kind of mistake.
|
|
341
|
-
*
|
|
342
|
-
* @param {string} text - The call's accumulated argument text
|
|
343
|
-
* @returns {object}
|
|
344
|
-
* @throws {SyntaxError | TypeError} Caught by the loop and returned to the model as a tool error
|
|
345
|
-
*/
|
|
346
|
-
function parseToolArguments(text: string): object {
|
|
347
|
-
if (!text) {
|
|
348
|
-
return {};
|
|
349
|
-
}
|
|
350
|
-
const parsed = JSON.parse(text) as unknown;
|
|
351
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
352
|
-
const type = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
353
|
-
throw new TypeError(`arguments must be a JSON object, got ${type}`);
|
|
354
|
-
}
|
|
355
|
-
return parsed;
|
|
356
|
-
}
|
|
@@ -4,27 +4,29 @@
|
|
|
4
4
|
* A tool call's outcome is recorded twice while a turn runs: as the `result` on its
|
|
5
5
|
* `ToolCallRecord`, which the chip renders, and as the `tool` message that answers it, which the
|
|
6
6
|
* provider reads. The second is the one that must survive, because it is the one the wire carries,
|
|
7
|
-
* and it is the one that always did: until the turn became accountable (specs/ai.md §3.2) the first
|
|
8
|
-
* finished call (the loop looked for the record after closing the
|
|
9
|
-
* `result` was `null`: every restored chip rendered as still pending,
|
|
10
|
-
* one still open (specs/ai.md §3.4).
|
|
7
|
+
* and it is the one that always did: until the turn became accountable (specs/ai.md §3.2) the first
|
|
8
|
+
* was never populated for a finished call (the loop looked for the record after closing the
|
|
9
|
+
* stream), so every persisted `result` was `null`: every restored chip rendered as still pending,
|
|
10
|
+
* and an answered question as one still open (specs/ai.md §3.4).
|
|
11
11
|
*
|
|
12
12
|
* So the tool message is the single source. A save strips `result` from every record, which keeps
|
|
13
13
|
* the payload free of a second, disagreeing copy; a restore backfills each record from the reply
|
|
14
|
-
* that answers it. A question nothing answered keeps no result, which is what makes it render
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* that answers it. A question nothing answered keeps no result, which is what makes it render inert
|
|
15
|
+
* rather than as one still waiting; an ordinary call nothing answered never completed, and says
|
|
16
|
+
* so.
|
|
17
17
|
*
|
|
18
18
|
* **A seal is not an answer.** When a question was left open, the send path seals its request with
|
|
19
19
|
* a synthesized failure so the wire stays well-formed (`pruneOrphanToolMessages`). For an ordinary
|
|
20
20
|
* call that failure IS the outcome to show: it never completed. For a question it is the absence of
|
|
21
21
|
* an answer, so a restore leaves it without a result, and the chip draws the inert "still open when
|
|
22
|
-
* the session was reloaded" card rather than a failure.
|
|
22
|
+
* the session was reloaded" card rather than a failure. Either seal counts, the one for a call that
|
|
23
|
+
* was cut off mid-arguments as well (`isSealContent`).
|
|
23
24
|
*
|
|
24
|
-
* @docs studio/ai/chat
|
|
25
25
|
* @license MIT
|
|
26
|
+
* @docs studio/ai/chat
|
|
26
27
|
*/
|
|
27
28
|
|
|
29
|
+
import { isSealContent } from "@jxsuite/ai/messages";
|
|
28
30
|
import type { ToolResult } from "@jxsuite/ai/tools";
|
|
29
31
|
import type { PersistedMessage } from "./ai-session-store";
|
|
30
32
|
import { UNANSWERED_TOOL_RESULT } from "./context-manager";
|
|
@@ -114,7 +116,7 @@ export function backfillToolResults(messages: readonly PersistedMessage[]): Pers
|
|
|
114
116
|
const result = parseToolResult(reply.content);
|
|
115
117
|
if (typeof reply.toolCallId === "string" && result && !replies.has(reply.toolCallId)) {
|
|
116
118
|
replies.set(reply.toolCallId, result);
|
|
117
|
-
if (reply.content
|
|
119
|
+
if (isSealContent(reply.content)) {
|
|
118
120
|
sealed.add(reply.toolCallId);
|
|
119
121
|
}
|
|
120
122
|
}
|
package/src/studio.ts
CHANGED
|
@@ -94,7 +94,6 @@ import { runInsertZoneAction } from "./editor/insert-zone-action";
|
|
|
94
94
|
import { canvasSlashHandler } from "./editor/canvas-slash-bridge";
|
|
95
95
|
import { makeCanvasContextMenuHandler } from "./editor/canvas-context-menu";
|
|
96
96
|
import { mountStatusbar, renderStatusbar } from "./surfaces/statusbar";
|
|
97
|
-
import { mountJumpBar } from "./panels/jump-bar";
|
|
98
97
|
import { cellForPane, paneGridReady } from "./panels/pane-grid";
|
|
99
98
|
import { notify } from "./services/notify";
|
|
100
99
|
import { createLiveSurfaceSaver } from "./services/live-surfaces";
|
|
@@ -916,11 +915,9 @@ registerRenderer("seoModal", renderSeoModal);
|
|
|
916
915
|
registerRenderer("chatPanel", () => chatPanelMod.render());
|
|
917
916
|
registerRenderer("overlays", () => overlaysPanel.render());
|
|
918
917
|
renderStatusbar();
|
|
918
|
+
// The status bar mounts the jump bar too: the focused pane's breadcrumb trail is its DOCUMENT
|
|
919
|
+
// Field, and the only breadcrumb in the shell.
|
|
919
920
|
mountStatusbar();
|
|
920
|
-
// The jump bar, in the pane's own grid cell above the context bar. It renders the whole address
|
|
921
|
-
// — project › file › node › node — and it is the only breadcrumb in the shell: the pane context
|
|
922
|
-
// Bar drew a second one, and it named a sub-document stack nothing could push onto.
|
|
923
|
-
mountJumpBar(primaryCell?.jump ?? document.createElement("div"));
|
|
924
921
|
mountActivityBar();
|
|
925
922
|
|
|
926
923
|
/* The background-click deselect moved into `editor/shortcuts.ts`'s `installStageGestures`, beside
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://jxsuite.com/schema/v1",
|
|
3
3
|
"$id": "StudioJumpBar",
|
|
4
|
-
"$description": "The jump bar:
|
|
4
|
+
"$description": "The jump bar: the focused pane's address as a breadcrumb trail, the document first and the selected element last, every step a command the adapter has already resolved against the registry. It is the status bar's DOCUMENT field, mounted into the slot `surfaces/statusbar.json` leaves for it. A step is a button when its command can run and a readout when it cannot — an address with a hole in it is a lie about containment, so a step never disappears. A step with more than one place to go carries the chevron that opens its siblings, upward, because the bar is the window's floor; one alternative is not a choice and draws no control. Nothing here decides what the bar says, which step is where you are, or what a chevron opens.",
|
|
5
5
|
"tagName": "nav",
|
|
6
6
|
"attributes": {
|
|
7
7
|
"part": "bar",
|
|
@@ -10,22 +10,18 @@
|
|
|
10
10
|
"hidden": "${state.empty ? '' : null}"
|
|
11
11
|
},
|
|
12
12
|
"style": {
|
|
13
|
-
"$description": "
|
|
13
|
+
"$description": "A field of the status bar, so it takes the bar's own row, font and colour rather than drawing a band of its own. It scrolls rather than wraps, because a deep selection must not grow the status bar a second line.",
|
|
14
14
|
"display": "flex",
|
|
15
15
|
"alignItems": "center",
|
|
16
16
|
"gap": "1px",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"flex": "1",
|
|
18
|
+
"height": "100%",
|
|
19
|
+
"padding": "0 var(--jx-space-2)",
|
|
19
20
|
"minWidth": "0",
|
|
20
21
|
"overflowX": "auto",
|
|
21
22
|
"scrollbarWidth": "none",
|
|
22
23
|
"whiteSpace": "nowrap",
|
|
23
24
|
"boxSizing": "border-box",
|
|
24
|
-
"fontFamily": "var(--jx-font-sans)",
|
|
25
|
-
"fontSize": "var(--jx-text-xs)",
|
|
26
|
-
"color": "var(--jx-fg-dim)",
|
|
27
|
-
"background": "var(--jx-bg-panel)",
|
|
28
|
-
"borderBottom": "1px solid var(--jx-border)",
|
|
29
25
|
"&[hidden]": {
|
|
30
26
|
"display": "none"
|
|
31
27
|
},
|
|
@@ -44,7 +40,7 @@
|
|
|
44
40
|
"opacity": "0.6"
|
|
45
41
|
},
|
|
46
42
|
"& [part=\"crumb\"]": {
|
|
47
|
-
"maxWidth": "
|
|
43
|
+
"maxWidth": "32ch",
|
|
48
44
|
"overflow": "hidden",
|
|
49
45
|
"margin": "0",
|
|
50
46
|
"padding": "0 var(--jx-space-2)",
|
|
@@ -74,7 +70,8 @@
|
|
|
74
70
|
},
|
|
75
71
|
"& [part=\"crumb\"][aria-current]": {
|
|
76
72
|
"$description": "Where you ARE is stated by weight, not by taking the control away: the leaf still selects, which is how a stray click in the canvas is undone without leaving the bar.",
|
|
77
|
-
"color": "var(--jx-fg)"
|
|
73
|
+
"color": "var(--jx-fg)",
|
|
74
|
+
"fontWeight": "600"
|
|
78
75
|
},
|
|
79
76
|
"& span[part=\"crumb\"]": {
|
|
80
77
|
"$description": "A step with no runnable command is a readout, not a dead button. No hover, no pointer, no promise.",
|
|
@@ -213,7 +210,7 @@
|
|
|
213
210
|
"aria-label": "${$map.item.altsLabel}",
|
|
214
211
|
"title": "${$map.item.altsLabel}"
|
|
215
212
|
},
|
|
216
|
-
"textContent": "
|
|
213
|
+
"textContent": "⌃",
|
|
217
214
|
"onclick": {
|
|
218
215
|
"$prototype": "Function",
|
|
219
216
|
"body": [
|
package/src/surfaces/jump-bar.ts
CHANGED
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `jump-bar.json` is the markup, the ARIA and the style; `panels/jump-bar.ts` is the flow — which
|
|
6
6
|
* pane this bar is about, what its address is, which of its steps the registry can actually run,
|
|
7
|
-
* and what a chevron opens. This module is the seam between them: one scope
|
|
8
|
-
*
|
|
7
|
+
* and what a chevron opens. This module is the seam between them: one scope, one document mounted
|
|
8
|
+
* into the slot the status bar leaves for it, and an `update()` that ASSIGNS rather than
|
|
9
|
+
* re-mounts.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* node, and the chevron the reader is aiming at does not move under them.
|
|
11
|
+
* The scope is reactive, so a repaint is an assignment to `segments` and the keyed `$map`
|
|
12
|
+
* reconciles: a step whose key survives keeps its node, and the chevron the reader is aiming at
|
|
13
|
+
* does not move under them.
|
|
14
14
|
*
|
|
15
|
-
* **The bar hides rather than un-mounts when
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* this surface and its stylesheet-free document share.
|
|
15
|
+
* **The bar hides rather than un-mounts when there is no address.** With no tab open there is
|
|
16
|
+
* nothing to print, and the document's own `hidden` takes it off the screen — the mount stays,
|
|
17
|
+
* because the next tab open is one assignment away and re-mounting would cost a frame of blank
|
|
18
|
+
* chrome.
|
|
20
19
|
*
|
|
21
20
|
* @docs studio/interface
|
|
22
21
|
*/
|
|
@@ -81,13 +80,13 @@ interface JumpBarScope extends Record<string, unknown>, JumpBarActions {
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
|
-
* Mount
|
|
83
|
+
* Mount the bar into the slot the status bar built for it.
|
|
85
84
|
*
|
|
86
|
-
* The host is CLEARED first:
|
|
87
|
-
*
|
|
85
|
+
* The host is CLEARED first: the slot is the bar's own, and whatever was in it belongs to a mount
|
|
86
|
+
* this bar is no longer drawing.
|
|
88
87
|
*
|
|
89
|
-
* @param {HTMLElement} host The
|
|
90
|
-
* @param {string} region The `data-jx-region` the bar stamps on itself — `
|
|
88
|
+
* @param {HTMLElement} host The status bar's slot for the bar.
|
|
89
|
+
* @param {string} region The `data-jx-region` the bar stamps on itself — `statusbar/document`.
|
|
91
90
|
* @param {JumpBarActions} actions What a press does. Read once, when the scope is made.
|
|
92
91
|
* @returns {JumpBarSurface}
|
|
93
92
|
*/
|
|
@@ -497,7 +497,7 @@
|
|
|
497
497
|
"$ref": "#/state/previewHint"
|
|
498
498
|
}
|
|
499
499
|
},
|
|
500
|
-
"
|
|
500
|
+
"onchange": {
|
|
501
501
|
"$prototype": "Function",
|
|
502
502
|
"body": [
|
|
503
503
|
{
|
|
@@ -1227,7 +1227,7 @@
|
|
|
1227
1227
|
"$ref": "$map/item/hint"
|
|
1228
1228
|
}
|
|
1229
1229
|
},
|
|
1230
|
-
"
|
|
1230
|
+
"onchange": {
|
|
1231
1231
|
"$prototype": "Function",
|
|
1232
1232
|
"body": [
|
|
1233
1233
|
{
|