@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.
@@ -5644,6 +5644,540 @@ var JobImplementation = class {
5644
5644
  }
5645
5645
  };
5646
5646
 
5647
+ // src/job-presentation.ts
5648
+ var RESPONSE_KEYS = [
5649
+ "reply",
5650
+ "response",
5651
+ "text",
5652
+ "message",
5653
+ "summary",
5654
+ "answer"
5655
+ ];
5656
+ var ENTRY_KEY_CANDIDATES = ["entryPath", "path"];
5657
+ var ENTRY_ARRAY_KEY_CANDIDATES = ["entryPaths", "paths"];
5658
+ var LIST_KEY_CANDIDATES = ["listName"];
5659
+ var LIST_ARRAY_KEY_CANDIDATES = ["listNames"];
5660
+ var VARIABLE_KEY_CANDIDATES = ["variableName"];
5661
+ var VARIABLE_ARRAY_KEY_CANDIDATES = ["variableNames"];
5662
+ var UI_CONTAINER_KEYS = ["show", "display", "present", "ui"];
5663
+ function asRecord2(value) {
5664
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5665
+ return value;
5666
+ }
5667
+ function normalizeText(value) {
5668
+ if (typeof value !== "string") return null;
5669
+ const trimmed = value.trim();
5670
+ if (!trimmed) return null;
5671
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
5672
+ return null;
5673
+ }
5674
+ return trimmed;
5675
+ }
5676
+ function humanTextFromStdout(stdout) {
5677
+ for (const line of [...stdout].reverse()) {
5678
+ const normalized = normalizeText(line);
5679
+ if (!normalized) continue;
5680
+ if (/^[A-Z_]+:/.test(normalized)) continue;
5681
+ return normalized;
5682
+ }
5683
+ return null;
5684
+ }
5685
+ function pushString(target, value) {
5686
+ if (typeof value === "string" && value.trim()) {
5687
+ target.add(value.trim());
5688
+ }
5689
+ }
5690
+ function pushStringArray(target, value) {
5691
+ if (!Array.isArray(value)) return;
5692
+ for (const item of value) {
5693
+ pushString(target, item);
5694
+ }
5695
+ }
5696
+ function collectReferencesFromRecord(record, refs) {
5697
+ for (const key of ENTRY_KEY_CANDIDATES)
5698
+ pushString(refs.entryPaths, record[key]);
5699
+ for (const key of ENTRY_ARRAY_KEY_CANDIDATES)
5700
+ pushStringArray(refs.entryPaths, record[key]);
5701
+ for (const key of LIST_KEY_CANDIDATES)
5702
+ pushString(refs.listNames, record[key]);
5703
+ for (const key of LIST_ARRAY_KEY_CANDIDATES)
5704
+ pushStringArray(refs.listNames, record[key]);
5705
+ for (const key of VARIABLE_KEY_CANDIDATES)
5706
+ pushString(refs.variableNames, record[key]);
5707
+ for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
5708
+ pushStringArray(refs.variableNames, record[key]);
5709
+ }
5710
+ function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__ */ new Set()) {
5711
+ if (value === null || value === void 0 || depth > 4 || seen.has(value))
5712
+ return;
5713
+ if (typeof value === "string") {
5714
+ const trimmed = value.trim();
5715
+ if (heap.entriesByPath?.[trimmed]) refs.entryPaths.add(trimmed);
5716
+ if (heap.listsByName?.[trimmed]) refs.listNames.add(trimmed);
5717
+ if (heap.variablesByName?.[trimmed]) refs.variableNames.add(trimmed);
5718
+ return;
5719
+ }
5720
+ if (Array.isArray(value)) {
5721
+ seen.add(value);
5722
+ for (const item of value.slice(0, 24)) {
5723
+ scanForHeapReferences(item, heap, refs, depth + 1, seen);
5724
+ }
5725
+ return;
5726
+ }
5727
+ const record = asRecord2(value);
5728
+ if (!record) return;
5729
+ seen.add(value);
5730
+ collectReferencesFromRecord(record, refs);
5731
+ for (const key of UI_CONTAINER_KEYS) {
5732
+ const nested = asRecord2(record[key]);
5733
+ if (nested) collectReferencesFromRecord(nested, refs);
5734
+ }
5735
+ for (const nested of Object.values(record).slice(0, 24)) {
5736
+ scanForHeapReferences(nested, heap, refs, depth + 1, seen);
5737
+ }
5738
+ }
5739
+ function resolveVariablesToReferences(variableNames, heap, refs) {
5740
+ for (const variableName of variableNames) {
5741
+ const variable = heap.variablesByName?.[variableName];
5742
+ if (!variable) continue;
5743
+ if (variable.kind === "entry" && variable.entryPath) {
5744
+ refs.entryPaths.add(variable.entryPath);
5745
+ }
5746
+ if (variable.kind === "list" && variable.listName) {
5747
+ refs.listNames.add(variable.listName);
5748
+ }
5749
+ }
5750
+ }
5751
+ function sortEntries(entries) {
5752
+ return [...entries].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
5753
+ }
5754
+ function sortLists(lists) {
5755
+ return [...lists].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
5756
+ }
5757
+ function dedupeEntries(entries) {
5758
+ const seen = /* @__PURE__ */ new Set();
5759
+ const result = [];
5760
+ for (const entry of entries) {
5761
+ if (!entry?.path || seen.has(entry.path)) continue;
5762
+ seen.add(entry.path);
5763
+ result.push(entry);
5764
+ }
5765
+ return result;
5766
+ }
5767
+ function dedupeLists(lists) {
5768
+ const seen = /* @__PURE__ */ new Set();
5769
+ const result = [];
5770
+ for (const list of lists) {
5771
+ if (!list?.name || seen.has(list.name)) continue;
5772
+ seen.add(list.name);
5773
+ result.push(list);
5774
+ }
5775
+ return result;
5776
+ }
5777
+ function extractResponseText(result, stdout) {
5778
+ const directText = normalizeText(result);
5779
+ if (directText) return directText;
5780
+ const record = asRecord2(result);
5781
+ if (record) {
5782
+ for (const key of RESPONSE_KEYS) {
5783
+ const normalized = normalizeText(record[key]);
5784
+ if (normalized) return normalized;
5785
+ }
5786
+ for (const containerKey of UI_CONTAINER_KEYS) {
5787
+ const nested = asRecord2(record[containerKey]);
5788
+ if (!nested) continue;
5789
+ for (const key of RESPONSE_KEYS) {
5790
+ const normalized = normalizeText(nested[key]);
5791
+ if (normalized) return normalized;
5792
+ }
5793
+ }
5794
+ }
5795
+ return humanTextFromStdout(stdout);
5796
+ }
5797
+ function fallbackResponseText(entries, lists) {
5798
+ if (entries.length > 0) {
5799
+ return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
5800
+ }
5801
+ if (lists.length > 0) {
5802
+ const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
5803
+ if (emptyOnly) {
5804
+ return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
5805
+ }
5806
+ return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
5807
+ }
5808
+ return null;
5809
+ }
5810
+ function getJobRelatedEntries(heap, jobId) {
5811
+ return sortEntries(
5812
+ Object.values(heap.entriesByPath || {}).filter(
5813
+ (entry) => entry.relatedJobIds?.includes(jobId)
5814
+ )
5815
+ );
5816
+ }
5817
+ function getJobRelatedLists(heap, jobId) {
5818
+ return sortLists(
5819
+ Object.values(heap.listsByName || {}).filter(
5820
+ (list) => list.relatedJobIds?.includes(jobId)
5821
+ )
5822
+ );
5823
+ }
5824
+ function entriesFromLists(lists, heap) {
5825
+ const entries = [];
5826
+ for (const list of lists) {
5827
+ for (const path2 of list.paths || []) {
5828
+ const entry = heap.entriesByPath?.[path2];
5829
+ if (entry) entries.push(entry);
5830
+ }
5831
+ }
5832
+ return entries;
5833
+ }
5834
+ function resolveJobPresentation({
5835
+ jobId,
5836
+ result,
5837
+ stdout = [],
5838
+ sessionHeap,
5839
+ allowExplicitArtifacts = true
5840
+ }) {
5841
+ const refs = {
5842
+ entryPaths: /* @__PURE__ */ new Set(),
5843
+ listNames: /* @__PURE__ */ new Set(),
5844
+ variableNames: /* @__PURE__ */ new Set()
5845
+ };
5846
+ if (allowExplicitArtifacts) {
5847
+ scanForHeapReferences(result, sessionHeap, refs);
5848
+ resolveVariablesToReferences(refs.variableNames, sessionHeap, refs);
5849
+ }
5850
+ const referencedLists = sortLists(
5851
+ [...refs.listNames].map((name) => sessionHeap.listsByName?.[name]).filter((list) => Boolean(list))
5852
+ );
5853
+ const referencedEntries = sortEntries(
5854
+ [...refs.entryPaths].map((path2) => sessionHeap.entriesByPath?.[path2]).filter((entry) => Boolean(entry))
5855
+ );
5856
+ const jobLists = getJobRelatedLists(sessionHeap, jobId);
5857
+ const jobEntries = getJobRelatedEntries(sessionHeap, jobId);
5858
+ const changedEntries = dedupeEntries([
5859
+ ...jobEntries,
5860
+ ...entriesFromLists(jobLists, sessionHeap)
5861
+ ]);
5862
+ const explicitLists = dedupeLists(referencedLists);
5863
+ const explicitEntries = dedupeEntries([
5864
+ ...referencedEntries,
5865
+ ...entriesFromLists(referencedLists, sessionHeap)
5866
+ ]);
5867
+ const hasExplicitArtifacts = allowExplicitArtifacts && (explicitEntries.length > 0 || explicitLists.length > 0);
5868
+ const lists = hasExplicitArtifacts ? explicitLists : jobLists;
5869
+ const entries = hasExplicitArtifacts ? explicitEntries : changedEntries;
5870
+ const responseText = extractResponseText(result, stdout) || fallbackResponseText(entries, lists);
5871
+ return {
5872
+ responseText,
5873
+ entries,
5874
+ lists,
5875
+ changedEntries,
5876
+ changedLists: jobLists,
5877
+ hasExplicitArtifacts
5878
+ };
5879
+ }
5880
+
5881
+ // src/session-transcript.ts
5882
+ var EMPTY_HEAP = {
5883
+ entriesByPath: {},
5884
+ listsByName: {},
5885
+ variablesByName: {}};
5886
+ function asRecord3(value) {
5887
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5888
+ return value;
5889
+ }
5890
+ function asArray(value) {
5891
+ return Array.isArray(value) ? value : [];
5892
+ }
5893
+ function asNumber(value) {
5894
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5895
+ }
5896
+ function asString(value) {
5897
+ return typeof value === "string" ? value : void 0;
5898
+ }
5899
+ function trimString(value) {
5900
+ return typeof value === "string" ? value.trim() : "";
5901
+ }
5902
+ function normalizeShowRefs(value) {
5903
+ const record = asRecord3(value);
5904
+ if (!record) return void 0;
5905
+ const normalizeRefs = (input) => {
5906
+ if (!Array.isArray(input)) return void 0;
5907
+ const refs = Array.from(
5908
+ new Set(
5909
+ input.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean)
5910
+ )
5911
+ );
5912
+ return refs.length > 0 ? refs : void 0;
5913
+ };
5914
+ const show = {
5915
+ entryPaths: normalizeRefs(record.entryPaths),
5916
+ listNames: normalizeRefs(record.listNames),
5917
+ variableNames: normalizeRefs(record.variableNames)
5918
+ };
5919
+ return show.entryPaths || show.listNames || show.variableNames ? show : void 0;
5920
+ }
5921
+ function stringifyTranscriptValue(value, fallback = "") {
5922
+ if (typeof value === "string") {
5923
+ return value.trim() || fallback;
5924
+ }
5925
+ if (typeof value === "boolean") {
5926
+ return value ? "Confirmed" : "Canceled";
5927
+ }
5928
+ if (value === void 0) {
5929
+ return fallback;
5930
+ }
5931
+ try {
5932
+ const json = JSON.stringify(value, null, 2);
5933
+ if (!json || json === "undefined") return fallback;
5934
+ return json.length > 2e3 ? `${json.slice(0, 2e3)}...` : json;
5935
+ } catch {
5936
+ return String(value);
5937
+ }
5938
+ }
5939
+ function buildArtifactHistory(show) {
5940
+ if (!show) return void 0;
5941
+ return `[Agent message]
5942
+ ${stringifyTranscriptValue({ show }, "")}`;
5943
+ }
5944
+ function normalizeConversationMessage(raw) {
5945
+ const record = asRecord3(raw);
5946
+ if (!record) return null;
5947
+ const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
5948
+ if (!role) return null;
5949
+ const content = trimString(
5950
+ record.content ?? record.reply ?? record.message ?? record.text
5951
+ );
5952
+ const show = normalizeShowRefs(record.show);
5953
+ const id = asString(record.id) || crypto.randomUUID();
5954
+ const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
5955
+ if (!content && !show) return null;
5956
+ return {
5957
+ id,
5958
+ role,
5959
+ content,
5960
+ timestamp,
5961
+ jobId: asString(record.jobId),
5962
+ promptId: asString(record.promptId),
5963
+ show,
5964
+ historyContent: role === "assistant" ? content ? `[Assistant reply]
5965
+ ${content}` : buildArtifactHistory(show) : void 0,
5966
+ source: "conversation"
5967
+ };
5968
+ }
5969
+ function normalizePromptEntries(jobId, rawPrompts, conversationPromptIds) {
5970
+ const promptsById = asRecord3(rawPrompts) || {};
5971
+ return Object.values(promptsById).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
5972
+ (left, right) => (asNumber(left.openedAt) || asNumber(left.answeredAt) || 0) - (asNumber(right.openedAt) || asNumber(right.answeredAt) || 0)
5973
+ ).flatMap((prompt) => {
5974
+ const promptId = asString(prompt.promptId);
5975
+ if (!promptId || conversationPromptIds.has(promptId)) return [];
5976
+ const title = trimString(prompt.title);
5977
+ const message = trimString(prompt.message);
5978
+ const assistantContent = message || title || "Input required";
5979
+ const openedAt = asNumber(prompt.openedAt) || 0;
5980
+ const answeredAt = asNumber(prompt.answeredAt) || openedAt;
5981
+ const entries = [
5982
+ {
5983
+ id: `prompt:${promptId}:assistant`,
5984
+ role: "assistant",
5985
+ content: assistantContent,
5986
+ timestamp: openedAt,
5987
+ jobId,
5988
+ promptId,
5989
+ historyContent: `[Assistant reply]
5990
+ ${assistantContent}`,
5991
+ source: "job_prompt"
5992
+ }
5993
+ ];
5994
+ if (Object.prototype.hasOwnProperty.call(prompt, "answer")) {
5995
+ entries.push({
5996
+ id: `prompt:${promptId}:user`,
5997
+ role: "user",
5998
+ content: stringifyTranscriptValue(prompt.answer, ""),
5999
+ timestamp: answeredAt,
6000
+ jobId,
6001
+ promptId,
6002
+ source: "job_prompt"
6003
+ });
6004
+ }
6005
+ return entries;
6006
+ });
6007
+ }
6008
+ function normalizeAgentMessageEntries(jobId, rawMessages) {
6009
+ return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6010
+ (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6011
+ ).flatMap((message) => {
6012
+ const messageId = asString(message.messageId) || asString(message.id) || crypto.randomUUID();
6013
+ const timestamp = asNumber(message.timestamp) || asNumber(message.ts) || 0;
6014
+ const reply = trimString(
6015
+ message.reply ?? message.message ?? message.text ?? message.content
6016
+ );
6017
+ const show = normalizeShowRefs(message.show);
6018
+ const entries = [];
6019
+ if (reply) {
6020
+ entries.push({
6021
+ id: `agent:${messageId}:text`,
6022
+ role: "assistant",
6023
+ content: reply,
6024
+ timestamp,
6025
+ jobId,
6026
+ historyContent: `[Assistant reply]
6027
+ ${reply}`,
6028
+ source: "job_agent_message"
6029
+ });
6030
+ }
6031
+ if (show) {
6032
+ entries.push({
6033
+ id: `agent:${messageId}:artifacts`,
6034
+ role: "assistant",
6035
+ content: "",
6036
+ timestamp,
6037
+ jobId,
6038
+ show,
6039
+ historyContent: buildArtifactHistory(show),
6040
+ source: "job_agent_message"
6041
+ });
6042
+ }
6043
+ return entries;
6044
+ });
6045
+ }
6046
+ function buildJobFallbackEntries(jobId, job, sessionHeap) {
6047
+ const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6048
+ const resultPreview = stringifyTranscriptValue(
6049
+ job.result,
6050
+ "No job result recorded."
6051
+ );
6052
+ const presentation = resolveJobPresentation({
6053
+ jobId,
6054
+ result: job.result,
6055
+ stdout: [],
6056
+ sessionHeap
6057
+ });
6058
+ const entries = [];
6059
+ const responseText = presentation.responseText || "";
6060
+ if (responseText) {
6061
+ entries.push({
6062
+ id: `job:${jobId}:result-text`,
6063
+ role: "assistant",
6064
+ content: responseText,
6065
+ timestamp,
6066
+ jobId,
6067
+ historyContent: `[Assistant reply]
6068
+ ${responseText}`,
6069
+ source: "job_result"
6070
+ });
6071
+ }
6072
+ const show = {
6073
+ entryPaths: presentation.entries.map((entry) => entry.path),
6074
+ listNames: presentation.lists.map((list) => list.name)
6075
+ };
6076
+ if (show.entryPaths && show.entryPaths.length > 0 || show.listNames && show.listNames.length > 0) {
6077
+ entries.push({
6078
+ id: `job:${jobId}:result-artifacts`,
6079
+ role: "assistant",
6080
+ content: "",
6081
+ timestamp,
6082
+ jobId,
6083
+ show,
6084
+ historyContent: buildArtifactHistory(show),
6085
+ source: "job_result"
6086
+ });
6087
+ }
6088
+ if (entries.length === 0 && trimString(job.error)) {
6089
+ entries.push({
6090
+ id: `job:${jobId}:result-error`,
6091
+ role: "assistant",
6092
+ content: trimString(job.error),
6093
+ timestamp,
6094
+ jobId,
6095
+ historyContent: `[Assistant reply]
6096
+ ${trimString(job.error)}`,
6097
+ source: "job_result"
6098
+ });
6099
+ }
6100
+ if (entries.length === 0 && resultPreview && resultPreview !== "No job result recorded.") {
6101
+ entries.push({
6102
+ id: `job:${jobId}:result-preview`,
6103
+ role: "assistant",
6104
+ content: resultPreview,
6105
+ timestamp,
6106
+ jobId,
6107
+ historyContent: `[Assistant reply]
6108
+ ${resultPreview}`,
6109
+ source: "job_result"
6110
+ });
6111
+ }
6112
+ return entries;
6113
+ }
6114
+ function buildJobCodeEntry(jobId, job) {
6115
+ const code = trimString(job.source);
6116
+ if (!code) return null;
6117
+ const jobStatus = asString(job.status);
6118
+ const error = jobStatus === "failed" || jobStatus === "canceled" || jobStatus === "timeout" ? trimString(job.error) || `Job ${jobStatus}` : void 0;
6119
+ return {
6120
+ id: `job:${jobId}:code`,
6121
+ role: "assistant",
6122
+ content: "",
6123
+ timestamp: asNumber(job.submittedAt) || asNumber(job.startedAt) || asNumber(job.finishedAt) || 0,
6124
+ jobId,
6125
+ code,
6126
+ jobStatus,
6127
+ jobResultPreview: stringifyTranscriptValue(job.result, "No job result recorded."),
6128
+ error,
6129
+ source: "job_code"
6130
+ };
6131
+ }
6132
+ function buildSessionTranscript(input) {
6133
+ const liveDoc = input.liveDoc || null;
6134
+ const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6135
+ const transcript = [];
6136
+ const conversationMessages = asArray(asRecord3(liveDoc?.conversation)?.messages).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6137
+ const conversationPromptIds = new Set(
6138
+ conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6139
+ );
6140
+ const assistantConversationJobIds = new Set(
6141
+ conversationMessages.filter((message) => message.role === "assistant" && Boolean(message.jobId)).map((message) => message.jobId)
6142
+ );
6143
+ transcript.push(...conversationMessages);
6144
+ const jobsById = asRecord3(asRecord3(liveDoc?.jobs)?.byId) || {};
6145
+ const jobs = Object.values(jobsById).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6146
+ (left, right) => (asNumber(left.submittedAt) || asNumber(left.startedAt) || asNumber(left.finishedAt) || 0) - (asNumber(right.submittedAt) || asNumber(right.startedAt) || asNumber(right.finishedAt) || 0)
6147
+ );
6148
+ for (const job of jobs) {
6149
+ const jobId = asString(job.jobId);
6150
+ if (!jobId) continue;
6151
+ const codeEntry = buildJobCodeEntry(jobId, job);
6152
+ if (codeEntry) {
6153
+ transcript.push(codeEntry);
6154
+ }
6155
+ transcript.push(
6156
+ ...normalizePromptEntries(jobId, job.prompts, conversationPromptIds)
6157
+ );
6158
+ if (!assistantConversationJobIds.has(jobId)) {
6159
+ const agentEntries = normalizeAgentMessageEntries(jobId, job.agentMessages);
6160
+ if (agentEntries.length > 0) {
6161
+ transcript.push(...agentEntries);
6162
+ } else {
6163
+ transcript.push(
6164
+ ...buildJobFallbackEntries(
6165
+ jobId,
6166
+ job,
6167
+ sessionHeap
6168
+ )
6169
+ );
6170
+ }
6171
+ }
6172
+ }
6173
+ return transcript.sort((left, right) => {
6174
+ if (left.timestamp !== right.timestamp) {
6175
+ return left.timestamp - right.timestamp;
6176
+ }
6177
+ return left.id.localeCompare(right.id);
6178
+ });
6179
+ }
6180
+
5647
6181
  // src/endpoints.ts
5648
6182
  var LOCAL_API_URL = "ws://localhost:8787/granular";
5649
6183
  var PRODUCTION_API_URL = "wss://cf-api-gateway.arthur6084.workers.dev/granular";
@@ -12183,7 +12717,9 @@ var Environment = class {
12183
12717
  { target: targetPath }
12184
12718
  );
12185
12719
  if (result.errors?.length) {
12186
- throw new Error(`attach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`);
12720
+ throw new Error(
12721
+ `attach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`
12722
+ );
12187
12723
  }
12188
12724
  }
12189
12725
  /**
@@ -12218,7 +12754,9 @@ var Environment = class {
12218
12754
  }`
12219
12755
  );
12220
12756
  if (result.errors?.length) {
12221
- throw new Error(`detach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`);
12757
+ throw new Error(
12758
+ `detach failed: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`
12759
+ );
12222
12760
  }
12223
12761
  }
12224
12762
  /**
@@ -12340,7 +12878,9 @@ var Environment = class {
12340
12878
  async _runGraphql(query, label) {
12341
12879
  const result = await this.graphql(query);
12342
12880
  if (result.errors?.length) {
12343
- throw new Error(`${label}: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`);
12881
+ throw new Error(
12882
+ `${label}: ${result.errors[0]?.message ?? "Unknown GraphQL error"}`
12883
+ );
12344
12884
  }
12345
12885
  return result.data;
12346
12886
  }
@@ -12875,6 +13415,9 @@ var EnvironmentSession = class extends Session {
12875
13415
  get envName() {
12876
13416
  return this.environment.envName;
12877
13417
  }
13418
+ get tag() {
13419
+ return this.environment.tag;
13420
+ }
12878
13421
  get versionId() {
12879
13422
  return this.environment.versionId;
12880
13423
  }
@@ -12894,12 +13437,171 @@ var EnvironmentSession = class extends Session {
12894
13437
  return this.environment.feedback;
12895
13438
  }
12896
13439
  /**
12897
- * Return a plain JS snapshot of the synced session heap.
13440
+ * Return a plain JS copy of the synced session heap.
12898
13441
  */
12899
13442
  getHeap() {
12900
13443
  const doc = this.document;
12901
13444
  return normalizeHeapSnapshot(doc?.heap);
12902
13445
  }
13446
+ async sessionDataRequest(path2, query) {
13447
+ const searchParams = new URLSearchParams();
13448
+ for (const [key, value] of Object.entries(query || {})) {
13449
+ if (value !== null && typeof value !== "undefined" && value !== "") {
13450
+ searchParams.set(key, String(value));
13451
+ }
13452
+ }
13453
+ const queryString = searchParams.toString();
13454
+ const response = await fetch(
13455
+ `${this.environment.runtimeBaseUrl}/orchestrator/ws/sessions/${encodeURIComponent(this.sessionId)}${path2}${queryString ? `?${queryString}` : ""}`,
13456
+ {
13457
+ method: "GET",
13458
+ headers: {
13459
+ Authorization: `Bearer ${this.environment.authToken}`,
13460
+ "Content-Type": "application/json"
13461
+ }
13462
+ }
13463
+ );
13464
+ if (!response.ok) {
13465
+ const errorText = await response.text();
13466
+ throw new Error(
13467
+ `Session data API Error (${response.status}): ${errorText}`
13468
+ );
13469
+ }
13470
+ return response.json();
13471
+ }
13472
+ async collectAllSessionItems(listPage) {
13473
+ const items = [];
13474
+ let cursor = null;
13475
+ do {
13476
+ const page = await listPage({ limit: 500, cursor });
13477
+ items.push(...page.items);
13478
+ cursor = page.nextCursor;
13479
+ } while (cursor);
13480
+ return items;
13481
+ }
13482
+ /**
13483
+ * Fetch the live session document from the runtime DO.
13484
+ *
13485
+ * For history and saved artifacts, prefer the collection APIs on
13486
+ * `messages`, `timeline`, `jobs`, and `heap`.
13487
+ */
13488
+ async getDocument() {
13489
+ return this.sessionDataRequest("/document");
13490
+ }
13491
+ get messages() {
13492
+ return {
13493
+ list: (options = {}) => this.sessionDataRequest(
13494
+ "/messages",
13495
+ options
13496
+ )
13497
+ };
13498
+ }
13499
+ get timeline() {
13500
+ return {
13501
+ list: (options = {}) => this.sessionDataRequest(
13502
+ "/timeline",
13503
+ options
13504
+ )
13505
+ };
13506
+ }
13507
+ get jobs() {
13508
+ return {
13509
+ list: (options = {}) => this.sessionDataRequest(
13510
+ "/jobs",
13511
+ options
13512
+ ),
13513
+ get: (jobId) => this.sessionDataRequest(
13514
+ `/jobs/${encodeURIComponent(jobId)}`
13515
+ )
13516
+ };
13517
+ }
13518
+ get heap() {
13519
+ return {
13520
+ entries: {
13521
+ list: (options = {}) => this.sessionDataRequest(
13522
+ "/heap/entries",
13523
+ options
13524
+ ),
13525
+ get: (path2) => this.sessionDataRequest(
13526
+ `/heap/entries/${encodeURIComponent(path2)}`
13527
+ )
13528
+ },
13529
+ lists: {
13530
+ list: (options = {}) => this.sessionDataRequest(
13531
+ "/heap/lists",
13532
+ options
13533
+ ),
13534
+ get: (name) => this.sessionDataRequest(
13535
+ `/heap/lists/${encodeURIComponent(name)}`
13536
+ )
13537
+ }
13538
+ };
13539
+ }
13540
+ get transcript() {
13541
+ return {
13542
+ list: async (options = {}) => {
13543
+ const [messages, jobs, entries, lists] = await Promise.all([
13544
+ this.collectAllSessionItems(this.messages.list),
13545
+ this.collectAllSessionItems(
13546
+ (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
13547
+ ),
13548
+ this.collectAllSessionItems(this.heap.entries.list),
13549
+ this.collectAllSessionItems(this.heap.lists.list)
13550
+ ]);
13551
+ const liveDoc = {
13552
+ conversation: { messages },
13553
+ jobs: {
13554
+ byId: Object.fromEntries(
13555
+ jobs.map((job) => {
13556
+ const record = job && typeof job === "object" ? job : null;
13557
+ const id = typeof record?.jobId === "string" ? record.jobId : typeof record?.id === "string" ? record.id : null;
13558
+ return id ? [id, record] : null;
13559
+ }).filter(
13560
+ (entry) => Boolean(entry)
13561
+ )
13562
+ )
13563
+ }
13564
+ };
13565
+ const heap = normalizeHeapSnapshot({
13566
+ entriesByPath: Object.fromEntries(
13567
+ entries.map((entry) => {
13568
+ return entry?.path ? [
13569
+ entry.path,
13570
+ entry
13571
+ ] : null;
13572
+ }).filter(
13573
+ (entry) => Boolean(entry)
13574
+ )
13575
+ ),
13576
+ listsByName: Object.fromEntries(
13577
+ lists.map((list) => {
13578
+ return list?.name ? [list.name, list] : null;
13579
+ }).filter(
13580
+ (entry) => Boolean(entry)
13581
+ )
13582
+ ),
13583
+ variablesByName: this.getHeap().variablesByName,
13584
+ updatedAt: Date.now()
13585
+ });
13586
+ const allItems = buildSessionTranscript({
13587
+ liveDoc,
13588
+ sessionHeap: heap
13589
+ });
13590
+ const limit = Math.max(
13591
+ 1,
13592
+ Math.min(500, Math.floor(options.limit ?? 100))
13593
+ );
13594
+ const offset = typeof options.cursor === "string" ? Number.parseInt(options.cursor, 10) || 0 : 0;
13595
+ const items = allItems.slice(offset, offset + limit);
13596
+ const nextOffset = offset + items.length;
13597
+ return {
13598
+ items,
13599
+ nextCursor: nextOffset < allItems.length ? String(nextOffset) : null,
13600
+ totalCount: allItems.length
13601
+ };
13602
+ }
13603
+ };
13604
+ }
12903
13605
  async graphql(query, variables) {
12904
13606
  return this.environment.graphql(query, variables);
12905
13607
  }
@@ -14109,15 +14811,15 @@ var Granular = class _Granular {
14109
14811
  };
14110
14812
 
14111
14813
  // src/agent-harness.ts
14112
- function asRecord2(value) {
14814
+ function asRecord4(value) {
14113
14815
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
14114
14816
  return value;
14115
14817
  }
14116
- function asArray(value) {
14818
+ function asArray2(value) {
14117
14819
  return Array.isArray(value) ? value : [];
14118
14820
  }
14119
14821
  function toSortedRecords(value) {
14120
- return Object.values(asRecord2(value) || {}).map((entry) => asRecord2(entry)).filter((entry) => Boolean(entry));
14822
+ return Object.values(asRecord4(value) || {}).map((entry) => asRecord4(entry)).filter((entry) => Boolean(entry));
14121
14823
  }
14122
14824
  function uniqueStrings(values, maxCount) {
14123
14825
  const seen = /* @__PURE__ */ new Set();
@@ -14143,7 +14845,7 @@ function describeHeapEntry(entry, previewFieldLimit = 3) {
14143
14845
  const headline = entry.label || entry.id || entry.path || "Unknown";
14144
14846
  const pathLabel = entry.path && entry.path !== headline ? ` <${entry.path}>` : "";
14145
14847
  const classLabel = entry.className || "unknown";
14146
- const preview = asArray(entry.fields).filter(
14848
+ const preview = asArray2(entry.fields).filter(
14147
14849
  (field) => field?.name && field.name !== "_realId" && field.name !== "real_id"
14148
14850
  ).slice(0, previewFieldLimit).map((field) => `${field.name}=${formatScalar(field.value)}`).join(", ");
14149
14851
  return preview ? `${headline}${pathLabel} [${classLabel}] ${preview}` : `${headline}${pathLabel} [${classLabel}]`;
@@ -14280,14 +14982,14 @@ function normalizeActionSummaryForPrompt(line) {
14280
14982
  return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
14281
14983
  }
14282
14984
  function getCurrentClosureId(liveDoc) {
14283
- const loop = asRecord2(liveDoc?.loop);
14985
+ const loop = asRecord4(liveDoc?.loop);
14284
14986
  return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
14285
14987
  }
14286
14988
  function getLatestClosure(liveDoc) {
14287
- const loop = asRecord2(liveDoc?.loop);
14989
+ const loop = asRecord4(liveDoc?.loop);
14288
14990
  const currentClosureId = getCurrentClosureId(liveDoc);
14289
- const closuresById = asRecord2(loop?.closuresById) || {};
14290
- const currentClosure = currentClosureId ? asRecord2(closuresById[currentClosureId]) : null;
14991
+ const closuresById = asRecord4(loop?.closuresById) || {};
14992
+ const currentClosure = currentClosureId ? asRecord4(closuresById[currentClosureId]) : null;
14291
14993
  if (currentClosure) {
14292
14994
  return {
14293
14995
  ...currentClosure,
@@ -14296,7 +14998,7 @@ function getLatestClosure(liveDoc) {
14296
14998
  }
14297
14999
  const closures = [];
14298
15000
  for (const [closureId, value] of Object.entries(closuresById)) {
14299
- const record = asRecord2(value);
15001
+ const record = asRecord4(value);
14300
15002
  if (!record) continue;
14301
15003
  closures.push({ ...record, closureId });
14302
15004
  }
@@ -14333,10 +15035,10 @@ function getJobTimestamp(job) {
14333
15035
  return Number(job.finishedAt) || Number(job.startedAt) || Number(job.submittedAt) || 0;
14334
15036
  }
14335
15037
  function getJobRecords(liveDoc) {
14336
- const jobsById = asRecord2(asRecord2(liveDoc?.jobs)?.byId) || {};
15038
+ const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
14337
15039
  const jobs = [];
14338
15040
  for (const [jobId, value] of Object.entries(jobsById)) {
14339
- const record = asRecord2(value);
15041
+ const record = asRecord4(value);
14340
15042
  if (!record) continue;
14341
15043
  jobs.push({ ...record, jobId });
14342
15044
  }
@@ -14346,7 +15048,7 @@ function getJobRecords(liveDoc) {
14346
15048
  return jobs;
14347
15049
  }
14348
15050
  function getPromptRecordsFromJobs(liveDoc) {
14349
- return getJobRecords(liveDoc).flatMap((job) => Object.values(asRecord2(job.prompts) || {})).map((prompt) => asRecord2(prompt)).filter((prompt) => Boolean(prompt));
15051
+ return getJobRecords(liveDoc).flatMap((job) => Object.values(asRecord4(job.prompts) || {})).map((prompt) => asRecord4(prompt)).filter((prompt) => Boolean(prompt));
14350
15052
  }
14351
15053
  function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14352
15054
  const boundary = getWorkflowBoundary(liveDoc, options);
@@ -14361,15 +15063,15 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14361
15063
  const openDecisionIds = [];
14362
15064
  const openPromptIds = [];
14363
15065
  for (const job of jobs) {
14364
- for (const line of asArray(job.actionSummary)) {
15066
+ for (const line of asArray2(job.actionSummary)) {
14365
15067
  if (typeof line === "string" && line.trim()) {
14366
15068
  actionSummaryLines.push(line.trim());
14367
15069
  }
14368
15070
  }
14369
- for (const rawEvent of asArray(job.actionTrace)) {
14370
- const event = asRecord2(rawEvent);
14371
- const details = asRecord2(event?.details);
14372
- const outcome = asRecord2(event?.outcome);
15071
+ for (const rawEvent of asArray2(job.actionTrace)) {
15072
+ const event = asRecord4(rawEvent);
15073
+ const details = asRecord4(event?.details);
15074
+ const outcome = asRecord4(event?.outcome);
14373
15075
  if (typeof details?.name === "string") {
14374
15076
  variableNames.push(details.name);
14375
15077
  }
@@ -14396,7 +15098,7 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14396
15098
  }
14397
15099
  }
14398
15100
  }
14399
- const loop = asRecord2(liveDoc?.loop);
15101
+ const loop = asRecord4(liveDoc?.loop);
14400
15102
  const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
14401
15103
  const updatedAt = Number(task.updatedAt) || Number(task.createdAt) || 0;
14402
15104
  const status = typeof task.status === "string" ? task.status : "pending";
@@ -14444,15 +15146,15 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14444
15146
  openPromptIds.push(prompt.id);
14445
15147
  }
14446
15148
  }
14447
- const heap = asRecord2(liveDoc?.heap);
14448
- const variablesByName = asRecord2(heap?.variablesByName) || {};
14449
- const listsByName = asRecord2(heap?.listsByName) || {};
15149
+ const heap = asRecord4(liveDoc?.heap);
15150
+ const variablesByName = asRecord4(heap?.variablesByName) || {};
15151
+ const listsByName = asRecord4(heap?.listsByName) || {};
14450
15152
  const recentHints = extractFocusHintsFromActionSummary(actionSummaryLines);
14451
15153
  variableNames.push(...recentHints.variableNames);
14452
15154
  listNames.push(...recentHints.listNames);
14453
15155
  entryPaths.push(...recentHints.entryPaths);
14454
15156
  for (const variableName of uniqueStrings(variableNames)) {
14455
- const variable = asRecord2(variablesByName[variableName]);
15157
+ const variable = asRecord4(variablesByName[variableName]);
14456
15158
  if (!variable) continue;
14457
15159
  if (typeof variable.listName === "string") {
14458
15160
  listNames.push(variable.listName);
@@ -14462,13 +15164,13 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
14462
15164
  }
14463
15165
  }
14464
15166
  for (const listName of uniqueStrings(listNames)) {
14465
- const list = asRecord2(listsByName[listName]);
14466
- for (const path2 of asArray(list?.paths).slice(0, 4)) {
15167
+ const list = asRecord4(listsByName[listName]);
15168
+ for (const path2 of asArray2(list?.paths).slice(0, 4)) {
14467
15169
  entryPaths.push(path2);
14468
15170
  }
14469
15171
  }
14470
15172
  if (variableNames.length === 0 && boundary.reason !== "request_start") {
14471
- 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);
15173
+ 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);
14472
15174
  for (const variable of recentVariables) {
14473
15175
  if (typeof variable.name === "string") {
14474
15176
  variableNames.push(variable.name);
@@ -14551,12 +15253,12 @@ function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
14551
15253
  }
14552
15254
  function hasOpenPrompt(liveDoc, pendingPrompts) {
14553
15255
  if (pendingPrompts.length > 0) return true;
14554
- const jobsById = asRecord2(asRecord2(liveDoc?.jobs)?.byId) || {};
15256
+ const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
14555
15257
  for (const job of Object.values(jobsById)) {
14556
- const prompts = asRecord2(asRecord2(job)?.prompts);
15258
+ const prompts = asRecord4(asRecord4(job)?.prompts);
14557
15259
  if (!prompts) continue;
14558
15260
  for (const prompt of Object.values(prompts)) {
14559
- const record = asRecord2(prompt);
15261
+ const record = asRecord4(prompt);
14560
15262
  if (record?.status === "open") return true;
14561
15263
  }
14562
15264
  }
@@ -14564,7 +15266,7 @@ function hasOpenPrompt(liveDoc, pendingPrompts) {
14564
15266
  }
14565
15267
  function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14566
15268
  const lines = [];
14567
- const loop = asRecord2(liveDoc?.loop);
15269
+ const loop = asRecord4(liveDoc?.loop);
14568
15270
  const boundary = getWorkflowBoundary(liveDoc, options);
14569
15271
  const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
14570
15272
  const updatedAt = Number(task.updatedAt) || Number(task.createdAt) || 0;
@@ -14624,8 +15326,8 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14624
15326
  const title = typeof decision.title === "string" && decision.title.trim() ? decision.title.trim() : "Decision";
14625
15327
  const decisionId = typeof decision.decisionId === "string" ? decision.decisionId : "unknown";
14626
15328
  if (status === "open") {
14627
- const candidatePreview = asArray(decision.candidates).slice(0, 3).map((candidate) => {
14628
- const record = asRecord2(candidate);
15329
+ const candidatePreview = asArray2(decision.candidates).slice(0, 3).map((candidate) => {
15330
+ const record = asRecord4(candidate);
14629
15331
  if (!record) return null;
14630
15332
  const candidateId = typeof record.id === "string" ? record.id : "unknown";
14631
15333
  const candidateLabel = typeof record.label === "string" && record.label.trim() ? record.label.trim() : candidateId;
@@ -14635,7 +15337,7 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14635
15337
  `- [open] ${title} (${decisionId})${candidatePreview ? ` \u2014 candidates: ${candidatePreview}` : ""}`
14636
15338
  );
14637
15339
  } else {
14638
- const selected = asRecord2(decision.selected);
15340
+ const selected = asRecord4(decision.selected);
14639
15341
  const label = typeof selected?.label === "string" ? selected.label : typeof selected?.id === "string" ? selected.id : "unknown";
14640
15342
  lines.push(`- [resolved] ${title} (${decisionId}) -> ${label}`);
14641
15343
  }
@@ -14648,12 +15350,12 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14648
15350
  title: prompt.title,
14649
15351
  message: prompt.message
14650
15352
  })),
14651
- ...Object.values(asRecord2(asRecord2(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord2(asRecord2(job)?.prompts) || {})).map((prompt) => asRecord2(prompt)).filter(
15353
+ ...Object.values(asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord4(asRecord4(job)?.prompts) || {})).map((prompt) => asRecord4(prompt)).filter(
14652
15354
  (prompt) => Boolean(prompt && prompt.status === "open")
14653
15355
  )
14654
15356
  ];
14655
15357
  const visiblePrompts = boundary.reason === "request_start" ? openPrompts.filter((prompt) => {
14656
- const promptRecord = asRecord2(prompt);
15358
+ const promptRecord = asRecord4(prompt);
14657
15359
  const openedAt = Number(promptRecord?.openedAt) || 0;
14658
15360
  const promptId = typeof promptRecord?.id === "string" ? promptRecord.id : typeof prompt.id === "string" ? prompt.id : null;
14659
15361
  return openedAt >= boundary.timestamp || (promptId ? pendingPrompts.some(
@@ -14672,7 +15374,7 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14672
15374
  }
14673
15375
  }
14674
15376
  const currentClosureId = getCurrentClosureId(liveDoc);
14675
- const closureRecord = currentClosureId ? asRecord2(asRecord2(loop?.closuresById)?.[currentClosureId]) : null;
15377
+ const closureRecord = currentClosureId ? asRecord4(asRecord4(loop?.closuresById)?.[currentClosureId]) : null;
14676
15378
  const visibleClosure = closureRecord && (boundary.reason !== "request_start" || (Number(closureRecord.createdAt) || 0) >= boundary.timestamp) ? closureRecord : null;
14677
15379
  lines.push("", "Loop Closure:");
14678
15380
  if (visibleClosure) {
@@ -14685,10 +15387,10 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
14685
15387
  return lines.join("\n");
14686
15388
  }
14687
15389
  function projectHeapSummary(heap, options) {
14688
- const heapRecord = asRecord2(heap) || {};
14689
- const entriesByPath = asRecord2(heapRecord.entriesByPath) || {};
14690
- const listsByName = asRecord2(heapRecord.listsByName) || {};
14691
- const variablesByName = asRecord2(heapRecord.variablesByName) || {};
15390
+ const heapRecord = asRecord4(heap) || {};
15391
+ const entriesByPath = asRecord4(heapRecord.entriesByPath) || {};
15392
+ const listsByName = asRecord4(heapRecord.listsByName) || {};
15393
+ const variablesByName = asRecord4(heapRecord.variablesByName) || {};
14692
15394
  const focusedVariableNames = new Set(
14693
15395
  uniqueStrings(options?.focus?.variableNames || [])
14694
15396
  );
@@ -14703,7 +15405,7 @@ function projectHeapSummary(heap, options) {
14703
15405
  const maxVariables = options?.maxVariables ?? (hasFocus ? 4 : 6);
14704
15406
  const maxLists = options?.maxLists ?? (hasFocus ? 3 : 4);
14705
15407
  const maxEntries = options?.maxEntries ?? (hasFocus ? 5 : 6);
14706
- const variables = suppressRecentFallback ? [] : Object.values(variablesByName).map((value) => asRecord2(value)).filter((value) => Boolean(value)).sort((left, right) => {
15408
+ const variables = suppressRecentFallback ? [] : Object.values(variablesByName).map((value) => asRecord4(value)).filter((value) => Boolean(value)).sort((left, right) => {
14707
15409
  const leftFocused = left.name && focusedVariableNames.has(left.name) ? 1 : 0;
14708
15410
  const rightFocused = right.name && focusedVariableNames.has(right.name) ? 1 : 0;
14709
15411
  return rightFocused - leftFocused || (right.updatedAt || 0) - (left.updatedAt || 0);
@@ -14717,7 +15419,7 @@ function projectHeapSummary(heap, options) {
14717
15419
  for (const variable of variables) {
14718
15420
  if (variable.entryPath) referencedPaths.add(variable.entryPath);
14719
15421
  if (variable.listName) {
14720
- const list = asRecord2(
15422
+ const list = asRecord4(
14721
15423
  listsByName[variable.listName]
14722
15424
  );
14723
15425
  for (const path2 of list?.paths || []) referencedPaths.add(path2);
@@ -14726,10 +15428,10 @@ function projectHeapSummary(heap, options) {
14726
15428
  for (const path2 of focusedEntryPaths) {
14727
15429
  referencedPaths.add(path2);
14728
15430
  }
14729
- const visibleLists = Object.values(listsByName).map((value) => asRecord2(value)).filter((value) => Boolean(value)).filter(
15431
+ const visibleLists = Object.values(listsByName).map((value) => asRecord4(value)).filter((value) => Boolean(value)).filter(
14730
15432
  (list) => variables.some((variable) => variable.listName === list.name) || Boolean(list.name && focusedListNames.has(list.name))
14731
15433
  ).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxLists);
14732
- 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);
15434
+ 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);
14733
15435
  const lines = [];
14734
15436
  lines.push("Variables:");
14735
15437
  if (variables.length === 0) {
@@ -14743,7 +15445,7 @@ function projectHeapSummary(heap, options) {
14743
15445
  continue;
14744
15446
  }
14745
15447
  if (variable.kind === "entry") {
14746
- const entry = variable.entryPath ? asRecord2(
15448
+ const entry = variable.entryPath ? asRecord4(
14747
15449
  entriesByPath[variable.entryPath]
14748
15450
  ) : null;
14749
15451
  lines.push(
@@ -14751,7 +15453,7 @@ function projectHeapSummary(heap, options) {
14751
15453
  );
14752
15454
  continue;
14753
15455
  }
14754
- const list = variable.listName ? asRecord2(listsByName[variable.listName]) : null;
15456
+ const list = variable.listName ? asRecord4(listsByName[variable.listName]) : null;
14755
15457
  lines.push(
14756
15458
  `- ${variable.name}: list<${variable.className || list?.className || "unknown"}> -> ${(list?.paths || []).length} item(s)`
14757
15459
  );
@@ -14784,7 +15486,7 @@ function createHarnessVerifierSnapshot(input) {
14784
15486
  input.projectionOptions
14785
15487
  );
14786
15488
  const heapDigest = hashString(
14787
- projectHeapSummary(asRecord2(input.liveDoc?.heap), {
15489
+ projectHeapSummary(asRecord4(input.liveDoc?.heap), {
14788
15490
  focus: workflowFocus
14789
15491
  })
14790
15492
  ) || "00000000";
@@ -15110,246 +15812,12 @@ ${loopBlock}
15110
15812
  - Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
15111
15813
  }
15112
15814
 
15113
- // src/job-presentation.ts
15114
- var RESPONSE_KEYS = [
15115
- "reply",
15116
- "response",
15117
- "text",
15118
- "message",
15119
- "summary",
15120
- "answer"
15121
- ];
15122
- var ENTRY_KEY_CANDIDATES = ["entryPath", "path"];
15123
- var ENTRY_ARRAY_KEY_CANDIDATES = ["entryPaths", "paths"];
15124
- var LIST_KEY_CANDIDATES = ["listName"];
15125
- var LIST_ARRAY_KEY_CANDIDATES = ["listNames"];
15126
- var VARIABLE_KEY_CANDIDATES = ["variableName"];
15127
- var VARIABLE_ARRAY_KEY_CANDIDATES = ["variableNames"];
15128
- var UI_CONTAINER_KEYS = ["show", "display", "present", "ui"];
15129
- function asRecord3(value) {
15130
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
15131
- return value;
15132
- }
15133
- function normalizeText(value) {
15134
- if (typeof value !== "string") return null;
15135
- const trimmed = value.trim();
15136
- if (!trimmed) return null;
15137
- if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
15138
- return null;
15139
- }
15140
- return trimmed;
15141
- }
15142
- function humanTextFromStdout(stdout) {
15143
- for (const line of [...stdout].reverse()) {
15144
- const normalized = normalizeText(line);
15145
- if (!normalized) continue;
15146
- if (/^[A-Z_]+:/.test(normalized)) continue;
15147
- return normalized;
15148
- }
15149
- return null;
15150
- }
15151
- function pushString(target, value) {
15152
- if (typeof value === "string" && value.trim()) {
15153
- target.add(value.trim());
15154
- }
15155
- }
15156
- function pushStringArray(target, value) {
15157
- if (!Array.isArray(value)) return;
15158
- for (const item of value) {
15159
- pushString(target, item);
15160
- }
15161
- }
15162
- function collectReferencesFromRecord(record, refs) {
15163
- for (const key of ENTRY_KEY_CANDIDATES)
15164
- pushString(refs.entryPaths, record[key]);
15165
- for (const key of ENTRY_ARRAY_KEY_CANDIDATES)
15166
- pushStringArray(refs.entryPaths, record[key]);
15167
- for (const key of LIST_KEY_CANDIDATES)
15168
- pushString(refs.listNames, record[key]);
15169
- for (const key of LIST_ARRAY_KEY_CANDIDATES)
15170
- pushStringArray(refs.listNames, record[key]);
15171
- for (const key of VARIABLE_KEY_CANDIDATES)
15172
- pushString(refs.variableNames, record[key]);
15173
- for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
15174
- pushStringArray(refs.variableNames, record[key]);
15175
- }
15176
- function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__ */ new Set()) {
15177
- if (value === null || value === void 0 || depth > 4 || seen.has(value))
15178
- return;
15179
- if (typeof value === "string") {
15180
- const trimmed = value.trim();
15181
- if (heap.entriesByPath?.[trimmed]) refs.entryPaths.add(trimmed);
15182
- if (heap.listsByName?.[trimmed]) refs.listNames.add(trimmed);
15183
- if (heap.variablesByName?.[trimmed]) refs.variableNames.add(trimmed);
15184
- return;
15185
- }
15186
- if (Array.isArray(value)) {
15187
- seen.add(value);
15188
- for (const item of value.slice(0, 24)) {
15189
- scanForHeapReferences(item, heap, refs, depth + 1, seen);
15190
- }
15191
- return;
15192
- }
15193
- const record = asRecord3(value);
15194
- if (!record) return;
15195
- seen.add(value);
15196
- collectReferencesFromRecord(record, refs);
15197
- for (const key of UI_CONTAINER_KEYS) {
15198
- const nested = asRecord3(record[key]);
15199
- if (nested) collectReferencesFromRecord(nested, refs);
15200
- }
15201
- for (const nested of Object.values(record).slice(0, 24)) {
15202
- scanForHeapReferences(nested, heap, refs, depth + 1, seen);
15203
- }
15204
- }
15205
- function resolveVariablesToReferences(variableNames, heap, refs) {
15206
- for (const variableName of variableNames) {
15207
- const variable = heap.variablesByName?.[variableName];
15208
- if (!variable) continue;
15209
- if (variable.kind === "entry" && variable.entryPath) {
15210
- refs.entryPaths.add(variable.entryPath);
15211
- }
15212
- if (variable.kind === "list" && variable.listName) {
15213
- refs.listNames.add(variable.listName);
15214
- }
15215
- }
15216
- }
15217
- function sortEntries(entries) {
15218
- return [...entries].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
15219
- }
15220
- function sortLists(lists) {
15221
- return [...lists].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
15222
- }
15223
- function dedupeEntries(entries) {
15224
- const seen = /* @__PURE__ */ new Set();
15225
- const result = [];
15226
- for (const entry of entries) {
15227
- if (!entry?.path || seen.has(entry.path)) continue;
15228
- seen.add(entry.path);
15229
- result.push(entry);
15230
- }
15231
- return result;
15232
- }
15233
- function dedupeLists(lists) {
15234
- const seen = /* @__PURE__ */ new Set();
15235
- const result = [];
15236
- for (const list of lists) {
15237
- if (!list?.name || seen.has(list.name)) continue;
15238
- seen.add(list.name);
15239
- result.push(list);
15240
- }
15241
- return result;
15242
- }
15243
- function extractResponseText(result, stdout) {
15244
- const directText = normalizeText(result);
15245
- if (directText) return directText;
15246
- const record = asRecord3(result);
15247
- if (record) {
15248
- for (const key of RESPONSE_KEYS) {
15249
- const normalized = normalizeText(record[key]);
15250
- if (normalized) return normalized;
15251
- }
15252
- for (const containerKey of UI_CONTAINER_KEYS) {
15253
- const nested = asRecord3(record[containerKey]);
15254
- if (!nested) continue;
15255
- for (const key of RESPONSE_KEYS) {
15256
- const normalized = normalizeText(nested[key]);
15257
- if (normalized) return normalized;
15258
- }
15259
- }
15260
- }
15261
- return humanTextFromStdout(stdout);
15262
- }
15263
- function fallbackResponseText(entries, lists) {
15264
- if (entries.length > 0) {
15265
- return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
15266
- }
15267
- if (lists.length > 0) {
15268
- const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
15269
- if (emptyOnly) {
15270
- return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
15271
- }
15272
- return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
15273
- }
15274
- return null;
15275
- }
15276
- function getJobRelatedEntries(heap, jobId) {
15277
- return sortEntries(
15278
- Object.values(heap.entriesByPath || {}).filter(
15279
- (entry) => entry.relatedJobIds?.includes(jobId)
15280
- )
15281
- );
15282
- }
15283
- function getJobRelatedLists(heap, jobId) {
15284
- return sortLists(
15285
- Object.values(heap.listsByName || {}).filter(
15286
- (list) => list.relatedJobIds?.includes(jobId)
15287
- )
15288
- );
15289
- }
15290
- function entriesFromLists(lists, heap) {
15291
- const entries = [];
15292
- for (const list of lists) {
15293
- for (const path2 of list.paths || []) {
15294
- const entry = heap.entriesByPath?.[path2];
15295
- if (entry) entries.push(entry);
15296
- }
15297
- }
15298
- return entries;
15299
- }
15300
- function resolveJobPresentation({
15301
- jobId,
15302
- result,
15303
- stdout = [],
15304
- sessionHeap,
15305
- allowExplicitArtifacts = true
15306
- }) {
15307
- const refs = {
15308
- entryPaths: /* @__PURE__ */ new Set(),
15309
- listNames: /* @__PURE__ */ new Set(),
15310
- variableNames: /* @__PURE__ */ new Set()
15311
- };
15312
- if (allowExplicitArtifacts) {
15313
- scanForHeapReferences(result, sessionHeap, refs);
15314
- resolveVariablesToReferences(refs.variableNames, sessionHeap, refs);
15315
- }
15316
- const referencedLists = sortLists(
15317
- [...refs.listNames].map((name) => sessionHeap.listsByName?.[name]).filter((list) => Boolean(list))
15318
- );
15319
- const referencedEntries = sortEntries(
15320
- [...refs.entryPaths].map((path2) => sessionHeap.entriesByPath?.[path2]).filter((entry) => Boolean(entry))
15321
- );
15322
- const jobLists = getJobRelatedLists(sessionHeap, jobId);
15323
- const jobEntries = getJobRelatedEntries(sessionHeap, jobId);
15324
- const changedEntries = dedupeEntries([
15325
- ...jobEntries,
15326
- ...entriesFromLists(jobLists, sessionHeap)
15327
- ]);
15328
- const explicitLists = dedupeLists(referencedLists);
15329
- const explicitEntries = dedupeEntries([
15330
- ...referencedEntries,
15331
- ...entriesFromLists(referencedLists, sessionHeap)
15332
- ]);
15333
- const hasExplicitArtifacts = allowExplicitArtifacts && (explicitEntries.length > 0 || explicitLists.length > 0);
15334
- const lists = hasExplicitArtifacts ? explicitLists : jobLists;
15335
- const entries = hasExplicitArtifacts ? explicitEntries : changedEntries;
15336
- const responseText = extractResponseText(result, stdout) || fallbackResponseText(entries, lists);
15337
- return {
15338
- responseText,
15339
- entries,
15340
- lists,
15341
- changedEntries,
15342
- changedLists: jobLists,
15343
- hasExplicitArtifacts
15344
- };
15345
- }
15346
-
15347
15815
  // src/agent-evals.ts
15348
15816
  var DEFAULT_CONTROLLER_BUDGETS = {
15349
15817
  maxIterations: 6,
15350
15818
  maxNoProgressIterations: 2
15351
15819
  };
15352
- function asRecord4(value) {
15820
+ function asRecord5(value) {
15353
15821
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
15354
15822
  return value;
15355
15823
  }
@@ -15397,7 +15865,7 @@ function buildArtifactDir(baseDir, suiteName = "granular-agent-evals") {
15397
15865
  function createTimestampedArtifactDirectory(options) {
15398
15866
  return buildArtifactDir(options?.baseDir, options?.suiteName);
15399
15867
  }
15400
- function asArray2(value) {
15868
+ function asArray3(value) {
15401
15869
  if (!value) return [];
15402
15870
  return Array.isArray(value) ? value : [value];
15403
15871
  }
@@ -15415,7 +15883,7 @@ function buildScenarioSteps(scenario) {
15415
15883
  request: scenario.request,
15416
15884
  human: scenario.human,
15417
15885
  expect: scenario.expect,
15418
- inspect: [...asArray2(scenario.inspect), ...asArray2(scenario.verify)],
15886
+ inspect: [...asArray3(scenario.inspect), ...asArray3(scenario.verify)],
15419
15887
  check: scenario.check,
15420
15888
  maxIterations: scenario.maxIterations,
15421
15889
  setup: {
@@ -15456,12 +15924,12 @@ function buildHistory(entries) {
15456
15924
  );
15457
15925
  }
15458
15926
  function getOpenPromptsFromDoc(liveDoc) {
15459
- const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
15927
+ const jobsById = asRecord5(asRecord5(liveDoc?.jobs)?.byId) || {};
15460
15928
  const prompts = [];
15461
15929
  for (const job of Object.values(jobsById)) {
15462
- const promptRecords = asRecord4(asRecord4(job)?.prompts) || {};
15930
+ const promptRecords = asRecord5(asRecord5(job)?.prompts) || {};
15463
15931
  for (const raw of Object.values(promptRecords)) {
15464
- const record = asRecord4(raw);
15932
+ const record = asRecord5(raw);
15465
15933
  if (!record || record.status !== "open" || typeof record.promptId !== "string")
15466
15934
  continue;
15467
15935
  const prompt = normalizePrompt({
@@ -15482,11 +15950,11 @@ function getOpenPromptsFromDoc(liveDoc) {
15482
15950
  return prompts;
15483
15951
  }
15484
15952
  function filterPromptsByBoundary(liveDoc, prompts, boundaryTimestamp) {
15485
- const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
15953
+ const jobsById = asRecord5(asRecord5(liveDoc?.jobs)?.byId) || {};
15486
15954
  return prompts.filter((prompt) => {
15487
15955
  for (const jobRecord of Object.values(jobsById)) {
15488
- const promptsById = asRecord4(asRecord4(jobRecord)?.prompts) || {};
15489
- const promptRecord = asRecord4(promptsById[prompt.id]);
15956
+ const promptsById = asRecord5(asRecord5(jobRecord)?.prompts) || {};
15957
+ const promptRecord = asRecord5(promptsById[prompt.id]);
15490
15958
  const openedAt = Number(promptRecord?.openedAt) || 0;
15491
15959
  if (openedAt >= boundaryTimestamp) return true;
15492
15960
  }
@@ -15579,10 +16047,10 @@ ${modelOutputInstruction()}`
15579
16047
  );
15580
16048
  }
15581
16049
  const raw = await response.json();
15582
- const content = asRecord4(
15583
- asRecord4(raw.choices?.[0])?.message
16050
+ const content = asRecord5(
16051
+ asRecord5(raw.choices?.[0])?.message
15584
16052
  )?.content;
15585
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord4(part)?.text || "").join("") : "";
16053
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord5(part)?.text || "").join("") : "";
15586
16054
  const parsed = extractJsonObject(text);
15587
16055
  if (!parsed) {
15588
16056
  if (attempt < 3) {
@@ -15634,17 +16102,17 @@ async function withTimeout(promise, ms, label) {
15634
16102
  }
15635
16103
  }
15636
16104
  function getActionSummary(liveDoc, jobId) {
15637
- const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
15638
- const job = asRecord4(jobsById[jobId]);
16105
+ const jobsById = asRecord5(asRecord5(liveDoc?.jobs)?.byId) || {};
16106
+ const job = asRecord5(jobsById[jobId]);
15639
16107
  return Array.isArray(job?.actionSummary) ? job.actionSummary.filter(
15640
16108
  (line) => typeof line === "string"
15641
16109
  ) : [];
15642
16110
  }
15643
16111
  function normalizeHeapSnapshot2(heap) {
15644
16112
  return {
15645
- entriesByPath: asRecord4(heap?.entriesByPath) || {},
15646
- listsByName: asRecord4(heap?.listsByName) || {},
15647
- variablesByName: asRecord4(heap?.variablesByName) || {},
16113
+ entriesByPath: asRecord5(heap?.entriesByPath) || {},
16114
+ listsByName: asRecord5(heap?.listsByName) || {},
16115
+ variablesByName: asRecord5(heap?.variablesByName) || {},
15648
16116
  updatedAt: typeof heap?.updatedAt === "number" ? heap.updatedAt : Date.now()
15649
16117
  };
15650
16118
  }
@@ -15830,8 +16298,8 @@ async function runAgentEvalSuite(options) {
15830
16298
  );
15831
16299
  }
15832
16300
  const inspectionResults = [];
15833
- const stepChecks = asArray2(step.check);
15834
- const stepInspections = asArray2(step.inspect);
16301
+ const stepChecks = asArray3(step.check);
16302
+ const stepInspections = asArray3(step.inspect);
15835
16303
  const context = {
15836
16304
  conversation,
15837
16305
  environment: conversation.environment,
@@ -15844,7 +16312,7 @@ async function runAgentEvalSuite(options) {
15844
16312
  promptInteractions: completed.promptInteractions,
15845
16313
  result: completed.result,
15846
16314
  heap: normalizeHeapSnapshot2(
15847
- asRecord4(
16315
+ asRecord5(
15848
16316
  cloneJson(conversation.environment.document)?.heap
15849
16317
  )
15850
16318
  ),
@@ -16055,7 +16523,7 @@ function createAgentEvalHarness(options) {
16055
16523
  }
16056
16524
  function buildCheckContext(conversation, completed, turnDir) {
16057
16525
  const liveDoc = cloneJson(conversation.environment.document);
16058
- const heap = normalizeHeapSnapshot2(asRecord4(liveDoc?.heap));
16526
+ const heap = normalizeHeapSnapshot2(asRecord5(liveDoc?.heap));
16059
16527
  return {
16060
16528
  conversation,
16061
16529
  environment: conversation.environment,
@@ -16131,7 +16599,7 @@ function createAgentEvalHarness(options) {
16131
16599
  jobId: pending.job.id,
16132
16600
  result: resumed.result,
16133
16601
  stdout: [...pending.stdout, ...resumed.stdout],
16134
- sessionHeap: normalizeHeapSnapshot2(asRecord4(liveDoc?.heap))
16602
+ sessionHeap: normalizeHeapSnapshot2(asRecord5(liveDoc?.heap))
16135
16603
  });
16136
16604
  const responseText = presentation.responseText || pending.finalReply || "Done.";
16137
16605
  pending.conversation.history.push({
@@ -16341,7 +16809,7 @@ function createAgentEvalHarness(options) {
16341
16809
  const settledLiveDoc = cloneJson(
16342
16810
  conversation.environment.document
16343
16811
  );
16344
- const sessionHeap = normalizeHeapSnapshot2(asRecord4(settledLiveDoc?.heap));
16812
+ const sessionHeap = normalizeHeapSnapshot2(asRecord5(settledLiveDoc?.heap));
16345
16813
  const presentation = resolveJobPresentation({
16346
16814
  jobId: job.id,
16347
16815
  result: outcome.result,