@granular-software/sdk 0.4.32 → 0.4.33

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.
@@ -5619,6 +5619,540 @@ var JobImplementation = class {
5619
5619
  }
5620
5620
  };
5621
5621
 
5622
+ // src/job-presentation.ts
5623
+ var RESPONSE_KEYS = [
5624
+ "reply",
5625
+ "response",
5626
+ "text",
5627
+ "message",
5628
+ "summary",
5629
+ "answer"
5630
+ ];
5631
+ var ENTRY_KEY_CANDIDATES = ["entryPath", "path"];
5632
+ var ENTRY_ARRAY_KEY_CANDIDATES = ["entryPaths", "paths"];
5633
+ var LIST_KEY_CANDIDATES = ["listName"];
5634
+ var LIST_ARRAY_KEY_CANDIDATES = ["listNames"];
5635
+ var VARIABLE_KEY_CANDIDATES = ["variableName"];
5636
+ var VARIABLE_ARRAY_KEY_CANDIDATES = ["variableNames"];
5637
+ var UI_CONTAINER_KEYS = ["show", "display", "present", "ui"];
5638
+ function asRecord2(value) {
5639
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5640
+ return value;
5641
+ }
5642
+ function normalizeText(value) {
5643
+ if (typeof value !== "string") return null;
5644
+ const trimmed = value.trim();
5645
+ if (!trimmed) return null;
5646
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
5647
+ return null;
5648
+ }
5649
+ return trimmed;
5650
+ }
5651
+ function humanTextFromStdout(stdout) {
5652
+ for (const line of [...stdout].reverse()) {
5653
+ const normalized = normalizeText(line);
5654
+ if (!normalized) continue;
5655
+ if (/^[A-Z_]+:/.test(normalized)) continue;
5656
+ return normalized;
5657
+ }
5658
+ return null;
5659
+ }
5660
+ function pushString(target, value) {
5661
+ if (typeof value === "string" && value.trim()) {
5662
+ target.add(value.trim());
5663
+ }
5664
+ }
5665
+ function pushStringArray(target, value) {
5666
+ if (!Array.isArray(value)) return;
5667
+ for (const item of value) {
5668
+ pushString(target, item);
5669
+ }
5670
+ }
5671
+ function collectReferencesFromRecord(record, refs) {
5672
+ for (const key of ENTRY_KEY_CANDIDATES)
5673
+ pushString(refs.entryPaths, record[key]);
5674
+ for (const key of ENTRY_ARRAY_KEY_CANDIDATES)
5675
+ pushStringArray(refs.entryPaths, record[key]);
5676
+ for (const key of LIST_KEY_CANDIDATES)
5677
+ pushString(refs.listNames, record[key]);
5678
+ for (const key of LIST_ARRAY_KEY_CANDIDATES)
5679
+ pushStringArray(refs.listNames, record[key]);
5680
+ for (const key of VARIABLE_KEY_CANDIDATES)
5681
+ pushString(refs.variableNames, record[key]);
5682
+ for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
5683
+ pushStringArray(refs.variableNames, record[key]);
5684
+ }
5685
+ function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__ */ new Set()) {
5686
+ if (value === null || value === void 0 || depth > 4 || seen.has(value))
5687
+ return;
5688
+ if (typeof value === "string") {
5689
+ const trimmed = value.trim();
5690
+ if (heap.entriesByPath?.[trimmed]) refs.entryPaths.add(trimmed);
5691
+ if (heap.listsByName?.[trimmed]) refs.listNames.add(trimmed);
5692
+ if (heap.variablesByName?.[trimmed]) refs.variableNames.add(trimmed);
5693
+ return;
5694
+ }
5695
+ if (Array.isArray(value)) {
5696
+ seen.add(value);
5697
+ for (const item of value.slice(0, 24)) {
5698
+ scanForHeapReferences(item, heap, refs, depth + 1, seen);
5699
+ }
5700
+ return;
5701
+ }
5702
+ const record = asRecord2(value);
5703
+ if (!record) return;
5704
+ seen.add(value);
5705
+ collectReferencesFromRecord(record, refs);
5706
+ for (const key of UI_CONTAINER_KEYS) {
5707
+ const nested = asRecord2(record[key]);
5708
+ if (nested) collectReferencesFromRecord(nested, refs);
5709
+ }
5710
+ for (const nested of Object.values(record).slice(0, 24)) {
5711
+ scanForHeapReferences(nested, heap, refs, depth + 1, seen);
5712
+ }
5713
+ }
5714
+ function resolveVariablesToReferences(variableNames, heap, refs) {
5715
+ for (const variableName of variableNames) {
5716
+ const variable = heap.variablesByName?.[variableName];
5717
+ if (!variable) continue;
5718
+ if (variable.kind === "entry" && variable.entryPath) {
5719
+ refs.entryPaths.add(variable.entryPath);
5720
+ }
5721
+ if (variable.kind === "list" && variable.listName) {
5722
+ refs.listNames.add(variable.listName);
5723
+ }
5724
+ }
5725
+ }
5726
+ function sortEntries(entries) {
5727
+ return [...entries].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
5728
+ }
5729
+ function sortLists(lists) {
5730
+ return [...lists].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
5731
+ }
5732
+ function dedupeEntries(entries) {
5733
+ const seen = /* @__PURE__ */ new Set();
5734
+ const result = [];
5735
+ for (const entry of entries) {
5736
+ if (!entry?.path || seen.has(entry.path)) continue;
5737
+ seen.add(entry.path);
5738
+ result.push(entry);
5739
+ }
5740
+ return result;
5741
+ }
5742
+ function dedupeLists(lists) {
5743
+ const seen = /* @__PURE__ */ new Set();
5744
+ const result = [];
5745
+ for (const list of lists) {
5746
+ if (!list?.name || seen.has(list.name)) continue;
5747
+ seen.add(list.name);
5748
+ result.push(list);
5749
+ }
5750
+ return result;
5751
+ }
5752
+ function extractResponseText(result, stdout) {
5753
+ const directText = normalizeText(result);
5754
+ if (directText) return directText;
5755
+ const record = asRecord2(result);
5756
+ if (record) {
5757
+ for (const key of RESPONSE_KEYS) {
5758
+ const normalized = normalizeText(record[key]);
5759
+ if (normalized) return normalized;
5760
+ }
5761
+ for (const containerKey of UI_CONTAINER_KEYS) {
5762
+ const nested = asRecord2(record[containerKey]);
5763
+ if (!nested) continue;
5764
+ for (const key of RESPONSE_KEYS) {
5765
+ const normalized = normalizeText(nested[key]);
5766
+ if (normalized) return normalized;
5767
+ }
5768
+ }
5769
+ }
5770
+ return humanTextFromStdout(stdout);
5771
+ }
5772
+ function fallbackResponseText(entries, lists) {
5773
+ if (entries.length > 0) {
5774
+ return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
5775
+ }
5776
+ if (lists.length > 0) {
5777
+ const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
5778
+ if (emptyOnly) {
5779
+ return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
5780
+ }
5781
+ return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
5782
+ }
5783
+ return null;
5784
+ }
5785
+ function getJobRelatedEntries(heap, jobId) {
5786
+ return sortEntries(
5787
+ Object.values(heap.entriesByPath || {}).filter(
5788
+ (entry) => entry.relatedJobIds?.includes(jobId)
5789
+ )
5790
+ );
5791
+ }
5792
+ function getJobRelatedLists(heap, jobId) {
5793
+ return sortLists(
5794
+ Object.values(heap.listsByName || {}).filter(
5795
+ (list) => list.relatedJobIds?.includes(jobId)
5796
+ )
5797
+ );
5798
+ }
5799
+ function entriesFromLists(lists, heap) {
5800
+ const entries = [];
5801
+ for (const list of lists) {
5802
+ for (const path2 of list.paths || []) {
5803
+ const entry = heap.entriesByPath?.[path2];
5804
+ if (entry) entries.push(entry);
5805
+ }
5806
+ }
5807
+ return entries;
5808
+ }
5809
+ function resolveJobPresentation({
5810
+ jobId,
5811
+ result,
5812
+ stdout = [],
5813
+ sessionHeap,
5814
+ allowExplicitArtifacts = true
5815
+ }) {
5816
+ const refs = {
5817
+ entryPaths: /* @__PURE__ */ new Set(),
5818
+ listNames: /* @__PURE__ */ new Set(),
5819
+ variableNames: /* @__PURE__ */ new Set()
5820
+ };
5821
+ if (allowExplicitArtifacts) {
5822
+ scanForHeapReferences(result, sessionHeap, refs);
5823
+ resolveVariablesToReferences(refs.variableNames, sessionHeap, refs);
5824
+ }
5825
+ const referencedLists = sortLists(
5826
+ [...refs.listNames].map((name) => sessionHeap.listsByName?.[name]).filter((list) => Boolean(list))
5827
+ );
5828
+ const referencedEntries = sortEntries(
5829
+ [...refs.entryPaths].map((path2) => sessionHeap.entriesByPath?.[path2]).filter((entry) => Boolean(entry))
5830
+ );
5831
+ const jobLists = getJobRelatedLists(sessionHeap, jobId);
5832
+ const jobEntries = getJobRelatedEntries(sessionHeap, jobId);
5833
+ const changedEntries = dedupeEntries([
5834
+ ...jobEntries,
5835
+ ...entriesFromLists(jobLists, sessionHeap)
5836
+ ]);
5837
+ const explicitLists = dedupeLists(referencedLists);
5838
+ const explicitEntries = dedupeEntries([
5839
+ ...referencedEntries,
5840
+ ...entriesFromLists(referencedLists, sessionHeap)
5841
+ ]);
5842
+ const hasExplicitArtifacts = allowExplicitArtifacts && (explicitEntries.length > 0 || explicitLists.length > 0);
5843
+ const lists = hasExplicitArtifacts ? explicitLists : jobLists;
5844
+ const entries = hasExplicitArtifacts ? explicitEntries : changedEntries;
5845
+ const responseText = extractResponseText(result, stdout) || fallbackResponseText(entries, lists);
5846
+ return {
5847
+ responseText,
5848
+ entries,
5849
+ lists,
5850
+ changedEntries,
5851
+ changedLists: jobLists,
5852
+ hasExplicitArtifacts
5853
+ };
5854
+ }
5855
+
5856
+ // src/session-transcript.ts
5857
+ var EMPTY_HEAP = {
5858
+ entriesByPath: {},
5859
+ listsByName: {},
5860
+ variablesByName: {}};
5861
+ function asRecord3(value) {
5862
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5863
+ return value;
5864
+ }
5865
+ function asArray(value) {
5866
+ return Array.isArray(value) ? value : [];
5867
+ }
5868
+ function asNumber(value) {
5869
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5870
+ }
5871
+ function asString(value) {
5872
+ return typeof value === "string" ? value : void 0;
5873
+ }
5874
+ function trimString(value) {
5875
+ return typeof value === "string" ? value.trim() : "";
5876
+ }
5877
+ function normalizeShowRefs(value) {
5878
+ const record = asRecord3(value);
5879
+ if (!record) return void 0;
5880
+ const normalizeRefs = (input) => {
5881
+ if (!Array.isArray(input)) return void 0;
5882
+ const refs = Array.from(
5883
+ new Set(
5884
+ input.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean)
5885
+ )
5886
+ );
5887
+ return refs.length > 0 ? refs : void 0;
5888
+ };
5889
+ const show = {
5890
+ entryPaths: normalizeRefs(record.entryPaths),
5891
+ listNames: normalizeRefs(record.listNames),
5892
+ variableNames: normalizeRefs(record.variableNames)
5893
+ };
5894
+ return show.entryPaths || show.listNames || show.variableNames ? show : void 0;
5895
+ }
5896
+ function stringifyTranscriptValue(value, fallback = "") {
5897
+ if (typeof value === "string") {
5898
+ return value.trim() || fallback;
5899
+ }
5900
+ if (typeof value === "boolean") {
5901
+ return value ? "Confirmed" : "Canceled";
5902
+ }
5903
+ if (value === void 0) {
5904
+ return fallback;
5905
+ }
5906
+ try {
5907
+ const json = JSON.stringify(value, null, 2);
5908
+ if (!json || json === "undefined") return fallback;
5909
+ return json.length > 2e3 ? `${json.slice(0, 2e3)}...` : json;
5910
+ } catch {
5911
+ return String(value);
5912
+ }
5913
+ }
5914
+ function buildArtifactHistory(show) {
5915
+ if (!show) return void 0;
5916
+ return `[Agent message]
5917
+ ${stringifyTranscriptValue({ show }, "")}`;
5918
+ }
5919
+ function normalizeConversationMessage(raw) {
5920
+ const record = asRecord3(raw);
5921
+ if (!record) return null;
5922
+ const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
5923
+ if (!role) return null;
5924
+ const content = trimString(
5925
+ record.content ?? record.reply ?? record.message ?? record.text
5926
+ );
5927
+ const show = normalizeShowRefs(record.show);
5928
+ const id = asString(record.id) || crypto.randomUUID();
5929
+ const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
5930
+ if (!content && !show) return null;
5931
+ return {
5932
+ id,
5933
+ role,
5934
+ content,
5935
+ timestamp,
5936
+ jobId: asString(record.jobId),
5937
+ promptId: asString(record.promptId),
5938
+ show,
5939
+ historyContent: role === "assistant" ? content ? `[Assistant reply]
5940
+ ${content}` : buildArtifactHistory(show) : void 0,
5941
+ source: "conversation"
5942
+ };
5943
+ }
5944
+ function normalizePromptEntries(jobId, rawPrompts, conversationPromptIds) {
5945
+ const promptsById = asRecord3(rawPrompts) || {};
5946
+ return Object.values(promptsById).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
5947
+ (left, right) => (asNumber(left.openedAt) || asNumber(left.answeredAt) || 0) - (asNumber(right.openedAt) || asNumber(right.answeredAt) || 0)
5948
+ ).flatMap((prompt) => {
5949
+ const promptId = asString(prompt.promptId);
5950
+ if (!promptId || conversationPromptIds.has(promptId)) return [];
5951
+ const title = trimString(prompt.title);
5952
+ const message = trimString(prompt.message);
5953
+ const assistantContent = message || title || "Input required";
5954
+ const openedAt = asNumber(prompt.openedAt) || 0;
5955
+ const answeredAt = asNumber(prompt.answeredAt) || openedAt;
5956
+ const entries = [
5957
+ {
5958
+ id: `prompt:${promptId}:assistant`,
5959
+ role: "assistant",
5960
+ content: assistantContent,
5961
+ timestamp: openedAt,
5962
+ jobId,
5963
+ promptId,
5964
+ historyContent: `[Assistant reply]
5965
+ ${assistantContent}`,
5966
+ source: "job_prompt"
5967
+ }
5968
+ ];
5969
+ if (Object.prototype.hasOwnProperty.call(prompt, "answer")) {
5970
+ entries.push({
5971
+ id: `prompt:${promptId}:user`,
5972
+ role: "user",
5973
+ content: stringifyTranscriptValue(prompt.answer, ""),
5974
+ timestamp: answeredAt,
5975
+ jobId,
5976
+ promptId,
5977
+ source: "job_prompt"
5978
+ });
5979
+ }
5980
+ return entries;
5981
+ });
5982
+ }
5983
+ function normalizeAgentMessageEntries(jobId, rawMessages) {
5984
+ return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
5985
+ (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
5986
+ ).flatMap((message) => {
5987
+ const messageId = asString(message.messageId) || asString(message.id) || crypto.randomUUID();
5988
+ const timestamp = asNumber(message.timestamp) || asNumber(message.ts) || 0;
5989
+ const reply = trimString(
5990
+ message.reply ?? message.message ?? message.text ?? message.content
5991
+ );
5992
+ const show = normalizeShowRefs(message.show);
5993
+ const entries = [];
5994
+ if (reply) {
5995
+ entries.push({
5996
+ id: `agent:${messageId}:text`,
5997
+ role: "assistant",
5998
+ content: reply,
5999
+ timestamp,
6000
+ jobId,
6001
+ historyContent: `[Assistant reply]
6002
+ ${reply}`,
6003
+ source: "job_agent_message"
6004
+ });
6005
+ }
6006
+ if (show) {
6007
+ entries.push({
6008
+ id: `agent:${messageId}:artifacts`,
6009
+ role: "assistant",
6010
+ content: "",
6011
+ timestamp,
6012
+ jobId,
6013
+ show,
6014
+ historyContent: buildArtifactHistory(show),
6015
+ source: "job_agent_message"
6016
+ });
6017
+ }
6018
+ return entries;
6019
+ });
6020
+ }
6021
+ function buildJobFallbackEntries(jobId, job, sessionHeap) {
6022
+ const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6023
+ const resultPreview = stringifyTranscriptValue(
6024
+ job.result,
6025
+ "No job result recorded."
6026
+ );
6027
+ const presentation = resolveJobPresentation({
6028
+ jobId,
6029
+ result: job.result,
6030
+ stdout: [],
6031
+ sessionHeap
6032
+ });
6033
+ const entries = [];
6034
+ const responseText = presentation.responseText || "";
6035
+ if (responseText) {
6036
+ entries.push({
6037
+ id: `job:${jobId}:result-text`,
6038
+ role: "assistant",
6039
+ content: responseText,
6040
+ timestamp,
6041
+ jobId,
6042
+ historyContent: `[Assistant reply]
6043
+ ${responseText}`,
6044
+ source: "job_result"
6045
+ });
6046
+ }
6047
+ const show = {
6048
+ entryPaths: presentation.entries.map((entry) => entry.path),
6049
+ listNames: presentation.lists.map((list) => list.name)
6050
+ };
6051
+ if (show.entryPaths && show.entryPaths.length > 0 || show.listNames && show.listNames.length > 0) {
6052
+ entries.push({
6053
+ id: `job:${jobId}:result-artifacts`,
6054
+ role: "assistant",
6055
+ content: "",
6056
+ timestamp,
6057
+ jobId,
6058
+ show,
6059
+ historyContent: buildArtifactHistory(show),
6060
+ source: "job_result"
6061
+ });
6062
+ }
6063
+ if (entries.length === 0 && trimString(job.error)) {
6064
+ entries.push({
6065
+ id: `job:${jobId}:result-error`,
6066
+ role: "assistant",
6067
+ content: trimString(job.error),
6068
+ timestamp,
6069
+ jobId,
6070
+ historyContent: `[Assistant reply]
6071
+ ${trimString(job.error)}`,
6072
+ source: "job_result"
6073
+ });
6074
+ }
6075
+ if (entries.length === 0 && resultPreview && resultPreview !== "No job result recorded.") {
6076
+ entries.push({
6077
+ id: `job:${jobId}:result-preview`,
6078
+ role: "assistant",
6079
+ content: resultPreview,
6080
+ timestamp,
6081
+ jobId,
6082
+ historyContent: `[Assistant reply]
6083
+ ${resultPreview}`,
6084
+ source: "job_result"
6085
+ });
6086
+ }
6087
+ return entries;
6088
+ }
6089
+ function buildJobCodeEntry(jobId, job) {
6090
+ const code = trimString(job.source);
6091
+ if (!code) return null;
6092
+ const jobStatus = asString(job.status);
6093
+ const error = jobStatus === "failed" || jobStatus === "canceled" || jobStatus === "timeout" ? trimString(job.error) || `Job ${jobStatus}` : void 0;
6094
+ return {
6095
+ id: `job:${jobId}:code`,
6096
+ role: "assistant",
6097
+ content: "",
6098
+ timestamp: asNumber(job.submittedAt) || asNumber(job.startedAt) || asNumber(job.finishedAt) || 0,
6099
+ jobId,
6100
+ code,
6101
+ jobStatus,
6102
+ jobResultPreview: stringifyTranscriptValue(job.result, "No job result recorded."),
6103
+ error,
6104
+ source: "job_code"
6105
+ };
6106
+ }
6107
+ function buildSessionTranscript(input) {
6108
+ const liveDoc = input.liveDoc || null;
6109
+ const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6110
+ const transcript = [];
6111
+ const conversationMessages = asArray(asRecord3(liveDoc?.conversation)?.messages).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6112
+ const conversationPromptIds = new Set(
6113
+ conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6114
+ );
6115
+ const assistantConversationJobIds = new Set(
6116
+ conversationMessages.filter((message) => message.role === "assistant" && Boolean(message.jobId)).map((message) => message.jobId)
6117
+ );
6118
+ transcript.push(...conversationMessages);
6119
+ const jobsById = asRecord3(asRecord3(liveDoc?.jobs)?.byId) || {};
6120
+ const jobs = Object.values(jobsById).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6121
+ (left, right) => (asNumber(left.submittedAt) || asNumber(left.startedAt) || asNumber(left.finishedAt) || 0) - (asNumber(right.submittedAt) || asNumber(right.startedAt) || asNumber(right.finishedAt) || 0)
6122
+ );
6123
+ for (const job of jobs) {
6124
+ const jobId = asString(job.jobId);
6125
+ if (!jobId) continue;
6126
+ const codeEntry = buildJobCodeEntry(jobId, job);
6127
+ if (codeEntry) {
6128
+ transcript.push(codeEntry);
6129
+ }
6130
+ transcript.push(
6131
+ ...normalizePromptEntries(jobId, job.prompts, conversationPromptIds)
6132
+ );
6133
+ if (!assistantConversationJobIds.has(jobId)) {
6134
+ const agentEntries = normalizeAgentMessageEntries(jobId, job.agentMessages);
6135
+ if (agentEntries.length > 0) {
6136
+ transcript.push(...agentEntries);
6137
+ } else {
6138
+ transcript.push(
6139
+ ...buildJobFallbackEntries(
6140
+ jobId,
6141
+ job,
6142
+ sessionHeap
6143
+ )
6144
+ );
6145
+ }
6146
+ }
6147
+ }
6148
+ return transcript.sort((left, right) => {
6149
+ if (left.timestamp !== right.timestamp) {
6150
+ return left.timestamp - right.timestamp;
6151
+ }
6152
+ return left.id.localeCompare(right.id);
6153
+ });
6154
+ }
6155
+
5622
6156
  // src/endpoints.ts
5623
6157
  var LOCAL_API_URL = "ws://localhost:8787/granular";
5624
6158
  var PRODUCTION_API_URL = "wss://cf-api-gateway.arthur6084.workers.dev/granular";
@@ -12158,7 +12692,9 @@ var Environment = class {
12158
12692
  { target: targetPath }
12159
12693
  );
12160
12694
  if (result.errors?.length) {
12161
- throw new Error(`attach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`);
12695
+ throw new Error(
12696
+ `attach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`
12697
+ );
12162
12698
  }
12163
12699
  }
12164
12700
  /**
@@ -12193,7 +12729,9 @@ var Environment = class {
12193
12729
  }`
12194
12730
  );
12195
12731
  if (result.errors?.length) {
12196
- throw new Error(`detach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`);
12732
+ throw new Error(
12733
+ `detach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`
12734
+ );
12197
12735
  }
12198
12736
  }
12199
12737
  /**
@@ -12315,7 +12853,9 @@ var Environment = class {
12315
12853
  async _runGraphql(query, label) {
12316
12854
  const result = await this.graphql(query);
12317
12855
  if (result.errors?.length) {
12318
- throw new Error(`${label}: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`);
12856
+ throw new Error(
12857
+ `${label}: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`
12858
+ );
12319
12859
  }
12320
12860
  return result.data;
12321
12861
  }
@@ -12850,6 +13390,9 @@ var EnvironmentSession = class extends Session {
12850
13390
  get envName() {
12851
13391
  return this.environment.envName;
12852
13392
  }
13393
+ get tag() {
13394
+ return this.environment.tag;
13395
+ }
12853
13396
  get versionId() {
12854
13397
  return this.environment.versionId;
12855
13398
  }
@@ -12869,12 +13412,171 @@ var EnvironmentSession = class extends Session {
12869
13412
  return this.environment.feedback;
12870
13413
  }
12871
13414
  /**
12872
- * Return a plain JS snapshot of the synced session heap.
13415
+ * Return a plain JS copy of the synced session heap.
12873
13416
  */
12874
13417
  getHeap() {
12875
13418
  const doc = this.document;
12876
13419
  return normalizeHeapSnapshot(doc?.heap);
12877
13420
  }
13421
+ async sessionDataRequest(path2, query) {
13422
+ const searchParams = new URLSearchParams();
13423
+ for (const [key, value] of Object.entries(query || {})) {
13424
+ if (value !== null && typeof value !== "undefined" && value !== "") {
13425
+ searchParams.set(key, String(value));
13426
+ }
13427
+ }
13428
+ const queryString = searchParams.toString();
13429
+ const response = await fetch(
13430
+ `${this.environment.runtimeBaseUrl}/orchestrator/ws/sessions/${encodeURIComponent(this.sessionId)}${path2}${queryString ? `?${queryString}` : ""}`,
13431
+ {
13432
+ method: "GET",
13433
+ headers: {
13434
+ Authorization: `Bearer ${this.environment.authToken}`,
13435
+ "Content-Type": "application/json"
13436
+ }
13437
+ }
13438
+ );
13439
+ if (!response.ok) {
13440
+ const errorText = await response.text();
13441
+ throw new Error(
13442
+ `Session data API Error (${response.status}): ${errorText}`
13443
+ );
13444
+ }
13445
+ return response.json();
13446
+ }
13447
+ async collectAllSessionItems(listPage) {
13448
+ const items = [];
13449
+ let cursor = null;
13450
+ do {
13451
+ const page = await listPage({ limit: 500, cursor });
13452
+ items.push(...page.items);
13453
+ cursor = page.nextCursor;
13454
+ } while (cursor);
13455
+ return items;
13456
+ }
13457
+ /**
13458
+ * Fetch the live session document from the runtime DO.
13459
+ *
13460
+ * For history and saved artifacts, prefer the collection APIs on
13461
+ * `messages`, `timeline`, `jobs`, and `heap`.
13462
+ */
13463
+ async getDocument() {
13464
+ return this.sessionDataRequest("/document");
13465
+ }
13466
+ get messages() {
13467
+ return {
13468
+ list: (options = {}) => this.sessionDataRequest(
13469
+ "/messages",
13470
+ options
13471
+ )
13472
+ };
13473
+ }
13474
+ get timeline() {
13475
+ return {
13476
+ list: (options = {}) => this.sessionDataRequest(
13477
+ "/timeline",
13478
+ options
13479
+ )
13480
+ };
13481
+ }
13482
+ get jobs() {
13483
+ return {
13484
+ list: (options = {}) => this.sessionDataRequest(
13485
+ "/jobs",
13486
+ options
13487
+ ),
13488
+ get: (jobId) => this.sessionDataRequest(
13489
+ `/jobs/${encodeURIComponent(jobId)}`
13490
+ )
13491
+ };
13492
+ }
13493
+ get heap() {
13494
+ return {
13495
+ entries: {
13496
+ list: (options = {}) => this.sessionDataRequest(
13497
+ "/heap/entries",
13498
+ options
13499
+ ),
13500
+ get: (path2) => this.sessionDataRequest(
13501
+ `/heap/entries/${encodeURIComponent(path2)}`
13502
+ )
13503
+ },
13504
+ lists: {
13505
+ list: (options = {}) => this.sessionDataRequest(
13506
+ "/heap/lists",
13507
+ options
13508
+ ),
13509
+ get: (name) => this.sessionDataRequest(
13510
+ `/heap/lists/${encodeURIComponent(name)}`
13511
+ )
13512
+ }
13513
+ };
13514
+ }
13515
+ get transcript() {
13516
+ return {
13517
+ list: async (options = {}) => {
13518
+ const [messages, jobs, entries, lists] = await Promise.all([
13519
+ this.collectAllSessionItems(this.messages.list),
13520
+ this.collectAllSessionItems(
13521
+ (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
13522
+ ),
13523
+ this.collectAllSessionItems(this.heap.entries.list),
13524
+ this.collectAllSessionItems(this.heap.lists.list)
13525
+ ]);
13526
+ const liveDoc = {
13527
+ conversation: { messages },
13528
+ jobs: {
13529
+ byId: Object.fromEntries(
13530
+ jobs.map((job) => {
13531
+ const record = job && typeof job === "object" ? job : null;
13532
+ const id = typeof record?.jobId === "string" ? record.jobId : typeof record?.id === "string" ? record.id : null;
13533
+ return id ? [id, record] : null;
13534
+ }).filter(
13535
+ (entry) => Boolean(entry)
13536
+ )
13537
+ )
13538
+ }
13539
+ };
13540
+ const heap = normalizeHeapSnapshot({
13541
+ entriesByPath: Object.fromEntries(
13542
+ entries.map((entry) => {
13543
+ return entry?.path ? [
13544
+ entry.path,
13545
+ entry
13546
+ ] : null;
13547
+ }).filter(
13548
+ (entry) => Boolean(entry)
13549
+ )
13550
+ ),
13551
+ listsByName: Object.fromEntries(
13552
+ lists.map((list) => {
13553
+ return list?.name ? [list.name, list] : null;
13554
+ }).filter(
13555
+ (entry) => Boolean(entry)
13556
+ )
13557
+ ),
13558
+ variablesByName: this.getHeap().variablesByName,
13559
+ updatedAt: Date.now()
13560
+ });
13561
+ const allItems = buildSessionTranscript({
13562
+ liveDoc,
13563
+ sessionHeap: heap
13564
+ });
13565
+ const limit = Math.max(
13566
+ 1,
13567
+ Math.min(500, Math.floor(options.limit ?? 100))
13568
+ );
13569
+ const offset = typeof options.cursor === "string" ? Number.parseInt(options.cursor, 10) || 0 : 0;
13570
+ const items = allItems.slice(offset, offset + limit);
13571
+ const nextOffset = offset + items.length;
13572
+ return {
13573
+ items,
13574
+ nextCursor: nextOffset < allItems.length ? String(nextOffset) : null,
13575
+ totalCount: allItems.length
13576
+ };
13577
+ }
13578
+ };
13579
+ }
12878
13580
  async graphql(query, variables) {
12879
13581
  return this.environment.graphql(query, variables);
12880
13582
  }
@@ -14084,15 +14786,15 @@ var Granular = class _Granular {
14084
14786
  };
14085
14787
 
14086
14788
  // src/agent-harness.ts
14087
- function asRecord2(value) {
14789
+ function asRecord4(value) {
14088
14790
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
14089
14791
  return value;
14090
14792
  }
14091
- function asArray(value) {
14793
+ function asArray2(value) {
14092
14794
  return Array.isArray(value) ? value : [];
14093
14795
  }
14094
14796
  function toSortedRecords(value) {
14095
- return Object.values(asRecord2(value) || {}).map((entry) => asRecord2(entry)).filter((entry) => Boolean(entry));
14797
+ return Object.values(asRecord4(value) || {}).map((entry) => asRecord4(entry)).filter((entry) => Boolean(entry));
14096
14798
  }
14097
14799
  function uniqueStrings(values, maxCount) {
14098
14800
  const seen = /* @__PURE__ */ new Set();
@@ -14118,7 +14820,7 @@ function describeHeapEntry(entry, previewFieldLimit = 3) {
14118
14820
  const headline = entry.label || entry.id || entry.path || "Unknown";
14119
14821
  const pathLabel = entry.path && entry.path !== headline ? ` <${entry.path}>` : "";
14120
14822
  const classLabel = entry.className || "unknown";
14121
- const preview = asArray(entry.fields).filter(
14823
+ const preview = asArray2(entry.fields).filter(
14122
14824
  (field) => field?.name && field.name !== "_realId" && field.name !== "real_id"
14123
14825
  ).slice(0, previewFieldLimit).map((field) => `${field.name}=${formatScalar(field.value)}`).join(", ");
14124
14826
  return preview ? `${headline}${pathLabel} [${classLabel}] ${preview}` : `${headline}${pathLabel} [${classLabel}]`;
@@ -14255,14 +14957,14 @@ function normalizeActionSummaryForPrompt(line) {
14255
14957
  return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
14256
14958
  }
14257
14959
  function getCurrentClosureId(liveDoc) {
14258
- const loop = asRecord2(liveDoc?.loop);
14960
+ const loop = asRecord4(liveDoc?.loop);
14259
14961
  return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
14260
14962
  }
14261
14963
  function getLatestClosure(liveDoc) {
14262
- const loop = asRecord2(liveDoc?.loop);
14964
+ const loop = asRecord4(liveDoc?.loop);
14263
14965
  const currentClosureId = getCurrentClosureId(liveDoc);
14264
- const closuresById = asRecord2(loop?.closuresById) || {};
14265
- const currentClosure = currentClosureId ? asRecord2(closuresById[currentClosureId]) : null;
14966
+ const closuresById = asRecord4(loop?.closuresById) || {};
14967
+ const currentClosure = currentClosureId ? asRecord4(closuresById[currentClosureId]) : null;
14266
14968
  if (currentClosure) {
14267
14969
  return {
14268
14970
  ...currentClosure,
@@ -14271,7 +14973,7 @@ function getLatestClosure(liveDoc) {
14271
14973
  }
14272
14974
  const closures = [];
14273
14975
  for (const [closureId, value] of Object.entries(closuresById)) {
14274
- const record = asRecord2(value);
14976
+ const record = asRecord4(value);
14275
14977
  if (!record) continue;
14276
14978
  closures.push({ ...record, closureId });
14277
14979
  }
@@ -14308,10 +15010,10 @@ function getJobTimestamp(job) {
14308
15010
  return Number(job.finishedAt) || Number(job.startedAt) || Number(job.submittedAt) || 0;
14309
15011
  }
14310
15012
  function getJobRecords(liveDoc) {
14311
- const jobsById = asRecord2(asRecord2(liveDoc?.jobs)?.byId) || {};
15013
+ const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
14312
15014
  const jobs = [];
14313
15015
  for (const [jobId, value] of Object.entries(jobsById)) {
14314
- const record = asRecord2(value);
15016
+ const record = asRecord4(value);
14315
15017
  if (!record) continue;
14316
15018
  jobs.push({ ...record, jobId });
14317
15019
  }
@@ -14321,7 +15023,7 @@ function getJobRecords(liveDoc) {
14321
15023
  return jobs;
14322
15024
  }
14323
15025
  function getPromptRecordsFromJobs(liveDoc) {
14324
- return getJobRecords(liveDoc).flatMap((job) => Object.values(asRecord2(job.prompts) || {})).map((prompt) => asRecord2(prompt)).filter((prompt) => Boolean(prompt));
15026
+ return getJobRecords(liveDoc).flatMap((job) => Object.values(asRecord4(job.prompts) || {})).map((prompt) => asRecord4(prompt)).filter((prompt) => Boolean(prompt));
14325
15027
  }
14326
15028
  function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14327
15029
  const boundary = getWorkflowBoundary(liveDoc, options);
@@ -14336,15 +15038,15 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14336
15038
  const openDecisionIds = [];
14337
15039
  const openPromptIds = [];
14338
15040
  for (const job of jobs) {
14339
- for (const line of asArray(job.actionSummary)) {
15041
+ for (const line of asArray2(job.actionSummary)) {
14340
15042
  if (typeof line === "string" && line.trim()) {
14341
15043
  actionSummaryLines.push(line.trim());
14342
15044
  }
14343
15045
  }
14344
- for (const rawEvent of asArray(job.actionTrace)) {
14345
- const event = asRecord2(rawEvent);
14346
- const details = asRecord2(event?.details);
14347
- const outcome = asRecord2(event?.outcome);
15046
+ for (const rawEvent of asArray2(job.actionTrace)) {
15047
+ const event = asRecord4(rawEvent);
15048
+ const details = asRecord4(event?.details);
15049
+ const outcome = asRecord4(event?.outcome);
14348
15050
  if (typeof details?.name === "string") {
14349
15051
  variableNames.push(details.name);
14350
15052
  }
@@ -14371,7 +15073,7 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14371
15073
  }
14372
15074
  }
14373
15075
  }
14374
- const loop = asRecord2(liveDoc?.loop);
15076
+ const loop = asRecord4(liveDoc?.loop);
14375
15077
  const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
14376
15078
  const updatedAt = Number(task.updatedAt) || Number(task.createdAt) || 0;
14377
15079
  const status = typeof task.status === "string" ? task.status : "pending";
@@ -14419,15 +15121,15 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14419
15121
  openPromptIds.push(prompt.id);
14420
15122
  }
14421
15123
  }
14422
- const heap = asRecord2(liveDoc?.heap);
14423
- const variablesByName = asRecord2(heap?.variablesByName) || {};
14424
- const listsByName = asRecord2(heap?.listsByName) || {};
15124
+ const heap = asRecord4(liveDoc?.heap);
15125
+ const variablesByName = asRecord4(heap?.variablesByName) || {};
15126
+ const listsByName = asRecord4(heap?.listsByName) || {};
14425
15127
  const recentHints = extractFocusHintsFromActionSummary(actionSummaryLines);
14426
15128
  variableNames.push(...recentHints.variableNames);
14427
15129
  listNames.push(...recentHints.listNames);
14428
15130
  entryPaths.push(...recentHints.entryPaths);
14429
15131
  for (const variableName of uniqueStrings(variableNames)) {
14430
- const variable = asRecord2(variablesByName[variableName]);
15132
+ const variable = asRecord4(variablesByName[variableName]);
14431
15133
  if (!variable) continue;
14432
15134
  if (typeof variable.listName === "string") {
14433
15135
  listNames.push(variable.listName);
@@ -14437,13 +15139,13 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14437
15139
  }
14438
15140
  }
14439
15141
  for (const listName of uniqueStrings(listNames)) {
14440
- const list = asRecord2(listsByName[listName]);
14441
- for (const path2 of asArray(list?.paths).slice(0, 4)) {
15142
+ const list = asRecord4(listsByName[listName]);
15143
+ for (const path2 of asArray2(list?.paths).slice(0, 4)) {
14442
15144
  entryPaths.push(path2);
14443
15145
  }
14444
15146
  }
14445
15147
  if (variableNames.length === 0 && boundary.reason !== "request_start") {
14446
- const recentVariables = Object.values(variablesByName).map((value) => asRecord2(value)).filter((value) => Boolean(value)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, 3);
15148
+ const recentVariables = Object.values(variablesByName).map((value) => asRecord4(value)).filter((value) => Boolean(value)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, 3);
14447
15149
  for (const variable of recentVariables) {
14448
15150
  if (typeof variable.name === "string") {
14449
15151
  variableNames.push(variable.name);
@@ -14526,12 +15228,12 @@ function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
14526
15228
  }
14527
15229
  function hasOpenPrompt(liveDoc, pendingPrompts) {
14528
15230
  if (pendingPrompts.length > 0) return true;
14529
- const jobsById = asRecord2(asRecord2(liveDoc?.jobs)?.byId) || {};
15231
+ const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
14530
15232
  for (const job of Object.values(jobsById)) {
14531
- const prompts = asRecord2(asRecord2(job)?.prompts);
15233
+ const prompts = asRecord4(asRecord4(job)?.prompts);
14532
15234
  if (!prompts) continue;
14533
15235
  for (const prompt of Object.values(prompts)) {
14534
- const record = asRecord2(prompt);
15236
+ const record = asRecord4(prompt);
14535
15237
  if (record?.status === "open") return true;
14536
15238
  }
14537
15239
  }
@@ -14539,7 +15241,7 @@ function hasOpenPrompt(liveDoc, pendingPrompts) {
14539
15241
  }
14540
15242
  function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14541
15243
  const lines = [];
14542
- const loop = asRecord2(liveDoc?.loop);
15244
+ const loop = asRecord4(liveDoc?.loop);
14543
15245
  const boundary = getWorkflowBoundary(liveDoc, options);
14544
15246
  const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
14545
15247
  const updatedAt = Number(task.updatedAt) || Number(task.createdAt) || 0;
@@ -14599,8 +15301,8 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14599
15301
  const title = typeof decision.title === "string" && decision.title.trim() ? decision.title.trim() : "Decision";
14600
15302
  const decisionId = typeof decision.decisionId === "string" ? decision.decisionId : "unknown";
14601
15303
  if (status === "open") {
14602
- const candidatePreview = asArray(decision.candidates).slice(0, 3).map((candidate) => {
14603
- const record = asRecord2(candidate);
15304
+ const candidatePreview = asArray2(decision.candidates).slice(0, 3).map((candidate) => {
15305
+ const record = asRecord4(candidate);
14604
15306
  if (!record) return null;
14605
15307
  const candidateId = typeof record.id === "string" ? record.id : "unknown";
14606
15308
  const candidateLabel = typeof record.label === "string" && record.label.trim() ? record.label.trim() : candidateId;
@@ -14610,7 +15312,7 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14610
15312
  `- [open] ${title} (${decisionId})${candidatePreview ? ` \u2014 candidates: ${candidatePreview}` : ""}`
14611
15313
  );
14612
15314
  } else {
14613
- const selected = asRecord2(decision.selected);
15315
+ const selected = asRecord4(decision.selected);
14614
15316
  const label = typeof selected?.label === "string" ? selected.label : typeof selected?.id === "string" ? selected.id : "unknown";
14615
15317
  lines.push(`- [resolved] ${title} (${decisionId}) -> ${label}`);
14616
15318
  }
@@ -14623,12 +15325,12 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14623
15325
  title: prompt.title,
14624
15326
  message: prompt.message
14625
15327
  })),
14626
- ...Object.values(asRecord2(asRecord2(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord2(asRecord2(job)?.prompts) || {})).map((prompt) => asRecord2(prompt)).filter(
15328
+ ...Object.values(asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord4(asRecord4(job)?.prompts) || {})).map((prompt) => asRecord4(prompt)).filter(
14627
15329
  (prompt) => Boolean(prompt && prompt.status === "open")
14628
15330
  )
14629
15331
  ];
14630
15332
  const visiblePrompts = boundary.reason === "request_start" ? openPrompts.filter((prompt) => {
14631
- const promptRecord = asRecord2(prompt);
15333
+ const promptRecord = asRecord4(prompt);
14632
15334
  const openedAt = Number(promptRecord?.openedAt) || 0;
14633
15335
  const promptId = typeof promptRecord?.id === "string" ? promptRecord.id : typeof prompt.id === "string" ? prompt.id : null;
14634
15336
  return openedAt >= boundary.timestamp || (promptId ? pendingPrompts.some(
@@ -14647,7 +15349,7 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14647
15349
  }
14648
15350
  }
14649
15351
  const currentClosureId = getCurrentClosureId(liveDoc);
14650
- const closureRecord = currentClosureId ? asRecord2(asRecord2(loop?.closuresById)?.[currentClosureId]) : null;
15352
+ const closureRecord = currentClosureId ? asRecord4(asRecord4(loop?.closuresById)?.[currentClosureId]) : null;
14651
15353
  const visibleClosure = closureRecord && (boundary.reason !== "request_start" || (Number(closureRecord.createdAt) || 0) >= boundary.timestamp) ? closureRecord : null;
14652
15354
  lines.push("", "Loop Closure:");
14653
15355
  if (visibleClosure) {
@@ -14660,10 +15362,10 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14660
15362
  return lines.join("\n");
14661
15363
  }
14662
15364
  function projectHeapSummary(heap, options) {
14663
- const heapRecord = asRecord2(heap) || {};
14664
- const entriesByPath = asRecord2(heapRecord.entriesByPath) || {};
14665
- const listsByName = asRecord2(heapRecord.listsByName) || {};
14666
- const variablesByName = asRecord2(heapRecord.variablesByName) || {};
15365
+ const heapRecord = asRecord4(heap) || {};
15366
+ const entriesByPath = asRecord4(heapRecord.entriesByPath) || {};
15367
+ const listsByName = asRecord4(heapRecord.listsByName) || {};
15368
+ const variablesByName = asRecord4(heapRecord.variablesByName) || {};
14667
15369
  const focusedVariableNames = new Set(
14668
15370
  uniqueStrings(options?.focus?.variableNames || [])
14669
15371
  );
@@ -14678,7 +15380,7 @@ function projectHeapSummary(heap, options) {
14678
15380
  const maxVariables = options?.maxVariables ?? (hasFocus ? 4 : 6);
14679
15381
  const maxLists = options?.maxLists ?? (hasFocus ? 3 : 4);
14680
15382
  const maxEntries = options?.maxEntries ?? (hasFocus ? 5 : 6);
14681
- const variables = suppressRecentFallback ? [] : Object.values(variablesByName).map((value) => asRecord2(value)).filter((value) => Boolean(value)).sort((left, right) => {
15383
+ const variables = suppressRecentFallback ? [] : Object.values(variablesByName).map((value) => asRecord4(value)).filter((value) => Boolean(value)).sort((left, right) => {
14682
15384
  const leftFocused = left.name && focusedVariableNames.has(left.name) ? 1 : 0;
14683
15385
  const rightFocused = right.name && focusedVariableNames.has(right.name) ? 1 : 0;
14684
15386
  return rightFocused - leftFocused || (right.updatedAt || 0) - (left.updatedAt || 0);
@@ -14692,7 +15394,7 @@ function projectHeapSummary(heap, options) {
14692
15394
  for (const variable of variables) {
14693
15395
  if (variable.entryPath) referencedPaths.add(variable.entryPath);
14694
15396
  if (variable.listName) {
14695
- const list = asRecord2(
15397
+ const list = asRecord4(
14696
15398
  listsByName[variable.listName]
14697
15399
  );
14698
15400
  for (const path2 of list?.paths || []) referencedPaths.add(path2);
@@ -14701,10 +15403,10 @@ function projectHeapSummary(heap, options) {
14701
15403
  for (const path2 of focusedEntryPaths) {
14702
15404
  referencedPaths.add(path2);
14703
15405
  }
14704
- const visibleLists = Object.values(listsByName).map((value) => asRecord2(value)).filter((value) => Boolean(value)).filter(
15406
+ const visibleLists = Object.values(listsByName).map((value) => asRecord4(value)).filter((value) => Boolean(value)).filter(
14705
15407
  (list) => variables.some((variable) => variable.listName === list.name) || Boolean(list.name && focusedListNames.has(list.name))
14706
15408
  ).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxLists);
14707
- const visibleEntries = Object.values(entriesByPath).map((value) => asRecord2(value)).filter((value) => Boolean(value)).filter((entry) => entry.path && referencedPaths.has(entry.path)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxEntries);
15409
+ const visibleEntries = Object.values(entriesByPath).map((value) => asRecord4(value)).filter((value) => Boolean(value)).filter((entry) => entry.path && referencedPaths.has(entry.path)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxEntries);
14708
15410
  const lines = [];
14709
15411
  lines.push("Variables:");
14710
15412
  if (variables.length === 0) {
@@ -14718,7 +15420,7 @@ function projectHeapSummary(heap, options) {
14718
15420
  continue;
14719
15421
  }
14720
15422
  if (variable.kind === "entry") {
14721
- const entry = variable.entryPath ? asRecord2(
15423
+ const entry = variable.entryPath ? asRecord4(
14722
15424
  entriesByPath[variable.entryPath]
14723
15425
  ) : null;
14724
15426
  lines.push(
@@ -14726,7 +15428,7 @@ function projectHeapSummary(heap, options) {
14726
15428
  );
14727
15429
  continue;
14728
15430
  }
14729
- const list = variable.listName ? asRecord2(listsByName[variable.listName]) : null;
15431
+ const list = variable.listName ? asRecord4(listsByName[variable.listName]) : null;
14730
15432
  lines.push(
14731
15433
  `- ${variable.name}: list<${variable.className || list?.className || "unknown"}> -> ${(list?.paths || []).length} item(s)`
14732
15434
  );
@@ -14759,7 +15461,7 @@ function createHarnessVerifierSnapshot(input) {
14759
15461
  input.projectionOptions
14760
15462
  );
14761
15463
  const heapDigest = hashString(
14762
- projectHeapSummary(asRecord2(input.liveDoc?.heap), {
15464
+ projectHeapSummary(asRecord4(input.liveDoc?.heap), {
14763
15465
  focus: workflowFocus
14764
15466
  })
14765
15467
  ) || "00000000";
@@ -15085,246 +15787,12 @@ ${loopBlock}
15085
15787
  - Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
15086
15788
  }
15087
15789
 
15088
- // src/job-presentation.ts
15089
- var RESPONSE_KEYS = [
15090
- "reply",
15091
- "response",
15092
- "text",
15093
- "message",
15094
- "summary",
15095
- "answer"
15096
- ];
15097
- var ENTRY_KEY_CANDIDATES = ["entryPath", "path"];
15098
- var ENTRY_ARRAY_KEY_CANDIDATES = ["entryPaths", "paths"];
15099
- var LIST_KEY_CANDIDATES = ["listName"];
15100
- var LIST_ARRAY_KEY_CANDIDATES = ["listNames"];
15101
- var VARIABLE_KEY_CANDIDATES = ["variableName"];
15102
- var VARIABLE_ARRAY_KEY_CANDIDATES = ["variableNames"];
15103
- var UI_CONTAINER_KEYS = ["show", "display", "present", "ui"];
15104
- function asRecord3(value) {
15105
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
15106
- return value;
15107
- }
15108
- function normalizeText(value) {
15109
- if (typeof value !== "string") return null;
15110
- const trimmed = value.trim();
15111
- if (!trimmed) return null;
15112
- if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
15113
- return null;
15114
- }
15115
- return trimmed;
15116
- }
15117
- function humanTextFromStdout(stdout) {
15118
- for (const line of [...stdout].reverse()) {
15119
- const normalized = normalizeText(line);
15120
- if (!normalized) continue;
15121
- if (/^[A-Z_]+:/.test(normalized)) continue;
15122
- return normalized;
15123
- }
15124
- return null;
15125
- }
15126
- function pushString(target, value) {
15127
- if (typeof value === "string" && value.trim()) {
15128
- target.add(value.trim());
15129
- }
15130
- }
15131
- function pushStringArray(target, value) {
15132
- if (!Array.isArray(value)) return;
15133
- for (const item of value) {
15134
- pushString(target, item);
15135
- }
15136
- }
15137
- function collectReferencesFromRecord(record, refs) {
15138
- for (const key of ENTRY_KEY_CANDIDATES)
15139
- pushString(refs.entryPaths, record[key]);
15140
- for (const key of ENTRY_ARRAY_KEY_CANDIDATES)
15141
- pushStringArray(refs.entryPaths, record[key]);
15142
- for (const key of LIST_KEY_CANDIDATES)
15143
- pushString(refs.listNames, record[key]);
15144
- for (const key of LIST_ARRAY_KEY_CANDIDATES)
15145
- pushStringArray(refs.listNames, record[key]);
15146
- for (const key of VARIABLE_KEY_CANDIDATES)
15147
- pushString(refs.variableNames, record[key]);
15148
- for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
15149
- pushStringArray(refs.variableNames, record[key]);
15150
- }
15151
- function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__ */ new Set()) {
15152
- if (value === null || value === void 0 || depth > 4 || seen.has(value))
15153
- return;
15154
- if (typeof value === "string") {
15155
- const trimmed = value.trim();
15156
- if (heap.entriesByPath?.[trimmed]) refs.entryPaths.add(trimmed);
15157
- if (heap.listsByName?.[trimmed]) refs.listNames.add(trimmed);
15158
- if (heap.variablesByName?.[trimmed]) refs.variableNames.add(trimmed);
15159
- return;
15160
- }
15161
- if (Array.isArray(value)) {
15162
- seen.add(value);
15163
- for (const item of value.slice(0, 24)) {
15164
- scanForHeapReferences(item, heap, refs, depth + 1, seen);
15165
- }
15166
- return;
15167
- }
15168
- const record = asRecord3(value);
15169
- if (!record) return;
15170
- seen.add(value);
15171
- collectReferencesFromRecord(record, refs);
15172
- for (const key of UI_CONTAINER_KEYS) {
15173
- const nested = asRecord3(record[key]);
15174
- if (nested) collectReferencesFromRecord(nested, refs);
15175
- }
15176
- for (const nested of Object.values(record).slice(0, 24)) {
15177
- scanForHeapReferences(nested, heap, refs, depth + 1, seen);
15178
- }
15179
- }
15180
- function resolveVariablesToReferences(variableNames, heap, refs) {
15181
- for (const variableName of variableNames) {
15182
- const variable = heap.variablesByName?.[variableName];
15183
- if (!variable) continue;
15184
- if (variable.kind === "entry" && variable.entryPath) {
15185
- refs.entryPaths.add(variable.entryPath);
15186
- }
15187
- if (variable.kind === "list" && variable.listName) {
15188
- refs.listNames.add(variable.listName);
15189
- }
15190
- }
15191
- }
15192
- function sortEntries(entries) {
15193
- return [...entries].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
15194
- }
15195
- function sortLists(lists) {
15196
- return [...lists].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
15197
- }
15198
- function dedupeEntries(entries) {
15199
- const seen = /* @__PURE__ */ new Set();
15200
- const result = [];
15201
- for (const entry of entries) {
15202
- if (!entry?.path || seen.has(entry.path)) continue;
15203
- seen.add(entry.path);
15204
- result.push(entry);
15205
- }
15206
- return result;
15207
- }
15208
- function dedupeLists(lists) {
15209
- const seen = /* @__PURE__ */ new Set();
15210
- const result = [];
15211
- for (const list of lists) {
15212
- if (!list?.name || seen.has(list.name)) continue;
15213
- seen.add(list.name);
15214
- result.push(list);
15215
- }
15216
- return result;
15217
- }
15218
- function extractResponseText(result, stdout) {
15219
- const directText = normalizeText(result);
15220
- if (directText) return directText;
15221
- const record = asRecord3(result);
15222
- if (record) {
15223
- for (const key of RESPONSE_KEYS) {
15224
- const normalized = normalizeText(record[key]);
15225
- if (normalized) return normalized;
15226
- }
15227
- for (const containerKey of UI_CONTAINER_KEYS) {
15228
- const nested = asRecord3(record[containerKey]);
15229
- if (!nested) continue;
15230
- for (const key of RESPONSE_KEYS) {
15231
- const normalized = normalizeText(nested[key]);
15232
- if (normalized) return normalized;
15233
- }
15234
- }
15235
- }
15236
- return humanTextFromStdout(stdout);
15237
- }
15238
- function fallbackResponseText(entries, lists) {
15239
- if (entries.length > 0) {
15240
- return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
15241
- }
15242
- if (lists.length > 0) {
15243
- const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
15244
- if (emptyOnly) {
15245
- return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
15246
- }
15247
- return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
15248
- }
15249
- return null;
15250
- }
15251
- function getJobRelatedEntries(heap, jobId) {
15252
- return sortEntries(
15253
- Object.values(heap.entriesByPath || {}).filter(
15254
- (entry) => entry.relatedJobIds?.includes(jobId)
15255
- )
15256
- );
15257
- }
15258
- function getJobRelatedLists(heap, jobId) {
15259
- return sortLists(
15260
- Object.values(heap.listsByName || {}).filter(
15261
- (list) => list.relatedJobIds?.includes(jobId)
15262
- )
15263
- );
15264
- }
15265
- function entriesFromLists(lists, heap) {
15266
- const entries = [];
15267
- for (const list of lists) {
15268
- for (const path2 of list.paths || []) {
15269
- const entry = heap.entriesByPath?.[path2];
15270
- if (entry) entries.push(entry);
15271
- }
15272
- }
15273
- return entries;
15274
- }
15275
- function resolveJobPresentation({
15276
- jobId,
15277
- result,
15278
- stdout = [],
15279
- sessionHeap,
15280
- allowExplicitArtifacts = true
15281
- }) {
15282
- const refs = {
15283
- entryPaths: /* @__PURE__ */ new Set(),
15284
- listNames: /* @__PURE__ */ new Set(),
15285
- variableNames: /* @__PURE__ */ new Set()
15286
- };
15287
- if (allowExplicitArtifacts) {
15288
- scanForHeapReferences(result, sessionHeap, refs);
15289
- resolveVariablesToReferences(refs.variableNames, sessionHeap, refs);
15290
- }
15291
- const referencedLists = sortLists(
15292
- [...refs.listNames].map((name) => sessionHeap.listsByName?.[name]).filter((list) => Boolean(list))
15293
- );
15294
- const referencedEntries = sortEntries(
15295
- [...refs.entryPaths].map((path2) => sessionHeap.entriesByPath?.[path2]).filter((entry) => Boolean(entry))
15296
- );
15297
- const jobLists = getJobRelatedLists(sessionHeap, jobId);
15298
- const jobEntries = getJobRelatedEntries(sessionHeap, jobId);
15299
- const changedEntries = dedupeEntries([
15300
- ...jobEntries,
15301
- ...entriesFromLists(jobLists, sessionHeap)
15302
- ]);
15303
- const explicitLists = dedupeLists(referencedLists);
15304
- const explicitEntries = dedupeEntries([
15305
- ...referencedEntries,
15306
- ...entriesFromLists(referencedLists, sessionHeap)
15307
- ]);
15308
- const hasExplicitArtifacts = allowExplicitArtifacts && (explicitEntries.length > 0 || explicitLists.length > 0);
15309
- const lists = hasExplicitArtifacts ? explicitLists : jobLists;
15310
- const entries = hasExplicitArtifacts ? explicitEntries : changedEntries;
15311
- const responseText = extractResponseText(result, stdout) || fallbackResponseText(entries, lists);
15312
- return {
15313
- responseText,
15314
- entries,
15315
- lists,
15316
- changedEntries,
15317
- changedLists: jobLists,
15318
- hasExplicitArtifacts
15319
- };
15320
- }
15321
-
15322
15790
  // src/agent-evals.ts
15323
15791
  var DEFAULT_CONTROLLER_BUDGETS = {
15324
15792
  maxIterations: 6,
15325
15793
  maxNoProgressIterations: 2
15326
15794
  };
15327
- function asRecord4(value) {
15795
+ function asRecord5(value) {
15328
15796
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
15329
15797
  return value;
15330
15798
  }
@@ -15372,7 +15840,7 @@ function buildArtifactDir(baseDir, suiteName = "granular-agent-evals") {
15372
15840
  function createTimestampedArtifactDirectory(options) {
15373
15841
  return buildArtifactDir(options?.baseDir, options?.suiteName);
15374
15842
  }
15375
- function asArray2(value) {
15843
+ function asArray3(value) {
15376
15844
  if (!value) return [];
15377
15845
  return Array.isArray(value) ? value : [value];
15378
15846
  }
@@ -15390,7 +15858,7 @@ function buildScenarioSteps(scenario) {
15390
15858
  request: scenario.request,
15391
15859
  human: scenario.human,
15392
15860
  expect: scenario.expect,
15393
- inspect: [...asArray2(scenario.inspect), ...asArray2(scenario.verify)],
15861
+ inspect: [...asArray3(scenario.inspect), ...asArray3(scenario.verify)],
15394
15862
  check: scenario.check,
15395
15863
  maxIterations: scenario.maxIterations,
15396
15864
  setup: {
@@ -15431,12 +15899,12 @@ function buildHistory(entries) {
15431
15899
  );
15432
15900
  }
15433
15901
  function getOpenPromptsFromDoc(liveDoc) {
15434
- const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
15902
+ const jobsById = asRecord5(asRecord5(liveDoc?.jobs)?.byId) || {};
15435
15903
  const prompts = [];
15436
15904
  for (const job of Object.values(jobsById)) {
15437
- const promptRecords = asRecord4(asRecord4(job)?.prompts) || {};
15905
+ const promptRecords = asRecord5(asRecord5(job)?.prompts) || {};
15438
15906
  for (const raw of Object.values(promptRecords)) {
15439
- const record = asRecord4(raw);
15907
+ const record = asRecord5(raw);
15440
15908
  if (!record || record.status !== "open" || typeof record.promptId !== "string")
15441
15909
  continue;
15442
15910
  const prompt = normalizePrompt({
@@ -15457,11 +15925,11 @@ function getOpenPromptsFromDoc(liveDoc) {
15457
15925
  return prompts;
15458
15926
  }
15459
15927
  function filterPromptsByBoundary(liveDoc, prompts, boundaryTimestamp) {
15460
- const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
15928
+ const jobsById = asRecord5(asRecord5(liveDoc?.jobs)?.byId) || {};
15461
15929
  return prompts.filter((prompt) => {
15462
15930
  for (const jobRecord of Object.values(jobsById)) {
15463
- const promptsById = asRecord4(asRecord4(jobRecord)?.prompts) || {};
15464
- const promptRecord = asRecord4(promptsById[prompt.id]);
15931
+ const promptsById = asRecord5(asRecord5(jobRecord)?.prompts) || {};
15932
+ const promptRecord = asRecord5(promptsById[prompt.id]);
15465
15933
  const openedAt = Number(promptRecord?.openedAt) || 0;
15466
15934
  if (openedAt >= boundaryTimestamp) return true;
15467
15935
  }
@@ -15554,10 +16022,10 @@ ${modelOutputInstruction()}`
15554
16022
  );
15555
16023
  }
15556
16024
  const raw = await response.json();
15557
- const content = asRecord4(
15558
- asRecord4(raw.choices?.[0])?.message
16025
+ const content = asRecord5(
16026
+ asRecord5(raw.choices?.[0])?.message
15559
16027
  )?.content;
15560
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord4(part)?.text || "").join("") : "";
16028
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord5(part)?.text || "").join("") : "";
15561
16029
  const parsed = extractJsonObject(text);
15562
16030
  if (!parsed) {
15563
16031
  if (attempt < 3) {
@@ -15609,17 +16077,17 @@ async function withTimeout(promise, ms, label) {
15609
16077
  }
15610
16078
  }
15611
16079
  function getActionSummary(liveDoc, jobId) {
15612
- const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
15613
- const job = asRecord4(jobsById[jobId]);
16080
+ const jobsById = asRecord5(asRecord5(liveDoc?.jobs)?.byId) || {};
16081
+ const job = asRecord5(jobsById[jobId]);
15614
16082
  return Array.isArray(job?.actionSummary) ? job.actionSummary.filter(
15615
16083
  (line) => typeof line === "string"
15616
16084
  ) : [];
15617
16085
  }
15618
16086
  function normalizeHeapSnapshot2(heap) {
15619
16087
  return {
15620
- entriesByPath: asRecord4(heap?.entriesByPath) || {},
15621
- listsByName: asRecord4(heap?.listsByName) || {},
15622
- variablesByName: asRecord4(heap?.variablesByName) || {},
16088
+ entriesByPath: asRecord5(heap?.entriesByPath) || {},
16089
+ listsByName: asRecord5(heap?.listsByName) || {},
16090
+ variablesByName: asRecord5(heap?.variablesByName) || {},
15623
16091
  updatedAt: typeof heap?.updatedAt === "number" ? heap.updatedAt : Date.now()
15624
16092
  };
15625
16093
  }
@@ -15805,8 +16273,8 @@ async function runAgentEvalSuite(options) {
15805
16273
  );
15806
16274
  }
15807
16275
  const inspectionResults = [];
15808
- const stepChecks = asArray2(step.check);
15809
- const stepInspections = asArray2(step.inspect);
16276
+ const stepChecks = asArray3(step.check);
16277
+ const stepInspections = asArray3(step.inspect);
15810
16278
  const context = {
15811
16279
  conversation,
15812
16280
  environment: conversation.environment,
@@ -15819,7 +16287,7 @@ async function runAgentEvalSuite(options) {
15819
16287
  promptInteractions: completed.promptInteractions,
15820
16288
  result: completed.result,
15821
16289
  heap: normalizeHeapSnapshot2(
15822
- asRecord4(
16290
+ asRecord5(
15823
16291
  cloneJson(conversation.environment.document)?.heap
15824
16292
  )
15825
16293
  ),
@@ -16030,7 +16498,7 @@ function createAgentEvalHarness(options) {
16030
16498
  }
16031
16499
  function buildCheckContext(conversation, completed, turnDir) {
16032
16500
  const liveDoc = cloneJson(conversation.environment.document);
16033
- const heap = normalizeHeapSnapshot2(asRecord4(liveDoc?.heap));
16501
+ const heap = normalizeHeapSnapshot2(asRecord5(liveDoc?.heap));
16034
16502
  return {
16035
16503
  conversation,
16036
16504
  environment: conversation.environment,
@@ -16106,7 +16574,7 @@ function createAgentEvalHarness(options) {
16106
16574
  jobId: pending.job.id,
16107
16575
  result: resumed.result,
16108
16576
  stdout: [...pending.stdout, ...resumed.stdout],
16109
- sessionHeap: normalizeHeapSnapshot2(asRecord4(liveDoc?.heap))
16577
+ sessionHeap: normalizeHeapSnapshot2(asRecord5(liveDoc?.heap))
16110
16578
  });
16111
16579
  const responseText = presentation.responseText || pending.finalReply || "Done.";
16112
16580
  pending.conversation.history.push({
@@ -16316,7 +16784,7 @@ function createAgentEvalHarness(options) {
16316
16784
  const settledLiveDoc = cloneJson(
16317
16785
  conversation.environment.document
16318
16786
  );
16319
- const sessionHeap = normalizeHeapSnapshot2(asRecord4(settledLiveDoc?.heap));
16787
+ const sessionHeap = normalizeHeapSnapshot2(asRecord5(settledLiveDoc?.heap));
16320
16788
  const presentation = resolveJobPresentation({
16321
16789
  jobId: job.id,
16322
16790
  result: outcome.result,