@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.js CHANGED
@@ -52,11 +52,13 @@ __export(index_exports, {
52
52
  UIBlock: () => UIBlock,
53
53
  UILogCollector: () => UILogCollector,
54
54
  UserManager: () => UserManager,
55
+ analyticsClient: () => analyticsClient,
55
56
  answerCatalogForBinding: () => answerCatalogForBinding,
56
57
  answerComponents: () => answerComponents,
57
58
  anthropicLLM: () => anthropicLLM,
58
59
  assertReadOnlySql: () => assertReadOnlySql,
59
60
  bindContext: () => bindContext,
61
+ bridgePreamble: () => bridgePreamble,
60
62
  captureContext: () => captureContext,
61
63
  clearContextTokens: () => clearContextTokens,
62
64
  contextTokenCount: () => contextTokenCount,
@@ -72,6 +74,7 @@ __export(index_exports, {
72
74
  geminiLLM: () => geminiLLM,
73
75
  getCodingAgent: () => getCodingAgent,
74
76
  getCodingAgentsConfig: () => getCodingAgentsConfig,
77
+ getPiSessionState: () => getPiSessionState,
75
78
  getRequestContext: () => getRequestContext,
76
79
  groqLLM: () => groqLLM,
77
80
  hybridRerank: () => hybridRerank,
@@ -84,9 +87,11 @@ __export(index_exports, {
84
87
  mintContextToken: () => mintContextToken,
85
88
  normalizeScriptBody: () => normalizeScriptBody,
86
89
  onAgentRun: () => onAgentRun,
90
+ onPiSessionUpdate: () => onPiSessionUpdate,
87
91
  openaiLLM: () => openaiLLM,
88
92
  policiesFromConfig: () => policiesFromConfig,
89
93
  projectForDatasetBinding: () => projectForDatasetBinding,
94
+ promptLoader: () => promptLoader,
90
95
  queryCache: () => queryCache,
91
96
  registerCodingAgentCollection: () => registerCodingAgentCollection,
92
97
  registerCodingAgents: () => registerCodingAgents,
@@ -108,7 +113,8 @@ __export(index_exports, {
108
113
  userPromptErrorLogger: () => userPromptErrorLogger,
109
114
  validateComponentSpec: () => validateComponentSpec,
110
115
  withCapturedContext: () => withCapturedContext,
111
- writeBridgeShims: () => writeBridgeShims
116
+ writeBridgeShims: () => writeBridgeShims,
117
+ writeShims: () => writeShims
112
118
  });
113
119
  module.exports = __toCommonJS(index_exports);
114
120
 
@@ -911,6 +917,101 @@ var CodingAgentAbortRequestMessageSchema = import_zod3.z.object({
911
917
  type: import_zod3.z.literal("CODING_AGENT_ABORT_REQ"),
912
918
  payload: CodingAgentAbortRequestPayloadSchema
913
919
  });
920
+ var DashBuilderRequestPayloadSchema = import_zod3.z.object({
921
+ /** The conversation. One per dashboard program, and the abort key. */
922
+ dashboardId: import_zod3.z.string(),
923
+ /** What the person said. */
924
+ text: import_zod3.z.string(),
925
+ /**
926
+ * A panel the person pointed at before typing.
927
+ *
928
+ * Its `Panel` id, the same key a saved arrangement stores against. Optional
929
+ * because most turns are about the dashboard rather than one panel of it, and
930
+ * carried as a field rather than folded into `text` so the transcript shows
931
+ * what the person actually wrote.
932
+ */
933
+ panelId: import_zod3.z.string().optional(),
934
+ projectId: import_zod3.z.string().optional(),
935
+ userId: import_zod3.z.string().optional()
936
+ });
937
+ var DashBuilderRequestMessageSchema = import_zod3.z.object({
938
+ id: import_zod3.z.string(),
939
+ from: MessageParticipantSchema,
940
+ type: import_zod3.z.literal("DASH_BUILDER_REQ"),
941
+ payload: DashBuilderRequestPayloadSchema
942
+ });
943
+ var DashBuilderUpdateSchema = import_zod3.z.discriminatedUnion("type", [
944
+ import_zod3.z.object({ type: import_zod3.z.literal("state"), running: import_zod3.z.boolean() }),
945
+ import_zod3.z.object({ type: import_zod3.z.literal("user"), text: import_zod3.z.string(), at: import_zod3.z.number() }),
946
+ /**
947
+ * The tail of an assistant message that is still being written.
948
+ *
949
+ * Append-only and never part of the transcript: the `assistant` update that
950
+ * follows carries the same prose in full, so a client shows the accumulated
951
+ * deltas while a turn runs and drops them the moment the real message lands.
952
+ * A turn here runs for minutes, and without this the pane sits empty for all
953
+ * of it.
954
+ */
955
+ import_zod3.z.object({ type: import_zod3.z.literal("delta"), text: import_zod3.z.string() }),
956
+ import_zod3.z.object({ type: import_zod3.z.literal("assistant"), text: import_zod3.z.string(), at: import_zod3.z.number() }),
957
+ import_zod3.z.object({ type: import_zod3.z.literal("tool"), text: import_zod3.z.string(), at: import_zod3.z.number() }),
958
+ import_zod3.z.object({ type: import_zod3.z.literal("error"), text: import_zod3.z.string(), at: import_zod3.z.number() })
959
+ ]);
960
+ var DashBuilderStreamPayloadSchema = import_zod3.z.object({
961
+ dashboardId: import_zod3.z.string(),
962
+ /**
963
+ * Monotonic per turn, so re-delivery is harmless.
964
+ *
965
+ * Copied from dashboard-agent rather than assumed unnecessary: duplicate and
966
+ * out-of-order delivery were observed there on long-running requests and were
967
+ * never explained by anything in the send/receive path itself. A turn here
968
+ * runs for minutes, which is the same exposure.
969
+ */
970
+ seq: import_zod3.z.number(),
971
+ update: DashBuilderUpdateSchema
972
+ });
973
+ var DashBuilderStreamMessageSchema = import_zod3.z.object({
974
+ id: import_zod3.z.string().optional(),
975
+ from: MessageParticipantSchema,
976
+ type: import_zod3.z.literal("DASH_BUILDER_STREAM"),
977
+ payload: DashBuilderStreamPayloadSchema
978
+ });
979
+ var DashBuilderAbortRequestPayloadSchema = import_zod3.z.object({
980
+ dashboardId: import_zod3.z.string()
981
+ });
982
+ var DashBuilderAbortRequestMessageSchema = import_zod3.z.object({
983
+ id: import_zod3.z.string().optional(),
984
+ from: MessageParticipantSchema,
985
+ type: import_zod3.z.literal("DASH_BUILDER_ABORT_REQ"),
986
+ payload: DashBuilderAbortRequestPayloadSchema
987
+ });
988
+ var DashComponentPropSchema = import_zod3.z.union([
989
+ import_zod3.z.string(),
990
+ import_zod3.z.object({
991
+ description: import_zod3.z.string(),
992
+ importance: import_zod3.z.enum(["required", "recommended", "optional"]).optional(),
993
+ default: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean()]).optional(),
994
+ caution: import_zod3.z.string().optional(),
995
+ group: import_zod3.z.string().optional()
996
+ })
997
+ ]);
998
+ var DashComponentSchema = import_zod3.z.object({
999
+ name: import_zod3.z.string(),
1000
+ kind: import_zod3.z.string(),
1001
+ purpose: import_zod3.z.string(),
1002
+ props: import_zod3.z.record(DashComponentPropSchema).optional(),
1003
+ useWhen: import_zod3.z.string().optional(),
1004
+ notes: import_zod3.z.string().optional()
1005
+ });
1006
+ var DashContractResponsePayloadSchema = import_zod3.z.object({
1007
+ components: import_zod3.z.array(DashComponentSchema)
1008
+ });
1009
+ var DashContractResponseMessageSchema = import_zod3.z.object({
1010
+ id: import_zod3.z.string(),
1011
+ from: MessageParticipantSchema,
1012
+ type: import_zod3.z.literal("DASH_CONTRACT_RES"),
1013
+ payload: DashContractResponsePayloadSchema
1014
+ });
914
1015
  var ReportCompRequestPayloadSchema = import_zod3.z.object({
915
1016
  prompt: import_zod3.z.string(),
916
1017
  userId: import_zod3.z.string().optional(),
@@ -3835,7 +3936,7 @@ var TaggedStreamBuffer = class extends StreamBuffer {
3835
3936
  }
3836
3937
  };
3837
3938
  function streamDelay(ms = STREAM_DELAY_MS) {
3838
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3939
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3839
3940
  }
3840
3941
  async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
3841
3942
  if (!streamBuffer.hasCallback()) {
@@ -11016,7 +11117,7 @@ var Semaphore = class {
11016
11117
  `Script execution at capacity (${this.max} running, ${this.waiters.length} queued). Try again shortly.`
11017
11118
  );
11018
11119
  }
11019
- return new Promise((resolve2) => this.waiters.push(resolve2));
11120
+ return new Promise((resolve3) => this.waiters.push(resolve3));
11020
11121
  }
11021
11122
  release() {
11022
11123
  const next = this.waiters.shift();
@@ -11146,7 +11247,7 @@ FIX: pass every required parameter to execute_script, or give it a default in wr
11146
11247
  }
11147
11248
  }
11148
11249
  function executeInSubprocess(scriptPath, params, paramSchema, externalTools, streamBuffer, timeoutMs, signal, mode, analysisData, analysisDatasets, entities, appliedPolicies) {
11149
- return new Promise((resolve2) => {
11250
+ return new Promise((resolve3) => {
11150
11251
  const tsxBin = resolveTsxBinary();
11151
11252
  const bootstrap = resolveBootstrapPath();
11152
11253
  const contextToken = mintContextToken();
@@ -11202,7 +11303,7 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
11202
11303
  clearTimeout(timer);
11203
11304
  if (signal && onAbort) signal.removeEventListener("abort", onAbort);
11204
11305
  cleanup();
11205
- resolve2(r);
11306
+ resolve3(r);
11206
11307
  };
11207
11308
  const timer = setTimeout(() => {
11208
11309
  finish({
@@ -18901,7 +19002,25 @@ RENDERING REQUIREMENT (overrides the default choice of component): ${carriedRequ
18901
19002
  }
18902
19003
  }
18903
19004
  if (!usedLightweight) {
18904
- logger.warn(`[AgentFlow] Component generation produced nothing \u2014 returning the analysis without a dashboard`);
19005
+ logger.warn(`[AgentFlow] Component generation produced no components \u2014 returning the analysis without a dashboard`);
19006
+ }
19007
+ }
19008
+ if (matchedComponents.length === 0) {
19009
+ const fallbackText = (agentResponse.text || "").replace(/<DashboardComponents>[\s\S]*?<\/DashboardComponents>/g, "").trim();
19010
+ if (fallbackText) {
19011
+ logger.info(`[AgentFlow] No components \u2014 wrapping the analysis in DynamicMarkdownBlock`);
19012
+ matchedComponents = [{
19013
+ id: "dynamic-markdown-block",
19014
+ name: "DynamicMarkdownBlock",
19015
+ type: "MarkdownBlock",
19016
+ description: "Text response rendered as markdown",
19017
+ props: { content: fallbackText }
19018
+ }];
19019
+ layoutTitle = "";
19020
+ layoutDescription = "";
19021
+ noDataAnswer = true;
19022
+ } else {
19023
+ logger.warn(`[AgentFlow] No components and no analysis text \u2014 returning an empty result`);
18905
19024
  }
18906
19025
  }
18907
19026
  const queryMap = {};
@@ -19842,12 +19961,13 @@ async function handleUserPromptRequest(data, components, sendMessage, anthropicA
19842
19961
  id: `comp_${Math.random().toString(36).substring(2, 8)}`
19843
19962
  }));
19844
19963
  }
19964
+ const { trace: _trace, ...dataWithoutTrace } = response.data ?? {};
19845
19965
  sendDataResponse4(
19846
19966
  response.id || data.id,
19847
19967
  {
19848
19968
  success: response.success,
19849
19969
  errors: response.errors,
19850
- data: response.data,
19970
+ data: response.data ? dataWithoutTrace : response.data,
19851
19971
  uiBlockId: response.uiBlockId,
19852
19972
  threadId: response.threadId
19853
19973
  },
@@ -20044,6 +20164,26 @@ async function handleComponentListResponse(data, storeComponents, collections) {
20044
20164
  }
20045
20165
  }
20046
20166
 
20167
+ // src/handlers/dash-contract-response.ts
20168
+ async function handleDashContractResponse(data, storeDashContract, collections) {
20169
+ const parsed = DashContractResponseMessageSchema.safeParse(data);
20170
+ if (!parsed.success) {
20171
+ logger.error("[DashContract] Invalid message \u2014 ignoring");
20172
+ return;
20173
+ }
20174
+ const { components } = parsed.data.payload;
20175
+ storeDashContract(components);
20176
+ const register = collections?.["dashboard-builder"]?.["registerContract"];
20177
+ if (!register) return;
20178
+ try {
20179
+ await register({ components });
20180
+ } catch (error) {
20181
+ logger.warn(
20182
+ `[DashContract] Collection refused the contract: ${error instanceof Error ? error.message : String(error)}`
20183
+ );
20184
+ }
20185
+ }
20186
+
20047
20187
  // src/handlers/workflow-list-response.ts
20048
20188
  async function handleWorkflowListResponse(data, storeWorkflows) {
20049
20189
  try {
@@ -24441,11 +24581,257 @@ ${marker}
24441
24581
  };
24442
24582
  }
24443
24583
 
24584
+ // src/codingAgent/drivers/pi.ts
24585
+ var dynamicImport2 = new Function("specifier", "return import(specifier)");
24586
+ var piModulePromise = null;
24587
+ function loadPi() {
24588
+ if (!piModulePromise) {
24589
+ piModulePromise = dynamicImport2("@earendil-works/pi-coding-agent").catch((err) => {
24590
+ piModulePromise = null;
24591
+ throw new Error(`@earendil-works/pi-coding-agent is not installed, so the pi harness is unavailable. (${err?.message ?? err})`);
24592
+ });
24593
+ }
24594
+ return piModulePromise;
24595
+ }
24596
+ var authAndRegistry = null;
24597
+ function getAuthAndRegistry() {
24598
+ if (!authAndRegistry) {
24599
+ authAndRegistry = loadPi().then(({ AuthStorage, ModelRegistry }) => {
24600
+ const authStorage = AuthStorage.create();
24601
+ return { authStorage, modelRegistry: ModelRegistry.create(authStorage) };
24602
+ });
24603
+ }
24604
+ return authAndRegistry;
24605
+ }
24606
+ var DEFAULT_PROVIDER = "openai-codex";
24607
+ var DEFAULT_MODEL_ID = process.env.PI_AGENT_MODEL || "gpt-5.5";
24608
+ var KNOWN_UNUSABLE_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.3-codex-spark"]);
24609
+ async function resolveModel(modelRegistry, spec) {
24610
+ const [provider, modelId] = spec?.includes("/") ? [spec.slice(0, spec.indexOf("/")), spec.slice(spec.indexOf("/") + 1)] : [spec || DEFAULT_PROVIDER, void 0];
24611
+ const available = modelRegistry.getAvailable().filter((m) => m.provider === provider);
24612
+ const wanted = modelId ?? (spec ? void 0 : DEFAULT_MODEL_ID);
24613
+ const exact = wanted && (modelRegistry.find(provider, wanted) ?? available.find((m) => m.id === wanted));
24614
+ if (exact && modelRegistry.hasConfiguredAuth(exact)) return exact;
24615
+ const usable = available.filter((m) => !KNOWN_UNUSABLE_MODEL_IDS.has(m.id));
24616
+ if (usable.length) return usable[0];
24617
+ if (available.length) return available[0];
24618
+ throw new Error(
24619
+ `No model available from "${provider}". Authorise it in a terminal on this machine: run \`pi\`, then \`/login\`.`
24620
+ );
24621
+ }
24622
+ function emptyUsage2() {
24623
+ return { inputTokens: 0, outputTokens: 0, reasoningTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, toolCalls: 0 };
24624
+ }
24625
+ var BUFFER_CAP2 = 64e3;
24626
+ var EVENT_CAP2 = 600;
24627
+ function normEvent2(ev) {
24628
+ switch (ev.type) {
24629
+ case "tool_execution_start":
24630
+ return { kind: "command", id: ev.toolCallId, command: ev.toolName ? `${ev.toolName} ${jsonPreview(ev.args)}` : void 0, status: "in_progress" };
24631
+ case "tool_execution_end":
24632
+ return { kind: "command", id: ev.toolCallId, status: ev.isError ? "failed" : "completed", done: true, output: textOf(ev.result) };
24633
+ default:
24634
+ return null;
24635
+ }
24636
+ }
24637
+ function jsonPreview(v) {
24638
+ if (v == null) return "";
24639
+ try {
24640
+ return JSON.stringify(v).slice(0, 160);
24641
+ } catch {
24642
+ return "";
24643
+ }
24644
+ }
24645
+ function textOf(content) {
24646
+ const blocks = content?.content ?? content;
24647
+ if (!Array.isArray(blocks)) return typeof content === "string" ? content : "";
24648
+ return blocks.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("").trim();
24649
+ }
24650
+ var TRANSCRIPT_CAP = 400;
24651
+ var sessionListeners = /* @__PURE__ */ new Set();
24652
+ function onPiSessionUpdate(listener) {
24653
+ sessionListeners.add(listener);
24654
+ return () => sessionListeners.delete(listener);
24655
+ }
24656
+ function getPiSessionState(sessionKey) {
24657
+ const live = liveSessions.get(sessionKey);
24658
+ return live ? { running: live.running, transcript: live.transcript } : void 0;
24659
+ }
24660
+ function publish(sessionKey, live, entry) {
24661
+ live.transcript.push(entry);
24662
+ if (live.transcript.length > TRANSCRIPT_CAP) live.transcript.splice(0, live.transcript.length - TRANSCRIPT_CAP);
24663
+ for (const listener of sessionListeners) listener(sessionKey, entry);
24664
+ }
24665
+ var liveSessions = /* @__PURE__ */ new Map();
24666
+ function newSessionId() {
24667
+ return `pi_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
24668
+ }
24669
+ var IDLE_TIMEOUT_MS = Number(process.env.PI_AGENT_IDLE_MS ?? 30 * 60 * 1e3);
24670
+ var sweeper = null;
24671
+ function startSweeper() {
24672
+ if (sweeper) return;
24673
+ sweeper = setInterval(() => {
24674
+ const cutoff = Date.now() - IDLE_TIMEOUT_MS;
24675
+ for (const [key, live] of liveSessions) {
24676
+ if (live.running || live.lastActive > cutoff) continue;
24677
+ live.session.dispose();
24678
+ liveSessions.delete(key);
24679
+ }
24680
+ if (liveSessions.size === 0 && sweeper) {
24681
+ clearInterval(sweeper);
24682
+ sweeper = null;
24683
+ }
24684
+ }, 6e4);
24685
+ sweeper.unref?.();
24686
+ }
24687
+ function createPiSession(opts) {
24688
+ let running = false;
24689
+ let sessionKey = opts.resumeId && liveSessions.has(opts.resumeId) ? opts.resumeId : void 0;
24690
+ async function ensureSession() {
24691
+ const existing = sessionKey ? liveSessions.get(sessionKey) : void 0;
24692
+ if (existing) return existing;
24693
+ const { createAgentSession, SessionManager } = await loadPi();
24694
+ const { authStorage, modelRegistry } = await getAuthAndRegistry();
24695
+ const model = await resolveModel(modelRegistry, opts.model);
24696
+ const { session } = await createAgentSession({
24697
+ cwd: opts.cwd,
24698
+ sessionManager: SessionManager.inMemory(),
24699
+ authStorage,
24700
+ modelRegistry,
24701
+ model,
24702
+ tools: opts.tools ?? ["bash"]
24703
+ });
24704
+ sessionKey = newSessionId();
24705
+ const live = { session, running: false, transcript: [], lastActive: Date.now(), eventLog: [], buf: "" };
24706
+ liveSessions.set(sessionKey, live);
24707
+ startSweeper();
24708
+ return live;
24709
+ }
24710
+ async function runTurn(prompt, handlers, signal) {
24711
+ const startedAt = Date.now();
24712
+ const usage = emptyUsage2();
24713
+ let status = "completed";
24714
+ let error;
24715
+ const finish = (live2) => ({
24716
+ status,
24717
+ answer: status === "error" || status === "aborted" ? "" : live2?.session.getLastAssistantText()?.trim() ?? "",
24718
+ ms: Date.now() - startedAt,
24719
+ usage,
24720
+ error,
24721
+ sessionId: sessionKey
24722
+ });
24723
+ if (signal?.aborted) {
24724
+ status = "aborted";
24725
+ return finish();
24726
+ }
24727
+ const live = await ensureSession();
24728
+ const key = sessionKey;
24729
+ live.running = true;
24730
+ live.lastActive = Date.now();
24731
+ publish(key, live, { at: Date.now(), kind: "user", text: prompt });
24732
+ const before = live.session.getSessionStats();
24733
+ const onAbort = () => {
24734
+ void live.session.abort();
24735
+ };
24736
+ signal?.addEventListener("abort", onAbort, { once: true });
24737
+ const emit = (chunk) => {
24738
+ live.buf = (live.buf + chunk).slice(-BUFFER_CAP2);
24739
+ handlers?.onOutput?.(chunk);
24740
+ };
24741
+ const unsubscribe = live.session.subscribe((ev) => {
24742
+ if (ev.type === "message_update" && ev.assistantMessageEvent?.type === "text_delta") {
24743
+ const delta = ev.assistantMessageEvent.delta;
24744
+ if (delta) {
24745
+ emit(delta);
24746
+ handlers?.onNarration?.(delta);
24747
+ }
24748
+ return;
24749
+ }
24750
+ if (ev.type === "tool_execution_start" || ev.type === "tool_execution_end") {
24751
+ if (ev.type === "tool_execution_end") usage.toolCalls++;
24752
+ const event = normEvent2(ev);
24753
+ if (!event) return;
24754
+ live.eventLog.push(event);
24755
+ if (live.eventLog.length > EVENT_CAP2) live.eventLog.shift();
24756
+ handlers?.onEvent?.(event);
24757
+ publish(key, live, { at: Date.now(), kind: "activity", event });
24758
+ if (ev.type === "tool_execution_start") emit(`
24759
+ \u2192 ${ev.toolName ?? "tool"} ${jsonPreview(ev.args)}
24760
+ `);
24761
+ return;
24762
+ }
24763
+ });
24764
+ try {
24765
+ if (signal?.aborted) {
24766
+ status = "aborted";
24767
+ return finish(live);
24768
+ }
24769
+ await live.session.prompt(prompt);
24770
+ const err = live.session.agent.state.errorMessage;
24771
+ if (err) {
24772
+ status = signal?.aborted ? "aborted" : "error";
24773
+ error = err;
24774
+ } else if (signal?.aborted) {
24775
+ status = "aborted";
24776
+ }
24777
+ } catch (err) {
24778
+ status = signal?.aborted ? "aborted" : "error";
24779
+ error = err?.message ?? String(err);
24780
+ } finally {
24781
+ unsubscribe();
24782
+ signal?.removeEventListener("abort", onAbort);
24783
+ live.running = false;
24784
+ live.lastActive = Date.now();
24785
+ }
24786
+ const after = live.session.getSessionStats();
24787
+ usage.inputTokens = Math.max(0, after.tokens.input - before.tokens.input);
24788
+ usage.outputTokens = Math.max(0, after.tokens.output - before.tokens.output);
24789
+ usage.cacheReadTokens = Math.max(0, after.tokens.cacheRead - before.tokens.cacheRead);
24790
+ usage.cacheWriteTokens = Math.max(0, after.tokens.cacheWrite - before.tokens.cacheWrite);
24791
+ usage.costUSD = Math.max(0, after.cost - before.cost);
24792
+ handlers?.onUsage?.({ ...usage });
24793
+ const result = finish(live);
24794
+ publish(key, live, result.status === "error" ? { at: Date.now(), kind: "error", text: result.error } : { at: Date.now(), kind: "assistant", text: result.answer });
24795
+ return result;
24796
+ }
24797
+ return {
24798
+ harness: "pi",
24799
+ kind: "events",
24800
+ async run(prompt, handlers, signal) {
24801
+ if (running) throw new Error("This pi session already has a turn in flight.");
24802
+ running = true;
24803
+ try {
24804
+ return await runTurn(prompt, handlers, signal);
24805
+ } finally {
24806
+ running = false;
24807
+ }
24808
+ },
24809
+ events: () => sessionKey ? liveSessions.get(sessionKey)?.eventLog ?? [] : [],
24810
+ buffer: () => sessionKey ? liveSessions.get(sessionKey)?.buf ?? "" : "",
24811
+ busy: () => running,
24812
+ sessionId: () => sessionKey,
24813
+ // Abandon for real — drop the live session so the next run starts fresh.
24814
+ reset: () => {
24815
+ if (sessionKey) {
24816
+ liveSessions.get(sessionKey)?.session.dispose();
24817
+ liveSessions.delete(sessionKey);
24818
+ }
24819
+ sessionKey = void 0;
24820
+ },
24821
+ // Local-only, same as the codex driver: the live session stays in the
24822
+ // module map so a later run with this sessionId can still resume it.
24823
+ dispose: () => {
24824
+ }
24825
+ };
24826
+ }
24827
+
24444
24828
  // src/codingAgent/factory.ts
24445
24829
  function createCodingAgentSession(harness, opts) {
24446
24830
  switch (harness) {
24447
24831
  case "codex":
24448
24832
  return createCodexSession(opts);
24833
+ case "pi":
24834
+ return createPiSession(opts);
24449
24835
  default:
24450
24836
  throw new Error(`Unknown coding-agent harness: ${harness}`);
24451
24837
  }
@@ -24847,9 +25233,6 @@ Fix ONLY that, and rewrite the same file at the same path. Do not re-run any que
24847
25233
  }
24848
25234
  }
24849
25235
 
24850
- // src/codingAgent/tools/bridge-shims.ts
24851
- var fs9 = __toESM(require("fs"));
24852
-
24853
25236
  // src/codingAgent/tools/read-only-sql.ts
24854
25237
  var WRITE_KEYWORDS = [
24855
25238
  "insert",
@@ -24904,9 +25287,45 @@ ${stripSqlNoise.toString()}
24904
25287
  ${assertReadOnlySql.toString().replace("export function", "function")}
24905
25288
  `;
24906
25289
 
25290
+ // src/codingAgent/tools/shims.ts
25291
+ var fs9 = __toESM(require("fs"));
25292
+ var path10 = __toESM(require("path"));
25293
+ var resolve2 = (target, name) => typeof target === "string" ? path10.join(target, name) : target.pathTo(name);
25294
+ function writeShims(defs, target) {
25295
+ for (const def of defs) {
25296
+ const file = resolve2(target, def.name);
25297
+ fs9.writeFileSync(file, def.body, "utf-8");
25298
+ fs9.chmodSync(file, 493);
25299
+ }
25300
+ return defs.map((def) => ({ name: `./${def.name}`, summary: def.summary }));
25301
+ }
25302
+ function bridgePreamble(bridgeUrl, constants = {}) {
25303
+ const declared = Object.entries(constants).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join("\n");
25304
+ return `const BRIDGE = ${JSON.stringify(bridgeUrl)};
25305
+ ${declared}
25306
+
25307
+ function post(path, payload) {
25308
+ return fetch(BRIDGE + path, {
25309
+ method: 'POST',
25310
+ headers: { 'content-type': 'application/json' },
25311
+ body: JSON.stringify(payload),
25312
+ }).then(async (r) => {
25313
+ const text = await r.text();
25314
+ if (!r.ok) { console.error('Request failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25315
+ try { return JSON.parse(text); } catch { console.error(text); process.exit(1); }
25316
+ }).catch((e) => { console.error('Could not reach the backend: ' + (e && e.message)); process.exit(1); });
25317
+ }
25318
+ `;
25319
+ }
25320
+
24907
25321
  // src/codingAgent/tools/bridge-shims.ts
24908
25322
  function writeBridgeShims(ws, opts) {
24909
25323
  const maxRows = opts.maxRows ?? 200;
25324
+ const directTools = opts.directTools ?? [];
25325
+ const entityTypes = opts.entityTypes ?? [];
25326
+ const conceptCatalog = opts.conceptCatalog ?? [];
25327
+ const hasKnowledgeBase = !!opts.knowledgeBase;
25328
+ const hasSearchableSource = opts.sources.some((s) => s.searchable);
24910
25329
  const query = `#!/usr/bin/env node
24911
25330
  // Read-only SQL against a data source. Generated per run \u2014 do not edit.
24912
25331
  ${READ_ONLY_SQL_SOURCE}
@@ -24916,7 +25335,7 @@ const CONTEXT_TOKEN = ${JSON.stringify(opts.contextToken ?? null)};
24916
25335
 
24917
25336
  const [toolId, sql, limitArg] = process.argv.slice(2);
24918
25337
  if (!toolId || !sql) {
24919
- console.error('usage: ./query "<toolId>" "<sql>" [limit]\\nRun ./sources for the toolIds.');
25338
+ console.error('usage: node ./query "<toolId>" "<sql>" [limit]\\nRun: node ./sources \u2014 for the toolIds.');
24920
25339
  process.exit(2);
24921
25340
  }
24922
25341
  try { assertReadOnlySql(sql); } catch (e) { console.error(String(e.message || e)); process.exit(2); }
@@ -24958,24 +25377,189 @@ fetch(BRIDGE + '/query', {
24958
25377
  })
24959
25378
  .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
24960
25379
  `;
24961
- const sources = `#!/usr/bin/env node
24962
- // The data sources you can query, and the exact toolId for ./query.
24963
- console.log(${JSON.stringify(JSON.stringify(opts.sources, null, 2))});
25380
+ const runTool = `#!/usr/bin/env node
25381
+ // Invoke a pre-built business-logic tool. Generated per run \u2014 do not edit.
25382
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25383
+
25384
+ const [toolId, paramsArg] = process.argv.slice(2);
25385
+ if (!toolId) {
25386
+ console.error('usage: node ./run-tool "<toolId>" [\\'<json params>\\']\\nRun: node ./sources \u2014 for the toolIds and their params.');
25387
+ process.exit(2);
25388
+ }
25389
+ let params = {};
25390
+ if (paramsArg) {
25391
+ try { params = JSON.parse(paramsArg); }
25392
+ catch (e) { console.error('params must be JSON: ' + String(e.message || e)); process.exit(2); }
25393
+ }
25394
+
25395
+ fetch(BRIDGE + '/run-tool', {
25396
+ method: 'POST',
25397
+ headers: { 'content-type': 'application/json' },
25398
+ body: JSON.stringify({ toolId, params }),
25399
+ })
25400
+ .then(async (r) => {
25401
+ const text = await r.text();
25402
+ if (!r.ok) { console.error('Tool call failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25403
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25404
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25405
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25406
+ })
25407
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25408
+ `;
25409
+ const schemaSearch = `#!/usr/bin/env node
25410
+ // Keyword search over a large source's schema \u2014 call this BEFORE writing SQL
25411
+ // against a "searchable" source (see ./sources); its schema is too big to
25412
+ // have been shown to you in full, and guessing column names wastes a turn on
25413
+ // a failed query. Generated per run \u2014 do not edit.
25414
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25415
+
25416
+ const [toolId, ...keywords] = process.argv.slice(2);
25417
+ if (!toolId || !keywords.length) {
25418
+ console.error('usage: node ./schema-search "<toolId>" <keyword> [keyword...]\\nRun: node ./sources \u2014 for which toolIds are searchable.');
25419
+ process.exit(2);
25420
+ }
25421
+
25422
+ fetch(BRIDGE + '/schema-search', {
25423
+ method: 'POST',
25424
+ headers: { 'content-type': 'application/json' },
25425
+ body: JSON.stringify({ toolId, keywords }),
25426
+ })
25427
+ .then(async (r) => {
25428
+ const text = await r.text();
25429
+ if (!r.ok) { console.error('Schema search failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25430
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25431
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25432
+ console.log(p.result ?? p);
25433
+ })
25434
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25435
+ `;
25436
+ const resolveEntities = `#!/usr/bin/env node
25437
+ // Resolve named things (customers, projects, people, ...) to their real
25438
+ // database ids, and business concepts to table/column/join facts \u2014 one round
25439
+ // trip, either half optional. Call this BEFORE writing SQL about anything
25440
+ // named in the question; never guess or fuzzy-match a name yourself.
25441
+ // Generated per run \u2014 do not edit.
25442
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25443
+
25444
+ const [argJson] = process.argv.slice(2);
25445
+ if (!argJson) {
25446
+ console.error('usage: node ./resolve-entities \\'{"mentions":[{"text":"..."}],"entityTypes":["..."]}\\'\\nRun: node ./sources \u2014 for which entity types exist. Either field may be omitted.');
25447
+ process.exit(2);
25448
+ }
25449
+ let args;
25450
+ try { args = JSON.parse(argJson); }
25451
+ catch (e) { console.error('argument must be JSON: ' + String(e.message || e)); process.exit(2); }
25452
+
25453
+ fetch(BRIDGE + '/resolve-entities', {
25454
+ method: 'POST',
25455
+ headers: { 'content-type': 'application/json' },
25456
+ body: JSON.stringify({ mentions: args.mentions ?? [], entityTypes: args.entityTypes ?? [] }),
25457
+ })
25458
+ .then(async (r) => {
25459
+ const text = await r.text();
25460
+ if (!r.ok) { console.error('Entity resolution failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25461
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25462
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25463
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25464
+ })
25465
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25466
+ `;
25467
+ const conceptCatalogShim = `#!/usr/bin/env node
25468
+ // Every verified concept \u2014 name, aliases, and what it is for. No bodies: the
25469
+ // whole vocabulary in one call, so you know what exists and which ones bear on
25470
+ // the question, instead of guessing a name into ./run-tool "concept". Read it,
25471
+ // then fetch the ones that apply. Call this once, early. There is no search
25472
+ // step and nothing to search: this is the complete list.
25473
+ // Generated per run \u2014 do not edit.
25474
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25475
+
25476
+ fetch(BRIDGE + '/concept-catalog', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
25477
+ .then(async (r) => {
25478
+ const text = await r.text();
25479
+ if (!r.ok) { console.error('Concept catalog fetch failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25480
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25481
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25482
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25483
+ })
25484
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
24964
25485
  `;
24965
- for (const [name, body] of [["query", query], ["sources", sources]]) {
24966
- const p = ws.pathTo(name);
24967
- fs9.writeFileSync(p, body, "utf-8");
24968
- fs9.chmodSync(p, 493);
25486
+ const knowledgeBaseShim = `#!/usr/bin/env node
25487
+ // Free-form domain knowledge (gotchas, join paths, table/column meanings) \u2014
25488
+ // lower priority than the concept catalog: check ./concept-catalog / the
25489
+ // concept tool first for anything with a formal business-rule meaning, use
25490
+ // this directly for everything else.
25491
+ //
25492
+ // No args lists every node; get fetches the bodies. There is no search step
25493
+ // and nothing to search \u2014 the corpus is tens of nodes with one short title
25494
+ // each, same as ./concept-catalog. Generated per run \u2014 do not edit.
25495
+ const BRIDGE = ${JSON.stringify(opts.bridgeUrl)};
25496
+
25497
+ const [argJson] = process.argv.slice(2);
25498
+ let args;
25499
+ if (!argJson) {
25500
+ args = { method: 'catalog' };
25501
+ } else {
25502
+ try { args = JSON.parse(argJson); }
25503
+ catch (e) {
25504
+ console.error('argument must be JSON: ' + String(e.message || e));
25505
+ console.error('usage: node ./knowledge-base \u2192 every node: id, title, category, tags');
25506
+ console.error(' node ./knowledge-base \\'{"method":"get","ids":[12,31]}\\' \u2192 their content');
25507
+ process.exit(2);
24969
25508
  }
24970
25509
  }
24971
25510
 
25511
+ fetch(BRIDGE + '/knowledge-base', {
25512
+ method: 'POST',
25513
+ headers: { 'content-type': 'application/json' },
25514
+ body: JSON.stringify(args),
25515
+ })
25516
+ .then(async (r) => {
25517
+ const text = await r.text();
25518
+ if (!r.ok) { console.error('Knowledge base lookup failed (' + r.status + '): ' + text.slice(0, 2000)); process.exit(1); }
25519
+ let p; try { p = JSON.parse(text); } catch { console.log(text); return; }
25520
+ if (p && p.error) { console.error(String(p.error)); process.exit(1); }
25521
+ console.log(JSON.stringify(p.result ?? p, null, 2));
25522
+ })
25523
+ .catch((e) => { console.error('Could not reach the data bridge: ' + (e && e.message)); process.exit(1); });
25524
+ `;
25525
+ const sources = `#!/usr/bin/env node
25526
+ // The data sources you can query with ./query (and search with
25527
+ // ./schema-search when "searchable" is true), the pre-built tools you can
25528
+ // invoke with ./run-tool, and the entity types ./resolve-entities can
25529
+ // resolve names against. The concept catalog itself is NOT repeated here \u2014
25530
+ // run ./concept-catalog for that.
25531
+ console.log(${JSON.stringify(
25532
+ JSON.stringify({
25533
+ sources: opts.sources,
25534
+ directTools,
25535
+ entityTypes,
25536
+ ...conceptCatalog.length ? { conceptCount: conceptCatalog.length } : {},
25537
+ ...hasKnowledgeBase ? { knowledgeBase: true } : {}
25538
+ }, null, 2)
25539
+ )});
25540
+ `;
25541
+ const files = [
25542
+ ["sources", sources, "(no arguments) \u2014 what you can query, which of those are searchable, the pre-built tools, and the entity types."],
25543
+ ["query", query, '"<toolId>" "<sql>" [limit] \u2014 read-only SQL against one source. The only way to see real data. ./sources for the toolIds.']
25544
+ ];
25545
+ 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".']);
25546
+ 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.`]);
25547
+ 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.`]);
25548
+ 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.`]);
25549
+ 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.`]);
25550
+ return writeShims(
25551
+ files.map(([name, body, summary]) => ({ name, body, summary })),
25552
+ ws
25553
+ );
25554
+ }
25555
+
24972
25556
  // src/codingAgent/triggers.ts
24973
25557
  var fs10 = __toESM(require("fs"));
24974
- var path10 = __toESM(require("path"));
25558
+ var path11 = __toESM(require("path"));
24975
25559
  var EVENT_BURST_CAP = 200;
24976
25560
  var capEvents = (events) => events.length > EVENT_BURST_CAP ? events.slice(-EVENT_BURST_CAP) : events;
24977
25561
  function statePath2(agentId) {
24978
- return path10.join(getCodingAgentsConfig().runtimeDir, agentId, "_triggers.json");
25562
+ return path11.join(getCodingAgentsConfig().runtimeDir, agentId, "_triggers.json");
24979
25563
  }
24980
25564
  function loadTriggerState(agentId) {
24981
25565
  let raw;
@@ -25000,7 +25584,7 @@ function loadTriggerState(agentId) {
25000
25584
  function saveTriggerState(agentId, state) {
25001
25585
  try {
25002
25586
  const p = statePath2(agentId);
25003
- fs10.mkdirSync(path10.dirname(p), { recursive: true });
25587
+ fs10.mkdirSync(path11.dirname(p), { recursive: true });
25004
25588
  fs10.writeFileSync(p, JSON.stringify(state, null, 2));
25005
25589
  } catch (err) {
25006
25590
  logger.warn(`[CodingAgent:${agentId}] failed to persist trigger state: ${err?.message ?? err}`);
@@ -25138,12 +25722,12 @@ function describeTriggers() {
25138
25722
 
25139
25723
  // src/codingAgent/collection.ts
25140
25724
  var fs11 = __toESM(require("fs"));
25141
- var path11 = __toESM(require("path"));
25725
+ var path12 = __toESM(require("path"));
25142
25726
  function isChattable(agent) {
25143
25727
  return agent.mode !== "task";
25144
25728
  }
25145
25729
  function registerCodingAgentCollection(sdk, config2 = {}) {
25146
- const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR || path11.join(process.cwd(), ".coding-agent-runtime");
25730
+ const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR || path12.join(process.cwd(), ".coding-agent-runtime");
25147
25731
  const defaultHarness = config2.harness || process.env.CODING_AGENT_HARNESS || "codex";
25148
25732
  const sessions = /* @__PURE__ */ new Map();
25149
25733
  const running = /* @__PURE__ */ new Set();
@@ -25203,7 +25787,7 @@ function registerCodingAgentCollection(sdk, config2 = {}) {
25203
25787
  ...record.status === "skipped" ? { error: `${def.title} is in the middle of a run \u2014 try again in a moment.` } : record.error ? { error: record.error } : {}
25204
25788
  };
25205
25789
  }
25206
- const cwd = path11.join(runtimeDir, "threads", threadId);
25790
+ const cwd = path12.join(runtimeDir, "threads", threadId);
25207
25791
  fs11.mkdirSync(cwd, { recursive: true });
25208
25792
  let session = sessions.get(threadId);
25209
25793
  if (!session) {
@@ -25369,6 +25953,134 @@ async function handleCodingAgentRequest(data, collections, sendMessage) {
25369
25953
  }
25370
25954
  }
25371
25955
 
25956
+ // src/dashBuilder/handler.ts
25957
+ var inFlightAborts4 = /* @__PURE__ */ new Map();
25958
+ var recentlyAborted4 = /* @__PURE__ */ new Map();
25959
+ var ABORT_TOMBSTONE_TTL_MS4 = 3e4;
25960
+ function setAbortTombstone4(dashboardId) {
25961
+ const now = Date.now();
25962
+ recentlyAborted4.set(dashboardId, now);
25963
+ for (const [key, ts] of recentlyAborted4) {
25964
+ if (now - ts > ABORT_TOMBSTONE_TTL_MS4) recentlyAborted4.delete(key);
25965
+ }
25966
+ }
25967
+ function consumeAbortTombstone4(dashboardId) {
25968
+ const ts = recentlyAborted4.get(dashboardId);
25969
+ if (ts === void 0) return false;
25970
+ recentlyAborted4.delete(dashboardId);
25971
+ return Date.now() - ts <= ABORT_TOMBSTONE_TTL_MS4;
25972
+ }
25973
+ function handleDashBuilderAbort(data, sendMessage) {
25974
+ const parsed = DashBuilderAbortRequestMessageSchema.safeParse(data);
25975
+ if (!parsed.success) {
25976
+ logger.warn("[DashBuilderAbort] Invalid message \u2014 ignoring");
25977
+ return;
25978
+ }
25979
+ const { payload, from } = parsed.data;
25980
+ const { dashboardId } = payload;
25981
+ const controller = inFlightAborts4.get(dashboardId);
25982
+ if (controller) {
25983
+ controller.abort();
25984
+ inFlightAborts4.delete(dashboardId);
25985
+ logger.info(`[DashBuilderAbort] Aborted in-flight turn for ${dashboardId}`);
25986
+ } else {
25987
+ setAbortTombstone4(dashboardId);
25988
+ logger.info(`[DashBuilderAbort] No in-flight turn for ${dashboardId} \u2014 tombstoned`);
25989
+ }
25990
+ try {
25991
+ sendMessage({
25992
+ id: `dash_builder_aborted_${dashboardId}`,
25993
+ type: "DASH_BUILDER_ABORTED",
25994
+ from: { type: "data-agent" },
25995
+ to: { type: "runtime", id: from.id },
25996
+ payload: { dashboardId }
25997
+ });
25998
+ } catch (err) {
25999
+ logger.warn(
26000
+ `[DashBuilderAbort] Failed to send abort ack: ${err instanceof Error ? err.message : String(err)}`
26001
+ );
26002
+ }
26003
+ }
26004
+ async function handleDashBuilderRequest(data, collections, sendMessage) {
26005
+ return runWithMessageIdentity(data, () => handleDashBuilderRequestInner(data, collections, sendMessage));
26006
+ }
26007
+ async function handleDashBuilderRequestInner(data, collections, sendMessage) {
26008
+ const parsed = DashBuilderRequestMessageSchema.safeParse(data);
26009
+ if (!parsed.success) {
26010
+ logger.error("[DashBuilderReq] Invalid message:", JSON.stringify(parsed.error.errors));
26011
+ return;
26012
+ }
26013
+ const { id, payload, from } = parsed.data;
26014
+ const { dashboardId, text, panelId, projectId, userId } = payload;
26015
+ const wsId = from.id;
26016
+ const respond = (res) => {
26017
+ sendMessage({
26018
+ id,
26019
+ type: "DASH_BUILDER_RES",
26020
+ from: { type: "data-agent" },
26021
+ to: { type: "runtime", id: wsId },
26022
+ payload: res
26023
+ });
26024
+ };
26025
+ if (!collections?.["dashboard-builder-agent"]?.["prompt"]) {
26026
+ respond({
26027
+ started: false,
26028
+ error: "The dashboard builder is not available in this environment (collection 'dashboard-builder-agent' not registered)."
26029
+ });
26030
+ return;
26031
+ }
26032
+ const abortController = new AbortController();
26033
+ if (consumeAbortTombstone4(dashboardId)) {
26034
+ abortController.abort();
26035
+ logger.info(`[DashBuilderReq] ${dashboardId} was pre-aborted \u2014 starting cancelled`);
26036
+ }
26037
+ inFlightAborts4.set(dashboardId, abortController);
26038
+ let seq = 0;
26039
+ const stream = (update) => {
26040
+ try {
26041
+ sendMessage({
26042
+ /**
26043
+ * Keyed by this request's id, not by dashboardId.
26044
+ *
26045
+ * dashboardId repeats across every turn in a conversation, so a
26046
+ * listener that failed to clean up would keep matching future turns
26047
+ * forever and interleave their output. A per-request id means a
26048
+ * stale listener simply never matches anything again.
26049
+ */
26050
+ id: `dash_builder_stream_${id}`,
26051
+ type: "DASH_BUILDER_STREAM",
26052
+ from: { type: "data-agent" },
26053
+ to: { type: "runtime", id: wsId },
26054
+ payload: { dashboardId, seq: seq++, update }
26055
+ });
26056
+ } catch (err) {
26057
+ logger.warn(
26058
+ `[DashBuilderReq] Stream send failed (socket may be closed): ${err instanceof Error ? err.message : String(err)}`
26059
+ );
26060
+ }
26061
+ };
26062
+ try {
26063
+ const result = await collections["dashboard-builder-agent"]["prompt"]({
26064
+ dashboardId,
26065
+ text,
26066
+ panelId,
26067
+ projectId,
26068
+ userId,
26069
+ signal: abortController.signal,
26070
+ onUpdate: stream
26071
+ });
26072
+ respond(result ?? { started: true });
26073
+ } catch (err) {
26074
+ const message = err instanceof Error ? err.message : String(err);
26075
+ logger.error(`[DashBuilderReq] ${dashboardId} failed to start: ${message}`);
26076
+ respond({ started: false, error: message });
26077
+ } finally {
26078
+ if (inFlightAborts4.get(dashboardId) === abortController) {
26079
+ inFlightAborts4.delete(dashboardId);
26080
+ }
26081
+ }
26082
+ }
26083
+
25372
26084
  // src/reportComp/types.ts
25373
26085
  var DEFAULT_REPORT_COMP_MODELS = {
25374
26086
  anthropic: "anthropic/claude-haiku-4-5-20251001",
@@ -25867,21 +26579,21 @@ Fixed SQL query:`;
25867
26579
  }
25868
26580
 
25869
26581
  // src/reportComp/index.ts
25870
- var inFlightAborts4 = /* @__PURE__ */ new Map();
25871
- var recentlyAborted4 = /* @__PURE__ */ new Map();
25872
- var ABORT_TOMBSTONE_TTL_MS4 = 3e4;
25873
- function setAbortTombstone4(reportId) {
26582
+ var inFlightAborts5 = /* @__PURE__ */ new Map();
26583
+ var recentlyAborted5 = /* @__PURE__ */ new Map();
26584
+ var ABORT_TOMBSTONE_TTL_MS5 = 3e4;
26585
+ function setAbortTombstone5(reportId) {
25874
26586
  const now = Date.now();
25875
- recentlyAborted4.set(reportId, now);
25876
- for (const [key, ts] of recentlyAborted4) {
25877
- if (now - ts > ABORT_TOMBSTONE_TTL_MS4) recentlyAborted4.delete(key);
26587
+ recentlyAborted5.set(reportId, now);
26588
+ for (const [key, ts] of recentlyAborted5) {
26589
+ if (now - ts > ABORT_TOMBSTONE_TTL_MS5) recentlyAborted5.delete(key);
25878
26590
  }
25879
26591
  }
25880
- function consumeAbortTombstone4(reportId) {
25881
- const ts = recentlyAborted4.get(reportId);
26592
+ function consumeAbortTombstone5(reportId) {
26593
+ const ts = recentlyAborted5.get(reportId);
25882
26594
  if (ts === void 0) return false;
25883
- recentlyAborted4.delete(reportId);
25884
- return Date.now() - ts <= ABORT_TOMBSTONE_TTL_MS4;
26595
+ recentlyAborted5.delete(reportId);
26596
+ return Date.now() - ts <= ABORT_TOMBSTONE_TTL_MS5;
25885
26597
  }
25886
26598
  function handleReportCompAbort(data, sendMessage) {
25887
26599
  const parseResult = ReportCompAbortRequestMessageSchema.safeParse(data);
@@ -25892,13 +26604,13 @@ function handleReportCompAbort(data, sendMessage) {
25892
26604
  const { payload, from } = parseResult.data;
25893
26605
  const { reportId } = payload;
25894
26606
  const wsId = from.id;
25895
- const controller = inFlightAborts4.get(reportId);
26607
+ const controller = inFlightAborts5.get(reportId);
25896
26608
  if (controller) {
25897
26609
  controller.abort();
25898
- inFlightAborts4.delete(reportId);
26610
+ inFlightAborts5.delete(reportId);
25899
26611
  logger.info(`[ReportCompAbort] Aborted in-flight run for report ${reportId}`);
25900
26612
  } else {
25901
- setAbortTombstone4(reportId);
26613
+ setAbortTombstone5(reportId);
25902
26614
  logger.info(`[ReportCompAbort] No in-flight run for report ${reportId} \u2014 tombstoned`);
25903
26615
  }
25904
26616
  try {
@@ -25944,11 +26656,11 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
25944
26656
  const trace = createRunTrace();
25945
26657
  const abortController = new AbortController();
25946
26658
  if (reportId) {
25947
- if (consumeAbortTombstone4(reportId)) {
26659
+ if (consumeAbortTombstone5(reportId)) {
25948
26660
  abortController.abort();
25949
26661
  logger.info(`[REPORT_COMP_REQ] reportId=${reportId} was pre-aborted \u2014 starting cancelled`);
25950
26662
  }
25951
- inFlightAborts4.set(reportId, abortController);
26663
+ inFlightAborts5.set(reportId, abortController);
25952
26664
  }
25953
26665
  let llmResponse;
25954
26666
  try {
@@ -25969,7 +26681,7 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
25969
26681
  trace
25970
26682
  );
25971
26683
  } finally {
25972
- if (reportId) inFlightAborts4.delete(reportId);
26684
+ if (reportId) inFlightAborts5.delete(reportId);
25973
26685
  }
25974
26686
  const wasAborted = abortController.signal.aborted || !llmResponse.success && Array.isArray(llmResponse.errors) && llmResponse.errors.includes("aborted");
25975
26687
  if (wasAborted) {
@@ -27678,16 +28390,16 @@ function buildDashboardAgentTools(sdk, signal, trace) {
27678
28390
  }
27679
28391
 
27680
28392
  // src/dashboardAgent/collection.ts
27681
- var dynamicImport2 = new Function("specifier", "return import(specifier)");
27682
- var piModulePromise = null;
27683
- function loadPi() {
27684
- if (!piModulePromise) piModulePromise = dynamicImport2("@earendil-works/pi-coding-agent");
27685
- return piModulePromise;
27686
- }
27687
- var authAndRegistry = null;
27688
- function getAuthAndRegistry() {
27689
- if (!authAndRegistry) {
27690
- authAndRegistry = loadPi().then(({ AuthStorage, ModelRegistry }) => {
28393
+ var dynamicImport3 = new Function("specifier", "return import(specifier)");
28394
+ var piModulePromise2 = null;
28395
+ function loadPi2() {
28396
+ if (!piModulePromise2) piModulePromise2 = dynamicImport3("@earendil-works/pi-coding-agent");
28397
+ return piModulePromise2;
28398
+ }
28399
+ var authAndRegistry2 = null;
28400
+ function getAuthAndRegistry2() {
28401
+ if (!authAndRegistry2) {
28402
+ authAndRegistry2 = loadPi2().then(({ AuthStorage, ModelRegistry }) => {
27691
28403
  const authStorage = AuthStorage.create();
27692
28404
  const modelRegistry = ModelRegistry.create(authStorage);
27693
28405
  const proxyRoot = process.env.SUPERATOM_LLM_PROXY_URL;
@@ -27704,7 +28416,7 @@ function getAuthAndRegistry() {
27704
28416
  return { authStorage, modelRegistry };
27705
28417
  });
27706
28418
  }
27707
- return authAndRegistry;
28419
+ return authAndRegistry2;
27708
28420
  }
27709
28421
  async function preResolveSchema(sdk, tool, intent, projectId) {
27710
28422
  const tier = tool.schemaTier;
@@ -27917,8 +28629,8 @@ ${fullPrompt}
27917
28629
  return { success: false, error: "aborted" };
27918
28630
  }
27919
28631
  try {
27920
- const { createAgentSession, SessionManager } = await loadPi();
27921
- const { authStorage, modelRegistry } = await getAuthAndRegistry();
28632
+ const { createAgentSession, SessionManager } = await loadPi2();
28633
+ const { authStorage, modelRegistry } = await getAuthAndRegistry2();
27922
28634
  let model;
27923
28635
  const explicitProvider = config2.provider || process.env.PI_AGENT_PROVIDER;
27924
28636
  const explicitModel = config2.model || process.env.PI_AGENT_MODEL;
@@ -28316,6 +29028,15 @@ var SuperatomSDK = class {
28316
29028
  // since an outage of unknown length shouldn't grow this without limit.
28317
29029
  this.pendingOutbox = [];
28318
29030
  this.MAX_OUTBOX_SIZE = 50;
29031
+ /**
29032
+ * The dashboard contract, as registered by the browser bundle that holds it.
29033
+ *
29034
+ * Empty until a client has connected and pushed it. That is a real state and
29035
+ * callers must handle it rather than treat it as an empty contract: an empty
29036
+ * list means "nobody has told us yet", not "there are no components", and
29037
+ * building against it would produce a dashboard that can import nothing.
29038
+ */
29039
+ this.dashContract = [];
28319
29040
  if (config2.logLevel) {
28320
29041
  logger.setLogLevel(config2.logLevel);
28321
29042
  }
@@ -28432,7 +29153,7 @@ var SuperatomSDK = class {
28432
29153
  logger.info("Already connected to WebSocket");
28433
29154
  return Promise.resolve();
28434
29155
  }
28435
- return new Promise((resolve2, reject) => {
29156
+ return new Promise((resolve3, reject) => {
28436
29157
  try {
28437
29158
  const url = new URL(this.url);
28438
29159
  if (this.apiKey) {
@@ -28448,7 +29169,7 @@ var SuperatomSDK = class {
28448
29169
  logger.info("WebSocket connected successfully");
28449
29170
  this.startHeartbeat();
28450
29171
  this.flushPendingOutbox();
28451
- resolve2();
29172
+ resolve3();
28452
29173
  });
28453
29174
  this.ws.addEventListener("message", (event) => {
28454
29175
  this.handleMessage(event.data);
@@ -28536,6 +29257,13 @@ var SuperatomSDK = class {
28536
29257
  logger.error("Failed to handle component list request:", error);
28537
29258
  });
28538
29259
  break;
29260
+ /* The dashboard contract, pushed by the bundle that holds it — on
29261
+ * connect and again on every reconnect. See types.ts. */
29262
+ case "DASH_CONTRACT_RES":
29263
+ handleDashContractResponse(parsed, (c) => this.storeDashContract(c), this.collections).catch((error) => {
29264
+ logger.error("Failed to handle dashboard contract:", error);
29265
+ });
29266
+ break;
28539
29267
  case "WORKFLOW_LIST_RES":
28540
29268
  handleWorkflowListResponse(parsed, (wf) => this.setWorkflows(wf)).catch((error) => {
28541
29269
  logger.error("Failed to handle workflow list request:", error);
@@ -28602,6 +29330,16 @@ var SuperatomSDK = class {
28602
29330
  case "CODING_AGENT_ABORT_REQ":
28603
29331
  handleCodingAgentAbort(parsed, (msg) => this.send(msg));
28604
29332
  break;
29333
+ // The dashboard BUILDER's conversation — distinct from
29334
+ // DASHBOARD_AGENT_REQ, which drives the older JSON-DSL flow.
29335
+ case "DASH_BUILDER_REQ":
29336
+ handleDashBuilderRequest(parsed, this.collections, (msg) => this.send(msg)).catch((error) => {
29337
+ logger.error("Failed to handle dash builder request:", error);
29338
+ });
29339
+ break;
29340
+ case "DASH_BUILDER_ABORT_REQ":
29341
+ handleDashBuilderAbort(parsed, (msg) => this.send(msg));
29342
+ break;
28605
29343
  case "REPORT_COMP_REQ":
28606
29344
  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) => {
28607
29345
  logger.error("Failed to handle report comp request:", error);
@@ -28824,6 +29562,14 @@ var SuperatomSDK = class {
28824
29562
  getComponents() {
28825
29563
  return this.components;
28826
29564
  }
29565
+ storeDashContract(components) {
29566
+ this.dashContract = components;
29567
+ logger.info(`Dashboard contract registered: ${components.length} components`);
29568
+ }
29569
+ /** The registered dashboard contract. Empty means "not registered yet". */
29570
+ getDashContract() {
29571
+ return this.dashContract;
29572
+ }
28827
29573
  /**
28828
29574
  * Set tools for the SDK instance
28829
29575
  */
@@ -28946,11 +29692,13 @@ var SuperatomSDK = class {
28946
29692
  UIBlock,
28947
29693
  UILogCollector,
28948
29694
  UserManager,
29695
+ analyticsClient,
28949
29696
  answerCatalogForBinding,
28950
29697
  answerComponents,
28951
29698
  anthropicLLM,
28952
29699
  assertReadOnlySql,
28953
29700
  bindContext,
29701
+ bridgePreamble,
28954
29702
  captureContext,
28955
29703
  clearContextTokens,
28956
29704
  contextTokenCount,
@@ -28966,6 +29714,7 @@ var SuperatomSDK = class {
28966
29714
  geminiLLM,
28967
29715
  getCodingAgent,
28968
29716
  getCodingAgentsConfig,
29717
+ getPiSessionState,
28969
29718
  getRequestContext,
28970
29719
  groqLLM,
28971
29720
  hybridRerank,
@@ -28978,9 +29727,11 @@ var SuperatomSDK = class {
28978
29727
  mintContextToken,
28979
29728
  normalizeScriptBody,
28980
29729
  onAgentRun,
29730
+ onPiSessionUpdate,
28981
29731
  openaiLLM,
28982
29732
  policiesFromConfig,
28983
29733
  projectForDatasetBinding,
29734
+ promptLoader,
28984
29735
  queryCache,
28985
29736
  registerCodingAgentCollection,
28986
29737
  registerCodingAgents,
@@ -29002,6 +29753,7 @@ var SuperatomSDK = class {
29002
29753
  userPromptErrorLogger,
29003
29754
  validateComponentSpec,
29004
29755
  withCapturedContext,
29005
- writeBridgeShims
29756
+ writeBridgeShims,
29757
+ writeShims
29006
29758
  });
29007
29759
  //# sourceMappingURL=index.js.map