@superatomai/sdk-node 0.0.68-dsp → 0.0.69-dsp

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/index.mjs CHANGED
@@ -804,6 +804,101 @@ var CodingAgentAbortRequestMessageSchema = z3.object({
804
804
  type: z3.literal("CODING_AGENT_ABORT_REQ"),
805
805
  payload: CodingAgentAbortRequestPayloadSchema
806
806
  });
807
+ var DashBuilderRequestPayloadSchema = z3.object({
808
+ /** The conversation. One per dashboard program, and the abort key. */
809
+ dashboardId: z3.string(),
810
+ /** What the person said. */
811
+ text: z3.string(),
812
+ /**
813
+ * A panel the person pointed at before typing.
814
+ *
815
+ * Its `Panel` id, the same key a saved arrangement stores against. Optional
816
+ * because most turns are about the dashboard rather than one panel of it, and
817
+ * carried as a field rather than folded into `text` so the transcript shows
818
+ * what the person actually wrote.
819
+ */
820
+ panelId: z3.string().optional(),
821
+ projectId: z3.string().optional(),
822
+ userId: z3.string().optional()
823
+ });
824
+ var DashBuilderRequestMessageSchema = z3.object({
825
+ id: z3.string(),
826
+ from: MessageParticipantSchema,
827
+ type: z3.literal("DASH_BUILDER_REQ"),
828
+ payload: DashBuilderRequestPayloadSchema
829
+ });
830
+ var DashBuilderUpdateSchema = z3.discriminatedUnion("type", [
831
+ z3.object({ type: z3.literal("state"), running: z3.boolean() }),
832
+ z3.object({ type: z3.literal("user"), text: z3.string(), at: z3.number() }),
833
+ /**
834
+ * The tail of an assistant message that is still being written.
835
+ *
836
+ * Append-only and never part of the transcript: the `assistant` update that
837
+ * follows carries the same prose in full, so a client shows the accumulated
838
+ * deltas while a turn runs and drops them the moment the real message lands.
839
+ * A turn here runs for minutes, and without this the pane sits empty for all
840
+ * of it.
841
+ */
842
+ z3.object({ type: z3.literal("delta"), text: z3.string() }),
843
+ z3.object({ type: z3.literal("assistant"), text: z3.string(), at: z3.number() }),
844
+ z3.object({ type: z3.literal("tool"), text: z3.string(), at: z3.number() }),
845
+ z3.object({ type: z3.literal("error"), text: z3.string(), at: z3.number() })
846
+ ]);
847
+ var DashBuilderStreamPayloadSchema = z3.object({
848
+ dashboardId: z3.string(),
849
+ /**
850
+ * Monotonic per turn, so re-delivery is harmless.
851
+ *
852
+ * Copied from dashboard-agent rather than assumed unnecessary: duplicate and
853
+ * out-of-order delivery were observed there on long-running requests and were
854
+ * never explained by anything in the send/receive path itself. A turn here
855
+ * runs for minutes, which is the same exposure.
856
+ */
857
+ seq: z3.number(),
858
+ update: DashBuilderUpdateSchema
859
+ });
860
+ var DashBuilderStreamMessageSchema = z3.object({
861
+ id: z3.string().optional(),
862
+ from: MessageParticipantSchema,
863
+ type: z3.literal("DASH_BUILDER_STREAM"),
864
+ payload: DashBuilderStreamPayloadSchema
865
+ });
866
+ var DashBuilderAbortRequestPayloadSchema = z3.object({
867
+ dashboardId: z3.string()
868
+ });
869
+ var DashBuilderAbortRequestMessageSchema = z3.object({
870
+ id: z3.string().optional(),
871
+ from: MessageParticipantSchema,
872
+ type: z3.literal("DASH_BUILDER_ABORT_REQ"),
873
+ payload: DashBuilderAbortRequestPayloadSchema
874
+ });
875
+ var DashComponentPropSchema = z3.union([
876
+ z3.string(),
877
+ z3.object({
878
+ description: z3.string(),
879
+ importance: z3.enum(["required", "recommended", "optional"]).optional(),
880
+ default: z3.union([z3.string(), z3.number(), z3.boolean()]).optional(),
881
+ caution: z3.string().optional(),
882
+ group: z3.string().optional()
883
+ })
884
+ ]);
885
+ var DashComponentSchema = z3.object({
886
+ name: z3.string(),
887
+ kind: z3.string(),
888
+ purpose: z3.string(),
889
+ props: z3.record(DashComponentPropSchema).optional(),
890
+ useWhen: z3.string().optional(),
891
+ notes: z3.string().optional()
892
+ });
893
+ var DashContractResponsePayloadSchema = z3.object({
894
+ components: z3.array(DashComponentSchema)
895
+ });
896
+ var DashContractResponseMessageSchema = z3.object({
897
+ id: z3.string(),
898
+ from: MessageParticipantSchema,
899
+ type: z3.literal("DASH_CONTRACT_RES"),
900
+ payload: DashContractResponsePayloadSchema
901
+ });
807
902
  var ReportCompRequestPayloadSchema = z3.object({
808
903
  prompt: z3.string(),
809
904
  userId: z3.string().optional(),
@@ -3728,7 +3823,7 @@ var TaggedStreamBuffer = class extends StreamBuffer {
3728
3823
  }
3729
3824
  };
3730
3825
  function streamDelay(ms = STREAM_DELAY_MS) {
3731
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3826
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3732
3827
  }
3733
3828
  async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
3734
3829
  if (!streamBuffer.hasCallback()) {
@@ -10909,7 +11004,7 @@ var Semaphore = class {
10909
11004
  `Script execution at capacity (${this.max} running, ${this.waiters.length} queued). Try again shortly.`
10910
11005
  );
10911
11006
  }
10912
- return new Promise((resolve2) => this.waiters.push(resolve2));
11007
+ return new Promise((resolve3) => this.waiters.push(resolve3));
10913
11008
  }
10914
11009
  release() {
10915
11010
  const next = this.waiters.shift();
@@ -11039,7 +11134,7 @@ FIX: pass every required parameter to execute_script, or give it a default in wr
11039
11134
  }
11040
11135
  }
11041
11136
  function executeInSubprocess(scriptPath, params, paramSchema, externalTools, streamBuffer, timeoutMs, signal, mode, analysisData, analysisDatasets, entities, appliedPolicies) {
11042
- return new Promise((resolve2) => {
11137
+ return new Promise((resolve3) => {
11043
11138
  const tsxBin = resolveTsxBinary();
11044
11139
  const bootstrap = resolveBootstrapPath();
11045
11140
  const contextToken = mintContextToken();
@@ -11095,7 +11190,7 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
11095
11190
  clearTimeout(timer);
11096
11191
  if (signal && onAbort) signal.removeEventListener("abort", onAbort);
11097
11192
  cleanup();
11098
- resolve2(r);
11193
+ resolve3(r);
11099
11194
  };
11100
11195
  const timer = setTimeout(() => {
11101
11196
  finish({
@@ -18794,7 +18889,25 @@ RENDERING REQUIREMENT (overrides the default choice of component): ${carriedRequ
18794
18889
  }
18795
18890
  }
18796
18891
  if (!usedLightweight) {
18797
- logger.warn(`[AgentFlow] Component generation produced nothing \u2014 returning the analysis without a dashboard`);
18892
+ logger.warn(`[AgentFlow] Component generation produced no components \u2014 returning the analysis without a dashboard`);
18893
+ }
18894
+ }
18895
+ if (matchedComponents.length === 0) {
18896
+ const fallbackText = (agentResponse.text || "").replace(/<DashboardComponents>[\s\S]*?<\/DashboardComponents>/g, "").trim();
18897
+ if (fallbackText) {
18898
+ logger.info(`[AgentFlow] No components \u2014 wrapping the analysis in DynamicMarkdownBlock`);
18899
+ matchedComponents = [{
18900
+ id: "dynamic-markdown-block",
18901
+ name: "DynamicMarkdownBlock",
18902
+ type: "MarkdownBlock",
18903
+ description: "Text response rendered as markdown",
18904
+ props: { content: fallbackText }
18905
+ }];
18906
+ layoutTitle = "";
18907
+ layoutDescription = "";
18908
+ noDataAnswer = true;
18909
+ } else {
18910
+ logger.warn(`[AgentFlow] No components and no analysis text \u2014 returning an empty result`);
18798
18911
  }
18799
18912
  }
18800
18913
  const queryMap = {};
@@ -19735,12 +19848,13 @@ async function handleUserPromptRequest(data, components, sendMessage, anthropicA
19735
19848
  id: `comp_${Math.random().toString(36).substring(2, 8)}`
19736
19849
  }));
19737
19850
  }
19851
+ const { trace: _trace, ...dataWithoutTrace } = response.data ?? {};
19738
19852
  sendDataResponse4(
19739
19853
  response.id || data.id,
19740
19854
  {
19741
19855
  success: response.success,
19742
19856
  errors: response.errors,
19743
- data: response.data,
19857
+ data: response.data ? dataWithoutTrace : response.data,
19744
19858
  uiBlockId: response.uiBlockId,
19745
19859
  threadId: response.threadId
19746
19860
  },
@@ -19937,6 +20051,26 @@ async function handleComponentListResponse(data, storeComponents, collections) {
19937
20051
  }
19938
20052
  }
19939
20053
 
20054
+ // src/handlers/dash-contract-response.ts
20055
+ async function handleDashContractResponse(data, storeDashContract, collections) {
20056
+ const parsed = DashContractResponseMessageSchema.safeParse(data);
20057
+ if (!parsed.success) {
20058
+ logger.error("[DashContract] Invalid message \u2014 ignoring");
20059
+ return;
20060
+ }
20061
+ const { components } = parsed.data.payload;
20062
+ storeDashContract(components);
20063
+ const register = collections?.["dashboard-builder"]?.["registerContract"];
20064
+ if (!register) return;
20065
+ try {
20066
+ await register({ components });
20067
+ } catch (error) {
20068
+ logger.warn(
20069
+ `[DashContract] Collection refused the contract: ${error instanceof Error ? error.message : String(error)}`
20070
+ );
20071
+ }
20072
+ }
20073
+
19940
20074
  // src/handlers/workflow-list-response.ts
19941
20075
  async function handleWorkflowListResponse(data, storeWorkflows) {
19942
20076
  try {
@@ -24334,11 +24468,257 @@ ${marker}
24334
24468
  };
24335
24469
  }
24336
24470
 
24471
+ // src/codingAgent/drivers/pi.ts
24472
+ var dynamicImport2 = new Function("specifier", "return import(specifier)");
24473
+ var piModulePromise = null;
24474
+ function loadPi() {
24475
+ if (!piModulePromise) {
24476
+ piModulePromise = dynamicImport2("@earendil-works/pi-coding-agent").catch((err) => {
24477
+ piModulePromise = null;
24478
+ throw new Error(`@earendil-works/pi-coding-agent is not installed, so the pi harness is unavailable. (${err?.message ?? err})`);
24479
+ });
24480
+ }
24481
+ return piModulePromise;
24482
+ }
24483
+ var authAndRegistry = null;
24484
+ function getAuthAndRegistry() {
24485
+ if (!authAndRegistry) {
24486
+ authAndRegistry = loadPi().then(({ AuthStorage, ModelRegistry }) => {
24487
+ const authStorage = AuthStorage.create();
24488
+ return { authStorage, modelRegistry: ModelRegistry.create(authStorage) };
24489
+ });
24490
+ }
24491
+ return authAndRegistry;
24492
+ }
24493
+ var DEFAULT_PROVIDER = "openai-codex";
24494
+ var DEFAULT_MODEL_ID = process.env.PI_AGENT_MODEL || "gpt-5.5";
24495
+ var KNOWN_UNUSABLE_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.3-codex-spark"]);
24496
+ async function resolveModel(modelRegistry, spec) {
24497
+ const [provider, modelId] = spec?.includes("/") ? [spec.slice(0, spec.indexOf("/")), spec.slice(spec.indexOf("/") + 1)] : [spec || DEFAULT_PROVIDER, void 0];
24498
+ const available = modelRegistry.getAvailable().filter((m) => m.provider === provider);
24499
+ const wanted = modelId ?? (spec ? void 0 : DEFAULT_MODEL_ID);
24500
+ const exact = wanted && (modelRegistry.find(provider, wanted) ?? available.find((m) => m.id === wanted));
24501
+ if (exact && modelRegistry.hasConfiguredAuth(exact)) return exact;
24502
+ const usable = available.filter((m) => !KNOWN_UNUSABLE_MODEL_IDS.has(m.id));
24503
+ if (usable.length) return usable[0];
24504
+ if (available.length) return available[0];
24505
+ throw new Error(
24506
+ `No model available from "${provider}". Authorise it in a terminal on this machine: run \`pi\`, then \`/login\`.`
24507
+ );
24508
+ }
24509
+ function emptyUsage2() {
24510
+ return { inputTokens: 0, outputTokens: 0, reasoningTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, toolCalls: 0 };
24511
+ }
24512
+ var BUFFER_CAP2 = 64e3;
24513
+ var EVENT_CAP2 = 600;
24514
+ function normEvent2(ev) {
24515
+ switch (ev.type) {
24516
+ case "tool_execution_start":
24517
+ return { kind: "command", id: ev.toolCallId, command: ev.toolName ? `${ev.toolName} ${jsonPreview(ev.args)}` : void 0, status: "in_progress" };
24518
+ case "tool_execution_end":
24519
+ return { kind: "command", id: ev.toolCallId, status: ev.isError ? "failed" : "completed", done: true, output: textOf(ev.result) };
24520
+ default:
24521
+ return null;
24522
+ }
24523
+ }
24524
+ function jsonPreview(v) {
24525
+ if (v == null) return "";
24526
+ try {
24527
+ return JSON.stringify(v).slice(0, 160);
24528
+ } catch {
24529
+ return "";
24530
+ }
24531
+ }
24532
+ function textOf(content) {
24533
+ const blocks = content?.content ?? content;
24534
+ if (!Array.isArray(blocks)) return typeof content === "string" ? content : "";
24535
+ return blocks.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("").trim();
24536
+ }
24537
+ var TRANSCRIPT_CAP = 400;
24538
+ var sessionListeners = /* @__PURE__ */ new Set();
24539
+ function onPiSessionUpdate(listener) {
24540
+ sessionListeners.add(listener);
24541
+ return () => sessionListeners.delete(listener);
24542
+ }
24543
+ function getPiSessionState(sessionKey) {
24544
+ const live = liveSessions.get(sessionKey);
24545
+ return live ? { running: live.running, transcript: live.transcript } : void 0;
24546
+ }
24547
+ function publish(sessionKey, live, entry) {
24548
+ live.transcript.push(entry);
24549
+ if (live.transcript.length > TRANSCRIPT_CAP) live.transcript.splice(0, live.transcript.length - TRANSCRIPT_CAP);
24550
+ for (const listener of sessionListeners) listener(sessionKey, entry);
24551
+ }
24552
+ var liveSessions = /* @__PURE__ */ new Map();
24553
+ function newSessionId() {
24554
+ return `pi_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
24555
+ }
24556
+ var IDLE_TIMEOUT_MS = Number(process.env.PI_AGENT_IDLE_MS ?? 30 * 60 * 1e3);
24557
+ var sweeper = null;
24558
+ function startSweeper() {
24559
+ if (sweeper) return;
24560
+ sweeper = setInterval(() => {
24561
+ const cutoff = Date.now() - IDLE_TIMEOUT_MS;
24562
+ for (const [key, live] of liveSessions) {
24563
+ if (live.running || live.lastActive > cutoff) continue;
24564
+ live.session.dispose();
24565
+ liveSessions.delete(key);
24566
+ }
24567
+ if (liveSessions.size === 0 && sweeper) {
24568
+ clearInterval(sweeper);
24569
+ sweeper = null;
24570
+ }
24571
+ }, 6e4);
24572
+ sweeper.unref?.();
24573
+ }
24574
+ function createPiSession(opts) {
24575
+ let running = false;
24576
+ let sessionKey = opts.resumeId && liveSessions.has(opts.resumeId) ? opts.resumeId : void 0;
24577
+ async function ensureSession() {
24578
+ const existing = sessionKey ? liveSessions.get(sessionKey) : void 0;
24579
+ if (existing) return existing;
24580
+ const { createAgentSession, SessionManager } = await loadPi();
24581
+ const { authStorage, modelRegistry } = await getAuthAndRegistry();
24582
+ const model = await resolveModel(modelRegistry, opts.model);
24583
+ const { session } = await createAgentSession({
24584
+ cwd: opts.cwd,
24585
+ sessionManager: SessionManager.inMemory(),
24586
+ authStorage,
24587
+ modelRegistry,
24588
+ model,
24589
+ tools: opts.tools ?? ["bash"]
24590
+ });
24591
+ sessionKey = newSessionId();
24592
+ const live = { session, running: false, transcript: [], lastActive: Date.now(), eventLog: [], buf: "" };
24593
+ liveSessions.set(sessionKey, live);
24594
+ startSweeper();
24595
+ return live;
24596
+ }
24597
+ async function runTurn(prompt, handlers, signal) {
24598
+ const startedAt = Date.now();
24599
+ const usage = emptyUsage2();
24600
+ let status = "completed";
24601
+ let error;
24602
+ const finish = (live2) => ({
24603
+ status,
24604
+ answer: status === "error" || status === "aborted" ? "" : live2?.session.getLastAssistantText()?.trim() ?? "",
24605
+ ms: Date.now() - startedAt,
24606
+ usage,
24607
+ error,
24608
+ sessionId: sessionKey
24609
+ });
24610
+ if (signal?.aborted) {
24611
+ status = "aborted";
24612
+ return finish();
24613
+ }
24614
+ const live = await ensureSession();
24615
+ const key = sessionKey;
24616
+ live.running = true;
24617
+ live.lastActive = Date.now();
24618
+ publish(key, live, { at: Date.now(), kind: "user", text: prompt });
24619
+ const before = live.session.getSessionStats();
24620
+ const onAbort = () => {
24621
+ void live.session.abort();
24622
+ };
24623
+ signal?.addEventListener("abort", onAbort, { once: true });
24624
+ const emit = (chunk) => {
24625
+ live.buf = (live.buf + chunk).slice(-BUFFER_CAP2);
24626
+ handlers?.onOutput?.(chunk);
24627
+ };
24628
+ const unsubscribe = live.session.subscribe((ev) => {
24629
+ if (ev.type === "message_update" && ev.assistantMessageEvent?.type === "text_delta") {
24630
+ const delta = ev.assistantMessageEvent.delta;
24631
+ if (delta) {
24632
+ emit(delta);
24633
+ handlers?.onNarration?.(delta);
24634
+ }
24635
+ return;
24636
+ }
24637
+ if (ev.type === "tool_execution_start" || ev.type === "tool_execution_end") {
24638
+ if (ev.type === "tool_execution_end") usage.toolCalls++;
24639
+ const event = normEvent2(ev);
24640
+ if (!event) return;
24641
+ live.eventLog.push(event);
24642
+ if (live.eventLog.length > EVENT_CAP2) live.eventLog.shift();
24643
+ handlers?.onEvent?.(event);
24644
+ publish(key, live, { at: Date.now(), kind: "activity", event });
24645
+ if (ev.type === "tool_execution_start") emit(`
24646
+ \u2192 ${ev.toolName ?? "tool"} ${jsonPreview(ev.args)}
24647
+ `);
24648
+ return;
24649
+ }
24650
+ });
24651
+ try {
24652
+ if (signal?.aborted) {
24653
+ status = "aborted";
24654
+ return finish(live);
24655
+ }
24656
+ await live.session.prompt(prompt);
24657
+ const err = live.session.agent.state.errorMessage;
24658
+ if (err) {
24659
+ status = signal?.aborted ? "aborted" : "error";
24660
+ error = err;
24661
+ } else if (signal?.aborted) {
24662
+ status = "aborted";
24663
+ }
24664
+ } catch (err) {
24665
+ status = signal?.aborted ? "aborted" : "error";
24666
+ error = err?.message ?? String(err);
24667
+ } finally {
24668
+ unsubscribe();
24669
+ signal?.removeEventListener("abort", onAbort);
24670
+ live.running = false;
24671
+ live.lastActive = Date.now();
24672
+ }
24673
+ const after = live.session.getSessionStats();
24674
+ usage.inputTokens = Math.max(0, after.tokens.input - before.tokens.input);
24675
+ usage.outputTokens = Math.max(0, after.tokens.output - before.tokens.output);
24676
+ usage.cacheReadTokens = Math.max(0, after.tokens.cacheRead - before.tokens.cacheRead);
24677
+ usage.cacheWriteTokens = Math.max(0, after.tokens.cacheWrite - before.tokens.cacheWrite);
24678
+ usage.costUSD = Math.max(0, after.cost - before.cost);
24679
+ handlers?.onUsage?.({ ...usage });
24680
+ const result = finish(live);
24681
+ publish(key, live, result.status === "error" ? { at: Date.now(), kind: "error", text: result.error } : { at: Date.now(), kind: "assistant", text: result.answer });
24682
+ return result;
24683
+ }
24684
+ return {
24685
+ harness: "pi",
24686
+ kind: "events",
24687
+ async run(prompt, handlers, signal) {
24688
+ if (running) throw new Error("This pi session already has a turn in flight.");
24689
+ running = true;
24690
+ try {
24691
+ return await runTurn(prompt, handlers, signal);
24692
+ } finally {
24693
+ running = false;
24694
+ }
24695
+ },
24696
+ events: () => sessionKey ? liveSessions.get(sessionKey)?.eventLog ?? [] : [],
24697
+ buffer: () => sessionKey ? liveSessions.get(sessionKey)?.buf ?? "" : "",
24698
+ busy: () => running,
24699
+ sessionId: () => sessionKey,
24700
+ // Abandon for real — drop the live session so the next run starts fresh.
24701
+ reset: () => {
24702
+ if (sessionKey) {
24703
+ liveSessions.get(sessionKey)?.session.dispose();
24704
+ liveSessions.delete(sessionKey);
24705
+ }
24706
+ sessionKey = void 0;
24707
+ },
24708
+ // Local-only, same as the codex driver: the live session stays in the
24709
+ // module map so a later run with this sessionId can still resume it.
24710
+ dispose: () => {
24711
+ }
24712
+ };
24713
+ }
24714
+
24337
24715
  // src/codingAgent/factory.ts
24338
24716
  function createCodingAgentSession(harness, opts) {
24339
24717
  switch (harness) {
24340
24718
  case "codex":
24341
24719
  return createCodexSession(opts);
24720
+ case "pi":
24721
+ return createPiSession(opts);
24342
24722
  default:
24343
24723
  throw new Error(`Unknown coding-agent harness: ${harness}`);
24344
24724
  }
@@ -24740,9 +25120,6 @@ Fix ONLY that, and rewrite the same file at the same path. Do not re-run any que
24740
25120
  }
24741
25121
  }
24742
25122
 
24743
- // src/codingAgent/tools/bridge-shims.ts
24744
- import * as fs9 from "fs";
24745
-
24746
25123
  // src/codingAgent/tools/read-only-sql.ts
24747
25124
  var WRITE_KEYWORDS = [
24748
25125
  "insert",
@@ -24797,9 +25174,45 @@ ${stripSqlNoise.toString()}
24797
25174
  ${assertReadOnlySql.toString().replace("export function", "function")}
24798
25175
  `;
24799
25176
 
25177
+ // src/codingAgent/tools/shims.ts
25178
+ import * as fs9 from "fs";
25179
+ import * as path10 from "path";
25180
+ var resolve2 = (target, name) => typeof target === "string" ? path10.join(target, name) : target.pathTo(name);
25181
+ function writeShims(defs, target) {
25182
+ for (const def of defs) {
25183
+ const file = resolve2(target, def.name);
25184
+ fs9.writeFileSync(file, def.body, "utf-8");
25185
+ fs9.chmodSync(file, 493);
25186
+ }
25187
+ return defs.map((def) => ({ name: `./${def.name}`, summary: def.summary }));
25188
+ }
25189
+ function bridgePreamble(bridgeUrl, constants = {}) {
25190
+ const declared = Object.entries(constants).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join("\n");
25191
+ return `const BRIDGE = ${JSON.stringify(bridgeUrl)};
25192
+ ${declared}
25193
+
25194
+ function post(path, payload) {
25195
+ return fetch(BRIDGE + path, {
25196
+ method: 'POST',
25197
+ headers: { 'content-type': 'application/json' },
25198
+ body: JSON.stringify(payload),
25199
+ }).then(async (r) => {
25200
+ const text = await r.text();
25201
+ if (!r.ok) { console.error('Request failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25202
+ try { return JSON.parse(text); } catch { console.error(text); process.exit(1); }
25203
+ }).catch((e) => { console.error('Could not reach the backend: ' + (e && e.message)); process.exit(1); });
25204
+ }
25205
+ `;
25206
+ }
25207
+
24800
25208
  // src/codingAgent/tools/bridge-shims.ts
24801
25209
  function writeBridgeShims(ws, opts) {
24802
25210
  const maxRows = opts.maxRows ?? 200;
25211
+ const directTools = opts.directTools ?? [];
25212
+ const entityTypes = opts.entityTypes ?? [];
25213
+ const conceptCatalog = opts.conceptCatalog ?? [];
25214
+ const hasKnowledgeBase = !!opts.knowledgeBase;
25215
+ const hasSearchableSource = opts.sources.some((s) => s.searchable);
24803
25216
  const query = `#!/usr/bin/env node
24804
25217
  // Read-only SQL against a data source. Generated per run \u2014 do not edit.
24805
25218
  ${READ_ONLY_SQL_SOURCE}
@@ -24809,7 +25222,7 @@ const CONTEXT_TOKEN = ${JSON.stringify(opts.contextToken ?? null)};
24809
25222
 
24810
25223
  const [toolId, sql, limitArg] = process.argv.slice(2);
24811
25224
  if (!toolId || !sql) {
24812
- console.error('usage: ./query "<toolId>" "<sql>" [limit]\\nRun ./sources for the toolIds.');
25225
+ console.error('usage: node ./query "<toolId>" "<sql>" [limit]\\nRun: node ./sources \u2014 for the toolIds.');
24813
25226
  process.exit(2);
24814
25227
  }
24815
25228
  try { assertReadOnlySql(sql); } catch (e) { console.error(String(e.message || e)); process.exit(2); }
@@ -24851,24 +25264,189 @@ fetch(BRIDGE + '/query', {
24851
25264
  })
24852
25265
  .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
24853
25266
  `;
24854
- const sources = `#!/usr/bin/env node
24855
- // The data sources you can query, and the exact toolId for ./query.
24856
- console.log(${JSON.stringify(JSON.stringify(opts.sources, null, 2))});
25267
+ const runTool = `#!/usr/bin/env node
25268
+ // Invoke a pre-built business-logic tool. Generated per run \u2014 do not edit.
25269
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25270
+
25271
+ const [toolId, paramsArg] = process.argv.slice(2);
25272
+ if (!toolId) {
25273
+ console.error('usage: node ./run-tool "<toolId>" [\\'<json params>\\']\\nRun: node ./sources \u2014 for the toolIds and their params.');
25274
+ process.exit(2);
25275
+ }
25276
+ let params = {};
25277
+ if (paramsArg) {
25278
+ try { params = JSON.parse(paramsArg); }
25279
+ catch (e) { console.error('params must be JSON: ' + String(e.message || e)); process.exit(2); }
25280
+ }
25281
+
25282
+ fetch(BRIDGE + '/run-tool', {
25283
+ method: 'POST',
25284
+ headers: { 'content-type': 'application/json' },
25285
+ body: JSON.stringify({ toolId, params }),
25286
+ })
25287
+ .then(async (r) => {
25288
+ const text = await r.text();
25289
+ if (!r.ok) { console.error('Tool call failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25290
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25291
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25292
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25293
+ })
25294
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
24857
25295
  `;
24858
- for (const [name, body] of [["query", query], ["sources", sources]]) {
24859
- const p = ws.pathTo(name);
24860
- fs9.writeFileSync(p, body, "utf-8");
24861
- fs9.chmodSync(p, 493);
25296
+ const schemaSearch = `#!/usr/bin/env node
25297
+ // Keyword search over a large source's schema \u2014 call this BEFORE writing SQL
25298
+ // against a "searchable" source (see ./sources); its schema is too big to
25299
+ // have been shown to you in full, and guessing column names wastes a turn on
25300
+ // a failed query. Generated per run \u2014 do not edit.
25301
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25302
+
25303
+ const [toolId, ...keywords] = process.argv.slice(2);
25304
+ if (!toolId || !keywords.length) {
25305
+ console.error('usage: node ./schema-search "<toolId>" <keyword> [keyword...]\\nRun: node ./sources \u2014 for which toolIds are searchable.');
25306
+ process.exit(2);
25307
+ }
25308
+
25309
+ fetch(BRIDGE + '/schema-search', {
25310
+ method: 'POST',
25311
+ headers: { 'content-type': 'application/json' },
25312
+ body: JSON.stringify({ toolId, keywords }),
25313
+ })
25314
+ .then(async (r) => {
25315
+ const text = await r.text();
25316
+ if (!r.ok) { console.error('Schema search failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25317
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25318
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25319
+ console.log(p.result ?? p);
25320
+ })
25321
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25322
+ `;
25323
+ const resolveEntities = `#!/usr/bin/env node
25324
+ // Resolve named things (customers, projects, people, ...) to their real
25325
+ // database ids, and business concepts to table/column/join facts \u2014 one round
25326
+ // trip, either half optional. Call this BEFORE writing SQL about anything
25327
+ // named in the question; never guess or fuzzy-match a name yourself.
25328
+ // Generated per run \u2014 do not edit.
25329
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25330
+
25331
+ const [argJson] = process.argv.slice(2);
25332
+ if (!argJson) {
25333
+ console.error('usage: node ./resolve-entities \\'{"mentions":[{"text":"..."}],"entityTypes":["..."]}\\'\\nRun: node ./sources \u2014 for which entity types exist. Either field may be omitted.');
25334
+ process.exit(2);
25335
+ }
25336
+ let args;
25337
+ try { args = JSON.parse(argJson); }
25338
+ catch (e) { console.error('argument must be JSON: ' + String(e.message || e)); process.exit(2); }
25339
+
25340
+ fetch(BRIDGE + '/resolve-entities', {
25341
+ method: 'POST',
25342
+ headers: { 'content-type': 'application/json' },
25343
+ body: JSON.stringify({ mentions: args.mentions ?? [], entityTypes: args.entityTypes ?? [] }),
25344
+ })
25345
+ .then(async (r) => {
25346
+ const text = await r.text();
25347
+ if (!r.ok) { console.error('Entity resolution failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25348
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25349
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25350
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25351
+ })
25352
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25353
+ `;
25354
+ const conceptCatalogShim = `#!/usr/bin/env node
25355
+ // Every verified concept \u2014 name, aliases, and what it is for. No bodies: the
25356
+ // whole vocabulary in one call, so you know what exists and which ones bear on
25357
+ // the question, instead of guessing a name into ./run-tool "concept". Read it,
25358
+ // then fetch the ones that apply. Call this once, early. There is no search
25359
+ // step and nothing to search: this is the complete list.
25360
+ // Generated per run \u2014 do not edit.
25361
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25362
+
25363
+ fetch(BRIDGE + '/concept-catalog', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
25364
+ .then(async (r) => {
25365
+ const text = await r.text();
25366
+ if (!r.ok) { console.error('Concept catalog fetch failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25367
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25368
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25369
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25370
+ })
25371
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25372
+ `;
25373
+ const knowledgeBaseShim = `#!/usr/bin/env node
25374
+ // Free-form domain knowledge (gotchas, join paths, table/column meanings) \u2014
25375
+ // lower priority than the concept catalog: check ./concept-catalog / the
25376
+ // concept tool first for anything with a formal business-rule meaning, use
25377
+ // this directly for everything else.
25378
+ //
25379
+ // No args lists every node; get fetches the bodies. There is no search step
25380
+ // and nothing to search \u2014 the corpus is tens of nodes with one short title
25381
+ // each, same as ./concept-catalog. Generated per run \u2014 do not edit.
25382
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25383
+
25384
+ const [argJson] = process.argv.slice(2);
25385
+ let args;
25386
+ if (!argJson) {
25387
+ args = { method: 'catalog' };
25388
+ } else {
25389
+ try { args = JSON.parse(argJson); }
25390
+ catch (e) {
25391
+ console.error('argument must be JSON: ' + String(e.message || e));
25392
+ console.error('usage: node ./knowledge-base \u2192 every node: id, title, category, tags');
25393
+ console.error(' node ./knowledge-base \\'{"method":"get","ids":[12,31]}\\' \u2192 their content');
25394
+ process.exit(2);
24862
25395
  }
24863
25396
  }
24864
25397
 
25398
+ fetch(BRIDGE + '/knowledge-base', {
25399
+ method: 'POST',
25400
+ headers: { 'content-type': 'application/json' },
25401
+ body: JSON.stringify(args),
25402
+ })
25403
+ .then(async (r) => {
25404
+ const text = await r.text();
25405
+ if (!r.ok) { console.error('Knowledge base lookup failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25406
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25407
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25408
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25409
+ })
25410
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25411
+ `;
25412
+ const sources = `#!/usr/bin/env node
25413
+ // The data sources you can query with ./query (and search with
25414
+ // ./schema-search when "searchable" is true), the pre-built tools you can
25415
+ // invoke with ./run-tool, and the entity types ./resolve-entities can
25416
+ // resolve names against. The concept catalog itself is NOT repeated here \u2014
25417
+ // run ./concept-catalog for that.
25418
+ console.log(${JSON.stringify(
25419
+ JSON.stringify({
25420
+ sources: opts.sources,
25421
+ directTools,
25422
+ entityTypes,
25423
+ ...conceptCatalog.length ? { conceptCount: conceptCatalog.length } : {},
25424
+ ...hasKnowledgeBase ? { knowledgeBase: true } : {}
25425
+ }, null, 2)
25426
+ )});
25427
+ `;
25428
+ const files = [
25429
+ ["sources", sources, "(no arguments) \u2014 what you can query, which of those are searchable, the pre-built tools, and the entity types."],
25430
+ ["query", query, '"<toolId>" "<sql>" [limit] \u2014 read-only SQL against one source. The only way to see real data. ./sources for the toolIds.']
25431
+ ];
25432
+ if (opts.conceptCatalog) files.push(["concept-catalog", conceptCatalogShim, '(no arguments) \u2014 every verified concept: name, aliases, what it is for. No bodies. Read this before the schema; fetch bodies with ./run-tool "concept".']);
25433
+ if (hasKnowledgeBase) files.push(["knowledge-base", knowledgeBaseShim, `[ '{"method":"get","ids":[12,31]}' ] \u2014 free-form domain knowledge: gotchas, join paths, what a column really holds. No arguments lists every node; get fetches bodies. Ranks below concepts, above your own reading of the schema.`]);
25434
+ if (entityTypes.length) files.push(["resolve-entities", resolveEntities, `'{"mentions":[{"text":"<name>"}]}' \u2014 turn a name a person typed into the value stored in the data. Run it before filtering on any proper noun.`]);
25435
+ if (hasSearchableSource) files.push(["schema-search", schemaSearch, `"<toolId>" <keyword> [keyword...] \u2014 keyword search over a large source's schema. Required before writing SQL against a "searchable" source \u2014 its schema was never shown to you in full.`]);
25436
+ if (directTools.length) files.push(["run-tool", runTool, `"<toolId>" '<json params>' \u2014 invoke a pre-built business-logic tool, including "concept" get/run. ./sources lists each one's parameters.`]);
25437
+ return writeShims(
25438
+ files.map(([name, body, summary]) => ({ name, body, summary })),
25439
+ ws
25440
+ );
25441
+ }
25442
+
24865
25443
  // src/codingAgent/triggers.ts
24866
25444
  import * as fs10 from "fs";
24867
- import * as path10 from "path";
25445
+ import * as path11 from "path";
24868
25446
  var EVENT_BURST_CAP = 200;
24869
25447
  var capEvents = (events) => events.length > EVENT_BURST_CAP ? events.slice(-EVENT_BURST_CAP) : events;
24870
25448
  function statePath2(agentId) {
24871
- return path10.join(getCodingAgentsConfig().runtimeDir, agentId, "_triggers.json");
25449
+ return path11.join(getCodingAgentsConfig().runtimeDir, agentId, "_triggers.json");
24872
25450
  }
24873
25451
  function loadTriggerState(agentId) {
24874
25452
  let raw;
@@ -24893,7 +25471,7 @@ function loadTriggerState(agentId) {
24893
25471
  function saveTriggerState(agentId, state) {
24894
25472
  try {
24895
25473
  const p = statePath2(agentId);
24896
- fs10.mkdirSync(path10.dirname(p), { recursive: true });
25474
+ fs10.mkdirSync(path11.dirname(p), { recursive: true });
24897
25475
  fs10.writeFileSync(p, JSON.stringify(state, null, 2));
24898
25476
  } catch (err) {
24899
25477
  logger.warn(`[CodingAgent:${agentId}] failed to persist trigger state: ${err?.message ?? err}`);
@@ -25031,12 +25609,12 @@ function describeTriggers() {
25031
25609
 
25032
25610
  // src/codingAgent/collection.ts
25033
25611
  import * as fs11 from "fs";
25034
- import * as path11 from "path";
25612
+ import * as path12 from "path";
25035
25613
  function isChattable(agent) {
25036
25614
  return agent.mode !== "task";
25037
25615
  }
25038
25616
  function registerCodingAgentCollection(sdk, config2 = {}) {
25039
- const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR || path11.join(process.cwd(), ".coding-agent-runtime");
25617
+ const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR || path12.join(process.cwd(), ".coding-agent-runtime");
25040
25618
  const defaultHarness = config2.harness || process.env.CODING_AGENT_HARNESS || "codex";
25041
25619
  const sessions = /* @__PURE__ */ new Map();
25042
25620
  const running = /* @__PURE__ */ new Set();
@@ -25096,7 +25674,7 @@ function registerCodingAgentCollection(sdk, config2 = {}) {
25096
25674
  ...record.status === "skipped" ? { error: `${def.title} is in the middle of a run \u2014 try again in a moment.` } : record.error ? { error: record.error } : {}
25097
25675
  };
25098
25676
  }
25099
- const cwd = path11.join(runtimeDir, "threads", threadId);
25677
+ const cwd = path12.join(runtimeDir, "threads", threadId);
25100
25678
  fs11.mkdirSync(cwd, { recursive: true });
25101
25679
  let session = sessions.get(threadId);
25102
25680
  if (!session) {
@@ -25262,6 +25840,134 @@ async function handleCodingAgentRequest(data, collections, sendMessage) {
25262
25840
  }
25263
25841
  }
25264
25842
 
25843
+ // src/dashBuilder/handler.ts
25844
+ var inFlightAborts4 = /* @__PURE__ */ new Map();
25845
+ var recentlyAborted4 = /* @__PURE__ */ new Map();
25846
+ var ABORT_TOMBSTONE_TTL_MS4 = 3e4;
25847
+ function setAbortTombstone4(dashboardId) {
25848
+ const now = Date.now();
25849
+ recentlyAborted4.set(dashboardId, now);
25850
+ for (const [key, ts] of recentlyAborted4) {
25851
+ if (now - ts > ABORT_TOMBSTONE_TTL_MS4) recentlyAborted4.delete(key);
25852
+ }
25853
+ }
25854
+ function consumeAbortTombstone4(dashboardId) {
25855
+ const ts = recentlyAborted4.get(dashboardId);
25856
+ if (ts === void 0) return false;
25857
+ recentlyAborted4.delete(dashboardId);
25858
+ return Date.now() - ts <= ABORT_TOMBSTONE_TTL_MS4;
25859
+ }
25860
+ function handleDashBuilderAbort(data, sendMessage) {
25861
+ const parsed = DashBuilderAbortRequestMessageSchema.safeParse(data);
25862
+ if (!parsed.success) {
25863
+ logger.warn("[DashBuilderAbort] Invalid message \u2014 ignoring");
25864
+ return;
25865
+ }
25866
+ const { payload, from } = parsed.data;
25867
+ const { dashboardId } = payload;
25868
+ const controller = inFlightAborts4.get(dashboardId);
25869
+ if (controller) {
25870
+ controller.abort();
25871
+ inFlightAborts4.delete(dashboardId);
25872
+ logger.info(`[DashBuilderAbort] Aborted in-flight turn for ${dashboardId}`);
25873
+ } else {
25874
+ setAbortTombstone4(dashboardId);
25875
+ logger.info(`[DashBuilderAbort] No in-flight turn for ${dashboardId} \u2014 tombstoned`);
25876
+ }
25877
+ try {
25878
+ sendMessage({
25879
+ id: `dash_builder_aborted_${dashboardId}`,
25880
+ type: "DASH_BUILDER_ABORTED",
25881
+ from: { type: "data-agent" },
25882
+ to: { type: "runtime", id: from.id },
25883
+ payload: { dashboardId }
25884
+ });
25885
+ } catch (err) {
25886
+ logger.warn(
25887
+ `[DashBuilderAbort] Failed to send abort ack: ${err instanceof Error ? err.message : String(err)}`
25888
+ );
25889
+ }
25890
+ }
25891
+ async function handleDashBuilderRequest(data, collections, sendMessage) {
25892
+ return runWithMessageIdentity(data, () => handleDashBuilderRequestInner(data, collections, sendMessage));
25893
+ }
25894
+ async function handleDashBuilderRequestInner(data, collections, sendMessage) {
25895
+ const parsed = DashBuilderRequestMessageSchema.safeParse(data);
25896
+ if (!parsed.success) {
25897
+ logger.error("[DashBuilderReq] Invalid message:", JSON.stringify(parsed.error.errors));
25898
+ return;
25899
+ }
25900
+ const { id, payload, from } = parsed.data;
25901
+ const { dashboardId, text, panelId, projectId, userId } = payload;
25902
+ const wsId = from.id;
25903
+ const respond = (res) => {
25904
+ sendMessage({
25905
+ id,
25906
+ type: "DASH_BUILDER_RES",
25907
+ from: { type: "data-agent" },
25908
+ to: { type: "runtime", id: wsId },
25909
+ payload: res
25910
+ });
25911
+ };
25912
+ if (!collections?.["dashboard-builder-agent"]?.["prompt"]) {
25913
+ respond({
25914
+ started: false,
25915
+ error: "The dashboard builder is not available in this environment (collection 'dashboard-builder-agent' not registered)."
25916
+ });
25917
+ return;
25918
+ }
25919
+ const abortController = new AbortController();
25920
+ if (consumeAbortTombstone4(dashboardId)) {
25921
+ abortController.abort();
25922
+ logger.info(`[DashBuilderReq] ${dashboardId} was pre-aborted \u2014 starting cancelled`);
25923
+ }
25924
+ inFlightAborts4.set(dashboardId, abortController);
25925
+ let seq = 0;
25926
+ const stream = (update) => {
25927
+ try {
25928
+ sendMessage({
25929
+ /**
25930
+ * Keyed by this request's id, not by dashboardId.
25931
+ *
25932
+ * dashboardId repeats across every turn in a conversation, so a
25933
+ * listener that failed to clean up would keep matching future turns
25934
+ * forever and interleave their output. A per-request id means a
25935
+ * stale listener simply never matches anything again.
25936
+ */
25937
+ id: `dash_builder_stream_${id}`,
25938
+ type: "DASH_BUILDER_STREAM",
25939
+ from: { type: "data-agent" },
25940
+ to: { type: "runtime", id: wsId },
25941
+ payload: { dashboardId, seq: seq++, update }
25942
+ });
25943
+ } catch (err) {
25944
+ logger.warn(
25945
+ `[DashBuilderReq] Stream send failed (socket may be closed): ${err instanceof Error ? err.message : String(err)}`
25946
+ );
25947
+ }
25948
+ };
25949
+ try {
25950
+ const result = await collections["dashboard-builder-agent"]["prompt"]({
25951
+ dashboardId,
25952
+ text,
25953
+ panelId,
25954
+ projectId,
25955
+ userId,
25956
+ signal: abortController.signal,
25957
+ onUpdate: stream
25958
+ });
25959
+ respond(result ?? { started: true });
25960
+ } catch (err) {
25961
+ const message = err instanceof Error ? err.message : String(err);
25962
+ logger.error(`[DashBuilderReq] ${dashboardId} failed to start: ${message}`);
25963
+ respond({ started: false, error: message });
25964
+ } finally {
25965
+ if (inFlightAborts4.get(dashboardId) === abortController) {
25966
+ inFlightAborts4.delete(dashboardId);
25967
+ }
25968
+ }
25969
+ }
25970
+
25265
25971
  // src/reportComp/types.ts
25266
25972
  var DEFAULT_REPORT_COMP_MODELS = {
25267
25973
  anthropic: "anthropic/claude-haiku-4-5-20251001",
@@ -25760,21 +26466,21 @@ Fixed SQL query:`;
25760
26466
  }
25761
26467
 
25762
26468
  // src/reportComp/index.ts
25763
- var inFlightAborts4 = /* @__PURE__ */ new Map();
25764
- var recentlyAborted4 = /* @__PURE__ */ new Map();
25765
- var ABORT_TOMBSTONE_TTL_MS4 = 3e4;
25766
- function setAbortTombstone4(reportId) {
26469
+ var inFlightAborts5 = /* @__PURE__ */ new Map();
26470
+ var recentlyAborted5 = /* @__PURE__ */ new Map();
26471
+ var ABORT_TOMBSTONE_TTL_MS5 = 3e4;
26472
+ function setAbortTombstone5(reportId) {
25767
26473
  const now = Date.now();
25768
- recentlyAborted4.set(reportId, now);
25769
- for (const [key, ts] of recentlyAborted4) {
25770
- if (now - ts > ABORT_TOMBSTONE_TTL_MS4) recentlyAborted4.delete(key);
26474
+ recentlyAborted5.set(reportId, now);
26475
+ for (const [key, ts] of recentlyAborted5) {
26476
+ if (now - ts > ABORT_TOMBSTONE_TTL_MS5) recentlyAborted5.delete(key);
25771
26477
  }
25772
26478
  }
25773
- function consumeAbortTombstone4(reportId) {
25774
- const ts = recentlyAborted4.get(reportId);
26479
+ function consumeAbortTombstone5(reportId) {
26480
+ const ts = recentlyAborted5.get(reportId);
25775
26481
  if (ts === void 0) return false;
25776
- recentlyAborted4.delete(reportId);
25777
- return Date.now() - ts <= ABORT_TOMBSTONE_TTL_MS4;
26482
+ recentlyAborted5.delete(reportId);
26483
+ return Date.now() - ts <= ABORT_TOMBSTONE_TTL_MS5;
25778
26484
  }
25779
26485
  function handleReportCompAbort(data, sendMessage) {
25780
26486
  const parseResult = ReportCompAbortRequestMessageSchema.safeParse(data);
@@ -25785,13 +26491,13 @@ function handleReportCompAbort(data, sendMessage) {
25785
26491
  const { payload, from } = parseResult.data;
25786
26492
  const { reportId } = payload;
25787
26493
  const wsId = from.id;
25788
- const controller = inFlightAborts4.get(reportId);
26494
+ const controller = inFlightAborts5.get(reportId);
25789
26495
  if (controller) {
25790
26496
  controller.abort();
25791
- inFlightAborts4.delete(reportId);
26497
+ inFlightAborts5.delete(reportId);
25792
26498
  logger.info(`[ReportCompAbort] Aborted in-flight run for report ${reportId}`);
25793
26499
  } else {
25794
- setAbortTombstone4(reportId);
26500
+ setAbortTombstone5(reportId);
25795
26501
  logger.info(`[ReportCompAbort] No in-flight run for report ${reportId} \u2014 tombstoned`);
25796
26502
  }
25797
26503
  try {
@@ -25837,11 +26543,11 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
25837
26543
  const trace = createRunTrace();
25838
26544
  const abortController = new AbortController();
25839
26545
  if (reportId) {
25840
- if (consumeAbortTombstone4(reportId)) {
26546
+ if (consumeAbortTombstone5(reportId)) {
25841
26547
  abortController.abort();
25842
26548
  logger.info(`[REPORT_COMP_REQ] reportId=${reportId} was pre-aborted \u2014 starting cancelled`);
25843
26549
  }
25844
- inFlightAborts4.set(reportId, abortController);
26550
+ inFlightAborts5.set(reportId, abortController);
25845
26551
  }
25846
26552
  let llmResponse;
25847
26553
  try {
@@ -25862,7 +26568,7 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
25862
26568
  trace
25863
26569
  );
25864
26570
  } finally {
25865
- if (reportId) inFlightAborts4.delete(reportId);
26571
+ if (reportId) inFlightAborts5.delete(reportId);
25866
26572
  }
25867
26573
  const wasAborted = abortController.signal.aborted || !llmResponse.success && Array.isArray(llmResponse.errors) && llmResponse.errors.includes("aborted");
25868
26574
  if (wasAborted) {
@@ -26249,7 +26955,7 @@ function getLLMProviders() {
26249
26955
 
26250
26956
  // src/auth/user-manager.ts
26251
26957
  import fs12 from "fs";
26252
- import path12 from "path";
26958
+ import path13 from "path";
26253
26959
  import os2 from "os";
26254
26960
  var UserManager = class {
26255
26961
  /**
@@ -26262,7 +26968,7 @@ var UserManager = class {
26262
26968
  this.hasChanged = false;
26263
26969
  this.syncInterval = null;
26264
26970
  this.isInitialized = false;
26265
- this.filePath = path12.join(os2.homedir(), ".superatom", "projects", projectId, "users.json");
26971
+ this.filePath = path13.join(os2.homedir(), ".superatom", "projects", projectId, "users.json");
26266
26972
  this.syncIntervalMs = syncIntervalMs;
26267
26973
  }
26268
26974
  /**
@@ -26287,7 +26993,7 @@ var UserManager = class {
26287
26993
  */
26288
26994
  async loadUsersFromFile() {
26289
26995
  try {
26290
- const dir = path12.dirname(this.filePath);
26996
+ const dir = path13.dirname(this.filePath);
26291
26997
  if (!fs12.existsSync(dir)) {
26292
26998
  logger.info(`Creating directory structure: ${dir}`);
26293
26999
  fs12.mkdirSync(dir, { recursive: true });
@@ -26319,7 +27025,7 @@ var UserManager = class {
26319
27025
  return;
26320
27026
  }
26321
27027
  try {
26322
- const dir = path12.dirname(this.filePath);
27028
+ const dir = path13.dirname(this.filePath);
26323
27029
  if (!fs12.existsSync(dir)) {
26324
27030
  fs12.mkdirSync(dir, { recursive: true });
26325
27031
  }
@@ -26547,7 +27253,7 @@ var UserManager = class {
26547
27253
 
26548
27254
  // src/dashboards/dashboard-manager.ts
26549
27255
  import fs13 from "fs";
26550
- import path13 from "path";
27256
+ import path14 from "path";
26551
27257
  import os3 from "os";
26552
27258
  var DashboardManager = class {
26553
27259
  /**
@@ -26556,7 +27262,7 @@ var DashboardManager = class {
26556
27262
  */
26557
27263
  constructor(projectId = "snowflake-dataset") {
26558
27264
  this.projectId = projectId;
26559
- this.dashboardsBasePath = path13.join(
27265
+ this.dashboardsBasePath = path14.join(
26560
27266
  os3.homedir(),
26561
27267
  ".superatom",
26562
27268
  "projects",
@@ -26570,7 +27276,7 @@ var DashboardManager = class {
26570
27276
  * @returns Full path to dashboard data.json file
26571
27277
  */
26572
27278
  getDashboardPath(dashboardId) {
26573
- return path13.join(this.dashboardsBasePath, dashboardId, "data.json");
27279
+ return path14.join(this.dashboardsBasePath, dashboardId, "data.json");
26574
27280
  }
26575
27281
  /**
26576
27282
  * Create a new dashboard
@@ -26580,7 +27286,7 @@ var DashboardManager = class {
26580
27286
  */
26581
27287
  createDashboard(dashboardId, dashboard) {
26582
27288
  const dashboardPath = this.getDashboardPath(dashboardId);
26583
- const dashboardDir = path13.dirname(dashboardPath);
27289
+ const dashboardDir = path14.dirname(dashboardPath);
26584
27290
  if (fs13.existsSync(dashboardPath)) {
26585
27291
  throw new Error(`Dashboard '${dashboardId}' already exists`);
26586
27292
  }
@@ -26668,7 +27374,7 @@ var DashboardManager = class {
26668
27374
  */
26669
27375
  deleteDashboard(dashboardId) {
26670
27376
  const dashboardPath = this.getDashboardPath(dashboardId);
26671
- const dashboardDir = path13.dirname(dashboardPath);
27377
+ const dashboardDir = path14.dirname(dashboardPath);
26672
27378
  if (!fs13.existsSync(dashboardPath)) {
26673
27379
  logger.warn(`Dashboard not found for deletion: ${dashboardId}`);
26674
27380
  return false;
@@ -26714,7 +27420,7 @@ var DashboardManager = class {
26714
27420
 
26715
27421
  // src/reports/report-manager.ts
26716
27422
  import fs14 from "fs";
26717
- import path14 from "path";
27423
+ import path15 from "path";
26718
27424
  import os4 from "os";
26719
27425
  var ReportManager = class {
26720
27426
  /**
@@ -26723,7 +27429,7 @@ var ReportManager = class {
26723
27429
  */
26724
27430
  constructor(projectId = "snowflake-dataset") {
26725
27431
  this.projectId = projectId;
26726
- this.reportsBasePath = path14.join(
27432
+ this.reportsBasePath = path15.join(
26727
27433
  os4.homedir(),
26728
27434
  ".superatom",
26729
27435
  "projects",
@@ -26737,7 +27443,7 @@ var ReportManager = class {
26737
27443
  * @returns Full path to report data.json file
26738
27444
  */
26739
27445
  getReportPath(reportId) {
26740
- return path14.join(this.reportsBasePath, reportId, "data.json");
27446
+ return path15.join(this.reportsBasePath, reportId, "data.json");
26741
27447
  }
26742
27448
  /**
26743
27449
  * Create a new report
@@ -26747,7 +27453,7 @@ var ReportManager = class {
26747
27453
  */
26748
27454
  createReport(reportId, report) {
26749
27455
  const reportPath = this.getReportPath(reportId);
26750
- const reportDir = path14.dirname(reportPath);
27456
+ const reportDir = path15.dirname(reportPath);
26751
27457
  if (fs14.existsSync(reportPath)) {
26752
27458
  throw new Error(`Report '${reportId}' already exists`);
26753
27459
  }
@@ -26835,7 +27541,7 @@ var ReportManager = class {
26835
27541
  */
26836
27542
  deleteReport(reportId) {
26837
27543
  const reportPath = this.getReportPath(reportId);
26838
- const reportDir = path14.dirname(reportPath);
27544
+ const reportDir = path15.dirname(reportPath);
26839
27545
  if (!fs14.existsSync(reportPath)) {
26840
27546
  logger.warn(`Report not found for deletion: ${reportId}`);
26841
27547
  return false;
@@ -27364,7 +28070,7 @@ var CleanupService = class _CleanupService {
27364
28070
  };
27365
28071
 
27366
28072
  // src/dashboardAgent/collection.ts
27367
- import path15 from "path";
28073
+ import path16 from "path";
27368
28074
  import fs15 from "fs";
27369
28075
 
27370
28076
  // src/dashboardAgent/tools.ts
@@ -27571,16 +28277,16 @@ function buildDashboardAgentTools(sdk, signal, trace) {
27571
28277
  }
27572
28278
 
27573
28279
  // src/dashboardAgent/collection.ts
27574
- var dynamicImport2 = new Function("specifier", "return import(specifier)");
27575
- var piModulePromise = null;
27576
- function loadPi() {
27577
- if (!piModulePromise) piModulePromise = dynamicImport2("@earendil-works/pi-coding-agent");
27578
- return piModulePromise;
27579
- }
27580
- var authAndRegistry = null;
27581
- function getAuthAndRegistry() {
27582
- if (!authAndRegistry) {
27583
- authAndRegistry = loadPi().then(({ AuthStorage, ModelRegistry }) => {
28280
+ var dynamicImport3 = new Function("specifier", "return import(specifier)");
28281
+ var piModulePromise2 = null;
28282
+ function loadPi2() {
28283
+ if (!piModulePromise2) piModulePromise2 = dynamicImport3("@earendil-works/pi-coding-agent");
28284
+ return piModulePromise2;
28285
+ }
28286
+ var authAndRegistry2 = null;
28287
+ function getAuthAndRegistry2() {
28288
+ if (!authAndRegistry2) {
28289
+ authAndRegistry2 = loadPi2().then(({ AuthStorage, ModelRegistry }) => {
27584
28290
  const authStorage = AuthStorage.create();
27585
28291
  const modelRegistry = ModelRegistry.create(authStorage);
27586
28292
  const proxyRoot = process.env.SUPERATOM_LLM_PROXY_URL;
@@ -27597,7 +28303,7 @@ function getAuthAndRegistry() {
27597
28303
  return { authStorage, modelRegistry };
27598
28304
  });
27599
28305
  }
27600
- return authAndRegistry;
28306
+ return authAndRegistry2;
27601
28307
  }
27602
28308
  async function preResolveSchema(sdk, tool, intent, projectId) {
27603
28309
  const tier = tool.schemaTier;
@@ -27693,12 +28399,12 @@ function textDelta(event) {
27693
28399
  }
27694
28400
  var runningDashboards = /* @__PURE__ */ new Set();
27695
28401
  function registerDashboardAgentCollection(sdk, config2 = {}) {
27696
- const piCwd = config2.cwd || process.env.PI_AGENT_DIR || path15.join(process.cwd(), ".prompts", "dashboard-agent");
27697
- const runtimeDir = config2.runtimeDir || process.env.PI_AGENT_RUNTIME_DIR || path15.join(process.cwd(), ".pi-dashboard-agent-runtime");
28402
+ const piCwd = config2.cwd || process.env.PI_AGENT_DIR || path16.join(process.cwd(), ".prompts", "dashboard-agent");
28403
+ const runtimeDir = config2.runtimeDir || process.env.PI_AGENT_RUNTIME_DIR || path16.join(process.cwd(), ".pi-dashboard-agent-runtime");
27698
28404
  const freshSession = config2.freshSession ?? process.env.PI_AGENT_FRESH_SESSION !== "false";
27699
- const SESSIONS_FILE = path15.join(runtimeDir, ".pi-dashboard-sessions.json");
27700
- const DRAFTS_DIR = path15.join(runtimeDir, "drafts");
27701
- const DASHBOARD_LOG_FILE = path15.join(runtimeDir, "dashboard.log");
28405
+ const SESSIONS_FILE = path16.join(runtimeDir, ".pi-dashboard-sessions.json");
28406
+ const DRAFTS_DIR = path16.join(runtimeDir, "drafts");
28407
+ const DASHBOARD_LOG_FILE = path16.join(runtimeDir, "dashboard.log");
27702
28408
  function appendLog(text) {
27703
28409
  try {
27704
28410
  fs15.mkdirSync(runtimeDir, { recursive: true });
@@ -27722,7 +28428,7 @@ ${fullPrompt}
27722
28428
  }
27723
28429
  function readDraft(dashboardId) {
27724
28430
  try {
27725
- const raw = fs15.readFileSync(path15.join(DRAFTS_DIR, `${dashboardId}.json`), "utf-8");
28431
+ const raw = fs15.readFileSync(path16.join(DRAFTS_DIR, `${dashboardId}.json`), "utf-8");
27726
28432
  return JSON.parse(raw);
27727
28433
  } catch (err) {
27728
28434
  console.warn(`[DashboardAgent] Failed to read draft for ${dashboardId}: ${err?.message ?? err}`);
@@ -27810,8 +28516,8 @@ ${fullPrompt}
27810
28516
  return { success: false, error: "aborted" };
27811
28517
  }
27812
28518
  try {
27813
- const { createAgentSession, SessionManager } = await loadPi();
27814
- const { authStorage, modelRegistry } = await getAuthAndRegistry();
28519
+ const { createAgentSession, SessionManager } = await loadPi2();
28520
+ const { authStorage, modelRegistry } = await getAuthAndRegistry2();
27815
28521
  let model;
27816
28522
  const explicitProvider = config2.provider || process.env.PI_AGENT_PROVIDER;
27817
28523
  const explicitModel = config2.model || process.env.PI_AGENT_MODEL;
@@ -27917,7 +28623,7 @@ ${turn.responseText ?? ""}`).join("\n\n");
27917
28623
  console.warn(`[DashboardAgent] Failed to fetch previous responses for ${dashboardId}: ${err?.message ?? err}`);
27918
28624
  }
27919
28625
  }
27920
- const draftPath = path15.join(DRAFTS_DIR, `${dashboardId}.json`);
28626
+ const draftPath = path16.join(DRAFTS_DIR, `${dashboardId}.json`);
27921
28627
  const fullPrompt = [
27922
28628
  `Dashboard ID: ${dashboardId}.`,
27923
28629
  projectId ? `Project ID: ${projectId}.` : null,
@@ -28209,6 +28915,15 @@ var SuperatomSDK = class {
28209
28915
  // since an outage of unknown length shouldn't grow this without limit.
28210
28916
  this.pendingOutbox = [];
28211
28917
  this.MAX_OUTBOX_SIZE = 50;
28918
+ /**
28919
+ * The dashboard contract, as registered by the browser bundle that holds it.
28920
+ *
28921
+ * Empty until a client has connected and pushed it. That is a real state and
28922
+ * callers must handle it rather than treat it as an empty contract: an empty
28923
+ * list means "nobody has told us yet", not "there are no components", and
28924
+ * building against it would produce a dashboard that can import nothing.
28925
+ */
28926
+ this.dashContract = [];
28212
28927
  if (config2.logLevel) {
28213
28928
  logger.setLogLevel(config2.logLevel);
28214
28929
  }
@@ -28325,7 +29040,7 @@ var SuperatomSDK = class {
28325
29040
  logger.info("Already connected to WebSocket");
28326
29041
  return Promise.resolve();
28327
29042
  }
28328
- return new Promise((resolve2, reject) => {
29043
+ return new Promise((resolve3, reject) => {
28329
29044
  try {
28330
29045
  const url = new URL(this.url);
28331
29046
  if (this.apiKey) {
@@ -28341,7 +29056,7 @@ var SuperatomSDK = class {
28341
29056
  logger.info("WebSocket connected successfully");
28342
29057
  this.startHeartbeat();
28343
29058
  this.flushPendingOutbox();
28344
- resolve2();
29059
+ resolve3();
28345
29060
  });
28346
29061
  this.ws.addEventListener("message", (event) => {
28347
29062
  this.handleMessage(event.data);
@@ -28429,6 +29144,13 @@ var SuperatomSDK = class {
28429
29144
  logger.error("Failed to handle component list request:", error);
28430
29145
  });
28431
29146
  break;
29147
+ /* The dashboard contract, pushed by the bundle that holds it — on
29148
+ * connect and again on every reconnect. See types.ts. */
29149
+ case "DASH_CONTRACT_RES":
29150
+ handleDashContractResponse(parsed, (c) => this.storeDashContract(c), this.collections).catch((error) => {
29151
+ logger.error("Failed to handle dashboard contract:", error);
29152
+ });
29153
+ break;
28432
29154
  case "WORKFLOW_LIST_RES":
28433
29155
  handleWorkflowListResponse(parsed, (wf) => this.setWorkflows(wf)).catch((error) => {
28434
29156
  logger.error("Failed to handle workflow list request:", error);
@@ -28495,6 +29217,16 @@ var SuperatomSDK = class {
28495
29217
  case "CODING_AGENT_ABORT_REQ":
28496
29218
  handleCodingAgentAbort(parsed, (msg) => this.send(msg));
28497
29219
  break;
29220
+ // The dashboard BUILDER's conversation — distinct from
29221
+ // DASHBOARD_AGENT_REQ, which drives the older JSON-DSL flow.
29222
+ case "DASH_BUILDER_REQ":
29223
+ handleDashBuilderRequest(parsed, this.collections, (msg) => this.send(msg)).catch((error) => {
29224
+ logger.error("Failed to handle dash builder request:", error);
29225
+ });
29226
+ break;
29227
+ case "DASH_BUILDER_ABORT_REQ":
29228
+ handleDashBuilderAbort(parsed, (msg) => this.send(msg));
29229
+ break;
28498
29230
  case "REPORT_COMP_REQ":
28499
29231
  handleReportCompRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.geminiApiKey, this.openaiApiKey, this.llmProviders, this.collections, this.tools, this.dashCompModels).catch((error) => {
28500
29232
  logger.error("Failed to handle report comp request:", error);
@@ -28717,6 +29449,14 @@ var SuperatomSDK = class {
28717
29449
  getComponents() {
28718
29450
  return this.components;
28719
29451
  }
29452
+ storeDashContract(components) {
29453
+ this.dashContract = components;
29454
+ logger.info(`Dashboard contract registered: ${components.length} components`);
29455
+ }
29456
+ /** The registered dashboard contract. Empty means "not registered yet". */
29457
+ getDashContract() {
29458
+ return this.dashContract;
29459
+ }
28720
29460
  /**
28721
29461
  * Set tools for the SDK instance
28722
29462
  */
@@ -28838,11 +29578,13 @@ export {
28838
29578
  UIBlock,
28839
29579
  UILogCollector,
28840
29580
  UserManager,
29581
+ analyticsClient,
28841
29582
  answerCatalogForBinding,
28842
29583
  answerComponents,
28843
29584
  anthropicLLM,
28844
29585
  assertReadOnlySql,
28845
29586
  bindContext,
29587
+ bridgePreamble,
28846
29588
  captureContext,
28847
29589
  clearContextTokens,
28848
29590
  contextTokenCount,
@@ -28858,6 +29600,7 @@ export {
28858
29600
  geminiLLM,
28859
29601
  getCodingAgent,
28860
29602
  getCodingAgentsConfig,
29603
+ getPiSessionState,
28861
29604
  getRequestContext,
28862
29605
  groqLLM,
28863
29606
  hybridRerank,
@@ -28870,9 +29613,11 @@ export {
28870
29613
  mintContextToken,
28871
29614
  normalizeScriptBody,
28872
29615
  onAgentRun,
29616
+ onPiSessionUpdate,
28873
29617
  openaiLLM,
28874
29618
  policiesFromConfig,
28875
29619
  projectForDatasetBinding,
29620
+ promptLoader,
28876
29621
  queryCache,
28877
29622
  registerCodingAgentCollection,
28878
29623
  registerCodingAgents,
@@ -28894,6 +29639,7 @@ export {
28894
29639
  userPromptErrorLogger,
28895
29640
  validateComponentSpec,
28896
29641
  withCapturedContext,
28897
- writeBridgeShims
29642
+ writeBridgeShims,
29643
+ writeShims
28898
29644
  };
28899
29645
  //# sourceMappingURL=index.mjs.map