aihand 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +136 -2
  2. package/dist/chunk-2NTK7H4W.js +10 -0
  3. package/dist/chunk-3X4FTHLC.cjs +369 -0
  4. package/dist/chunk-BXVNR4E2.js +399 -0
  5. package/dist/chunk-C7DGE6MY.cjs +1456 -0
  6. package/dist/chunk-DUUCVLC3.cjs +254 -0
  7. package/dist/chunk-FAHI53KO.cjs +125 -0
  8. package/dist/chunk-G7KVJ7NF.js +369 -0
  9. package/dist/chunk-GNEUSRGP.js +52 -0
  10. package/dist/chunk-IGNEAOLT.cjs +130 -0
  11. package/dist/chunk-IS5XFUDB.js +125 -0
  12. package/dist/chunk-JLYC76XL.js +2448 -0
  13. package/dist/chunk-KQOABC2O.cjs +52 -0
  14. package/dist/chunk-OVMK33AC.cjs +104 -0
  15. package/dist/chunk-OWYK2IGV.js +250 -0
  16. package/dist/chunk-PQSQN4CN.js +126 -0
  17. package/dist/chunk-QF6AG3M5.cjs +410 -0
  18. package/dist/chunk-QSAMLXML.js +1456 -0
  19. package/dist/chunk-VEKYRKPF.cjs +399 -0
  20. package/dist/chunk-Y6H7W7PI.cjs +2451 -0
  21. package/dist/chunk-YKSYW77R.js +410 -0
  22. package/dist/chunk-Z2Y65YOY.cjs +7 -0
  23. package/dist/chunk-ZJQRNIK7.js +104 -0
  24. package/dist/cli-FDS2C2CZ.cjs +651 -0
  25. package/dist/cli-HHRGYPSM.js +649 -0
  26. package/dist/cli-JQEIE7RQ.js +120 -0
  27. package/dist/cli-K3OS2QQH.cjs +122 -0
  28. package/dist/cli-OSYG6LJD.cjs +89 -0
  29. package/dist/cli-TXRW5PG6.js +89 -0
  30. package/dist/cli.cjs +81 -0
  31. package/dist/cli.js +81 -0
  32. package/dist/config-5KEQLN6L.cjs +13 -0
  33. package/dist/config-PJPYKDLQ.js +13 -0
  34. package/dist/graph-IH56SCPK.js +8 -0
  35. package/dist/graph-ZUXXCJ5A.cjs +8 -0
  36. package/dist/index.cjs +481 -0
  37. package/dist/index.d.cts +461 -0
  38. package/dist/index.d.ts +461 -0
  39. package/dist/index.js +479 -0
  40. package/dist/locate-5XFSXJ5J.cjs +15 -0
  41. package/dist/locate-NKSUGL3A.js +15 -0
  42. package/dist/refactor-5FWSZIBN.cjs +19 -0
  43. package/dist/refactor-BOB3SZSA.js +19 -0
  44. package/dist/scan-4R7GQG2W.cjs +9 -0
  45. package/dist/scan-VF54GAAX.js +9 -0
  46. package/dist/ui/probe/server.cjs +505 -0
  47. package/dist/ui/probe/server.js +507 -0
  48. package/dist/vite.cjs +12 -0
  49. package/dist/vite.d.cts +12 -0
  50. package/dist/vite.d.ts +12 -0
  51. package/dist/vite.js +12 -0
  52. package/package.json +82 -9
  53. package/src/cli.ts +107 -0
  54. package/src/index.ts +54 -0
  55. package/src/read/cli.ts +650 -0
  56. package/src/read/compact.ts +286 -0
  57. package/src/read/config.ts +62 -0
  58. package/src/read/graph.ts +182 -0
  59. package/src/read/index.ts +12 -0
  60. package/src/read/inject.ts +121 -0
  61. package/src/read/locate.ts +104 -0
  62. package/src/read/panel.ts +335 -0
  63. package/src/read/pipeline.ts +78 -0
  64. package/src/read/refactor.ts +576 -0
  65. package/src/read/render.ts +1118 -0
  66. package/src/read/scan.ts +61 -0
  67. package/src/read/seam.ts +0 -0
  68. package/src/read/security.ts +171 -0
  69. package/src/read/signals.ts +333 -0
  70. package/src/read/state.ts +71 -0
  71. package/src/read/stategraph.ts +205 -0
  72. package/src/read/types.ts +162 -0
  73. package/src/read/vite.ts +77 -0
  74. package/src/ui/babel/line-profiler.ts +197 -0
  75. package/src/ui/babel/source-loc.ts +68 -0
  76. package/src/ui/bridge/cdp-bridge.ts +138 -0
  77. package/src/ui/bridge/compile-probe.ts +80 -0
  78. package/src/ui/bridge/transport.ts +26 -0
  79. package/src/ui/bridge/vite-bridge.ts +116 -0
  80. package/src/ui/client/client-patch.ts +899 -0
  81. package/src/ui/client/client.ts +2562 -0
  82. package/src/ui/core/action.ts +747 -0
  83. package/src/ui/core/candidates.ts +348 -0
  84. package/src/ui/core/canvas.ts +305 -0
  85. package/src/ui/core/check.ts +34 -0
  86. package/src/ui/core/compact.ts +314 -0
  87. package/src/ui/core/detail.ts +244 -0
  88. package/src/ui/core/diff.ts +253 -0
  89. package/src/ui/core/emit.ts +198 -0
  90. package/src/ui/core/knob-exec.ts +137 -0
  91. package/src/ui/core/perf.ts +254 -0
  92. package/src/ui/core/types.ts +164 -0
  93. package/src/ui/core/util.ts +221 -0
  94. package/src/ui/index.ts +5 -0
  95. package/src/ui/probe/cli.ts +139 -0
  96. package/src/ui/probe/server.ts +468 -0
  97. package/src/ui/self/act.ts +47 -0
  98. package/src/ui/self/discover.ts +101 -0
  99. package/src/ui/self/grow.ts +121 -0
  100. package/src/ui/self/install.ts +100 -0
  101. package/src/ui/self/probe.ts +105 -0
  102. package/src/ui/self/screen-hook.ts +44 -0
  103. package/src/ui/self/self.ts +48 -0
  104. package/src/ui/self/store-refs.ts +123 -0
  105. package/src/ui/self/store-schema.ts +65 -0
  106. package/src/ui/self/synth.ts +37 -0
  107. package/src/ui/server/cli.ts +102 -0
  108. package/src/ui/server/dispatch.ts +276 -0
  109. package/src/ui/server/help-text.ts +237 -0
  110. package/src/ui/server/knob-schema.ts +87 -0
  111. package/src/ui/server/plugin.ts +1151 -0
  112. package/src/vite.ts +39 -0
  113. package/index.js +0 -2
@@ -0,0 +1,410 @@
1
+ // src/ui/core/util.ts
2
+ function truncate(s, max) {
3
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
4
+ }
5
+ function formatValue(v, seen = /* @__PURE__ */ new Set()) {
6
+ if (v === null || v === void 0)
7
+ return String(v);
8
+ const t = typeof v;
9
+ if (t === "string")
10
+ return v;
11
+ if (t === "number" || t === "boolean" || t === "bigint")
12
+ return String(v);
13
+ if (t === "symbol")
14
+ return v.toString();
15
+ if (t === "function")
16
+ return `[Function: ${v.name || "anonymous"}]`;
17
+ const obj = v;
18
+ if (seen.has(obj))
19
+ return "[Circular]";
20
+ if (v instanceof Error)
21
+ return v.stack || `${v.name}: ${v.message}`;
22
+ seen.add(obj);
23
+ if (v instanceof Map) {
24
+ const items = [...v.entries()].slice(0, 15).map(([k, val]) => `${formatValue(k, seen)} => ${formatValue(val, seen)}`);
25
+ return `Map(${v.size}) {${items.join(", ")}${v.size > 15 ? ", \u2026" : ""}}`;
26
+ }
27
+ if (v instanceof Set) {
28
+ const items = [...v.values()].slice(0, 15).map((val) => formatValue(val, seen));
29
+ return `Set(${v.size}) {${items.join(", ")}${v.size > 15 ? ", \u2026" : ""}}`;
30
+ }
31
+ if (Array.isArray(v)) {
32
+ const items = v.slice(0, 30).map((val) => formatValue(val, seen));
33
+ return `[${items.join(", ")}${v.length > 30 ? ", \u2026" : ""}]`;
34
+ }
35
+ try {
36
+ return JSON.stringify(v);
37
+ } catch {
38
+ const entries = Object.entries(v).slice(0, 15);
39
+ const parts = entries.map(([k, val]) => `${k}: ${formatValue(val, seen)}`);
40
+ return `{${parts.join(", ")}}`;
41
+ }
42
+ }
43
+ function appStackFrames(stack, max) {
44
+ return stack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ") && !l.includes("node_modules") && !l.includes("<anonymous>")).slice(0, max);
45
+ }
46
+ function compactUrl(url, search) {
47
+ try {
48
+ const u = new URL(url);
49
+ if (search && u.search)
50
+ return `${u.pathname}?${truncate(u.search.slice(1), search)}`;
51
+ return u.pathname;
52
+ } catch {
53
+ return truncate(url, 80);
54
+ }
55
+ }
56
+ function isRouteChange(fromUrl, toUrl) {
57
+ try {
58
+ const a = new URL(fromUrl);
59
+ const b = new URL(toUrl);
60
+ return a.pathname !== b.pathname || a.search !== b.search;
61
+ } catch {
62
+ return fromUrl !== toUrl && stripHash(fromUrl) !== stripHash(toUrl);
63
+ }
64
+ }
65
+ function stripHash(u) {
66
+ const i = u.indexOf("#");
67
+ return i < 0 ? u : u.slice(0, i);
68
+ }
69
+ var STALE_MS = 3e4;
70
+ var isLive = (t, now) => now - t.lastSeen < STALE_MS;
71
+ function formatTabs(tabs, now) {
72
+ const live = tabs.filter((t) => isLive(t, now));
73
+ if (!live.length)
74
+ return "(no live tabs)";
75
+ const rows = live.sort((a, b) => Number(b.visible) - Number(a.visible) || a.id.localeCompare(b.id)).map((t) => {
76
+ const age = Math.round((now - t.lastSeen) / 1e3);
77
+ const mark = t.visible ? "\u25CF visible" : "\u25CB background";
78
+ return `${t.id} ${mark} ${truncate(t.title || "(untitled)", 40)} ${compactUrl(t.url)} ${age}s ago`;
79
+ }).join("\n");
80
+ if (live.some((t) => t.visible))
81
+ return rows;
82
+ return `${rows}
83
+ (all backgrounded because the user is looking at the terminal \u2014 this is normal. Reads and clicks/fills work on background tabs; do NOT ask the user to foreground a tab. Only /profile needs focus.)`;
84
+ }
85
+ function diagnose(tab, tabs, now, socketCount, port) {
86
+ if (tab) {
87
+ const t = tabs.find((x) => x.id === tab);
88
+ return t && isLive(t, now) ? `tab '${tab}' is connected but didn't answer in time \u2014 most likely the HMR socket dropped while the tab was backgrounded and self-heal hasn't reconnected yet (retry in ~2s, it reloads itself), or the handler threw/hung on a bad selector. Reads (/screen /dom) work backgrounded \u2014 do NOT ask the user to foreground the tab. Check /__aihand/console.` : `tab '${tab}' is not connected \u2014 it closed, or the server restarted and it hasn't re-handshaked yet. Check /__aihand/tabs for live ids; if the page is mid self-heal, retry in ~2s.`;
89
+ }
90
+ const live = tabs.filter((t) => isLive(t, now));
91
+ if (live.length)
92
+ return `${live.length} tab(s) connected but none answered \u2014 a handler threw, or they just closed. Check /__aihand/tabs (still live?) and /__aihand/console (errors?).`;
93
+ return socketCount > 0 ? `no aihand tab registered, but ${socketCount} page(s) are connected to this dev server via HMR \u2014 the page is open yet the aihand client didn't load. Is it served by THIS vite server on :${port}? A separate vite config / vite preview / production build won't inject it.` : `no browser tab connected to this dev server on :${port} \u2014 open the app, or check you're hitting the right port.`;
94
+ }
95
+ function formatActions(entries, anchorTs, dropped = 0) {
96
+ if (!entries.length)
97
+ return dropped > 0 ? `\u2504 \u2026 \u66F4\u65E9 ${dropped} \u6761\u52A8\u4F5C\u5DF2\u8D85\u51FA\u7A97\u53E3\uFF08\u6B64\u5904\u975E\u4F1A\u8BDD\u8D77\u70B9\uFF09` : "(no actions)";
98
+ const sorted = [...entries].sort((a, b) => a.ts - b.ts);
99
+ const multiTab = new Set(sorted.map((e) => e.tab).filter(Boolean)).size > 1;
100
+ const lines = [];
101
+ if (dropped > 0)
102
+ lines.push(`\u2504 \u2026 \u66F4\u65E9 ${dropped} \u6761\u52A8\u4F5C\u5DF2\u8D85\u51FA\u7A97\u53E3\uFF08\u4E0B\u65B9\u975E\u4F1A\u8BDD\u8D77\u70B9\uFF09`);
103
+ const prevByTab = /* @__PURE__ */ new Map();
104
+ let anchored = false;
105
+ for (const e of sorted) {
106
+ const isAnchor = anchorTs !== void 0 && !anchored && e.ts === anchorTs;
107
+ if (isAnchor) {
108
+ lines.push("\u2014\u2014\u2014\u2014\u2014\u2014 \u4F60\u5F53\u524D\u7684\u884C\u4E3A \u2014\u2014\u2014\u2014\u2014\u2014");
109
+ anchored = true;
110
+ }
111
+ const who = e.trusted ? "T" : "S";
112
+ const tag = multiTab ? `${e.tab ?? "?"} ` : "";
113
+ const val = e.value ? ` value="${e.value}"` : "";
114
+ const change = uiChange(prevByTab.get(e.tab), e);
115
+ lines.push(`${tag}[${who}] ${e.type} ${e.target}${val}${change ? ` \u2192 ${change}` : ""}`);
116
+ if (isAnchor)
117
+ lines.push("\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014");
118
+ prevByTab.set(e.tab, e);
119
+ }
120
+ return lines.join("\n");
121
+ }
122
+ function appendAction(log, tab, entry, max) {
123
+ const last = log[log.length - 1];
124
+ if (entry.type === "input" && last?.type === "input" && last.tab === tab && last.target === entry.target) {
125
+ log[log.length - 1] = { ...entry, tab };
126
+ return;
127
+ }
128
+ log.push({ ...entry, tab });
129
+ if (log.length > max)
130
+ log.shift();
131
+ }
132
+ function uiChange(prev, cur) {
133
+ if (cur.modal === void 0)
134
+ return "";
135
+ const prevModal = prev?.modal ?? "none";
136
+ if (cur.modal !== prevModal) {
137
+ if (cur.modal === "none")
138
+ return "\u5F39\u7A97\u5173\u95ED";
139
+ if (prevModal === "none")
140
+ return `\u5F39\u7A97\u6253\u5F00\u300C${cur.modal}\u300D`;
141
+ return `\u5F39\u7A97\u5207\u6362\u300C${cur.modal}\u300D`;
142
+ }
143
+ if (prev && cur.view !== void 0 && prev.view !== void 0 && cur.view !== prev.view)
144
+ return `\u89C6\u56FE ${prev.view}\u2192${cur.view}`;
145
+ return "";
146
+ }
147
+
148
+ // src/ui/core/candidates.ts
149
+ var SECRET_HINTS = ["password", "api key", "apikey", "api-key", "api_key", "secret", "token", "private key", "authorization", "cookie"];
150
+ var SECRET_EXEMPTIONS = ["tokens", "tokencount"];
151
+ function isSecretKey(name) {
152
+ const lower = name.toLowerCase();
153
+ if (SECRET_EXEMPTIONS.some((e) => lower.includes(e)))
154
+ return false;
155
+ return SECRET_HINTS.some((h) => lower.includes(h));
156
+ }
157
+ function redactSecretValue(value) {
158
+ return value ? `\u2039redacted ${value.length} chars\u203A` : value;
159
+ }
160
+
161
+ // src/ui/core/action.ts
162
+ var TYPES = ["click", "dblclick", "fill", "press", "wait", "screenshot", "realclick", "query", "assert", "drag", "scrollIntoView", "drop", "clipboard", "hover"];
163
+ function resolveAction(type, args) {
164
+ if (!TYPES.includes(type))
165
+ return { valid: false, error: `unknown action: ${type}` };
166
+ const hasTarget = !!(args.sel || args.text);
167
+ switch (type) {
168
+ case "click":
169
+ return hasTarget ? { valid: true } : { valid: false, error: "click needs sel= or text=" };
170
+ case "dblclick":
171
+ return hasTarget ? { valid: true } : { valid: false, error: "dblclick needs sel= or text=" };
172
+ case "hover":
173
+ return hasTarget ? { valid: true } : { valid: false, error: "hover needs sel= or text=" };
174
+ case "realclick":
175
+ return hasTarget || args.x !== void 0 && args.y !== void 0 ? { valid: true } : { valid: false, error: "realclick needs sel=, text=, or x= & y=" };
176
+ case "fill":
177
+ if (!hasTarget)
178
+ return { valid: false, error: "fill needs sel= or text=" };
179
+ if (args.value === void 0)
180
+ return { valid: false, error: "fill needs value=" };
181
+ return { valid: true };
182
+ case "press":
183
+ return args.key ? { valid: true } : { valid: false, error: "press needs key=" };
184
+ case "wait":
185
+ return hasTarget ? { valid: true } : { valid: false, error: "wait needs sel= or text=" };
186
+ case "screenshot":
187
+ return { valid: true };
188
+ case "query":
189
+ return args.sel ? { valid: true } : { valid: false, error: "query needs sel=" };
190
+ case "assert":
191
+ if (!args.screen && !args.sel)
192
+ return { valid: false, error: "assert needs screen= (a __AIPEEK_SCREEN__ key) or sel=" };
193
+ if (args.equals === void 0)
194
+ return { valid: false, error: "assert needs equals=" };
195
+ return { valid: true };
196
+ case "drag":
197
+ if (!hasTarget)
198
+ return { valid: false, error: "drag needs sel= or text= (the source)" };
199
+ if (!args.to)
200
+ return { valid: false, error: "drag needs to= (destination selector)" };
201
+ return { valid: true };
202
+ case "scrollIntoView":
203
+ return hasTarget ? { valid: true } : { valid: false, error: "scrollIntoView needs sel= or text=" };
204
+ case "drop":
205
+ if (!hasTarget)
206
+ return { valid: false, error: "drop needs sel= or text= (the drop target)" };
207
+ if (!args.files || !args.files.length)
208
+ return { valid: false, error: "drop needs files= (array of file names)" };
209
+ return { valid: true };
210
+ case "clipboard":
211
+ if (args.mode === "write" && args.value === void 0)
212
+ return { valid: false, error: "clipboard write needs value=" };
213
+ return { valid: true };
214
+ default:
215
+ return { valid: false, error: `unknown action: ${type}` };
216
+ }
217
+ }
218
+ function diagnoseEval(code, ok, error) {
219
+ const has = (hay, needle) => !!hay && hay.includes(needle);
220
+ if (!ok) {
221
+ if (has(error, "is not a valid selector") || has(error, "Failed to execute 'querySelector"))
222
+ return "illegal CSS selector \u2014 selectors match on class/role/attr, not text. For non-ASCII or text matching use /query?sel= or /click?text=, not a raw querySelector.";
223
+ return void 0;
224
+ }
225
+ if (has(code, "indexedDB.open") || has(code, "localStorage.getItem") || has(code, "localforage") || has(code, "__AIPEEK_STORES__"))
226
+ return "reading store state by hand \u2014 the /state axis is typed: /state lists every store, /state/<name> drills into one, /state?path=store.field.0 expands a nested value (e.g. /state?path=imStore.conversations.0).";
227
+ return void 0;
228
+ }
229
+ var MISROUTE = {
230
+ text: "/click?text= (or /fill /press)",
231
+ sel: "/click?sel= (or /fill /hover)",
232
+ value: "/fill?sel=\u2026&value=",
233
+ key: "/press?key="
234
+ };
235
+ function misroutedAction(keys) {
236
+ for (const k of keys) {
237
+ const dest = MISROUTE[k];
238
+ if (dest)
239
+ return `\`${k}=\` is a /click\xB7/fill\xB7/press param, not /action. /action is the green channel (?knob= replays a panel morphism, ?name= calls a mounted fn). To simulate a human click/type, use ${dest}.`;
240
+ }
241
+ return void 0;
242
+ }
243
+
244
+ // src/ui/server/dispatch.ts
245
+ var SPECIAL_CONTRACT = /* @__PURE__ */ new Set(["screenshot", "realclick", "query", "assert"]);
246
+ var ACTION_TYPES = TYPES.filter((t) => !SPECIAL_CONTRACT.has(t));
247
+ function isDispatchAction(type) {
248
+ return ACTION_TYPES.includes(type);
249
+ }
250
+ function argsFromQuery(q) {
251
+ return {
252
+ sel: q.get("sel") || void 0,
253
+ text: q.get("text") || void 0,
254
+ value: q.has("value") ? q.get("value") : void 0,
255
+ key: q.get("key") || void 0,
256
+ timeout: q.has("timeout") ? Number(q.get("timeout")) : void 0,
257
+ gone: q.has("gone") ? q.get("gone") !== "false" : void 0,
258
+ enabled: q.has("enabled") ? q.get("enabled") !== "false" : void 0,
259
+ button: q.get("button") === "right" ? "right" : q.get("button") === "left" ? "left" : void 0,
260
+ x: q.has("x") ? Number(q.get("x")) : void 0,
261
+ y: q.has("y") ? Number(q.get("y")) : void 0,
262
+ screen: q.get("screen") || void 0,
263
+ equals: q.has("equals") ? q.get("equals") : void 0,
264
+ to: q.get("to") || void 0,
265
+ files: q.has("files") ? q.get("files").split(",").map((s) => s.trim()).filter(Boolean) : void 0,
266
+ mode: q.get("mode") === "write" ? "write" : q.get("mode") === "read" ? "read" : void 0
267
+ };
268
+ }
269
+ async function dispatchScreen(bridge, opts) {
270
+ const reply = await bridge.request("aihand:collect-screen", "aihand:screen", { form: opts?.form }, { tab: opts?.tab });
271
+ const body = [reply.canvas, reply.screen].filter(Boolean).join("\n\n");
272
+ return { reply, result: { status: 200, body: body || "(empty)" } };
273
+ }
274
+ async function runChain(steps, runStep) {
275
+ const lines = [];
276
+ let lastActions = "";
277
+ let allOk = true;
278
+ for (let i = 0; i < steps.length; i++) {
279
+ const { type, ...args } = steps[i];
280
+ const r = await runStep(type, args, i);
281
+ lines.push(`[${i}] ${r.ok ? "\u2713" : "\u2717"} ${r.label}: ${r.ok ? r.detail || "ok" : r.error}`);
282
+ if (!r.ok && r.detail)
283
+ lines.push(` clickable: ${r.detail}`);
284
+ if (r.screen)
285
+ lines.push(r.screen.split("\n").map((l) => ` ${l}`).join("\n"));
286
+ if (r.actions)
287
+ lastActions = r.actions;
288
+ if (!r.ok) {
289
+ allOk = false;
290
+ break;
291
+ }
292
+ }
293
+ if (!allOk && lines.length) {
294
+ const ran = lines.filter((l) => l.startsWith("[")).length;
295
+ const skipped = steps.length - ran;
296
+ if (skipped > 0)
297
+ lines.push(`[${ran}..${steps.length - 1}] skipped (${skipped} step${skipped > 1 ? "s" : ""} not run \u2014 chain stopped at first failure)`);
298
+ }
299
+ const chainActions = lastActions ? `
300
+
301
+ --- recent actions ---
302
+ ${lastActions}` : "";
303
+ return { status: allOk ? 200 : 422, body: `${lines.join("\n")}${chainActions}` };
304
+ }
305
+ var SPA_NAV_CAP = 1500;
306
+ function trivialDiff(changed) {
307
+ if (!changed)
308
+ return true;
309
+ const lines = changed.split("\n").map((l) => l.trim()).filter(Boolean);
310
+ return lines.length === 0 || lines.every((l) => l === "(no state change)" || l.startsWith("focus:") || l.startsWith("--- "));
311
+ }
312
+ async function executeAction(bridge, type, args, ctx) {
313
+ const check = resolveAction(type, args);
314
+ if (!check.valid)
315
+ return { status: 400 };
316
+ const id = ctx.nextId();
317
+ const fullMs = Math.max(args.timeout ?? 0, 3e3) + 2e3;
318
+ const preUrl = ctx.afterNav ? await ctx.afterNav.url() : void 0;
319
+ const spaArm = ctx.afterNav?.armSameDocNav && (type === "click" || type === "press") ? ctx.afterNav.armSameDocNav() : void 0;
320
+ let navigated = false;
321
+ const result = await bridge.request("aihand:action", "aihand:result", { id, type, args }, { tab: ctx.tab, timeoutMs: fullMs, onNav: () => {
322
+ navigated = true;
323
+ } });
324
+ let changed = result.screen;
325
+ if (ctx.afterNav && result.ok && navigated) {
326
+ const renav = await ctx.afterNav.renavScreen(preUrl ?? "");
327
+ if (renav)
328
+ changed = renav;
329
+ } else if (spaArm && result.ok && !navigated) {
330
+ const cap = trivialDiff(changed) ? SPA_NAV_CAP : 0;
331
+ if (await spaArm.wait(cap)) {
332
+ navigated = true;
333
+ const renav = await ctx.afterNav.renavScreen(preUrl ?? "");
334
+ if (renav)
335
+ changed = renav;
336
+ }
337
+ } else if (spaArm) {
338
+ spaArm.cancel();
339
+ }
340
+ return { status: result.ok ? 200 : 422, result, changed, navigated };
341
+ }
342
+ async function dispatchAction(bridge, type, args, ctx) {
343
+ const ex = await executeAction(bridge, type, args, ctx);
344
+ if (!ex.result) {
345
+ const check = resolveAction(type, args);
346
+ return { status: 400, body: check.error ?? "invalid action" };
347
+ }
348
+ const { result } = ex;
349
+ const head = result.ok ? result.detail || "ok" : `${result.error}${result.detail ? `
350
+
351
+ clickable: ${result.detail}` : ""}`;
352
+ const actionsTail = result.actions && !ex.navigated ? `
353
+
354
+ --- recent actions ---
355
+ ${result.actions}` : "";
356
+ const changedTail = ex.changed ? `
357
+
358
+ --- changed ---
359
+ ${ex.changed}` : "";
360
+ const flowTail = result.flow ? `
361
+
362
+ --- flow ---
363
+ ${result.flow}` : "";
364
+ return { status: result.ok ? 200 : 422, body: `${head}${actionsTail}${changedTail}${flowTail}` };
365
+ }
366
+ async function dispatchChainStep(bridge, type, args, ctx) {
367
+ if (!isDispatchAction(type))
368
+ return { ok: false, label: type, error: `probe chain supports ${ACTION_TYPES.join("/")} only (got ${type})` };
369
+ const ex = await executeAction(bridge, type, args, ctx);
370
+ if (!ex.result) {
371
+ const check = resolveAction(type, args);
372
+ return { ok: false, label: type, error: check.error ?? "invalid action" };
373
+ }
374
+ const { result } = ex;
375
+ return {
376
+ ok: result.ok,
377
+ label: type,
378
+ detail: result.detail,
379
+ error: result.error,
380
+ // 单发把 flow 单列一段尾;chain 单步把 changed + flow 都当缩进 delta 显示在步下。
381
+ screen: [ex.changed, result.flow].filter(Boolean).join("\n") || void 0,
382
+ // 导航步的旧页动作环同样是噪音(与单发 dispatchAction 一致),抑制。
383
+ actions: ex.navigated ? void 0 : result.actions
384
+ };
385
+ }
386
+
387
+ export {
388
+ truncate,
389
+ formatValue,
390
+ appStackFrames,
391
+ compactUrl,
392
+ isRouteChange,
393
+ isLive,
394
+ formatTabs,
395
+ diagnose,
396
+ formatActions,
397
+ appendAction,
398
+ isSecretKey,
399
+ redactSecretValue,
400
+ TYPES,
401
+ resolveAction,
402
+ diagnoseEval,
403
+ misroutedAction,
404
+ isDispatchAction,
405
+ argsFromQuery,
406
+ dispatchScreen,
407
+ runChain,
408
+ dispatchAction,
409
+ dispatchChainStep
410
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// node_modules/tsup/assets/cjs_shims.js
2
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
3
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
4
+
5
+
6
+
7
+ exports.importMetaUrl = importMetaUrl;
@@ -0,0 +1,104 @@
1
+ // src/read/seam.ts
2
+ function fileOfInspPath(inspPath) {
3
+ const parts = inspPath.split(":");
4
+ return parts.length <= 3 ? parts[0] : parts.slice(0, parts.length - 3).join(":");
5
+ }
6
+ var key = (label, file) => `${label}\0${file}`;
7
+ function joinPanel(staticKnobs, runtime) {
8
+ const liveSet = /* @__PURE__ */ new Set();
9
+ for (const r of runtime)
10
+ for (const f of r.files)
11
+ liveSet.add(key(r.label, f));
12
+ return staticKnobs.map((k) => ({
13
+ ...k,
14
+ live: k.label !== null && liveSet.has(key(k.label, k.filePath))
15
+ }));
16
+ }
17
+ var COLLECT_KNOBS_SCRIPT = `JSON.stringify(window.__aihandCollectKnobs ? window.__aihandCollectKnobs() : null)`;
18
+ async function fetchRuntimeKnobs(port) {
19
+ const resp = await fetch(`http://localhost:${port}/__aihand/eval`, {
20
+ method: "POST",
21
+ body: COLLECT_KNOBS_SCRIPT
22
+ });
23
+ if (!resp.ok)
24
+ throw new Error(`runtime probe failed (${resp.status}) \u2014 is the dev server running with aihand() on :${port}?`);
25
+ const raw = JSON.parse(await resp.text());
26
+ if (!raw)
27
+ throw new Error("runtime knob collector missing (__aihandCollectKnobs) \u2014 the page is running an older aihand client; reload it");
28
+ return raw.map((r) => ({ label: r.label, files: r.files, tag: r.tag }));
29
+ }
30
+
31
+ // src/read/locate.ts
32
+ function symbolAtLine(g, file, line) {
33
+ let best;
34
+ for (const s of Object.values(g.symbols)) {
35
+ if (s.filePath !== file || line < s.startLine || line > s.endLine)
36
+ continue;
37
+ if (!best || s.endLine - s.startLine < best.endLine - best.startLine)
38
+ best = s;
39
+ }
40
+ return best;
41
+ }
42
+ function parseInspPath(p) {
43
+ const parts = p.split(":");
44
+ const file = fileOfInspPath(p);
45
+ const lineStr = parts.length <= 3 ? parts[1] : parts[parts.length - 3];
46
+ return { file, line: Number(lineStr) };
47
+ }
48
+ function locate(g, file, line) {
49
+ const symbol = symbolAtLine(g, file, line);
50
+ if (!symbol)
51
+ return null;
52
+ const byUid = (uids) => (uids ?? []).map((u) => g.symbols[u]).filter(Boolean);
53
+ return {
54
+ symbol,
55
+ callers: byUid(g.in[symbol.uid]),
56
+ callees: byUid(g.out[symbol.uid])
57
+ };
58
+ }
59
+ var at = (s) => `${s.filePath}:${s.startLine + 1}`;
60
+ function renderLocate(r, json = false) {
61
+ if (json)
62
+ return JSON.stringify(r, null, 2);
63
+ const lines = [`${r.symbol.name} ${r.symbol.kind} ${at(r.symbol)}`];
64
+ if (r.callers.length) {
65
+ lines.push(`
66
+ Callers (${r.callers.length}):`);
67
+ for (const c of r.callers)
68
+ lines.push(` ${c.name} ${at(c)}`);
69
+ }
70
+ if (r.callees.length) {
71
+ lines.push(`
72
+ Callees (${r.callees.length}):`);
73
+ for (const c of r.callees)
74
+ lines.push(` ${c.name} ${at(c)}`);
75
+ }
76
+ if (!r.callers.length && !r.callees.length)
77
+ lines.push(" (no call edges found)");
78
+ return lines.join("\n");
79
+ }
80
+ function sourcePanelHtml(r, esc = escHtml) {
81
+ const row = (c) => `<div class="aihand-src-edge" data-src-path="${esc(at(c))}">${esc(c.name)} <span class="aihand-src-at">${esc(at(c))}</span></div>`;
82
+ const out = [`<div class="aihand-src-head">${esc(r.symbol.name)} <span class="aihand-src-kind">${esc(r.symbol.kind)}</span> <span class="aihand-src-at">${esc(at(r.symbol))}</span></div>`];
83
+ if (r.callers.length)
84
+ out.push(`<div class="aihand-src-sec">Callers (${r.callers.length})</div>`, ...r.callers.map(row));
85
+ if (r.callees.length)
86
+ out.push(`<div class="aihand-src-sec">Callees (${r.callees.length})</div>`, ...r.callees.map(row));
87
+ if (!r.callers.length && !r.callees.length)
88
+ out.push(`<div class="aihand-src-empty">(no call edges found)</div>`);
89
+ return out.join("");
90
+ }
91
+ function escHtml(s) {
92
+ return s.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;");
93
+ }
94
+
95
+ export {
96
+ fileOfInspPath,
97
+ joinPanel,
98
+ fetchRuntimeKnobs,
99
+ symbolAtLine,
100
+ parseInspPath,
101
+ locate,
102
+ renderLocate,
103
+ sourcePanelHtml
104
+ };