@granular-software/sdk 0.4.36 → 0.4.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -2
- package/dist/agent-evals.d.mts +77 -3
- package/dist/agent-evals.d.ts +77 -3
- package/dist/agent-evals.js +2764 -643
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +2763 -643
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.d.mts +39 -4
- package/dist/agent-harness.d.ts +39 -4
- package/dist/agent-harness.js +1051 -456
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +1049 -457
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +2470 -298
- package/dist/client-BNbWA9jQ.d.ts +1064 -0
- package/dist/client-HDJgcJC5.d.mts +1064 -0
- package/dist/index.d.mts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +2162 -575
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2152 -576
- package/dist/index.mjs.map +1 -1
- package/dist/spend-rzS1rlFr.d.mts +1559 -0
- package/dist/spend-rzS1rlFr.d.ts +1559 -0
- package/dist/spend.d.mts +2 -0
- package/dist/spend.d.ts +2 -0
- package/dist/spend.js +111 -0
- package/dist/spend.js.map +1 -0
- package/dist/spend.mjs +107 -0
- package/dist/spend.mjs.map +1 -0
- package/package.json +8 -1
- package/dist/client-Cq8onk2D.d.mts +0 -2402
- package/dist/client-Cq8onk2D.d.ts +0 -2402
package/dist/agent-evals.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var promises = require('fs/promises');
|
|
4
4
|
var path = require('path');
|
|
5
|
+
var OpenAI = require('openai');
|
|
5
6
|
var Automerge = require('@automerge/automerge');
|
|
6
7
|
|
|
7
8
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -25,6 +26,7 @@ function _interopNamespace(e) {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
29
|
+
var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
|
|
28
30
|
var Automerge__namespace = /*#__PURE__*/_interopNamespace(Automerge);
|
|
29
31
|
|
|
30
32
|
var __create = Object.create;
|
|
@@ -3956,11 +3958,22 @@ var TOKEN_REFRESH_LEEWAY_MS = 2 * 60 * 1e3;
|
|
|
3956
3958
|
var TOKEN_REFRESH_RETRY_MS = 30 * 1e3;
|
|
3957
3959
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
3958
3960
|
var DEBUG_WS = process.env.GRANULAR_DEBUG_WS === "1";
|
|
3961
|
+
var DEFAULT_RPC_TIMEOUT_MS = 3e4;
|
|
3962
|
+
var DOMAIN_PACKAGE_RPC_TIMEOUT_MS = 12e4;
|
|
3959
3963
|
function debugWs(...args) {
|
|
3960
3964
|
if (DEBUG_WS) {
|
|
3961
3965
|
console.log(...args);
|
|
3962
3966
|
}
|
|
3963
3967
|
}
|
|
3968
|
+
function rpcTimeoutMsForMethod(method) {
|
|
3969
|
+
switch (method) {
|
|
3970
|
+
case "domain.fetchPackagePart":
|
|
3971
|
+
case "domain.getSummary":
|
|
3972
|
+
return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
|
|
3973
|
+
default:
|
|
3974
|
+
return DEFAULT_RPC_TIMEOUT_MS;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3964
3977
|
var WSClient = class {
|
|
3965
3978
|
ws = null;
|
|
3966
3979
|
url;
|
|
@@ -4385,13 +4398,14 @@ var WSClient = class {
|
|
|
4385
4398
|
return new Promise((resolve, reject) => {
|
|
4386
4399
|
this.messageQueue.push({ resolve, reject, id });
|
|
4387
4400
|
this.ws.send(JSON.stringify(request));
|
|
4401
|
+
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
4388
4402
|
setTimeout(() => {
|
|
4389
4403
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
4390
4404
|
if (pending) {
|
|
4391
4405
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4392
4406
|
reject(new Error(`RPC timeout: ${method}`));
|
|
4393
4407
|
}
|
|
4394
|
-
},
|
|
4408
|
+
}, timeoutMs);
|
|
4395
4409
|
});
|
|
4396
4410
|
}
|
|
4397
4411
|
async handleIncomingRpc(request) {
|
|
@@ -4505,10 +4519,48 @@ function normalizePromptText(value) {
|
|
|
4505
4519
|
function extractPromptTokens(value) {
|
|
4506
4520
|
return normalizePromptText(value).split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
4507
4521
|
}
|
|
4522
|
+
function parseJsonPromptChoiceOption(option) {
|
|
4523
|
+
const trimmed = option.trim();
|
|
4524
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
|
|
4525
|
+
try {
|
|
4526
|
+
const parsed = JSON.parse(trimmed);
|
|
4527
|
+
return asRecord(parsed);
|
|
4528
|
+
} catch {
|
|
4529
|
+
return null;
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
function normalizePromptChoiceOption(option) {
|
|
4533
|
+
if (typeof option === "string") {
|
|
4534
|
+
const record2 = parseJsonPromptChoiceOption(option);
|
|
4535
|
+
if (!record2) {
|
|
4536
|
+
return { value: option, label: option };
|
|
4537
|
+
}
|
|
4538
|
+
const value2 = typeof record2.value === "string" ? record2.value : typeof record2.id === "string" ? record2.id : typeof record2.label === "string" ? record2.label : JSON.stringify(record2);
|
|
4539
|
+
return {
|
|
4540
|
+
value: value2,
|
|
4541
|
+
label: typeof record2.label === "string" ? record2.label : value2,
|
|
4542
|
+
description: typeof record2.description === "string" ? record2.description : void 0
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
4545
|
+
const record = option;
|
|
4546
|
+
if (!record) {
|
|
4547
|
+
return { value: "", label: "" };
|
|
4548
|
+
}
|
|
4549
|
+
const nestedJson = (typeof record.value === "string" ? parseJsonPromptChoiceOption(record.value) : null) || (typeof record.label === "string" ? parseJsonPromptChoiceOption(record.label) : null);
|
|
4550
|
+
if (nestedJson) {
|
|
4551
|
+
return normalizePromptChoiceOption(nestedJson);
|
|
4552
|
+
}
|
|
4553
|
+
const value = typeof record.value === "string" ? record.value : typeof record.label === "string" ? record.label : JSON.stringify(record);
|
|
4554
|
+
return {
|
|
4555
|
+
value,
|
|
4556
|
+
label: typeof record.label === "string" ? record.label : value,
|
|
4557
|
+
description: typeof record.description === "string" ? record.description : void 0
|
|
4558
|
+
};
|
|
4559
|
+
}
|
|
4508
4560
|
function scorePromptChoiceMatch(answer, answerTokens, option) {
|
|
4509
|
-
const
|
|
4510
|
-
const
|
|
4511
|
-
const description =
|
|
4561
|
+
const choice = normalizePromptChoiceOption(option);
|
|
4562
|
+
const { value, label } = choice;
|
|
4563
|
+
const description = choice.description || "";
|
|
4512
4564
|
const haystack = normalizePromptText([value, label, description].filter(Boolean).join(" "));
|
|
4513
4565
|
if (!haystack) return { score: 0, resolvedValue: value || label || null };
|
|
4514
4566
|
let score = 0;
|
|
@@ -4544,7 +4596,9 @@ function normalizePrompt(rawValue) {
|
|
|
4544
4596
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
4545
4597
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
4546
4598
|
message: typeof source.message === "string" ? source.message : "",
|
|
4547
|
-
options: Array.isArray(source.options) ? source.options
|
|
4599
|
+
options: Array.isArray(source.options) ? source.options.map(
|
|
4600
|
+
(option) => typeof option === "string" || asRecord(option) ? normalizePromptChoiceOption(option) : option
|
|
4601
|
+
) : void 0,
|
|
4548
4602
|
defaultValue: source.defaultValue,
|
|
4549
4603
|
placeholder: typeof source.placeholder === "string" ? source.placeholder : void 0,
|
|
4550
4604
|
allowEmpty: typeof source.allowEmpty === "boolean" ? source.allowEmpty : void 0,
|
|
@@ -4572,9 +4626,26 @@ function resolvePromptAnswer(prompt, answer) {
|
|
|
4572
4626
|
}
|
|
4573
4627
|
|
|
4574
4628
|
// src/session.ts
|
|
4629
|
+
var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
|
|
4630
|
+
function withPromptTranscriptTimeout(promise) {
|
|
4631
|
+
let timeout = null;
|
|
4632
|
+
return Promise.race([
|
|
4633
|
+
promise,
|
|
4634
|
+
new Promise((_, reject) => {
|
|
4635
|
+
timeout = setTimeout(() => {
|
|
4636
|
+
reject(new Error("Timed out appending prompt answer transcript."));
|
|
4637
|
+
}, PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS);
|
|
4638
|
+
})
|
|
4639
|
+
]).finally(() => {
|
|
4640
|
+
if (timeout) {
|
|
4641
|
+
clearTimeout(timeout);
|
|
4642
|
+
}
|
|
4643
|
+
});
|
|
4644
|
+
}
|
|
4575
4645
|
var Session = class {
|
|
4576
4646
|
client;
|
|
4577
4647
|
clientId;
|
|
4648
|
+
initialQuota;
|
|
4578
4649
|
jobsMap = /* @__PURE__ */ new Map();
|
|
4579
4650
|
pendingAgentMessagesByJobId = /* @__PURE__ */ new Map();
|
|
4580
4651
|
eventListeners = /* @__PURE__ */ new Map();
|
|
@@ -4588,9 +4659,12 @@ var Session = class {
|
|
|
4588
4659
|
lastKnownTools = /* @__PURE__ */ new Map();
|
|
4589
4660
|
/** Last seen live prompts, keyed by prompt id, for answer normalization */
|
|
4590
4661
|
promptCache = /* @__PURE__ */ new Map();
|
|
4591
|
-
|
|
4662
|
+
/** Prompt ids locally answered before the document sync catches up. */
|
|
4663
|
+
hiddenPromptIds = /* @__PURE__ */ new Set();
|
|
4664
|
+
constructor(client, clientId, options = {}) {
|
|
4592
4665
|
this.client = client;
|
|
4593
4666
|
this.clientId = clientId || `client_${Date.now()}`;
|
|
4667
|
+
this.initialQuota = options.initialQuota || null;
|
|
4594
4668
|
this.setupEventHandlers();
|
|
4595
4669
|
this.setupToolInvokeHandler();
|
|
4596
4670
|
}
|
|
@@ -4639,6 +4713,16 @@ var Session = class {
|
|
|
4639
4713
|
get document() {
|
|
4640
4714
|
return this.client.doc;
|
|
4641
4715
|
}
|
|
4716
|
+
get quota() {
|
|
4717
|
+
return this.getQuota();
|
|
4718
|
+
}
|
|
4719
|
+
getQuota() {
|
|
4720
|
+
const quota = this.client.doc.billing?.quota;
|
|
4721
|
+
if (quota && typeof quota === "object") {
|
|
4722
|
+
return quota;
|
|
4723
|
+
}
|
|
4724
|
+
return this.initialQuota;
|
|
4725
|
+
}
|
|
4642
4726
|
get sessionId() {
|
|
4643
4727
|
return this.client.currentSessionId;
|
|
4644
4728
|
}
|
|
@@ -4723,8 +4807,9 @@ var Session = class {
|
|
|
4723
4807
|
* `effect.invoke` RPC back to the sandbox effect host, where the registered handlers
|
|
4724
4808
|
* execute locally and return the result to the sandbox.
|
|
4725
4809
|
*/
|
|
4726
|
-
async submitJob(code,
|
|
4727
|
-
|
|
4810
|
+
async submitJob(code, domainRevisionOrOptions) {
|
|
4811
|
+
const options = typeof domainRevisionOrOptions === "string" ? { domainRevision: domainRevisionOrOptions } : domainRevisionOrOptions || {};
|
|
4812
|
+
let revision = options.domainRevision || this.currentDomainRevision || this.extractDomainRevisionFromDoc(this.client.doc) || void 0;
|
|
4728
4813
|
if (!revision) {
|
|
4729
4814
|
try {
|
|
4730
4815
|
const summary = await this.getDomain();
|
|
@@ -4739,7 +4824,9 @@ var Session = class {
|
|
|
4739
4824
|
}
|
|
4740
4825
|
const result = await this.client.call("job.submit", {
|
|
4741
4826
|
domainRevision: revision,
|
|
4742
|
-
code
|
|
4827
|
+
code,
|
|
4828
|
+
metadata: options.metadata,
|
|
4829
|
+
agent: options.agent
|
|
4743
4830
|
});
|
|
4744
4831
|
if (!result.jobId) {
|
|
4745
4832
|
throw new Error("Failed to submit job: no jobId returned");
|
|
@@ -4780,25 +4867,39 @@ var Session = class {
|
|
|
4780
4867
|
const prompt = this.promptCache.get(promptId);
|
|
4781
4868
|
const resolvedAnswer = resolvePromptAnswer(prompt, answer);
|
|
4782
4869
|
this.promptCache.delete(promptId);
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
answer
|
|
4786
|
-
|
|
4787
|
-
|
|
4870
|
+
this.hiddenPromptIds.add(promptId);
|
|
4871
|
+
try {
|
|
4872
|
+
await this.client.call("prompt.answer", {
|
|
4873
|
+
promptId,
|
|
4874
|
+
answer: resolvedAnswer,
|
|
4875
|
+
value: resolvedAnswer
|
|
4876
|
+
});
|
|
4877
|
+
} catch (error) {
|
|
4878
|
+
this.hiddenPromptIds.delete(promptId);
|
|
4879
|
+
if (prompt) {
|
|
4880
|
+
this.promptCache.set(promptId, prompt);
|
|
4881
|
+
}
|
|
4882
|
+
throw error;
|
|
4883
|
+
}
|
|
4788
4884
|
try {
|
|
4789
4885
|
const content = this.stringifyConversationValue(resolvedAnswer);
|
|
4790
4886
|
if (content.trim()) {
|
|
4791
|
-
await
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4887
|
+
await withPromptTranscriptTimeout(
|
|
4888
|
+
this.appendConversationMessage({
|
|
4889
|
+
role: "user",
|
|
4890
|
+
content,
|
|
4891
|
+
promptId
|
|
4892
|
+
})
|
|
4893
|
+
);
|
|
4796
4894
|
}
|
|
4797
4895
|
} catch {
|
|
4798
4896
|
}
|
|
4799
4897
|
}
|
|
4800
4898
|
async appendConversationMessage(input) {
|
|
4801
|
-
return this.client.call(
|
|
4899
|
+
return this.client.call(
|
|
4900
|
+
"conversation.append",
|
|
4901
|
+
input
|
|
4902
|
+
);
|
|
4802
4903
|
}
|
|
4803
4904
|
/**
|
|
4804
4905
|
* Get the current list of available effects.
|
|
@@ -4807,9 +4908,53 @@ var Session = class {
|
|
|
4807
4908
|
getEffects() {
|
|
4808
4909
|
const doc = this.client.doc;
|
|
4809
4910
|
const toolMap = /* @__PURE__ */ new Map();
|
|
4810
|
-
const
|
|
4811
|
-
|
|
4812
|
-
|
|
4911
|
+
const domainPackages = doc.domain?.packages;
|
|
4912
|
+
const packageCandidates = domainPackages && typeof domainPackages === "object" ? [
|
|
4913
|
+
domainPackages.domain,
|
|
4914
|
+
domainPackages["@sandbox/domain"],
|
|
4915
|
+
...Object.values(domainPackages)
|
|
4916
|
+
].filter(Boolean) : [];
|
|
4917
|
+
for (const domainPkg of packageCandidates) {
|
|
4918
|
+
if (domainPkg?.tools && Array.isArray(domainPkg.tools)) {
|
|
4919
|
+
for (const tool of domainPkg.tools) {
|
|
4920
|
+
if (!tool?.name || toolMap.has(tool.name)) continue;
|
|
4921
|
+
toolMap.set(tool.name, {
|
|
4922
|
+
name: tool.name,
|
|
4923
|
+
description: tool.description,
|
|
4924
|
+
inputSchema: tool.inputSchema,
|
|
4925
|
+
outputSchema: tool.outputSchema,
|
|
4926
|
+
className: tool.className || void 0,
|
|
4927
|
+
static: tool.static || false,
|
|
4928
|
+
ready: false,
|
|
4929
|
+
publishedAt: void 0
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
if (!domainPkg?.classes || typeof domainPkg.classes !== "object") {
|
|
4934
|
+
continue;
|
|
4935
|
+
}
|
|
4936
|
+
for (const [className, classDef] of Object.entries(
|
|
4937
|
+
domainPkg.classes
|
|
4938
|
+
)) {
|
|
4939
|
+
const methods = Array.isArray(classDef?.methods) ? classDef.methods : [];
|
|
4940
|
+
for (const method of methods) {
|
|
4941
|
+
if (!method?.name || toolMap.has(method.name)) continue;
|
|
4942
|
+
toolMap.set(method.name, {
|
|
4943
|
+
name: method.name,
|
|
4944
|
+
description: method.description,
|
|
4945
|
+
inputSchema: method.inputSchema,
|
|
4946
|
+
outputSchema: method.outputSchema,
|
|
4947
|
+
className: method.className || classDef?.name || className,
|
|
4948
|
+
static: method.static || false,
|
|
4949
|
+
ready: false,
|
|
4950
|
+
publishedAt: void 0
|
|
4951
|
+
});
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
const legacyDomainPkg = doc.domain?.packages?.domain;
|
|
4956
|
+
if (legacyDomainPkg?.tools && Array.isArray(legacyDomainPkg.tools)) {
|
|
4957
|
+
for (const tool of legacyDomainPkg.tools) {
|
|
4813
4958
|
if (!tool?.name) continue;
|
|
4814
4959
|
toolMap.set(tool.name, {
|
|
4815
4960
|
name: tool.name,
|
|
@@ -4823,6 +4968,27 @@ var Session = class {
|
|
|
4823
4968
|
});
|
|
4824
4969
|
}
|
|
4825
4970
|
}
|
|
4971
|
+
if (legacyDomainPkg?.classes && typeof legacyDomainPkg.classes === "object") {
|
|
4972
|
+
for (const [className, classDef] of Object.entries(
|
|
4973
|
+
legacyDomainPkg.classes
|
|
4974
|
+
)) {
|
|
4975
|
+
const methods = Array.isArray(classDef?.methods) ? classDef.methods : [];
|
|
4976
|
+
for (const method of methods) {
|
|
4977
|
+
if (!method?.name || toolMap.has(method.name)) continue;
|
|
4978
|
+
toolMap.set(method.name, {
|
|
4979
|
+
name: method.name,
|
|
4980
|
+
description: method.description,
|
|
4981
|
+
inputSchema: method.inputSchema,
|
|
4982
|
+
outputSchema: method.outputSchema,
|
|
4983
|
+
className: method.className || classDef?.name || className,
|
|
4984
|
+
static: method.static || false,
|
|
4985
|
+
ready: false,
|
|
4986
|
+
publishedAt: void 0
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
const hasPolicyFilteredDomainTools = toolMap.size > 0;
|
|
4826
4992
|
const catalogs = doc.catalog?.rawToolCatalogs || {};
|
|
4827
4993
|
for (const [clientId, catalog] of Object.entries(catalogs)) {
|
|
4828
4994
|
const cat = catalog;
|
|
@@ -4830,6 +4996,7 @@ var Session = class {
|
|
|
4830
4996
|
for (const tool of cat.tools) {
|
|
4831
4997
|
if (!tool?.name) continue;
|
|
4832
4998
|
const existing = toolMap.get(tool.name);
|
|
4999
|
+
if (hasPolicyFilteredDomainTools && !existing) continue;
|
|
4833
5000
|
if (existing?.publishedAt && cat.publishedAt && existing.publishedAt > cat.publishedAt)
|
|
4834
5001
|
continue;
|
|
4835
5002
|
const isLocal = clientId === this.clientId;
|
|
@@ -4849,6 +5016,24 @@ var Session = class {
|
|
|
4849
5016
|
}
|
|
4850
5017
|
return Array.from(toolMap.values());
|
|
4851
5018
|
}
|
|
5019
|
+
/**
|
|
5020
|
+
* Return the currently open prompt payloads known to this session.
|
|
5021
|
+
*
|
|
5022
|
+
* These come from live `prompt` / `prompt.request` websocket events and
|
|
5023
|
+
* preserve the exact shape used by `answerPrompt(...)`.
|
|
5024
|
+
*/
|
|
5025
|
+
getPrompts() {
|
|
5026
|
+
return Array.from(this.promptCache.values()).map((prompt) => ({
|
|
5027
|
+
...prompt,
|
|
5028
|
+
options: Array.isArray(prompt.options) ? prompt.options.map(
|
|
5029
|
+
(option) => typeof option === "string" ? option : { ...option }
|
|
5030
|
+
) : void 0,
|
|
5031
|
+
metadata: prompt.metadata ? { ...prompt.metadata } : void 0
|
|
5032
|
+
}));
|
|
5033
|
+
}
|
|
5034
|
+
getHiddenPromptIds() {
|
|
5035
|
+
return Array.from(this.hiddenPromptIds);
|
|
5036
|
+
}
|
|
4852
5037
|
/**
|
|
4853
5038
|
* Backwards-compatible alias for `getEffects()`.
|
|
4854
5039
|
*/
|
|
@@ -4948,11 +5133,7 @@ var Session = class {
|
|
|
4948
5133
|
if (!normalizedDocs) {
|
|
4949
5134
|
return normalizedTypes;
|
|
4950
5135
|
}
|
|
4951
|
-
return [
|
|
4952
|
-
normalizedTypes,
|
|
4953
|
-
"Generated usage notes from ./sandbox-tools docs:",
|
|
4954
|
-
normalizedDocs
|
|
4955
|
-
].join("\n\n");
|
|
5136
|
+
return [normalizedTypes, "[Docs]", normalizedDocs].join("\n\n");
|
|
4956
5137
|
}
|
|
4957
5138
|
if (normalizedDocs) {
|
|
4958
5139
|
return normalizedDocs;
|
|
@@ -5174,6 +5355,7 @@ import { ${allImports} } from "./sandbox-tools";
|
|
|
5174
5355
|
const emitPrompt = (payload) => {
|
|
5175
5356
|
const prompt = normalizePrompt(payload);
|
|
5176
5357
|
if (!prompt) return;
|
|
5358
|
+
this.hiddenPromptIds.delete(prompt.id);
|
|
5177
5359
|
this.promptCache.set(prompt.id, prompt);
|
|
5178
5360
|
this.emit("prompt", prompt);
|
|
5179
5361
|
};
|
|
@@ -5356,6 +5538,7 @@ var JobImplementation = class {
|
|
|
5356
5538
|
eventListeners = /* @__PURE__ */ new Map();
|
|
5357
5539
|
bufferedAgentMessages = [];
|
|
5358
5540
|
bufferedAgentMessageIds = /* @__PURE__ */ new Set();
|
|
5541
|
+
resultSettled = false;
|
|
5359
5542
|
metadata;
|
|
5360
5543
|
constructor(id, client, initialState) {
|
|
5361
5544
|
this.id = id;
|
|
@@ -5380,7 +5563,9 @@ var JobImplementation = class {
|
|
|
5380
5563
|
if (execData.error) {
|
|
5381
5564
|
this.finalize("failed", void 0, execData.error);
|
|
5382
5565
|
} else {
|
|
5383
|
-
this.finalize("succeeded", execData.result
|
|
5566
|
+
this.finalize("succeeded", execData.result, void 0, {
|
|
5567
|
+
hasResult: Object.prototype.hasOwnProperty.call(execData, "result")
|
|
5568
|
+
});
|
|
5384
5569
|
}
|
|
5385
5570
|
this.emit("status", this.status);
|
|
5386
5571
|
}
|
|
@@ -5416,9 +5601,6 @@ var JobImplementation = class {
|
|
|
5416
5601
|
if (normalizedStatus === "failed" || normalizedStatus === "timeout" || normalizedStatus === "canceled") {
|
|
5417
5602
|
this.finalize(normalizedStatus);
|
|
5418
5603
|
}
|
|
5419
|
-
if (normalizedStatus === "succeeded") {
|
|
5420
|
-
this.finalize("succeeded");
|
|
5421
|
-
}
|
|
5422
5604
|
this.emit("status", normalizedStatus);
|
|
5423
5605
|
});
|
|
5424
5606
|
this.client.on(`job.${id}.stdout`, (line) => {
|
|
@@ -5438,7 +5620,7 @@ var JobImplementation = class {
|
|
|
5438
5620
|
this.emit("stderr", line);
|
|
5439
5621
|
});
|
|
5440
5622
|
this.client.on(`job.${id}.result`, (result) => {
|
|
5441
|
-
this.finalize("succeeded", result);
|
|
5623
|
+
this.finalize("succeeded", result, void 0, { hasResult: true });
|
|
5442
5624
|
});
|
|
5443
5625
|
this.client.on(`job.${id}.error`, (error) => {
|
|
5444
5626
|
this.finalize("failed", void 0, error);
|
|
@@ -5459,7 +5641,9 @@ var JobImplementation = class {
|
|
|
5459
5641
|
this.client.on("job.completed", (data) => {
|
|
5460
5642
|
const jobData = data;
|
|
5461
5643
|
if (jobData.jobId === id) {
|
|
5462
|
-
this.finalize("succeeded", jobData.result
|
|
5644
|
+
this.finalize("succeeded", jobData.result, void 0, {
|
|
5645
|
+
hasResult: true
|
|
5646
|
+
});
|
|
5463
5647
|
this.emit("status", this.status);
|
|
5464
5648
|
}
|
|
5465
5649
|
});
|
|
@@ -5584,7 +5768,7 @@ var JobImplementation = class {
|
|
|
5584
5768
|
this.metadata.status = "running";
|
|
5585
5769
|
}
|
|
5586
5770
|
}
|
|
5587
|
-
finalize(status, result, error) {
|
|
5771
|
+
finalize(status, result, error, options = {}) {
|
|
5588
5772
|
if (!this.metadata.startedAt) {
|
|
5589
5773
|
this.metadata.startedAt = Date.now();
|
|
5590
5774
|
}
|
|
@@ -5592,14 +5776,18 @@ var JobImplementation = class {
|
|
|
5592
5776
|
this.metadata.status = status;
|
|
5593
5777
|
this.metadata.completedAt = this.metadata.completedAt || Date.now();
|
|
5594
5778
|
this.metadata.durationMs = this.metadata.completedAt - this.metadata.startedAt;
|
|
5595
|
-
if (result !== void 0) {
|
|
5779
|
+
if (!this.resultSettled && (options.hasResult || result !== void 0)) {
|
|
5596
5780
|
this.metadata.result = sanitizeFeedbackValue(result);
|
|
5781
|
+
this.resultSettled = true;
|
|
5597
5782
|
this._resolveResult(result);
|
|
5598
5783
|
}
|
|
5599
|
-
if (error !== void 0) {
|
|
5600
|
-
const
|
|
5784
|
+
if (!this.resultSettled && (error !== void 0 || status === "failed" || status === "timeout" || status === "canceled")) {
|
|
5785
|
+
const fallbackError = new Error(`Job ${this.id} ${status}.`);
|
|
5786
|
+
const cause = error ?? fallbackError;
|
|
5787
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
5601
5788
|
this.metadata.error = truncateFeedbackString(message);
|
|
5602
|
-
this.
|
|
5789
|
+
this.resultSettled = true;
|
|
5790
|
+
this._rejectResult(cause);
|
|
5603
5791
|
}
|
|
5604
5792
|
}
|
|
5605
5793
|
upsertToolCall(next) {
|
|
@@ -5682,6 +5870,17 @@ function humanTextFromStdout(stdout) {
|
|
|
5682
5870
|
}
|
|
5683
5871
|
return null;
|
|
5684
5872
|
}
|
|
5873
|
+
function responseTextFromAgentMessages(agentMessages) {
|
|
5874
|
+
for (const message of [...agentMessages].reverse()) {
|
|
5875
|
+
const record = asRecord2(message);
|
|
5876
|
+
if (!record) continue;
|
|
5877
|
+
for (const key of RESPONSE_KEYS) {
|
|
5878
|
+
const normalized = normalizeText(record[key]);
|
|
5879
|
+
if (normalized) return normalized;
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
return null;
|
|
5883
|
+
}
|
|
5685
5884
|
function pushString(target, value) {
|
|
5686
5885
|
if (typeof value === "string" && value.trim()) {
|
|
5687
5886
|
target.add(value.trim());
|
|
@@ -5707,6 +5906,41 @@ function collectReferencesFromRecord(record, refs) {
|
|
|
5707
5906
|
for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
|
|
5708
5907
|
pushStringArray(refs.variableNames, record[key]);
|
|
5709
5908
|
}
|
|
5909
|
+
function stringValue(record, keys) {
|
|
5910
|
+
for (const key of keys) {
|
|
5911
|
+
const value = record[key];
|
|
5912
|
+
if (typeof value === "string" && value.trim()) {
|
|
5913
|
+
return value.trim();
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
return null;
|
|
5917
|
+
}
|
|
5918
|
+
function findEntryPathForRecord(record, heap) {
|
|
5919
|
+
const directPath = stringValue(record, ["entryPath", "path"]);
|
|
5920
|
+
if (directPath && heap.entriesByPath?.[directPath]) {
|
|
5921
|
+
return directPath;
|
|
5922
|
+
}
|
|
5923
|
+
const id = stringValue(record, ["id", "_id", "recordId", "objectId"]);
|
|
5924
|
+
if (!id) {
|
|
5925
|
+
return null;
|
|
5926
|
+
}
|
|
5927
|
+
const className = stringValue(record, [
|
|
5928
|
+
"className",
|
|
5929
|
+
"_className",
|
|
5930
|
+
"__className",
|
|
5931
|
+
"prototype",
|
|
5932
|
+
"type"
|
|
5933
|
+
]);
|
|
5934
|
+
const entries = Object.values(heap.entriesByPath || {});
|
|
5935
|
+
const exact = entries.find(
|
|
5936
|
+
(entry) => entry.id === id && (!className || entry.className === className || entry.prototypes?.includes(className))
|
|
5937
|
+
);
|
|
5938
|
+
if (exact?.path) {
|
|
5939
|
+
return exact.path;
|
|
5940
|
+
}
|
|
5941
|
+
const idOnlyMatches = entries.filter((entry) => entry.id === id);
|
|
5942
|
+
return idOnlyMatches.length === 1 ? idOnlyMatches[0].path : null;
|
|
5943
|
+
}
|
|
5710
5944
|
function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
5711
5945
|
if (value === null || value === void 0 || depth > 4 || seen.has(value))
|
|
5712
5946
|
return;
|
|
@@ -5727,6 +5961,8 @@ function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__
|
|
|
5727
5961
|
const record = asRecord2(value);
|
|
5728
5962
|
if (!record) return;
|
|
5729
5963
|
seen.add(value);
|
|
5964
|
+
const entryPath = findEntryPathForRecord(record, heap);
|
|
5965
|
+
if (entryPath) refs.entryPaths.add(entryPath);
|
|
5730
5966
|
collectReferencesFromRecord(record, refs);
|
|
5731
5967
|
for (const key of UI_CONTAINER_KEYS) {
|
|
5732
5968
|
const nested = asRecord2(record[key]);
|
|
@@ -5835,6 +6071,7 @@ function resolveJobPresentation({
|
|
|
5835
6071
|
jobId,
|
|
5836
6072
|
result,
|
|
5837
6073
|
stdout = [],
|
|
6074
|
+
agentMessages = [],
|
|
5838
6075
|
sessionHeap,
|
|
5839
6076
|
allowExplicitArtifacts = true
|
|
5840
6077
|
}) {
|
|
@@ -5867,7 +6104,7 @@ function resolveJobPresentation({
|
|
|
5867
6104
|
const hasExplicitArtifacts = allowExplicitArtifacts && (explicitEntries.length > 0 || explicitLists.length > 0);
|
|
5868
6105
|
const lists = hasExplicitArtifacts ? explicitLists : jobLists;
|
|
5869
6106
|
const entries = hasExplicitArtifacts ? explicitEntries : changedEntries;
|
|
5870
|
-
const responseText = extractResponseText(result, stdout) || fallbackResponseText(entries, lists);
|
|
6107
|
+
const responseText = extractResponseText(result, stdout) || responseTextFromAgentMessages(agentMessages) || fallbackResponseText(entries, lists);
|
|
5871
6108
|
return {
|
|
5872
6109
|
responseText,
|
|
5873
6110
|
entries,
|
|
@@ -10343,6 +10580,67 @@ external_exports.object({
|
|
|
10343
10580
|
transitions: external_exports.array(StateMachineTransitionSchema),
|
|
10344
10581
|
finalStates: external_exports.array(external_exports.string()).optional()
|
|
10345
10582
|
}).strict();
|
|
10583
|
+
var POLICY_OPERATORS = [
|
|
10584
|
+
"eq",
|
|
10585
|
+
"neq",
|
|
10586
|
+
"gt",
|
|
10587
|
+
"gte",
|
|
10588
|
+
"lt",
|
|
10589
|
+
"lte",
|
|
10590
|
+
"contains",
|
|
10591
|
+
"not_contains",
|
|
10592
|
+
"starts_with",
|
|
10593
|
+
"ends_with",
|
|
10594
|
+
"exists"
|
|
10595
|
+
];
|
|
10596
|
+
var PolicyPredicateSchema = external_exports.object({
|
|
10597
|
+
path: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).optional(),
|
|
10598
|
+
field: external_exports.string().optional(),
|
|
10599
|
+
input: external_exports.string().optional(),
|
|
10600
|
+
operator: external_exports.enum([...POLICY_OPERATORS]),
|
|
10601
|
+
stringValue: external_exports.string().optional(),
|
|
10602
|
+
numberValue: external_exports.number().optional(),
|
|
10603
|
+
booleanValue: external_exports.boolean().optional(),
|
|
10604
|
+
value: external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]).optional()
|
|
10605
|
+
}).strict();
|
|
10606
|
+
var PolicyStateMachinePredicateSchema = external_exports.object({
|
|
10607
|
+
machine: external_exports.string().min(1),
|
|
10608
|
+
operator: external_exports.enum([...POLICY_OPERATORS]),
|
|
10609
|
+
state: external_exports.string().optional(),
|
|
10610
|
+
stringValue: external_exports.string().optional()
|
|
10611
|
+
}).strict();
|
|
10612
|
+
var PolicyConditionSchema = external_exports.lazy(
|
|
10613
|
+
() => external_exports.object({
|
|
10614
|
+
all: external_exports.array(PolicyConditionSchema).optional(),
|
|
10615
|
+
any: external_exports.array(PolicyConditionSchema).optional(),
|
|
10616
|
+
not: PolicyConditionSchema.optional(),
|
|
10617
|
+
input: PolicyPredicateSchema.optional(),
|
|
10618
|
+
object: PolicyPredicateSchema.optional(),
|
|
10619
|
+
stateMachine: PolicyStateMachinePredicateSchema.optional()
|
|
10620
|
+
}).strict().refine(
|
|
10621
|
+
(data) => [
|
|
10622
|
+
data.all,
|
|
10623
|
+
data.any,
|
|
10624
|
+
data.not,
|
|
10625
|
+
data.input,
|
|
10626
|
+
data.object,
|
|
10627
|
+
data.stateMachine
|
|
10628
|
+
].filter((value) => value !== void 0).length === 1,
|
|
10629
|
+
{
|
|
10630
|
+
message: "Policy condition must define exactly one of all, any, not, input, object, or stateMachine"
|
|
10631
|
+
}
|
|
10632
|
+
)
|
|
10633
|
+
);
|
|
10634
|
+
var PolicyRuleSchema = external_exports.object({
|
|
10635
|
+
id: external_exports.string().min(1).optional(),
|
|
10636
|
+
reason: external_exports.string().optional(),
|
|
10637
|
+
when: PolicyConditionSchema
|
|
10638
|
+
}).strict();
|
|
10639
|
+
var PoliciesSchema = external_exports.object({
|
|
10640
|
+
allowWhen: external_exports.array(PolicyRuleSchema).optional(),
|
|
10641
|
+
confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
|
|
10642
|
+
denyWhen: external_exports.array(PolicyRuleSchema).optional()
|
|
10643
|
+
}).strict();
|
|
10346
10644
|
external_exports.object({
|
|
10347
10645
|
postCondition: external_exports.union([
|
|
10348
10646
|
external_exports.string(),
|
|
@@ -10372,7 +10670,8 @@ external_exports.object({
|
|
|
10372
10670
|
reason: external_exports.string().optional(),
|
|
10373
10671
|
mode: external_exports.string().optional()
|
|
10374
10672
|
}).strict()
|
|
10375
|
-
]).optional()
|
|
10673
|
+
]).optional(),
|
|
10674
|
+
policies: PoliciesSchema.optional()
|
|
10376
10675
|
}).strict();
|
|
10377
10676
|
|
|
10378
10677
|
// ../metamodel-core/src/index.ts
|
|
@@ -11046,6 +11345,110 @@ async function invokeRegisteredEffect(effectMap, request) {
|
|
|
11046
11345
|
return resolved.handler(request.input, context);
|
|
11047
11346
|
}
|
|
11048
11347
|
|
|
11348
|
+
// src/spend.ts
|
|
11349
|
+
function toGranularHttpBase(apiUrl) {
|
|
11350
|
+
const url = new URL(apiUrl);
|
|
11351
|
+
if (url.protocol === "ws:") {
|
|
11352
|
+
url.protocol = "http:";
|
|
11353
|
+
} else if (url.protocol === "wss:") {
|
|
11354
|
+
url.protocol = "https:";
|
|
11355
|
+
}
|
|
11356
|
+
url.pathname = url.pathname.replace(/\/ws\/connect$/, "").replace(/\/ws$/, "");
|
|
11357
|
+
if (!url.pathname || url.pathname === "/") {
|
|
11358
|
+
url.pathname = "/granular";
|
|
11359
|
+
}
|
|
11360
|
+
url.search = "";
|
|
11361
|
+
url.hash = "";
|
|
11362
|
+
return url.toString().replace(/\/$/, "");
|
|
11363
|
+
}
|
|
11364
|
+
function cleanIdPart(value) {
|
|
11365
|
+
return value.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
11366
|
+
}
|
|
11367
|
+
function buildOpenAISpendEventId(usage, context = {}) {
|
|
11368
|
+
const requestId = usage.requestId?.trim();
|
|
11369
|
+
if (!requestId) return void 0;
|
|
11370
|
+
const scope = context.sessionId || context.environmentId || context.subjectId || context.sandboxId || "global";
|
|
11371
|
+
return ["spend", "openai", scope, requestId].map(cleanIdPart).join("_");
|
|
11372
|
+
}
|
|
11373
|
+
function pricingEffectiveAtSeconds(value) {
|
|
11374
|
+
if (!value) return null;
|
|
11375
|
+
const parsed = Date.parse(value);
|
|
11376
|
+
return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : null;
|
|
11377
|
+
}
|
|
11378
|
+
function compactContext(context) {
|
|
11379
|
+
return Object.fromEntries(
|
|
11380
|
+
Object.entries(context).filter(
|
|
11381
|
+
([, value]) => value != null && value !== ""
|
|
11382
|
+
)
|
|
11383
|
+
);
|
|
11384
|
+
}
|
|
11385
|
+
function omitTenantId(context) {
|
|
11386
|
+
const scopedContext = { ...context };
|
|
11387
|
+
delete scopedContext.tenantId;
|
|
11388
|
+
return scopedContext;
|
|
11389
|
+
}
|
|
11390
|
+
async function recordOpenAIUsageSpend(options) {
|
|
11391
|
+
const usageContext = compactContext({
|
|
11392
|
+
...options.usage.usageContext || {},
|
|
11393
|
+
...options.context || {}
|
|
11394
|
+
});
|
|
11395
|
+
const context = omitTenantId(usageContext);
|
|
11396
|
+
const spendEventId = options.usage.spendEventId || buildOpenAISpendEventId(options.usage, context);
|
|
11397
|
+
const metadata = {
|
|
11398
|
+
...options.metadata || {},
|
|
11399
|
+
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
11400
|
+
usageContext: context
|
|
11401
|
+
};
|
|
11402
|
+
const response = await fetch(
|
|
11403
|
+
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
11404
|
+
{
|
|
11405
|
+
method: "POST",
|
|
11406
|
+
cache: "no-store",
|
|
11407
|
+
headers: {
|
|
11408
|
+
Authorization: `Bearer ${options.token}`,
|
|
11409
|
+
"Content-Type": "application/json"
|
|
11410
|
+
},
|
|
11411
|
+
body: JSON.stringify({
|
|
11412
|
+
...spendEventId ? { spendEventId } : {},
|
|
11413
|
+
sandboxId: context.sandboxId || null,
|
|
11414
|
+
environmentId: context.environmentId || null,
|
|
11415
|
+
sessionId: context.sessionId || null,
|
|
11416
|
+
subjectId: context.subjectId || null,
|
|
11417
|
+
permissionProfileId: context.permissionProfileId || null,
|
|
11418
|
+
source: "openai",
|
|
11419
|
+
lineItemType: "llm_tokens",
|
|
11420
|
+
provider: options.usage.provider,
|
|
11421
|
+
model: options.usage.model,
|
|
11422
|
+
operation: options.usage.operation || "chat.completions",
|
|
11423
|
+
requestId: options.usage.requestId || null,
|
|
11424
|
+
inputTokens: options.usage.inputTokens,
|
|
11425
|
+
outputTokens: options.usage.outputTokens,
|
|
11426
|
+
cachedInputTokens: options.usage.cachedInputTokens,
|
|
11427
|
+
reasoningTokens: options.usage.reasoningTokens,
|
|
11428
|
+
quantity: options.usage.totalTokens,
|
|
11429
|
+
quantityUnit: "tokens",
|
|
11430
|
+
inputPricePerMillionMicros: options.usage.inputPricePerMillionMicros,
|
|
11431
|
+
cachedInputPricePerMillionMicros: options.usage.cachedInputPricePerMillionMicros,
|
|
11432
|
+
outputPricePerMillionMicros: options.usage.outputPricePerMillionMicros,
|
|
11433
|
+
amountMicros: options.usage.amountMicros,
|
|
11434
|
+
currency: options.usage.currency,
|
|
11435
|
+
pricingSource: options.usage.pricingSource,
|
|
11436
|
+
pricingEffectiveAt: pricingEffectiveAtSeconds(
|
|
11437
|
+
options.usage.pricingEffectiveAt
|
|
11438
|
+
),
|
|
11439
|
+
estimated: false,
|
|
11440
|
+
metadata
|
|
11441
|
+
})
|
|
11442
|
+
}
|
|
11443
|
+
);
|
|
11444
|
+
if (!response.ok) {
|
|
11445
|
+
throw new Error(
|
|
11446
|
+
`Granular spend event failed (${response.status}): ${await response.text()}`
|
|
11447
|
+
);
|
|
11448
|
+
}
|
|
11449
|
+
return response.json();
|
|
11450
|
+
}
|
|
11451
|
+
|
|
11049
11452
|
// ../metamodel-enum/src/index.ts
|
|
11050
11453
|
function renderInlineStringUnion(values) {
|
|
11051
11454
|
return values.map((value) => JSON.stringify(value)).join(" | ");
|
|
@@ -11404,6 +11807,148 @@ var noteMetamodelPackage = defineMetamodelPackage({
|
|
|
11404
11807
|
}
|
|
11405
11808
|
});
|
|
11406
11809
|
|
|
11810
|
+
// ../policy-engine/src/index.ts
|
|
11811
|
+
function isRecord(value) {
|
|
11812
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11813
|
+
}
|
|
11814
|
+
function normalizePath(value) {
|
|
11815
|
+
if (Array.isArray(value)) {
|
|
11816
|
+
return value.map((part) => String(part)).filter(Boolean);
|
|
11817
|
+
}
|
|
11818
|
+
if (typeof value === "string") {
|
|
11819
|
+
return value.includes(".") ? value.split(".").filter(Boolean) : [value];
|
|
11820
|
+
}
|
|
11821
|
+
return [];
|
|
11822
|
+
}
|
|
11823
|
+
function firstDefinedValue(spec) {
|
|
11824
|
+
if ("value" in spec) return spec.value;
|
|
11825
|
+
if ("stringValue" in spec) return spec.stringValue;
|
|
11826
|
+
if ("numberValue" in spec) return spec.numberValue;
|
|
11827
|
+
if ("booleanValue" in spec) return spec.booleanValue;
|
|
11828
|
+
if ("state" in spec) return spec.state;
|
|
11829
|
+
return void 0;
|
|
11830
|
+
}
|
|
11831
|
+
function normalizeCondition(input) {
|
|
11832
|
+
if (input === void 0 || input === null) return { kind: "always" };
|
|
11833
|
+
if (!isRecord(input)) {
|
|
11834
|
+
throw new Error("Policy condition must be an object");
|
|
11835
|
+
}
|
|
11836
|
+
if (Array.isArray(input.all)) {
|
|
11837
|
+
return {
|
|
11838
|
+
kind: "all",
|
|
11839
|
+
conditions: input.all.map((item) => normalizeCondition(item))
|
|
11840
|
+
};
|
|
11841
|
+
}
|
|
11842
|
+
if (Array.isArray(input.any)) {
|
|
11843
|
+
return {
|
|
11844
|
+
kind: "any",
|
|
11845
|
+
conditions: input.any.map((item) => normalizeCondition(item))
|
|
11846
|
+
};
|
|
11847
|
+
}
|
|
11848
|
+
if (input.not !== void 0) {
|
|
11849
|
+
return { kind: "not", condition: normalizeCondition(input.not) };
|
|
11850
|
+
}
|
|
11851
|
+
for (const source of ["input", "object", "stateMachine"]) {
|
|
11852
|
+
const raw = input[source];
|
|
11853
|
+
if (!isRecord(raw)) continue;
|
|
11854
|
+
const operator = raw.operator;
|
|
11855
|
+
if (operator !== "eq" && operator !== "neq" && operator !== "gt" && operator !== "gte" && operator !== "lt" && operator !== "lte" && operator !== "contains" && operator !== "not_contains" && operator !== "starts_with" && operator !== "ends_with" && operator !== "exists") {
|
|
11856
|
+
throw new Error(`Unsupported policy operator: ${String(operator)}`);
|
|
11857
|
+
}
|
|
11858
|
+
if (source === "stateMachine") {
|
|
11859
|
+
const machine = typeof raw.machine === "string" ? raw.machine : "";
|
|
11860
|
+
if (!machine) throw new Error("stateMachine condition requires machine");
|
|
11861
|
+
return {
|
|
11862
|
+
kind: "predicate",
|
|
11863
|
+
source,
|
|
11864
|
+
path: [machine],
|
|
11865
|
+
machine,
|
|
11866
|
+
operator,
|
|
11867
|
+
value: firstDefinedValue(raw)
|
|
11868
|
+
};
|
|
11869
|
+
}
|
|
11870
|
+
const path2 = normalizePath(raw.path ?? raw.field ?? raw.input);
|
|
11871
|
+
if (path2.length === 0) {
|
|
11872
|
+
throw new Error(`${source} condition requires a path`);
|
|
11873
|
+
}
|
|
11874
|
+
return {
|
|
11875
|
+
kind: "predicate",
|
|
11876
|
+
source,
|
|
11877
|
+
path: path2,
|
|
11878
|
+
operator,
|
|
11879
|
+
value: firstDefinedValue(raw)
|
|
11880
|
+
};
|
|
11881
|
+
}
|
|
11882
|
+
throw new Error(
|
|
11883
|
+
"Policy condition must contain all, any, not, input, object, or stateMachine"
|
|
11884
|
+
);
|
|
11885
|
+
}
|
|
11886
|
+
function summarizeCondition(condition) {
|
|
11887
|
+
switch (condition.kind) {
|
|
11888
|
+
case "always":
|
|
11889
|
+
return "always";
|
|
11890
|
+
case "all":
|
|
11891
|
+
return condition.conditions.map(summarizeCondition).join(" and ");
|
|
11892
|
+
case "any":
|
|
11893
|
+
return condition.conditions.map(summarizeCondition).join(" or ");
|
|
11894
|
+
case "not":
|
|
11895
|
+
return `not (${summarizeCondition(condition.condition)})`;
|
|
11896
|
+
case "predicate": {
|
|
11897
|
+
const path2 = condition.source === "stateMachine" ? `stateMachine.${condition.machine || condition.path.join(".")}` : `${condition.source}.${condition.path.join(".")}`;
|
|
11898
|
+
if (condition.operator === "exists") return `${path2} exists`;
|
|
11899
|
+
return `${path2} ${condition.operator} ${String(condition.value)}`;
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
|
|
11904
|
+
// ../metamodel-policy/src/index.ts
|
|
11905
|
+
function escapeGraphqlString(value) {
|
|
11906
|
+
return JSON.stringify(value);
|
|
11907
|
+
}
|
|
11908
|
+
function buildPolicyMutations(effectKey, spec) {
|
|
11909
|
+
const policies = spec.policies;
|
|
11910
|
+
if (!policies) return [];
|
|
11911
|
+
const mutations = [];
|
|
11912
|
+
const addRules = (key, outcome) => {
|
|
11913
|
+
const rules = policies[key] || [];
|
|
11914
|
+
rules.forEach((rule, index) => {
|
|
11915
|
+
const condition = normalizeCondition(rule.when);
|
|
11916
|
+
const summary = rule.reason || summarizeCondition(condition);
|
|
11917
|
+
const id = rule.id || `${effectKey}:${outcome}:${index + 1}`;
|
|
11918
|
+
mutations.push({
|
|
11919
|
+
label: `set policy ${outcome} on ${effectKey}`,
|
|
11920
|
+
query: `mutation { set_policy_rule(effect_key: ${escapeGraphqlString(effectKey)}, policy_id: ${escapeGraphqlString(id)}, outcome: ${escapeGraphqlString(outcome)}, reason: ${escapeGraphqlString(summary)}, condition_json: ${escapeGraphqlString(JSON.stringify(condition))}) }`
|
|
11921
|
+
});
|
|
11922
|
+
});
|
|
11923
|
+
};
|
|
11924
|
+
addRules("allowWhen", "allow");
|
|
11925
|
+
addRules("confirmWhen", "confirm");
|
|
11926
|
+
addRules("denyWhen", "deny");
|
|
11927
|
+
return mutations;
|
|
11928
|
+
}
|
|
11929
|
+
var policyMetamodelPackage = defineMetamodelPackage({
|
|
11930
|
+
id: "policy",
|
|
11931
|
+
manifest: {
|
|
11932
|
+
buildEffectMutations: buildPolicyMutations
|
|
11933
|
+
},
|
|
11934
|
+
summary: {
|
|
11935
|
+
selections: {
|
|
11936
|
+
methodFields: ["policies"]
|
|
11937
|
+
},
|
|
11938
|
+
readMethodSummary(rawMethod) {
|
|
11939
|
+
return rawMethod.policies ? { metamodels: { policies: rawMethod.policies } } : {};
|
|
11940
|
+
}
|
|
11941
|
+
},
|
|
11942
|
+
docs: {
|
|
11943
|
+
effectRows: [
|
|
11944
|
+
{
|
|
11945
|
+
key: "policies",
|
|
11946
|
+
description: "Universal effect policies with allowWhen, confirmWhen, and denyWhen structural conditions."
|
|
11947
|
+
}
|
|
11948
|
+
]
|
|
11949
|
+
}
|
|
11950
|
+
});
|
|
11951
|
+
|
|
11407
11952
|
// ../metamodel-required/src/index.ts
|
|
11408
11953
|
function buildRequiredFieldMutations(fieldPath, required) {
|
|
11409
11954
|
if (!required) return [];
|
|
@@ -12178,7 +12723,8 @@ var DEFAULT_METAMODEL_PACKAGES = [
|
|
|
12178
12723
|
searchableMetamodelPackage,
|
|
12179
12724
|
validationRuleMetamodelPackage,
|
|
12180
12725
|
stateMachineMetamodelPackage,
|
|
12181
|
-
effectBehaviorsMetamodelPackage
|
|
12726
|
+
effectBehaviorsMetamodelPackage,
|
|
12727
|
+
policyMetamodelPackage
|
|
12182
12728
|
];
|
|
12183
12729
|
createMetamodelRegistry(
|
|
12184
12730
|
DEFAULT_METAMODEL_PACKAGES
|
|
@@ -12245,6 +12791,12 @@ var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
|
|
|
12245
12791
|
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS = 1e3;
|
|
12246
12792
|
var LOCAL_CONTROL_REQUEST_RETRY_COUNT = 4;
|
|
12247
12793
|
var LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS = 500;
|
|
12794
|
+
var SESSION_DATA_REQUEST_RETRY_COUNT = 4;
|
|
12795
|
+
var SESSION_DATA_REQUEST_RETRY_DELAY_MS = 500;
|
|
12796
|
+
var EFFECT_HOST_CONNECT_TIMEOUT_MS = 15e3;
|
|
12797
|
+
var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 3e4;
|
|
12798
|
+
var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
|
|
12799
|
+
var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
|
|
12248
12800
|
function planRecordObjectsChunks(records, batchSize) {
|
|
12249
12801
|
const total = records.length;
|
|
12250
12802
|
const size = Math.max(1, Math.min(batchSize, total));
|
|
@@ -12259,6 +12811,19 @@ function planRecordObjectsChunks(records, batchSize) {
|
|
|
12259
12811
|
function sleep(ms) {
|
|
12260
12812
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12261
12813
|
}
|
|
12814
|
+
function withTimeout(promise, timeoutMs, label) {
|
|
12815
|
+
let timer = null;
|
|
12816
|
+
const timeout = new Promise((_, reject) => {
|
|
12817
|
+
timer = setTimeout(() => {
|
|
12818
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
12819
|
+
}, timeoutMs);
|
|
12820
|
+
});
|
|
12821
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
12822
|
+
if (timer) {
|
|
12823
|
+
clearTimeout(timer);
|
|
12824
|
+
}
|
|
12825
|
+
});
|
|
12826
|
+
}
|
|
12262
12827
|
function isLocalControlUrl(url) {
|
|
12263
12828
|
try {
|
|
12264
12829
|
const parsed = new URL(url);
|
|
@@ -12272,7 +12837,19 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
12272
12837
|
}
|
|
12273
12838
|
function isRetryableRecordObjectsError(error) {
|
|
12274
12839
|
const message = error instanceof Error ? error.message : String(error);
|
|
12275
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
12840
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out|bad gateway|too many requests|gateway timeout|control plane api error \((?:429|500|502|503|504)\)|graphql api error \((?:429|500|502|503|504)\)|failed to record batch/i.test(
|
|
12841
|
+
message
|
|
12842
|
+
);
|
|
12843
|
+
}
|
|
12844
|
+
function isRetryableEffectRegistrationError(error) {
|
|
12845
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12846
|
+
return /timed out|websocket disconnected|websocket not connected|rpc timeout|worker restarted mid-request|network connection lost|bad gateway|gateway timeout|too many requests|(?:control plane|granular|graphql) api error \((?:429|500|502|503|504)\)/i.test(
|
|
12847
|
+
message
|
|
12848
|
+
);
|
|
12849
|
+
}
|
|
12850
|
+
function isRetryableSessionDataError(error) {
|
|
12851
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12852
|
+
return /network connection lost|worker restarted mid-request|econnreset|socket connection was closed unexpectedly|bad gateway|gateway timeout|service unavailable|session data api error \((?:429|500|502|503|504)\)/i.test(
|
|
12276
12853
|
message
|
|
12277
12854
|
);
|
|
12278
12855
|
}
|
|
@@ -12294,16 +12871,28 @@ function computeEffectRegistrationKey(effect) {
|
|
|
12294
12871
|
effect.versionSelector
|
|
12295
12872
|
)}`;
|
|
12296
12873
|
}
|
|
12297
|
-
function buildEffectHostUrl(apiUrl, sandboxId, effectClientId, clientId) {
|
|
12298
|
-
const
|
|
12299
|
-
|
|
12874
|
+
function buildEffectHostUrl(apiUrl, sandboxId, effectClientId, clientId, effectHostUrl) {
|
|
12875
|
+
const overrideUrl = effectHostUrl || process.env.GRANULAR_EFFECT_HOST_URL || process.env.EFFECT_HOST_URL;
|
|
12876
|
+
const api = new URL(apiUrl);
|
|
12877
|
+
const localRuntimeBase = process.env.RUNTIME_ORCHESTRATOR_URL || (isLocalControlUrl(apiUrl) ? `${api.protocol}//${api.hostname}:8791` : "");
|
|
12878
|
+
const url = new URL(overrideUrl || localRuntimeBase || apiUrl);
|
|
12879
|
+
if (url.protocol === "https:") {
|
|
12880
|
+
url.protocol = "wss:";
|
|
12881
|
+
} else if (url.protocol === "http:") {
|
|
12882
|
+
url.protocol = "ws:";
|
|
12883
|
+
}
|
|
12884
|
+
if (!overrideUrl && isLocalControlUrl(apiUrl) && api.pathname.endsWith("/granular")) {
|
|
12885
|
+
url.pathname = "/granular/orchestrator/effects/connect";
|
|
12886
|
+
} else if (url.pathname.endsWith("/granular/ws/connect")) {
|
|
12300
12887
|
url.pathname = url.pathname.replace(/\/ws\/connect$/, "/effects/connect");
|
|
12301
12888
|
} else if (url.pathname.endsWith("/granular")) {
|
|
12302
|
-
url.pathname = `${url.pathname.replace(/\/$/, "")}/effects/connect`;
|
|
12889
|
+
url.pathname = isLocalControlUrl(url.toString()) ? "/granular/orchestrator/effects/connect" : `${url.pathname.replace(/\/$/, "")}/effects/connect`;
|
|
12303
12890
|
} else if (url.pathname.endsWith("/v2/ws/connect")) {
|
|
12304
12891
|
url.pathname = url.pathname.replace(/\/ws\/connect$/, "/effects/connect");
|
|
12305
12892
|
} else if (url.pathname.endsWith("/v2/ws")) {
|
|
12306
12893
|
url.pathname = url.pathname.replace(/\/ws$/, "/effects/connect");
|
|
12894
|
+
} else if (url.pathname === "/" && isLocalControlUrl(url.toString()) && (url.port === "8791" || !overrideUrl && Boolean(localRuntimeBase))) {
|
|
12895
|
+
url.pathname = "/granular/orchestrator/effects/connect";
|
|
12307
12896
|
} else if (url.pathname.endsWith("/ws/connect")) {
|
|
12308
12897
|
url.pathname = url.pathname.replace(/\/ws\/connect$/, "/effects/connect");
|
|
12309
12898
|
} else if (url.pathname.endsWith("/ws")) {
|
|
@@ -12338,6 +12927,79 @@ function normalizeHeapSnapshot(raw) {
|
|
|
12338
12927
|
updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
12339
12928
|
};
|
|
12340
12929
|
}
|
|
12930
|
+
function normalizeGraphPathSegment(value) {
|
|
12931
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
12932
|
+
}
|
|
12933
|
+
function extractRecordIdFromGraphPath(path2, className) {
|
|
12934
|
+
const normalizedPrefix = `${normalizeGraphPathSegment(className)}_`;
|
|
12935
|
+
if (path2.startsWith(normalizedPrefix)) {
|
|
12936
|
+
return path2.slice(normalizedPrefix.length);
|
|
12937
|
+
}
|
|
12938
|
+
const legacyPrefix = `${className}_`;
|
|
12939
|
+
if (path2.startsWith(legacyPrefix)) {
|
|
12940
|
+
return path2.slice(legacyPrefix.length);
|
|
12941
|
+
}
|
|
12942
|
+
return path2;
|
|
12943
|
+
}
|
|
12944
|
+
function toRecordSearchResult(className, node) {
|
|
12945
|
+
const path2 = typeof node.path === "string" ? node.path : "";
|
|
12946
|
+
if (!path2) return null;
|
|
12947
|
+
const fields = Array.isArray(node.submodels) ? node.submodels.flatMap(
|
|
12948
|
+
(submodel) => {
|
|
12949
|
+
const name = typeof submodel?.label === "string" && submodel.label.trim() ? submodel.label : typeof submodel?.path === "string" ? submodel.path.split(":").pop() || submodel.path : "";
|
|
12950
|
+
if (!name) return [];
|
|
12951
|
+
if (typeof submodel.string_value === "string") {
|
|
12952
|
+
return [{ name, type: "string", value: submodel.string_value }];
|
|
12953
|
+
}
|
|
12954
|
+
if (typeof submodel.number_value === "number") {
|
|
12955
|
+
return [{ name, type: "number", value: submodel.number_value }];
|
|
12956
|
+
}
|
|
12957
|
+
if (typeof submodel.boolean_value === "boolean") {
|
|
12958
|
+
return [
|
|
12959
|
+
{
|
|
12960
|
+
name,
|
|
12961
|
+
type: "boolean",
|
|
12962
|
+
value: submodel.boolean_value
|
|
12963
|
+
}
|
|
12964
|
+
];
|
|
12965
|
+
}
|
|
12966
|
+
return [];
|
|
12967
|
+
}
|
|
12968
|
+
) : [];
|
|
12969
|
+
return {
|
|
12970
|
+
path: path2,
|
|
12971
|
+
className,
|
|
12972
|
+
id: extractRecordIdFromGraphPath(path2, className),
|
|
12973
|
+
label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path2, className),
|
|
12974
|
+
description: typeof node.description === "string" && node.description.trim() ? node.description : null,
|
|
12975
|
+
fields
|
|
12976
|
+
};
|
|
12977
|
+
}
|
|
12978
|
+
function normalizeRecordSearchText(value) {
|
|
12979
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
12980
|
+
}
|
|
12981
|
+
function rankRecordSearchResult(result, query, index) {
|
|
12982
|
+
const normalizedQuery = normalizeRecordSearchText(query);
|
|
12983
|
+
if (!normalizedQuery) {
|
|
12984
|
+
return index;
|
|
12985
|
+
}
|
|
12986
|
+
const label = normalizeRecordSearchText(result.label || "");
|
|
12987
|
+
const id = normalizeRecordSearchText(result.id || "");
|
|
12988
|
+
const path2 = normalizeRecordSearchText(result.path || "");
|
|
12989
|
+
const className = normalizeRecordSearchText(result.className || "");
|
|
12990
|
+
const searchable = [label, id, path2, className].filter(Boolean);
|
|
12991
|
+
if (label === normalizedQuery) return index;
|
|
12992
|
+
if (id === normalizedQuery || path2 === normalizedQuery) return 100 + index;
|
|
12993
|
+
if (label.startsWith(normalizedQuery)) return 200 + index;
|
|
12994
|
+
if (searchable.some((value) => value.startsWith(normalizedQuery))) {
|
|
12995
|
+
return 300 + index;
|
|
12996
|
+
}
|
|
12997
|
+
if (label.includes(normalizedQuery)) return 400 + index;
|
|
12998
|
+
if (searchable.some((value) => value.includes(normalizedQuery))) {
|
|
12999
|
+
return 500 + index;
|
|
13000
|
+
}
|
|
13001
|
+
return 900 + index;
|
|
13002
|
+
}
|
|
12341
13003
|
function deriveRuntimeBaseUrl(apiEndpoint) {
|
|
12342
13004
|
try {
|
|
12343
13005
|
const endpoint = new URL(apiEndpoint);
|
|
@@ -12426,7 +13088,7 @@ function normalizeEnvironmentData(environment) {
|
|
|
12426
13088
|
setup: normalizeEnvironmentSetupSummary(environment.setup)
|
|
12427
13089
|
};
|
|
12428
13090
|
}
|
|
12429
|
-
var Environment = class {
|
|
13091
|
+
var Environment = class _Environment {
|
|
12430
13092
|
granular;
|
|
12431
13093
|
envData;
|
|
12432
13094
|
_apiKey;
|
|
@@ -12621,28 +13283,30 @@ var Environment = class {
|
|
|
12621
13283
|
return response.json();
|
|
12622
13284
|
}
|
|
12623
13285
|
// ==================== ID ↔ GRAPH PATH MAPPING ====================
|
|
13286
|
+
static normalizeGraphPathSegment(value) {
|
|
13287
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
13288
|
+
}
|
|
12624
13289
|
/**
|
|
12625
|
-
* Convert a class name +
|
|
12626
|
-
*
|
|
12627
|
-
* Two objects of *different* classes may share the same real-world ID,
|
|
12628
|
-
* so the graph path must incorporate the class to guarantee uniqueness.
|
|
12629
|
-
*
|
|
12630
|
-
* Format: `{className}_{id}` — deterministic, human-readable.
|
|
13290
|
+
* Convert a class name + application record ID into Granular's graph path.
|
|
12631
13291
|
*
|
|
12632
|
-
*
|
|
12633
|
-
*
|
|
12634
|
-
*
|
|
13292
|
+
* This mirrors the record-write path normalization used by the control plane.
|
|
13293
|
+
* Keep the original customer/system ID in `real_id`; graph paths are stable
|
|
13294
|
+
* internal addresses, not the source of truth for business identity.
|
|
12635
13295
|
*/
|
|
12636
13296
|
static toGraphPath(className, id) {
|
|
12637
|
-
return `${className}_${id}`;
|
|
13297
|
+
return `${_Environment.normalizeGraphPathSegment(className)}_${_Environment.normalizeGraphPathSegment(id)}`;
|
|
12638
13298
|
}
|
|
12639
13299
|
/**
|
|
12640
|
-
*
|
|
13300
|
+
* Best-effort extraction of an ID-like suffix from a graph path.
|
|
12641
13301
|
*
|
|
12642
|
-
*
|
|
12643
|
-
*
|
|
13302
|
+
* Prefer the record's `real_id` field whenever exact customer/system IDs
|
|
13303
|
+
* matter, because graph path normalization is intentionally lossy.
|
|
12644
13304
|
*/
|
|
12645
13305
|
static extractIdFromGraphPath(graphPath, className) {
|
|
13306
|
+
const normalizedPrefix = `${_Environment.normalizeGraphPathSegment(className)}_`;
|
|
13307
|
+
if (graphPath.startsWith(normalizedPrefix)) {
|
|
13308
|
+
return graphPath.substring(normalizedPrefix.length);
|
|
13309
|
+
}
|
|
12646
13310
|
const prefix = `${className}_`;
|
|
12647
13311
|
return graphPath.startsWith(prefix) ? graphPath.substring(prefix.length) : graphPath;
|
|
12648
13312
|
}
|
|
@@ -12689,6 +13353,62 @@ var Environment = class {
|
|
|
12689
13353
|
}
|
|
12690
13354
|
return response.json();
|
|
12691
13355
|
}
|
|
13356
|
+
async searchRecords(query, options = {}) {
|
|
13357
|
+
const normalizedQuery = query.replace(/\s+/g, " ").trim();
|
|
13358
|
+
const limit = Math.max(1, Math.min(50, Math.floor(options.limit ?? 12)));
|
|
13359
|
+
const offset = Math.max(0, Math.floor(options.offset ?? 0));
|
|
13360
|
+
const response = await this.graphql(
|
|
13361
|
+
`
|
|
13362
|
+
query RecordMentionSearch(
|
|
13363
|
+
$query: String
|
|
13364
|
+
$limit: Int
|
|
13365
|
+
$offset: Int
|
|
13366
|
+
$classNames: [String!]
|
|
13367
|
+
) {
|
|
13368
|
+
record_search(
|
|
13369
|
+
query: $query
|
|
13370
|
+
limit: $limit
|
|
13371
|
+
offset: $offset
|
|
13372
|
+
class_names: $classNames
|
|
13373
|
+
) {
|
|
13374
|
+
className
|
|
13375
|
+
model {
|
|
13376
|
+
path
|
|
13377
|
+
label
|
|
13378
|
+
description
|
|
13379
|
+
submodels {
|
|
13380
|
+
path
|
|
13381
|
+
label
|
|
13382
|
+
string_value
|
|
13383
|
+
number_value
|
|
13384
|
+
boolean_value
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
`,
|
|
13390
|
+
{
|
|
13391
|
+
query: normalizedQuery,
|
|
13392
|
+
limit,
|
|
13393
|
+
offset,
|
|
13394
|
+
classNames: options.classNames?.length ? options.classNames : []
|
|
13395
|
+
}
|
|
13396
|
+
);
|
|
13397
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13398
|
+
const results = (response.data?.record_search || []).flatMap((entry) => {
|
|
13399
|
+
const className = entry.className?.trim();
|
|
13400
|
+
const item = className && entry.model ? toRecordSearchResult(className, entry.model) : null;
|
|
13401
|
+
if (!item || seen.has(item.path)) {
|
|
13402
|
+
return [];
|
|
13403
|
+
}
|
|
13404
|
+
seen.add(item.path);
|
|
13405
|
+
return [item];
|
|
13406
|
+
});
|
|
13407
|
+
return results.map((result, index) => ({
|
|
13408
|
+
result,
|
|
13409
|
+
rank: rankRecordSearchResult(result, normalizedQuery, index)
|
|
13410
|
+
})).sort((left, right) => left.rank - right.rank).map((item) => item.result).slice(0, limit);
|
|
13411
|
+
}
|
|
12692
13412
|
// ==================== RELATIONSHIP METHODS ====================
|
|
12693
13413
|
/**
|
|
12694
13414
|
* Define a relationship between two model types.
|
|
@@ -13454,7 +14174,8 @@ var Environment = class {
|
|
|
13454
14174
|
body: JSON.stringify({
|
|
13455
14175
|
records,
|
|
13456
14176
|
batchSize: options.batchSize,
|
|
13457
|
-
setupRunId: options.setupRunId
|
|
14177
|
+
setupRunId: options.setupRunId,
|
|
14178
|
+
writeMode: options.writeMode
|
|
13458
14179
|
})
|
|
13459
14180
|
}
|
|
13460
14181
|
);
|
|
@@ -13506,11 +14227,13 @@ var Environment = class {
|
|
|
13506
14227
|
};
|
|
13507
14228
|
var EnvironmentSession = class extends Session {
|
|
13508
14229
|
environment;
|
|
14230
|
+
sessionDataRoutePrefix;
|
|
13509
14231
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
13510
14232
|
graphContainerStatus = null;
|
|
13511
|
-
constructor(client, environment, clientId) {
|
|
13512
|
-
super(client, clientId);
|
|
14233
|
+
constructor(client, environment, clientId, options = {}) {
|
|
14234
|
+
super(client, clientId, { initialQuota: options.initialQuota });
|
|
13513
14235
|
this.environment = environment;
|
|
14236
|
+
this.sessionDataRoutePrefix = options.sessionDataRoutePrefix || "/orchestrator/ws/sessions";
|
|
13514
14237
|
}
|
|
13515
14238
|
get environmentId() {
|
|
13516
14239
|
return this.environment.environmentId;
|
|
@@ -13555,7 +14278,7 @@ var EnvironmentSession = class extends Session {
|
|
|
13555
14278
|
const doc = this.document;
|
|
13556
14279
|
return normalizeHeapSnapshot(doc?.heap);
|
|
13557
14280
|
}
|
|
13558
|
-
async sessionDataRequest(path2, query) {
|
|
14281
|
+
async sessionDataRequest(path2, query, init2 = {}) {
|
|
13559
14282
|
const searchParams = new URLSearchParams();
|
|
13560
14283
|
for (const [key, value] of Object.entries(query || {})) {
|
|
13561
14284
|
if (value !== null && typeof value !== "undefined" && value !== "") {
|
|
@@ -13563,23 +14286,39 @@ var EnvironmentSession = class extends Session {
|
|
|
13563
14286
|
}
|
|
13564
14287
|
}
|
|
13565
14288
|
const queryString = searchParams.toString();
|
|
13566
|
-
const
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
14289
|
+
const url = `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path2}${queryString ? `?${queryString}` : ""}`;
|
|
14290
|
+
const body = typeof init2.body === "undefined" ? void 0 : JSON.stringify(init2.body);
|
|
14291
|
+
for (let attempt = 1; attempt <= SESSION_DATA_REQUEST_RETRY_COUNT; attempt += 1) {
|
|
14292
|
+
try {
|
|
14293
|
+
const response = await fetch(url, {
|
|
14294
|
+
method: init2.method || "GET",
|
|
14295
|
+
headers: {
|
|
14296
|
+
Authorization: `Bearer ${this.environment.authToken}`,
|
|
14297
|
+
"Content-Type": "application/json"
|
|
14298
|
+
},
|
|
14299
|
+
...typeof body === "undefined" ? {} : { body }
|
|
14300
|
+
});
|
|
14301
|
+
if (response.ok) {
|
|
14302
|
+
return response.json();
|
|
13573
14303
|
}
|
|
14304
|
+
const errorText = await response.text();
|
|
14305
|
+
const error = new Error(
|
|
14306
|
+
`Session data API Error (${response.status}): ${errorText}`
|
|
14307
|
+
);
|
|
14308
|
+
if (isLocalControlUrl(url) && isRetryableSessionDataError(error) && attempt < SESSION_DATA_REQUEST_RETRY_COUNT) {
|
|
14309
|
+
await sleep(SESSION_DATA_REQUEST_RETRY_DELAY_MS * attempt);
|
|
14310
|
+
continue;
|
|
14311
|
+
}
|
|
14312
|
+
throw error;
|
|
14313
|
+
} catch (error) {
|
|
14314
|
+
if (isLocalControlUrl(url) && isRetryableSessionDataError(error) && attempt < SESSION_DATA_REQUEST_RETRY_COUNT) {
|
|
14315
|
+
await sleep(SESSION_DATA_REQUEST_RETRY_DELAY_MS * attempt);
|
|
14316
|
+
continue;
|
|
14317
|
+
}
|
|
14318
|
+
throw error;
|
|
13574
14319
|
}
|
|
13575
|
-
);
|
|
13576
|
-
if (!response.ok) {
|
|
13577
|
-
const errorText = await response.text();
|
|
13578
|
-
throw new Error(
|
|
13579
|
-
`Session data API Error (${response.status}): ${errorText}`
|
|
13580
|
-
);
|
|
13581
14320
|
}
|
|
13582
|
-
|
|
14321
|
+
throw new Error(`Session data API Error: exhausted retries for ${url}`);
|
|
13583
14322
|
}
|
|
13584
14323
|
async collectAllSessionItems(listPage) {
|
|
13585
14324
|
const items = [];
|
|
@@ -13637,10 +14376,21 @@ var EnvironmentSession = class extends Session {
|
|
|
13637
14376
|
get: (name) => this.sessionDataRequest(
|
|
13638
14377
|
`/heap/lists/${encodeURIComponent(name)}`
|
|
13639
14378
|
)
|
|
13640
|
-
}
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
14379
|
+
},
|
|
14380
|
+
variables: {
|
|
14381
|
+
list: (options = {}) => this.sessionDataRequest("/heap/variables", options),
|
|
14382
|
+
get: (name) => this.sessionDataRequest(
|
|
14383
|
+
`/heap/variables/${encodeURIComponent(name)}`
|
|
14384
|
+
),
|
|
14385
|
+
delete: (name) => this.sessionDataRequest(
|
|
14386
|
+
`/heap/variables/${encodeURIComponent(name)}`,
|
|
14387
|
+
void 0,
|
|
14388
|
+
{ method: "DELETE" }
|
|
14389
|
+
)
|
|
14390
|
+
}
|
|
14391
|
+
};
|
|
14392
|
+
}
|
|
14393
|
+
get transcript() {
|
|
13644
14394
|
return {
|
|
13645
14395
|
list: async (options = {}) => {
|
|
13646
14396
|
const [messages, jobs, entries, lists] = await Promise.all([
|
|
@@ -13705,6 +14455,19 @@ var EnvironmentSession = class extends Session {
|
|
|
13705
14455
|
async graphql(query, variables) {
|
|
13706
14456
|
return this.environment.graphql(query, variables);
|
|
13707
14457
|
}
|
|
14458
|
+
async searchRecords(query, options = {}) {
|
|
14459
|
+
return this.environment.searchRecords(query, options);
|
|
14460
|
+
}
|
|
14461
|
+
async mentionRecord(input) {
|
|
14462
|
+
return this.sessionDataRequest(
|
|
14463
|
+
"/records/mention",
|
|
14464
|
+
void 0,
|
|
14465
|
+
{
|
|
14466
|
+
method: "POST",
|
|
14467
|
+
body: input
|
|
14468
|
+
}
|
|
14469
|
+
);
|
|
14470
|
+
}
|
|
13708
14471
|
async defineRelationship(options) {
|
|
13709
14472
|
return this.environment.defineRelationship(options);
|
|
13710
14473
|
}
|
|
@@ -13852,6 +14615,7 @@ var Granular = class _Granular {
|
|
|
13852
14615
|
WebSocketCtor;
|
|
13853
14616
|
onUnexpectedClose;
|
|
13854
14617
|
onReconnectError;
|
|
14618
|
+
effectHostUrl;
|
|
13855
14619
|
debugHttp = process.env.GRANULAR_DEBUG_HTTP === "1";
|
|
13856
14620
|
/** Sandbox-level effect registry: sandboxId → (effectKey@selector → ToolWithHandler) */
|
|
13857
14621
|
sandboxEffects = /* @__PURE__ */ new Map();
|
|
@@ -13880,6 +14644,7 @@ var Granular = class _Granular {
|
|
|
13880
14644
|
this.WebSocketCtor = options.WebSocketCtor;
|
|
13881
14645
|
this.onUnexpectedClose = options.onUnexpectedClose;
|
|
13882
14646
|
this.onReconnectError = options.onReconnectError;
|
|
14647
|
+
this.effectHostUrl = options.effectHostUrl;
|
|
13883
14648
|
this.httpUrl = this.apiUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://").replace(/\/ws$/, "");
|
|
13884
14649
|
}
|
|
13885
14650
|
/**
|
|
@@ -14050,6 +14815,30 @@ var Granular = class _Granular {
|
|
|
14050
14815
|
permissions: options.permissions || options.user?.permissions || []
|
|
14051
14816
|
});
|
|
14052
14817
|
}
|
|
14818
|
+
/**
|
|
14819
|
+
* Run a registered environment importer against an environment that was
|
|
14820
|
+
* opened outside this SDK instance, for example by a delegated browser flow.
|
|
14821
|
+
*
|
|
14822
|
+
* This uses the same setup-run and queued record-import plumbing as
|
|
14823
|
+
* `openEnvironment()`: importer stages, expected object counts, and queued
|
|
14824
|
+
* import counters remain visible through `environment.setup` and
|
|
14825
|
+
* `getRecordImportSummary()`.
|
|
14826
|
+
*/
|
|
14827
|
+
async runEnvironmentImporterForEnvironment(environmentId, options = {}) {
|
|
14828
|
+
const environmentData = await this.environments.get(environmentId);
|
|
14829
|
+
const environment = this.bindEnvironmentHandle(environmentData);
|
|
14830
|
+
const requestedOntology = options.ontology || environmentData.ontologyId || environmentData.sandboxId;
|
|
14831
|
+
return this.runEnvironmentImporter(
|
|
14832
|
+
{
|
|
14833
|
+
environment: environmentData,
|
|
14834
|
+
requestedOntology,
|
|
14835
|
+
sandboxId: environmentData.sandboxId,
|
|
14836
|
+
subjectId: environmentData.subjectId,
|
|
14837
|
+
setupTriggerReason: options.reason || "new_environment"
|
|
14838
|
+
},
|
|
14839
|
+
environment
|
|
14840
|
+
);
|
|
14841
|
+
}
|
|
14053
14842
|
resolveRequestedTag(options, methodName) {
|
|
14054
14843
|
const tag = options.tag?.trim() || options.tagName?.trim() || options.environment?.trim();
|
|
14055
14844
|
if (!tag) {
|
|
@@ -14241,6 +15030,15 @@ var Granular = class _Granular {
|
|
|
14241
15030
|
const environment = this.bindEnvironmentHandle(envData);
|
|
14242
15031
|
return this.bindWebSocketEnvironmentSession(environment, clientId, minted);
|
|
14243
15032
|
}
|
|
15033
|
+
async recordOpenAIUsageSpend(usage, context, options) {
|
|
15034
|
+
return recordOpenAIUsageSpend({
|
|
15035
|
+
apiUrl: this.apiUrl,
|
|
15036
|
+
token: this.apiKey,
|
|
15037
|
+
usage,
|
|
15038
|
+
context,
|
|
15039
|
+
metadata: options?.metadata
|
|
15040
|
+
});
|
|
15041
|
+
}
|
|
14244
15042
|
/**
|
|
14245
15043
|
* Mark a session closed in the control plane. If `environment` is the connected handle for that
|
|
14246
15044
|
* `sessionId`, disconnects the WebSocket so the runtime tears down cleanly.
|
|
@@ -14293,15 +15091,25 @@ var Granular = class _Granular {
|
|
|
14293
15091
|
return ontologyImporter;
|
|
14294
15092
|
}
|
|
14295
15093
|
async maybeRunEnvironmentImporter(resolved, environment) {
|
|
14296
|
-
|
|
14297
|
-
|
|
15094
|
+
const setupTriggerReason = resolved.setupTriggerReason;
|
|
15095
|
+
if (!setupTriggerReason) {
|
|
15096
|
+
return null;
|
|
14298
15097
|
}
|
|
15098
|
+
return this.runEnvironmentImporter(
|
|
15099
|
+
{
|
|
15100
|
+
...resolved,
|
|
15101
|
+
setupTriggerReason
|
|
15102
|
+
},
|
|
15103
|
+
environment
|
|
15104
|
+
);
|
|
15105
|
+
}
|
|
15106
|
+
async runEnvironmentImporter(resolved, environment) {
|
|
14299
15107
|
const importer = this.resolveEnvironmentImporter(
|
|
14300
15108
|
resolved.requestedOntology,
|
|
14301
15109
|
resolved.sandboxId
|
|
14302
15110
|
);
|
|
14303
15111
|
if (!importer) {
|
|
14304
|
-
return;
|
|
15112
|
+
return null;
|
|
14305
15113
|
}
|
|
14306
15114
|
const setupRun = await this.request(
|
|
14307
15115
|
`/control/environments/${environment.environmentId}/setup-runs`,
|
|
@@ -14341,16 +15149,24 @@ var Granular = class _Granular {
|
|
|
14341
15149
|
},
|
|
14342
15150
|
importRecords: async (records, options) => environment.enqueueRecordImport(records, {
|
|
14343
15151
|
batchSize: options?.batchSize,
|
|
15152
|
+
writeMode: options?.writeMode,
|
|
14344
15153
|
setupRunId
|
|
14345
15154
|
})
|
|
14346
15155
|
};
|
|
14347
15156
|
try {
|
|
14348
15157
|
await importer(importerContext);
|
|
14349
|
-
|
|
15158
|
+
const completedSetupRun = await this.request(
|
|
15159
|
+
`/control/environment-setup-runs/${setupRunId}`,
|
|
15160
|
+
{
|
|
15161
|
+
method: "PATCH",
|
|
15162
|
+
body: JSON.stringify({ markHookCompleted: true })
|
|
15163
|
+
}
|
|
15164
|
+
);
|
|
14350
15165
|
const refreshedEnvironment = await this.environments.get(
|
|
14351
15166
|
environment.environmentId
|
|
14352
15167
|
);
|
|
14353
15168
|
environment.syncEnvironmentData(refreshedEnvironment);
|
|
15169
|
+
return completedSetupRun;
|
|
14354
15170
|
} catch (error) {
|
|
14355
15171
|
await updateSetupRun({
|
|
14356
15172
|
status: "failed",
|
|
@@ -14377,7 +15193,8 @@ var Granular = class _Granular {
|
|
|
14377
15193
|
const environmentSession = new EnvironmentSession(
|
|
14378
15194
|
client,
|
|
14379
15195
|
environment,
|
|
14380
|
-
clientId
|
|
15196
|
+
clientId,
|
|
15197
|
+
{ initialQuota: session.quota || null }
|
|
14381
15198
|
);
|
|
14382
15199
|
await environmentSession.hello();
|
|
14383
15200
|
return environmentSession;
|
|
@@ -14398,27 +15215,45 @@ var Granular = class _Granular {
|
|
|
14398
15215
|
return effects;
|
|
14399
15216
|
}
|
|
14400
15217
|
serializeEffect(effect) {
|
|
14401
|
-
|
|
15218
|
+
const serialized = {
|
|
14402
15219
|
effectKey: computeEffectKey2(effect),
|
|
14403
15220
|
name: effect.name,
|
|
14404
15221
|
description: effect.description,
|
|
14405
15222
|
inputSchema: effect.inputSchema,
|
|
14406
|
-
outputSchema: effect.outputSchema,
|
|
14407
15223
|
stability: effect.stability || "stable",
|
|
14408
|
-
provenance: effect.provenance || { source: "custom" }
|
|
14409
|
-
tags: effect.tags,
|
|
14410
|
-
className: effect.className,
|
|
14411
|
-
static: effect.static,
|
|
14412
|
-
versionSelector: effect.versionSelector
|
|
15224
|
+
provenance: effect.provenance || { source: "custom" }
|
|
14413
15225
|
};
|
|
15226
|
+
if (effect.outputSchema !== void 0) {
|
|
15227
|
+
serialized.outputSchema = effect.outputSchema;
|
|
15228
|
+
}
|
|
15229
|
+
if (effect.tags !== void 0) {
|
|
15230
|
+
serialized.tags = effect.tags;
|
|
15231
|
+
}
|
|
15232
|
+
if (effect.className !== void 0) {
|
|
15233
|
+
serialized.className = effect.className;
|
|
15234
|
+
}
|
|
15235
|
+
if (effect.static !== void 0) {
|
|
15236
|
+
serialized.static = effect.static;
|
|
15237
|
+
}
|
|
15238
|
+
if (effect.versionSelector !== void 0) {
|
|
15239
|
+
serialized.versionSelector = effect.versionSelector;
|
|
15240
|
+
}
|
|
15241
|
+
if (effect.metamodels !== void 0) {
|
|
15242
|
+
serialized.metamodels = effect.metamodels;
|
|
15243
|
+
}
|
|
15244
|
+
return serialized;
|
|
14414
15245
|
}
|
|
14415
15246
|
async publishSandboxEffectCatalog(host) {
|
|
14416
15247
|
const effects = Array.from(
|
|
14417
15248
|
this.getSandboxEffectMap(host.sandboxId).values()
|
|
14418
15249
|
).map((effect) => this.serializeEffect(effect));
|
|
14419
|
-
const result = await
|
|
14420
|
-
effects
|
|
14421
|
-
|
|
15250
|
+
const result = await withTimeout(
|
|
15251
|
+
host.wsClient.call("effects.publishCatalog", {
|
|
15252
|
+
effects
|
|
15253
|
+
}),
|
|
15254
|
+
EFFECT_CATALOG_SYNC_TIMEOUT_MS,
|
|
15255
|
+
`effects.publishCatalog for sandbox ${host.sandboxId}`
|
|
15256
|
+
);
|
|
14422
15257
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
14423
15258
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
14424
15259
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
@@ -14437,8 +15272,26 @@ var Granular = class _Granular {
|
|
|
14437
15272
|
}
|
|
14438
15273
|
}
|
|
14439
15274
|
async syncSandboxEffectCatalog(sandboxId) {
|
|
14440
|
-
|
|
14441
|
-
|
|
15275
|
+
let lastError;
|
|
15276
|
+
for (let attempt = 1; attempt <= EFFECT_CATALOG_SYNC_RETRY_COUNT; attempt += 1) {
|
|
15277
|
+
try {
|
|
15278
|
+
const host = await this.ensureSandboxEffectHost(sandboxId);
|
|
15279
|
+
await this.publishSandboxEffectCatalog(host);
|
|
15280
|
+
return;
|
|
15281
|
+
} catch (error) {
|
|
15282
|
+
lastError = error;
|
|
15283
|
+
this.disconnectSandboxEffectHost(sandboxId);
|
|
15284
|
+
if (attempt === EFFECT_CATALOG_SYNC_RETRY_COUNT || !isRetryableEffectRegistrationError(error)) {
|
|
15285
|
+
throw error;
|
|
15286
|
+
}
|
|
15287
|
+
console.warn(
|
|
15288
|
+
`[Granular] Retrying effect registration for sandbox ${sandboxId} after transient failure (${attempt}/${EFFECT_CATALOG_SYNC_RETRY_COUNT - 1} retries used):`,
|
|
15289
|
+
error
|
|
15290
|
+
);
|
|
15291
|
+
await sleep(EFFECT_CATALOG_SYNC_RETRY_DELAY_MS * attempt);
|
|
15292
|
+
}
|
|
15293
|
+
}
|
|
15294
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
14442
15295
|
}
|
|
14443
15296
|
recoverEffectHost(host, error) {
|
|
14444
15297
|
if (host.recovering) {
|
|
@@ -14531,7 +15384,8 @@ var Granular = class _Granular {
|
|
|
14531
15384
|
this.apiUrl,
|
|
14532
15385
|
sandboxId,
|
|
14533
15386
|
effectClientId,
|
|
14534
|
-
clientId
|
|
15387
|
+
clientId,
|
|
15388
|
+
this.effectHostUrl
|
|
14535
15389
|
),
|
|
14536
15390
|
sessionId: `effect-host:${effectClientId}`,
|
|
14537
15391
|
token: this.apiKey,
|
|
@@ -14567,7 +15421,11 @@ var Granular = class _Granular {
|
|
|
14567
15421
|
wsClient.on("disconnect", () => {
|
|
14568
15422
|
this.stopEffectHostHeartbeat(host);
|
|
14569
15423
|
});
|
|
14570
|
-
await
|
|
15424
|
+
await withTimeout(
|
|
15425
|
+
wsClient.connect(),
|
|
15426
|
+
EFFECT_HOST_CONNECT_TIMEOUT_MS,
|
|
15427
|
+
`effect host WebSocket connect for sandbox ${sandboxId}`
|
|
15428
|
+
);
|
|
14571
15429
|
await this.synchronizeEffectHost(host);
|
|
14572
15430
|
this.sandboxEffectHosts.set(sandboxId, host);
|
|
14573
15431
|
return host;
|
|
@@ -14690,7 +15548,7 @@ var Granular = class _Granular {
|
|
|
14690
15548
|
/**
|
|
14691
15549
|
* Ensure a permission profile exists for a sandbox, creating it if needed.
|
|
14692
15550
|
* If profileName matches an existing profile name, returns its ID.
|
|
14693
|
-
* Otherwise, creates a
|
|
15551
|
+
* Otherwise, creates a v1 source-profile file shape with an allow default.
|
|
14694
15552
|
*/
|
|
14695
15553
|
async ensurePermissionProfile(sandboxId, profileName) {
|
|
14696
15554
|
try {
|
|
@@ -14704,8 +15562,11 @@ var Granular = class _Granular {
|
|
|
14704
15562
|
const created = await this.permissionProfiles.create(sandboxId, {
|
|
14705
15563
|
name: profileName,
|
|
14706
15564
|
rules: {
|
|
14707
|
-
|
|
14708
|
-
|
|
15565
|
+
schemaVersion: 1,
|
|
15566
|
+
name: profileName,
|
|
15567
|
+
description: profileName === "allow-all" ? "Every declared action is visible unless a manifest policy denies it." : `Generated permission profile ${profileName}`,
|
|
15568
|
+
defaults: { actionPolicy: "allow" },
|
|
15569
|
+
actions: []
|
|
14709
15570
|
}
|
|
14710
15571
|
});
|
|
14711
15572
|
return created.permissionProfileId;
|
|
@@ -14778,33 +15639,63 @@ var Granular = class _Granular {
|
|
|
14778
15639
|
* Permission Profile management for sandboxes
|
|
14779
15640
|
*/
|
|
14780
15641
|
get permissionProfiles() {
|
|
15642
|
+
const profileSourceFromRecord = (record) => {
|
|
15643
|
+
const profile = record.profile || record.rules || {};
|
|
15644
|
+
return {
|
|
15645
|
+
...profile,
|
|
15646
|
+
schemaVersion: profile.schemaVersion || 1,
|
|
15647
|
+
name: profile.name || record.name,
|
|
15648
|
+
description: profile.description || record.description
|
|
15649
|
+
};
|
|
15650
|
+
};
|
|
14781
15651
|
return {
|
|
14782
15652
|
list: async (sandboxId) => {
|
|
14783
15653
|
const result = await this.request(
|
|
14784
|
-
`/control/sandboxes/${sandboxId}/permission-
|
|
15654
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`
|
|
14785
15655
|
);
|
|
14786
15656
|
return result.items;
|
|
14787
15657
|
},
|
|
14788
15658
|
get: async (sandboxId, profileId) => {
|
|
14789
|
-
|
|
14790
|
-
`/control/sandboxes/${sandboxId}/permission-
|
|
15659
|
+
const result = await this.request(
|
|
15660
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`
|
|
14791
15661
|
);
|
|
15662
|
+
const profile = result.items.find(
|
|
15663
|
+
(item) => item.permissionProfileId === profileId || item.name === profileId
|
|
15664
|
+
);
|
|
15665
|
+
if (!profile) {
|
|
15666
|
+
throw new Error(`Permission profile source not found: ${profileId}`);
|
|
15667
|
+
}
|
|
15668
|
+
return profile;
|
|
14792
15669
|
},
|
|
14793
15670
|
create: async (sandboxId, data) => {
|
|
14794
|
-
|
|
14795
|
-
|
|
15671
|
+
const profile = {
|
|
15672
|
+
...data.rules,
|
|
15673
|
+
schemaVersion: 1,
|
|
15674
|
+
name: data.name
|
|
15675
|
+
};
|
|
15676
|
+
const existingProfiles = await this.permissionProfiles.list(sandboxId);
|
|
15677
|
+
const profiles = [
|
|
15678
|
+
...existingProfiles.filter((existing) => existing.name !== data.name).map((existing) => profileSourceFromRecord(existing)),
|
|
15679
|
+
profile
|
|
15680
|
+
];
|
|
15681
|
+
const result = await this.request(
|
|
15682
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`,
|
|
14796
15683
|
{
|
|
14797
|
-
method: "
|
|
14798
|
-
body: JSON.stringify(
|
|
15684
|
+
method: "PUT",
|
|
15685
|
+
body: JSON.stringify({ profiles })
|
|
14799
15686
|
}
|
|
14800
15687
|
);
|
|
15688
|
+
const synced = result.items.find((item) => item.name === data.name) || result.items[0];
|
|
15689
|
+
if (!synced) {
|
|
15690
|
+
throw new Error(
|
|
15691
|
+
`Permission profile source sync did not return ${data.name}`
|
|
15692
|
+
);
|
|
15693
|
+
}
|
|
15694
|
+
return synced;
|
|
14801
15695
|
},
|
|
14802
|
-
delete: async (
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
{
|
|
14806
|
-
method: "DELETE"
|
|
14807
|
-
}
|
|
15696
|
+
delete: async (_sandboxId, _profileId) => {
|
|
15697
|
+
throw new Error(
|
|
15698
|
+
"Permission profile sources are updated by syncing the desired source set."
|
|
14808
15699
|
);
|
|
14809
15700
|
}
|
|
14810
15701
|
};
|
|
@@ -15083,21 +15974,8 @@ function uniqueStrings(values, maxCount) {
|
|
|
15083
15974
|
}
|
|
15084
15975
|
return output;
|
|
15085
15976
|
}
|
|
15086
|
-
function
|
|
15087
|
-
|
|
15088
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
15089
|
-
return String(value);
|
|
15090
|
-
if (value === null) return "null";
|
|
15091
|
-
return "unknown";
|
|
15092
|
-
}
|
|
15093
|
-
function describeHeapEntry(entry, previewFieldLimit = 3) {
|
|
15094
|
-
const headline = entry.label || entry.id || entry.path || "Unknown";
|
|
15095
|
-
const pathLabel = entry.path && entry.path !== headline ? ` <${entry.path}>` : "";
|
|
15096
|
-
const classLabel = entry.className || "unknown";
|
|
15097
|
-
const preview = asArray2(entry.fields).filter(
|
|
15098
|
-
(field) => field?.name && field.name !== "_realId" && field.name !== "real_id"
|
|
15099
|
-
).slice(0, previewFieldLimit).map((field) => `${field.name}=${formatScalar(field.value)}`).join(", ");
|
|
15100
|
-
return preview ? `${headline}${pathLabel} [${classLabel}] ${preview}` : `${headline}${pathLabel} [${classLabel}]`;
|
|
15977
|
+
function renderConstBlock(name, value) {
|
|
15978
|
+
return `const ${name} = ${JSON.stringify(value, null, 2)} as const;`;
|
|
15101
15979
|
}
|
|
15102
15980
|
function hashString(value) {
|
|
15103
15981
|
if (!value) return null;
|
|
@@ -15108,101 +15986,6 @@ function hashString(value) {
|
|
|
15108
15986
|
}
|
|
15109
15987
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
15110
15988
|
}
|
|
15111
|
-
function hasSubstantiveAwaitAfterPrompt(code, marker) {
|
|
15112
|
-
const startIndex = code.indexOf(marker);
|
|
15113
|
-
if (startIndex === -1) return true;
|
|
15114
|
-
const segment = code.slice(startIndex + marker.length);
|
|
15115
|
-
const callMatches = segment.matchAll(
|
|
15116
|
-
/await\s+([A-Za-z0-9_$.]+)\.([A-Za-z0-9_]+)\s*\(/g
|
|
15117
|
-
);
|
|
15118
|
-
for (const match of callMatches) {
|
|
15119
|
-
const receiver = match[1] || "";
|
|
15120
|
-
const method = match[2] || "";
|
|
15121
|
-
if (receiver === "loop" || receiver === "heap") continue;
|
|
15122
|
-
if (method.startsWith("get_") || method.startsWith("get")) continue;
|
|
15123
|
-
return true;
|
|
15124
|
-
}
|
|
15125
|
-
return false;
|
|
15126
|
-
}
|
|
15127
|
-
function reviewGeneratedJobCode(code) {
|
|
15128
|
-
const normalized = typeof code === "string" ? code : "";
|
|
15129
|
-
if (!normalized.trim()) return [];
|
|
15130
|
-
const issues = [];
|
|
15131
|
-
if (/require\s*\(\s*['"]\.\/sandbox-tools['"]\s*\)/.test(normalized)) {
|
|
15132
|
-
issues.push({
|
|
15133
|
-
code: "commonjs_require",
|
|
15134
|
-
severity: "error",
|
|
15135
|
-
message: "Use ESM imports like `import { Customer, loop } from './sandbox-tools';` instead of require('./sandbox-tools'). Generated jobs must be plain runnable JavaScript for the sandbox runtime."
|
|
15136
|
-
});
|
|
15137
|
-
}
|
|
15138
|
-
const placeholderPatterns = [
|
|
15139
|
-
/ready to make the change next/i,
|
|
15140
|
-
/ready to .* next/i,
|
|
15141
|
-
/ready to .* now/i,
|
|
15142
|
-
/i can make the change now/i,
|
|
15143
|
-
/i can do that next/i,
|
|
15144
|
-
/i'?m ready to continue/i,
|
|
15145
|
-
/have your approval .* ready to make/i,
|
|
15146
|
-
/approved\./i
|
|
15147
|
-
];
|
|
15148
|
-
if (normalized.includes("await loop.confirm(")) {
|
|
15149
|
-
const postConfirm = normalized.slice(
|
|
15150
|
-
normalized.indexOf("await loop.confirm(")
|
|
15151
|
-
);
|
|
15152
|
-
const hasPlaceholder = placeholderPatterns.some(
|
|
15153
|
-
(pattern) => pattern.test(postConfirm)
|
|
15154
|
-
);
|
|
15155
|
-
const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(
|
|
15156
|
-
normalized,
|
|
15157
|
-
"await loop.confirm("
|
|
15158
|
-
);
|
|
15159
|
-
if (!hasSubstantiveAwait || hasPlaceholder) {
|
|
15160
|
-
issues.push({
|
|
15161
|
-
code: "placeholder_after_confirm",
|
|
15162
|
-
severity: "error",
|
|
15163
|
-
message: "After await loop.confirm(...) returns true, the job must perform the approved mutation in the same resumed run. Do not stop with placeholder text like 'Approved, I can make the change now.'"
|
|
15164
|
-
});
|
|
15165
|
-
}
|
|
15166
|
-
}
|
|
15167
|
-
if (normalized.includes("await loop.ask_user(")) {
|
|
15168
|
-
const postPrompt = normalized.slice(
|
|
15169
|
-
normalized.indexOf("await loop.ask_user(")
|
|
15170
|
-
);
|
|
15171
|
-
const hasPlaceholder = placeholderPatterns.some(
|
|
15172
|
-
(pattern) => pattern.test(postPrompt)
|
|
15173
|
-
);
|
|
15174
|
-
const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(
|
|
15175
|
-
normalized,
|
|
15176
|
-
"await loop.ask_user("
|
|
15177
|
-
);
|
|
15178
|
-
if (hasPlaceholder && !hasSubstantiveAwait) {
|
|
15179
|
-
issues.push({
|
|
15180
|
-
code: "placeholder_after_ask_user",
|
|
15181
|
-
severity: "error",
|
|
15182
|
-
message: "After await loop.ask_user(...) returns a usable answer, continue the workflow in the same resumed run instead of stopping with placeholder text about doing the work later."
|
|
15183
|
-
});
|
|
15184
|
-
}
|
|
15185
|
-
}
|
|
15186
|
-
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
15187
|
-
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
15188
|
-
const returnsShowPayload = /return\s+\{[\s\S]*?\bshow\s*:/.test(normalized);
|
|
15189
|
-
const closesLoop = /loop\.close_loop\s*\(/.test(normalized);
|
|
15190
|
-
if (!hasConversationalReturn && returnsObjectLiteral && !closesLoop) {
|
|
15191
|
-
issues.push({
|
|
15192
|
-
code: "missing_user_reply",
|
|
15193
|
-
severity: "error",
|
|
15194
|
-
message: "User-facing jobs must end with a natural-language answer. Return a short string, an object with a top-level `reply` string, or post text with agent_text_message(...). Do not end with bare structured JSON."
|
|
15195
|
-
});
|
|
15196
|
-
}
|
|
15197
|
-
if (returnsShowPayload) {
|
|
15198
|
-
issues.push({
|
|
15199
|
-
code: "return_show_not_for_ui",
|
|
15200
|
-
severity: "error",
|
|
15201
|
-
message: "Do not use the final return value to send UI record refs through `show`. Use agent_heap_objects(...) for heap-backed UI, then return plain text if you still want a final textual answer."
|
|
15202
|
-
});
|
|
15203
|
-
}
|
|
15204
|
-
return issues;
|
|
15205
|
-
}
|
|
15206
15989
|
function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
15207
15990
|
const variableNames = [];
|
|
15208
15991
|
const listNames = [];
|
|
@@ -15230,6 +16013,247 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
15230
16013
|
function normalizeActionSummaryForPrompt(line) {
|
|
15231
16014
|
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
15232
16015
|
}
|
|
16016
|
+
function collectConversationReferents(liveDoc) {
|
|
16017
|
+
const conversation = asRecord4(liveDoc?.conversation);
|
|
16018
|
+
const persistedReferents = asArray2(conversation?.referents).map((value) => asRecord4(value)).filter((value) => Boolean(value));
|
|
16019
|
+
if (persistedReferents.length > 0) {
|
|
16020
|
+
return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
|
|
16021
|
+
}
|
|
16022
|
+
const heap = asRecord4(liveDoc?.heap);
|
|
16023
|
+
const entriesByPath = asRecord4(heap?.entriesByPath) || {};
|
|
16024
|
+
const listsByName = asRecord4(heap?.listsByName) || {};
|
|
16025
|
+
const variablesByName = asRecord4(heap?.variablesByName) || {};
|
|
16026
|
+
const messages = asArray2(conversation?.messages).map((value) => asRecord4(value)).filter((value) => Boolean(value)).slice().sort((left, right) => (Number(right.ts) || 0) - (Number(left.ts) || 0));
|
|
16027
|
+
const referents = [];
|
|
16028
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16029
|
+
const pushReferent = (referent) => {
|
|
16030
|
+
if (!referent?.kind || !referent.ref) return;
|
|
16031
|
+
const key = `${referent.kind}:${referent.ref}`;
|
|
16032
|
+
if (seen.has(key)) return;
|
|
16033
|
+
seen.add(key);
|
|
16034
|
+
referents.push(referent);
|
|
16035
|
+
};
|
|
16036
|
+
for (const message of messages) {
|
|
16037
|
+
if (message.role !== "assistant") continue;
|
|
16038
|
+
const show = asRecord4(message.show);
|
|
16039
|
+
if (!show) continue;
|
|
16040
|
+
const ts = Number(message.ts) || 0;
|
|
16041
|
+
const messageId = typeof message.id === "string" ? message.id : void 0;
|
|
16042
|
+
const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
|
|
16043
|
+
const entryPaths = uniqueStrings(asArray2(show.entryPaths));
|
|
16044
|
+
const entryClassCounts = /* @__PURE__ */ new Map();
|
|
16045
|
+
const entryMetadata = entryPaths.map((entryPath) => {
|
|
16046
|
+
const entry = asRecord4(entriesByPath[entryPath]);
|
|
16047
|
+
const className = typeof entry?.className === "string" ? entry.className : void 0;
|
|
16048
|
+
if (className) {
|
|
16049
|
+
entryClassCounts.set(
|
|
16050
|
+
className,
|
|
16051
|
+
(entryClassCounts.get(className) || 0) + 1
|
|
16052
|
+
);
|
|
16053
|
+
}
|
|
16054
|
+
return { entryPath, entry, className };
|
|
16055
|
+
});
|
|
16056
|
+
const displayGroupId = entryMetadata.length > 1 ? `message:${messageId || jobId || ts}:entries` : void 0;
|
|
16057
|
+
for (const [
|
|
16058
|
+
index,
|
|
16059
|
+
{ entryPath, entry, className }
|
|
16060
|
+
] of entryMetadata.entries()) {
|
|
16061
|
+
pushReferent({
|
|
16062
|
+
id: `entry:${entryPath}`,
|
|
16063
|
+
kind: "entry",
|
|
16064
|
+
ref: entryPath,
|
|
16065
|
+
role: "assistant",
|
|
16066
|
+
source: "heap_objects",
|
|
16067
|
+
entryPath,
|
|
16068
|
+
recordId: typeof entry?.id === "string" ? entry.id : void 0,
|
|
16069
|
+
className,
|
|
16070
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
|
|
16071
|
+
...displayGroupId ? {
|
|
16072
|
+
displayGroupId,
|
|
16073
|
+
displayGroupIndex: index,
|
|
16074
|
+
displayGroupSize: entryMetadata.length,
|
|
16075
|
+
...className && (entryClassCounts.get(className) || 0) > 1 ? { displayGroupSameTypeSize: entryClassCounts.get(className) } : {}
|
|
16076
|
+
} : {},
|
|
16077
|
+
messageId,
|
|
16078
|
+
jobId,
|
|
16079
|
+
ts
|
|
16080
|
+
});
|
|
16081
|
+
}
|
|
16082
|
+
for (const listName of uniqueStrings(asArray2(show.listNames))) {
|
|
16083
|
+
const list = asRecord4(listsByName[listName]);
|
|
16084
|
+
pushReferent({
|
|
16085
|
+
id: `list:${listName}`,
|
|
16086
|
+
kind: "list",
|
|
16087
|
+
ref: listName,
|
|
16088
|
+
role: "assistant",
|
|
16089
|
+
source: "heap_objects",
|
|
16090
|
+
listName,
|
|
16091
|
+
className: typeof list?.className === "string" ? list.className : void 0,
|
|
16092
|
+
count: Array.isArray(list?.paths) ? list.paths.length : null,
|
|
16093
|
+
messageId,
|
|
16094
|
+
jobId,
|
|
16095
|
+
ts
|
|
16096
|
+
});
|
|
16097
|
+
}
|
|
16098
|
+
for (const variableName of uniqueStrings(
|
|
16099
|
+
asArray2(show.variableNames)
|
|
16100
|
+
)) {
|
|
16101
|
+
const variable = asRecord4(variablesByName[variableName]);
|
|
16102
|
+
const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
|
|
16103
|
+
const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
|
|
16104
|
+
const entry = entryPath ? asRecord4(entriesByPath[entryPath]) : null;
|
|
16105
|
+
const list = listName ? asRecord4(listsByName[listName]) : null;
|
|
16106
|
+
pushReferent({
|
|
16107
|
+
id: `variable:${variableName}`,
|
|
16108
|
+
kind: "variable",
|
|
16109
|
+
ref: variableName,
|
|
16110
|
+
role: "assistant",
|
|
16111
|
+
source: "heap_objects",
|
|
16112
|
+
variableName,
|
|
16113
|
+
variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
|
|
16114
|
+
entryPath,
|
|
16115
|
+
recordId: typeof entry?.id === "string" ? entry.id : void 0,
|
|
16116
|
+
listName,
|
|
16117
|
+
className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
|
|
16118
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
16119
|
+
count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
|
|
16120
|
+
scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
|
|
16121
|
+
messageId,
|
|
16122
|
+
jobId,
|
|
16123
|
+
ts
|
|
16124
|
+
});
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
return referents;
|
|
16128
|
+
}
|
|
16129
|
+
function projectConversationReferentFocus(liveDoc) {
|
|
16130
|
+
const heap = asRecord4(liveDoc?.heap);
|
|
16131
|
+
const listsByName = asRecord4(heap?.listsByName) || {};
|
|
16132
|
+
const referents = collectConversationReferents(liveDoc);
|
|
16133
|
+
const entryPaths = [];
|
|
16134
|
+
const listNames = [];
|
|
16135
|
+
const variableNames = [];
|
|
16136
|
+
let entryCount = 0;
|
|
16137
|
+
let listCount = 0;
|
|
16138
|
+
let variableCount = 0;
|
|
16139
|
+
for (const referent of referents) {
|
|
16140
|
+
if (referent.kind === "entry" && typeof referent.entryPath === "string" && entryCount < 8) {
|
|
16141
|
+
entryCount += 1;
|
|
16142
|
+
entryPaths.push(referent.entryPath);
|
|
16143
|
+
continue;
|
|
16144
|
+
}
|
|
16145
|
+
if (referent.kind === "list" && typeof referent.listName === "string" && listCount < 4) {
|
|
16146
|
+
listCount += 1;
|
|
16147
|
+
listNames.push(referent.listName);
|
|
16148
|
+
const list = asRecord4(listsByName[referent.listName]);
|
|
16149
|
+
entryPaths.push(...asArray2(list?.paths).slice(0, 4));
|
|
16150
|
+
continue;
|
|
16151
|
+
}
|
|
16152
|
+
if (referent.kind === "variable" && typeof referent.variableName === "string" && variableCount < 4) {
|
|
16153
|
+
variableCount += 1;
|
|
16154
|
+
variableNames.push(referent.variableName);
|
|
16155
|
+
if (typeof referent.entryPath === "string") {
|
|
16156
|
+
entryPaths.push(referent.entryPath);
|
|
16157
|
+
}
|
|
16158
|
+
if (typeof referent.listName === "string") {
|
|
16159
|
+
listNames.push(referent.listName);
|
|
16160
|
+
const list = asRecord4(listsByName[referent.listName]);
|
|
16161
|
+
entryPaths.push(...asArray2(list?.paths).slice(0, 4));
|
|
16162
|
+
}
|
|
16163
|
+
}
|
|
16164
|
+
}
|
|
16165
|
+
return {
|
|
16166
|
+
entryPaths: uniqueStrings(entryPaths, 8),
|
|
16167
|
+
listNames: uniqueStrings(listNames, 4),
|
|
16168
|
+
variableNames: uniqueStrings(variableNames, 4)
|
|
16169
|
+
};
|
|
16170
|
+
}
|
|
16171
|
+
function selectConversationReferentsForPrompt(referents) {
|
|
16172
|
+
const selected = [];
|
|
16173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16174
|
+
let entryCount = 0;
|
|
16175
|
+
let listCount = 0;
|
|
16176
|
+
let variableCount = 0;
|
|
16177
|
+
for (const referent of referents) {
|
|
16178
|
+
if (!referent.kind || !referent.ref) continue;
|
|
16179
|
+
const key = `${referent.kind}:${referent.ref}`;
|
|
16180
|
+
if (seen.has(key)) continue;
|
|
16181
|
+
if (referent.kind === "entry") {
|
|
16182
|
+
if (entryCount >= 8) continue;
|
|
16183
|
+
entryCount += 1;
|
|
16184
|
+
} else if (referent.kind === "list") {
|
|
16185
|
+
if (listCount >= 4) continue;
|
|
16186
|
+
listCount += 1;
|
|
16187
|
+
} else if (referent.kind === "variable") {
|
|
16188
|
+
if (variableCount >= 4) continue;
|
|
16189
|
+
variableCount += 1;
|
|
16190
|
+
}
|
|
16191
|
+
seen.add(key);
|
|
16192
|
+
selected.push(referent);
|
|
16193
|
+
}
|
|
16194
|
+
return selected;
|
|
16195
|
+
}
|
|
16196
|
+
function projectConversationReferentSummary(liveDoc) {
|
|
16197
|
+
const referents = selectConversationReferentsForPrompt(
|
|
16198
|
+
collectConversationReferents(liveDoc)
|
|
16199
|
+
);
|
|
16200
|
+
const compact = referents.map((referent) => {
|
|
16201
|
+
if (referent.kind === "entry" && referent.entryPath) {
|
|
16202
|
+
return {
|
|
16203
|
+
kind: "entry",
|
|
16204
|
+
role: referent.role || null,
|
|
16205
|
+
source: referent.source || null,
|
|
16206
|
+
path: referent.entryPath,
|
|
16207
|
+
id: referent.recordId || null,
|
|
16208
|
+
type: referent.className || "unknown",
|
|
16209
|
+
label: referent.label || referent.entryPath,
|
|
16210
|
+
group: referent.displayGroupId ? {
|
|
16211
|
+
id: referent.displayGroupId,
|
|
16212
|
+
index: typeof referent.displayGroupIndex === "number" ? referent.displayGroupIndex : null,
|
|
16213
|
+
size: typeof referent.displayGroupSize === "number" ? referent.displayGroupSize : null,
|
|
16214
|
+
sameTypeSize: typeof referent.displayGroupSameTypeSize === "number" ? referent.displayGroupSameTypeSize : null
|
|
16215
|
+
} : void 0
|
|
16216
|
+
};
|
|
16217
|
+
}
|
|
16218
|
+
if (referent.kind === "entry" && referent.recordId) {
|
|
16219
|
+
return {
|
|
16220
|
+
kind: "entry",
|
|
16221
|
+
role: referent.role || null,
|
|
16222
|
+
source: referent.source || null,
|
|
16223
|
+
id: referent.recordId,
|
|
16224
|
+
type: referent.className || "unknown",
|
|
16225
|
+
label: referent.label || referent.recordId
|
|
16226
|
+
};
|
|
16227
|
+
}
|
|
16228
|
+
if (referent.kind === "list" && referent.listName) {
|
|
16229
|
+
return {
|
|
16230
|
+
kind: "list",
|
|
16231
|
+
role: referent.role || null,
|
|
16232
|
+
source: referent.source || null,
|
|
16233
|
+
name: referent.listName,
|
|
16234
|
+
type: referent.className || "unknown",
|
|
16235
|
+
count: typeof referent.count === "number" ? referent.count : null
|
|
16236
|
+
};
|
|
16237
|
+
}
|
|
16238
|
+
if (referent.kind === "variable" && referent.variableName) {
|
|
16239
|
+
return {
|
|
16240
|
+
kind: "variable",
|
|
16241
|
+
role: referent.role || null,
|
|
16242
|
+
source: referent.source || null,
|
|
16243
|
+
name: referent.variableName,
|
|
16244
|
+
valueKind: referent.variableKind || null,
|
|
16245
|
+
type: referent.className || null,
|
|
16246
|
+
path: referent.entryPath || null,
|
|
16247
|
+
list: referent.listName || null,
|
|
16248
|
+
label: referent.label || null,
|
|
16249
|
+
count: typeof referent.count === "number" ? referent.count : null,
|
|
16250
|
+
value: referent.variableKind === "scalar" ? referent.scalarValue ?? null : void 0
|
|
16251
|
+
};
|
|
16252
|
+
}
|
|
16253
|
+
return null;
|
|
16254
|
+
}).filter(Boolean);
|
|
16255
|
+
return renderConstBlock("recentReferences", compact);
|
|
16256
|
+
}
|
|
15233
16257
|
function getCurrentClosureId(liveDoc) {
|
|
15234
16258
|
const loop = asRecord4(liveDoc?.loop);
|
|
15235
16259
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -15449,56 +16473,24 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
15449
16473
|
}
|
|
15450
16474
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
15451
16475
|
const focus = projectWorkflowFocus(liveDoc, pendingPrompts, options);
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
} else {
|
|
15469
|
-
for (const line of focus.recentActionSummary) {
|
|
15470
|
-
lines.push(line.startsWith("- ") ? line : `- ${line}`);
|
|
15471
|
-
}
|
|
15472
|
-
}
|
|
15473
|
-
lines.push("", "Working Set Hints:");
|
|
15474
|
-
if (focus.variableNames.length === 0 && focus.listNames.length === 0 && focus.entryPaths.length === 0) {
|
|
15475
|
-
lines.push("- none");
|
|
15476
|
-
} else {
|
|
15477
|
-
if (focus.variableNames.length > 0) {
|
|
15478
|
-
lines.push(`- variables: ${focus.variableNames.join(", ")}`);
|
|
15479
|
-
}
|
|
15480
|
-
if (focus.listNames.length > 0) {
|
|
15481
|
-
lines.push(`- lists: ${focus.listNames.join(", ")}`);
|
|
15482
|
-
}
|
|
15483
|
-
if (focus.entryPaths.length > 0) {
|
|
15484
|
-
lines.push(`- entries: ${focus.entryPaths.join(", ")}`);
|
|
15485
|
-
}
|
|
15486
|
-
}
|
|
15487
|
-
lines.push("", "Open Workflow Handles:");
|
|
15488
|
-
if (focus.activeTaskIds.length === 0 && focus.openDecisionIds.length === 0 && focus.openPromptIds.length === 0) {
|
|
15489
|
-
lines.push("- none");
|
|
15490
|
-
} else {
|
|
15491
|
-
if (focus.activeTaskIds.length > 0) {
|
|
15492
|
-
lines.push(`- tasks: ${focus.activeTaskIds.join(", ")}`);
|
|
15493
|
-
}
|
|
15494
|
-
if (focus.openDecisionIds.length > 0) {
|
|
15495
|
-
lines.push(`- decisions: ${focus.openDecisionIds.join(", ")}`);
|
|
15496
|
-
}
|
|
15497
|
-
if (focus.openPromptIds.length > 0) {
|
|
15498
|
-
lines.push(`- prompts: ${focus.openPromptIds.join(", ")}`);
|
|
16476
|
+
return renderConstBlock("workflowContext", {
|
|
16477
|
+
boundary: {
|
|
16478
|
+
timestamp: focus.boundaryTimestamp,
|
|
16479
|
+
reason: focus.boundaryReason,
|
|
16480
|
+
latestClosureId: focus.latestClosureId || null
|
|
16481
|
+
},
|
|
16482
|
+
recentActions: focus.recentActionSummary,
|
|
16483
|
+
workingSet: {
|
|
16484
|
+
variables: focus.variableNames,
|
|
16485
|
+
lists: focus.listNames,
|
|
16486
|
+
entries: focus.entryPaths
|
|
16487
|
+
},
|
|
16488
|
+
openHandles: {
|
|
16489
|
+
tasks: focus.activeTaskIds,
|
|
16490
|
+
decisions: focus.openDecisionIds,
|
|
16491
|
+
prompts: focus.openPromptIds
|
|
15499
16492
|
}
|
|
15500
|
-
}
|
|
15501
|
-
return lines.join("\n");
|
|
16493
|
+
});
|
|
15502
16494
|
}
|
|
15503
16495
|
function hasOpenPrompt(liveDoc, pendingPrompts) {
|
|
15504
16496
|
if (pendingPrompts.length > 0) return true;
|
|
@@ -15514,7 +16506,6 @@ function hasOpenPrompt(liveDoc, pendingPrompts) {
|
|
|
15514
16506
|
return false;
|
|
15515
16507
|
}
|
|
15516
16508
|
function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
15517
|
-
const lines = [];
|
|
15518
16509
|
const loop = asRecord4(liveDoc?.loop);
|
|
15519
16510
|
const boundary = getWorkflowBoundary(liveDoc, options);
|
|
15520
16511
|
const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
|
|
@@ -15536,22 +16527,12 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
15536
16527
|
5
|
|
15537
16528
|
);
|
|
15538
16529
|
const hiddenTaskCount = Math.max(0, activeTasks.length - visibleTasks.length);
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
const title = typeof task.title === "string" ? task.title : "Untitled task";
|
|
15546
|
-
const taskId = typeof task.taskId === "string" ? task.taskId : "unknown";
|
|
15547
|
-
const status = typeof task.status === "string" ? task.status : "pending";
|
|
15548
|
-
const summary = typeof task.summary === "string" && task.summary.trim() ? ` \u2014 ${task.summary.trim()}` : "";
|
|
15549
|
-
lines.push(`- [${status}] ${title} (${taskId})${summary}`);
|
|
15550
|
-
}
|
|
15551
|
-
if (hiddenTaskCount > 0) {
|
|
15552
|
-
lines.push(`- ${hiddenTaskCount} more active task(s) omitted`);
|
|
15553
|
-
}
|
|
15554
|
-
}
|
|
16530
|
+
const compactTasks = visibleTasks.map((task) => ({
|
|
16531
|
+
id: typeof task.taskId === "string" ? task.taskId : "unknown",
|
|
16532
|
+
title: typeof task.title === "string" ? task.title : "Untitled task",
|
|
16533
|
+
status: typeof task.status === "string" ? task.status : "pending",
|
|
16534
|
+
summary: typeof task.summary === "string" && task.summary.trim() ? task.summary.trim() : null
|
|
16535
|
+
}));
|
|
15555
16536
|
const decisions = toSortedRecords(loop?.decisionsById).filter((decision) => {
|
|
15556
16537
|
const updatedAt = Number(decision.updatedAt) || Number(decision.createdAt) || 0;
|
|
15557
16538
|
if (boundary.reason === "request_start") {
|
|
@@ -15565,33 +16546,29 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
15565
16546
|
(decision) => decision.status === "open"
|
|
15566
16547
|
);
|
|
15567
16548
|
const visibleDecisions = (openDecisions.length > 0 ? openDecisions : decisions.slice(0, 1)).slice(0, 3);
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
} else {
|
|
15589
|
-
const selected = asRecord4(decision.selected);
|
|
15590
|
-
const label = typeof selected?.label === "string" ? selected.label : typeof selected?.id === "string" ? selected.id : "unknown";
|
|
15591
|
-
lines.push(`- [resolved] ${title} (${decisionId}) -> ${label}`);
|
|
16549
|
+
const compactDecisions = visibleDecisions.map((decision) => {
|
|
16550
|
+
const status = typeof decision.status === "string" ? decision.status : "resolved";
|
|
16551
|
+
const selected = asRecord4(decision.selected);
|
|
16552
|
+
return {
|
|
16553
|
+
id: typeof decision.decisionId === "string" ? decision.decisionId : "unknown",
|
|
16554
|
+
title: typeof decision.title === "string" && decision.title.trim() ? decision.title.trim() : "Decision",
|
|
16555
|
+
status,
|
|
16556
|
+
candidates: status === "open" ? asArray2(decision.candidates).slice(0, 5).map((candidate) => {
|
|
16557
|
+
const record = asRecord4(candidate);
|
|
16558
|
+
if (!record) return null;
|
|
16559
|
+
return {
|
|
16560
|
+
id: typeof record.id === "string" ? record.id : "unknown",
|
|
16561
|
+
label: typeof record.label === "string" && record.label.trim() ? record.label.trim() : null,
|
|
16562
|
+
description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
|
|
16563
|
+
metadata: asRecord4(record.metadata)
|
|
16564
|
+
};
|
|
16565
|
+
}).filter(Boolean) : [],
|
|
16566
|
+
selected: status === "open" ? null : {
|
|
16567
|
+
id: typeof selected?.id === "string" ? selected.id : null,
|
|
16568
|
+
label: typeof selected?.label === "string" ? selected.label : null
|
|
15592
16569
|
}
|
|
15593
|
-
}
|
|
15594
|
-
}
|
|
16570
|
+
};
|
|
16571
|
+
});
|
|
15595
16572
|
const openPrompts = [
|
|
15596
16573
|
...pendingPrompts.map((prompt) => ({
|
|
15597
16574
|
id: prompt.id,
|
|
@@ -15611,29 +16588,29 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
15611
16588
|
(pendingPrompt) => pendingPrompt.id === promptId
|
|
15612
16589
|
) : false);
|
|
15613
16590
|
}) : openPrompts;
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15618
|
-
|
|
15619
|
-
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
}
|
|
15624
|
-
}
|
|
16591
|
+
const compactPrompts = visiblePrompts.slice(0, 3).map((prompt) => {
|
|
16592
|
+
const promptRecord = asRecord4(prompt) || {};
|
|
16593
|
+
return {
|
|
16594
|
+
id: typeof promptRecord.id === "string" ? promptRecord.id : typeof promptRecord.promptId === "string" ? promptRecord.promptId : null,
|
|
16595
|
+
type: typeof promptRecord.type === "string" ? promptRecord.type : "input",
|
|
16596
|
+
title: typeof promptRecord.title === "string" && promptRecord.title.trim() ? promptRecord.title.trim() : "Input required",
|
|
16597
|
+
message: typeof promptRecord.message === "string" && promptRecord.message.trim() ? promptRecord.message.trim() : null
|
|
16598
|
+
};
|
|
16599
|
+
});
|
|
15625
16600
|
const currentClosureId = getCurrentClosureId(liveDoc);
|
|
15626
16601
|
const closureRecord = currentClosureId ? asRecord4(asRecord4(loop?.closuresById)?.[currentClosureId]) : null;
|
|
15627
16602
|
const visibleClosure = closureRecord && (boundary.reason !== "request_start" || (Number(closureRecord.createdAt) || 0) >= boundary.timestamp) ? closureRecord : null;
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
16603
|
+
return renderConstBlock("workflowState", {
|
|
16604
|
+
tasks: compactTasks,
|
|
16605
|
+
hiddenActiveTaskCount: hiddenTaskCount,
|
|
16606
|
+
decisions: compactDecisions,
|
|
16607
|
+
openPrompts: compactPrompts,
|
|
16608
|
+
closure: visibleClosure ? {
|
|
16609
|
+
id: currentClosureId,
|
|
16610
|
+
status: typeof visibleClosure.status === "string" ? visibleClosure.status : "completed",
|
|
16611
|
+
summary: typeof visibleClosure.summary === "string" ? visibleClosure.summary : null
|
|
16612
|
+
} : null
|
|
16613
|
+
});
|
|
15637
16614
|
}
|
|
15638
16615
|
function projectHeapSummary(heap, options) {
|
|
15639
16616
|
const heapRecord = asRecord4(heap) || {};
|
|
@@ -15678,55 +16655,72 @@ function projectHeapSummary(heap, options) {
|
|
|
15678
16655
|
referencedPaths.add(path2);
|
|
15679
16656
|
}
|
|
15680
16657
|
const visibleLists = Object.values(listsByName).map((value) => asRecord4(value)).filter((value) => Boolean(value)).filter(
|
|
15681
|
-
(list) => variables.some(
|
|
16658
|
+
(list) => variables.some(
|
|
16659
|
+
(variable) => Boolean(variable?.listName === list.name)
|
|
16660
|
+
) || Boolean(list.name && focusedListNames.has(list.name))
|
|
15682
16661
|
).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxLists);
|
|
15683
16662
|
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);
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
)
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
16663
|
+
return renderConstBlock("savedData", {
|
|
16664
|
+
variables: Object.fromEntries(
|
|
16665
|
+
variables.filter((variable) => typeof variable.name === "string").map((variable) => {
|
|
16666
|
+
if (variable.kind === "scalar") {
|
|
16667
|
+
return [
|
|
16668
|
+
variable.name,
|
|
16669
|
+
{ kind: "scalar", value: variable.value ?? null }
|
|
16670
|
+
];
|
|
16671
|
+
}
|
|
16672
|
+
if (variable.kind === "entry") {
|
|
16673
|
+
const entry = variable.entryPath ? asRecord4(
|
|
16674
|
+
entriesByPath[variable.entryPath]
|
|
16675
|
+
) : null;
|
|
16676
|
+
return [
|
|
16677
|
+
variable.name,
|
|
16678
|
+
{
|
|
16679
|
+
kind: "entry",
|
|
16680
|
+
type: variable.className || entry?.className || "unknown",
|
|
16681
|
+
path: variable.entryPath || null,
|
|
16682
|
+
label: entry?.label || entry?.id || null
|
|
16683
|
+
}
|
|
16684
|
+
];
|
|
16685
|
+
}
|
|
16686
|
+
const list = variable.listName ? asRecord4(listsByName[variable.listName]) : null;
|
|
16687
|
+
return [
|
|
16688
|
+
variable.name,
|
|
16689
|
+
{
|
|
16690
|
+
kind: "list",
|
|
16691
|
+
type: variable.className || list?.className || "unknown",
|
|
16692
|
+
list: variable.listName || null,
|
|
16693
|
+
count: (list?.paths || []).length
|
|
16694
|
+
}
|
|
16695
|
+
];
|
|
16696
|
+
})
|
|
16697
|
+
),
|
|
16698
|
+
lists: Object.fromEntries(
|
|
16699
|
+
visibleLists.filter((list) => typeof list.name === "string").map((list) => [
|
|
16700
|
+
list.name,
|
|
16701
|
+
{
|
|
16702
|
+
type: list.className || "unknown",
|
|
16703
|
+
count: (list.paths || []).length
|
|
16704
|
+
}
|
|
16705
|
+
])
|
|
16706
|
+
),
|
|
16707
|
+
entries: Object.fromEntries(
|
|
16708
|
+
visibleEntries.filter((entry) => typeof entry.path === "string").map((entry) => [
|
|
16709
|
+
entry.path,
|
|
16710
|
+
{
|
|
16711
|
+
type: entry.className || "unknown",
|
|
16712
|
+
id: entry.id || null,
|
|
16713
|
+
label: entry.label || entry.id || null,
|
|
16714
|
+
fields: asArray2(entry.fields).filter(
|
|
16715
|
+
(field) => field?.name && field.name !== "_realId" && field.name !== "real_id"
|
|
16716
|
+
).slice(0, 3).map((field) => ({
|
|
16717
|
+
name: field.name,
|
|
16718
|
+
value: field.value ?? null
|
|
16719
|
+
}))
|
|
16720
|
+
}
|
|
16721
|
+
])
|
|
16722
|
+
)
|
|
16723
|
+
});
|
|
15730
16724
|
}
|
|
15731
16725
|
function createHarnessVerifierSnapshot(input) {
|
|
15732
16726
|
const workflowFocus = projectWorkflowFocus(
|
|
@@ -15823,8 +16817,8 @@ function buildContinuationInstruction(resultPreview) {
|
|
|
15823
16817
|
"If the user names a concrete record that is not already in the heap, resolve it from the graph before saying it is missing: try a broad search, then a small set of normalized/fuzzy variants or a paged scan when the domain supports it.",
|
|
15824
16818
|
"If the request needs all matching records, use iterate(...) or page until hasMore is false. A single list(...) or page(...) call is only one page.",
|
|
15825
16819
|
"If this request clearly spans multiple steps and there are no active tasks yet, create 2-4 short user-visible tasks now.",
|
|
15826
|
-
"Reuse any existing taskId and decisionId values exactly as they appear in
|
|
15827
|
-
"When progress depends on the user's choice, missing detail, or
|
|
16820
|
+
"Reuse any existing taskId and decisionId values exactly as they appear in [State].",
|
|
16821
|
+
"When progress depends on the user's choice, missing detail, or confirmation, use loop.ask_user(...) or loop.confirm(...) so the job pauses and resumes through the live workflow.",
|
|
15828
16822
|
"After a resumed ask_user or confirm call, continue the same job and perform the newly authorized action when the answer is sufficient. Do not stop with placeholder text like 'I'm ready to do it next.'",
|
|
15829
16823
|
"If you ask the user a new question in this job, do not also close the loop in the same job.",
|
|
15830
16824
|
"Write the smallest straightforward code for the current step. Avoid defensive fallback branches for hypothetical states that are not currently true.",
|
|
@@ -15835,39 +16829,101 @@ ${resultPreview}` : null
|
|
|
15835
16829
|
].filter(Boolean).join("\n\n");
|
|
15836
16830
|
}
|
|
15837
16831
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
15838
|
-
return domainDocumentation?.trim() || "No domain
|
|
16832
|
+
return domainDocumentation?.trim() || "No domain contract available. The graph may not be ready yet.";
|
|
15839
16833
|
}
|
|
15840
16834
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
15848
|
-
if (activeRows.length === 0) return "No session metadata available.";
|
|
15849
|
-
return activeRows.map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
16835
|
+
return renderConstBlock("session", {
|
|
16836
|
+
runtimeId: sessionContext?.sandboxId || null,
|
|
16837
|
+
environmentId: sessionContext?.environmentId || null,
|
|
16838
|
+
userName: sessionContext?.userName || null,
|
|
16839
|
+
domainRevision: sessionContext?.domainRevision || null
|
|
16840
|
+
});
|
|
15850
16841
|
}
|
|
15851
16842
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
15852
|
-
return heapSummary?.trim() || "
|
|
16843
|
+
return heapSummary?.trim() || renderConstBlock("savedData", {
|
|
16844
|
+
variables: {},
|
|
16845
|
+
lists: {},
|
|
16846
|
+
entries: {}
|
|
16847
|
+
});
|
|
15853
16848
|
}
|
|
15854
16849
|
function buildGranularAgentReferentBlock(referentSummary) {
|
|
15855
|
-
return referentSummary?.trim() || "
|
|
16850
|
+
return referentSummary?.trim() || renderConstBlock("recentReferences", []);
|
|
15856
16851
|
}
|
|
15857
16852
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
15858
|
-
return loopSummary?.trim() || "
|
|
16853
|
+
return loopSummary?.trim() || renderConstBlock("workflowState", {
|
|
16854
|
+
tasks: [],
|
|
16855
|
+
decisions: [],
|
|
16856
|
+
openPrompts: [],
|
|
16857
|
+
closure: null
|
|
16858
|
+
});
|
|
15859
16859
|
}
|
|
15860
16860
|
function buildGranularAgentWorkflowBlock(workflowSummary) {
|
|
15861
|
-
return workflowSummary?.trim() || "
|
|
15862
|
-
|
|
15863
|
-
|
|
16861
|
+
return workflowSummary?.trim() || renderConstBlock("workflowContext", {
|
|
16862
|
+
boundary: null,
|
|
16863
|
+
recentActions: [],
|
|
16864
|
+
workingSet: {
|
|
16865
|
+
variables: [],
|
|
16866
|
+
lists: [],
|
|
16867
|
+
entries: []
|
|
16868
|
+
},
|
|
16869
|
+
openHandles: {
|
|
16870
|
+
tasks: [],
|
|
16871
|
+
decisions: [],
|
|
16872
|
+
prompts: []
|
|
16873
|
+
}
|
|
16874
|
+
});
|
|
16875
|
+
}
|
|
16876
|
+
function resolvePromptCapabilities(capabilities) {
|
|
16877
|
+
return {
|
|
16878
|
+
executeCode: capabilities?.executeCode !== false,
|
|
16879
|
+
readEntities: capabilities?.readEntities !== false,
|
|
16880
|
+
workflowHelpers: Array.isArray(capabilities?.workflowHelpers) ? capabilities.workflowHelpers : [
|
|
16881
|
+
"ask_user",
|
|
16882
|
+
"confirm",
|
|
16883
|
+
"open_decision",
|
|
16884
|
+
"close_decision",
|
|
16885
|
+
"create_task",
|
|
16886
|
+
"update_task",
|
|
16887
|
+
"complete_task",
|
|
16888
|
+
"close_loop"
|
|
16889
|
+
],
|
|
16890
|
+
savedData: capabilities?.savedData !== false,
|
|
16891
|
+
showRecords: capabilities?.showRecords !== false
|
|
16892
|
+
};
|
|
16893
|
+
}
|
|
16894
|
+
function buildGranularAgentToolBlock(tools, capabilityOverrides) {
|
|
16895
|
+
const resolvedCapabilities = resolvePromptCapabilities(capabilityOverrides);
|
|
16896
|
+
const normalizedTools = (tools || []).filter((tool) => tool?.name).slice().sort((left, right) => {
|
|
16897
|
+
const leftScope = `${left.className || "global"}:${left.static ? "static" : "instance"}`;
|
|
16898
|
+
const rightScope = `${right.className || "global"}:${right.static ? "static" : "instance"}`;
|
|
16899
|
+
return leftScope.localeCompare(rightScope) || left.name.localeCompare(right.name);
|
|
16900
|
+
});
|
|
16901
|
+
const writeActions = normalizedTools.filter((tool) => tool.ready !== false).map((tool) => {
|
|
16902
|
+
const scope = tool.className ? `${tool.static ? "class" : "record"}:${tool.className}` : "global";
|
|
16903
|
+
return {
|
|
16904
|
+
name: tool.name,
|
|
16905
|
+
scope,
|
|
16906
|
+
description: tool.description?.trim() || null
|
|
16907
|
+
};
|
|
16908
|
+
});
|
|
16909
|
+
const capabilities = {
|
|
16910
|
+
executeCode: resolvedCapabilities.executeCode,
|
|
16911
|
+
readEntities: resolvedCapabilities.readEntities,
|
|
16912
|
+
writeActions,
|
|
16913
|
+
workflowHelpers: resolvedCapabilities.workflowHelpers,
|
|
16914
|
+
savedData: resolvedCapabilities.savedData,
|
|
16915
|
+
showRecords: resolvedCapabilities.showRecords
|
|
16916
|
+
};
|
|
16917
|
+
return renderConstBlock("capabilities", capabilities);
|
|
16918
|
+
}
|
|
16919
|
+
function buildGranularAgentActionIndex(tools) {
|
|
15864
16920
|
const normalizedTools = (tools || []).filter((tool) => tool?.name).slice().sort((left, right) => {
|
|
15865
16921
|
const leftScope = `${left.className || "global"}:${left.static ? "static" : "instance"}`;
|
|
15866
16922
|
const rightScope = `${right.className || "global"}:${right.static ? "static" : "instance"}`;
|
|
15867
16923
|
return leftScope.localeCompare(rightScope) || left.name.localeCompare(right.name);
|
|
15868
16924
|
});
|
|
15869
16925
|
if (normalizedTools.length === 0) {
|
|
15870
|
-
return "No
|
|
16926
|
+
return "No domain write actions are available.";
|
|
15871
16927
|
}
|
|
15872
16928
|
const globalTools = normalizedTools.filter((tool) => !tool.className);
|
|
15873
16929
|
const staticTools = normalizedTools.filter(
|
|
@@ -15876,9 +16932,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
15876
16932
|
const instanceTools = normalizedTools.filter(
|
|
15877
16933
|
(tool) => Boolean(tool.className && !tool.static)
|
|
15878
16934
|
);
|
|
15879
|
-
const lines = [
|
|
15880
|
-
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
15881
|
-
];
|
|
16935
|
+
const lines = ["Available actions by scope:"];
|
|
15882
16936
|
const appendGroup = (title, group) => {
|
|
15883
16937
|
lines.push(`- ${title}:`);
|
|
15884
16938
|
if (group.length === 0) {
|
|
@@ -15887,187 +16941,561 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
15887
16941
|
}
|
|
15888
16942
|
for (const tool of group.slice(0, 10)) {
|
|
15889
16943
|
const availability = tool.ready === false ? " [not ready]" : "";
|
|
16944
|
+
const schema = formatActionSchemaSummary(tool);
|
|
15890
16945
|
const description = tool.description?.trim() ? ` - ${tool.description.trim()}` : "";
|
|
15891
|
-
lines.push(` ${tool.name}${availability}${description}`);
|
|
16946
|
+
lines.push(` ${tool.name}${availability}${schema}${description}`);
|
|
15892
16947
|
}
|
|
15893
16948
|
if (group.length > 10) {
|
|
15894
16949
|
lines.push(` +${group.length - 10} more`);
|
|
15895
16950
|
}
|
|
15896
16951
|
};
|
|
15897
|
-
appendGroup("Global
|
|
15898
|
-
appendGroup("Class-level
|
|
15899
|
-
appendGroup("Record-level
|
|
16952
|
+
appendGroup("Global", globalTools);
|
|
16953
|
+
appendGroup("Class-level", staticTools);
|
|
16954
|
+
appendGroup("Record-level", instanceTools);
|
|
15900
16955
|
return lines.join("\n");
|
|
15901
16956
|
}
|
|
15902
|
-
function
|
|
15903
|
-
if (
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
}
|
|
15910
|
-
if (checkpoint.latestJobStatus) {
|
|
15911
|
-
lines.push(`latestJobStatus: ${checkpoint.latestJobStatus}`);
|
|
15912
|
-
}
|
|
15913
|
-
if (checkpoint.controllerOutcome) {
|
|
15914
|
-
lines.push(`controllerOutcome: ${checkpoint.controllerOutcome}`);
|
|
16957
|
+
function normalizeJsonSchema(value) {
|
|
16958
|
+
if (typeof value === "string") {
|
|
16959
|
+
try {
|
|
16960
|
+
return asRecord4(JSON.parse(value));
|
|
16961
|
+
} catch {
|
|
16962
|
+
return null;
|
|
16963
|
+
}
|
|
15915
16964
|
}
|
|
15916
|
-
|
|
15917
|
-
|
|
16965
|
+
return asRecord4(value);
|
|
16966
|
+
}
|
|
16967
|
+
function jsonSchemaTypeName(schema) {
|
|
16968
|
+
const record = normalizeJsonSchema(schema);
|
|
16969
|
+
if (!record) return "unknown";
|
|
16970
|
+
const type = record.type;
|
|
16971
|
+
if (typeof type === "string") {
|
|
16972
|
+
if (type === "array") return "array";
|
|
16973
|
+
if (type === "object") return "object";
|
|
16974
|
+
return type;
|
|
15918
16975
|
}
|
|
15919
|
-
|
|
15920
|
-
|
|
16976
|
+
return "unknown";
|
|
16977
|
+
}
|
|
16978
|
+
function summarizeObjectSchema(schema) {
|
|
16979
|
+
const record = normalizeJsonSchema(schema);
|
|
16980
|
+
const properties = asRecord4(record?.properties);
|
|
16981
|
+
if (!properties || Object.keys(properties).length === 0) {
|
|
16982
|
+
return record ? "{}" : null;
|
|
16983
|
+
}
|
|
16984
|
+
const required = new Set(asArray2(record?.required));
|
|
16985
|
+
const fields = Object.entries(properties).slice(0, 8).map(([name, property]) => {
|
|
16986
|
+
const marker = required.has(name) ? "*" : "?";
|
|
16987
|
+
return `${name}${marker}: ${jsonSchemaTypeName(property)}`;
|
|
16988
|
+
});
|
|
16989
|
+
const remaining = Object.keys(properties).length - fields.length;
|
|
16990
|
+
return remaining > 0 ? `${fields.join(", ")}, +${remaining}` : fields.join(", ");
|
|
16991
|
+
}
|
|
16992
|
+
function formatActionSchemaSummary(tool) {
|
|
16993
|
+
const input = summarizeObjectSchema(tool.inputSchema);
|
|
16994
|
+
const output = summarizeObjectSchema(tool.outputSchema);
|
|
16995
|
+
const parts = [];
|
|
16996
|
+
if (input) parts.push(`input { ${input} }`);
|
|
16997
|
+
if (output) parts.push(`output { ${output} }`);
|
|
16998
|
+
return parts.length ? ` (${parts.join("; ")})` : "";
|
|
16999
|
+
}
|
|
17000
|
+
function splitDomainDocumentation(domainDocumentation) {
|
|
17001
|
+
const normalized = domainDocumentation?.trim() || "";
|
|
17002
|
+
if (!normalized) return { types: "", docs: "" };
|
|
17003
|
+
const docsSectionMatch = normalized.match(/\n\s*\[Docs\]\s*\n/i);
|
|
17004
|
+
if (docsSectionMatch?.index !== void 0) {
|
|
17005
|
+
return {
|
|
17006
|
+
types: normalized.slice(0, docsSectionMatch.index).trim(),
|
|
17007
|
+
docs: normalized.slice(docsSectionMatch.index + docsSectionMatch[0].length).trim()
|
|
17008
|
+
};
|
|
15921
17009
|
}
|
|
15922
|
-
|
|
15923
|
-
|
|
17010
|
+
const legacyMarker = "Generated usage notes from ./sandbox-tools docs:";
|
|
17011
|
+
const legacyIndex = normalized.indexOf(legacyMarker);
|
|
17012
|
+
if (legacyIndex !== -1) {
|
|
17013
|
+
return {
|
|
17014
|
+
types: normalized.slice(0, legacyIndex).trim(),
|
|
17015
|
+
docs: normalized.slice(legacyIndex + legacyMarker.length).trim()
|
|
17016
|
+
};
|
|
15924
17017
|
}
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
17018
|
+
return { types: normalized, docs: "" };
|
|
17019
|
+
}
|
|
17020
|
+
function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
17021
|
+
if (!checkpoint) {
|
|
17022
|
+
return renderConstBlock("previousCodeResult", null);
|
|
17023
|
+
}
|
|
17024
|
+
return renderConstBlock("previousCodeResult", {
|
|
17025
|
+
iteration: typeof checkpoint.iteration === "number" ? checkpoint.iteration : null,
|
|
17026
|
+
latestJobStatus: checkpoint.latestJobStatus || null,
|
|
17027
|
+
controllerOutcome: checkpoint.controllerOutcome || null,
|
|
17028
|
+
controllerReason: checkpoint.controllerReason || null,
|
|
17029
|
+
noProgressCount: typeof checkpoint.noProgressCount === "number" ? checkpoint.noProgressCount : null,
|
|
17030
|
+
latestJobError: checkpoint.latestJobError?.trim() || null,
|
|
17031
|
+
latestActionSummary: Array.isArray(checkpoint.latestActionSummary) ? checkpoint.latestActionSummary.slice(0, 8).map(normalizeActionSummaryForPrompt) : [],
|
|
17032
|
+
latestJobResult: checkpoint.latestJobResult?.trim() || null
|
|
17033
|
+
});
|
|
17034
|
+
}
|
|
17035
|
+
function parseSummaryOutcome(summary) {
|
|
17036
|
+
const outcome = {};
|
|
17037
|
+
for (const part of summary.split(",")) {
|
|
17038
|
+
const trimmed = part.trim();
|
|
17039
|
+
const match = /^([A-Za-z0-9_]+)=(.+)$/.exec(trimmed);
|
|
17040
|
+
if (!match) continue;
|
|
17041
|
+
const [, key, rawValue] = match;
|
|
17042
|
+
const unquoted = rawValue.replace(/^"|"$/g, "");
|
|
17043
|
+
if (/^-?\d+(?:\.\d+)?$/.test(unquoted)) {
|
|
17044
|
+
outcome[key] = Number(unquoted);
|
|
17045
|
+
} else if (unquoted === "true" || unquoted === "false") {
|
|
17046
|
+
outcome[key] = unquoted === "true";
|
|
17047
|
+
} else {
|
|
17048
|
+
outcome[key] = unquoted;
|
|
15932
17049
|
}
|
|
15933
17050
|
}
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
17051
|
+
return outcome;
|
|
17052
|
+
}
|
|
17053
|
+
function buildKnownFactsFromCheckpoint(checkpoint) {
|
|
17054
|
+
const summaries = Array.isArray(checkpoint?.latestActionSummary) ? checkpoint.latestActionSummary.map(normalizeActionSummaryForPrompt) : [];
|
|
17055
|
+
const facts = [];
|
|
17056
|
+
for (const summary of summaries) {
|
|
17057
|
+
const countedMatch = /^-\s*Counted\s+([A-Za-z0-9_]+).*?->\s*value=(\d+)/.exec(summary);
|
|
17058
|
+
if (countedMatch) {
|
|
17059
|
+
facts.push({
|
|
17060
|
+
entity: countedMatch[1],
|
|
17061
|
+
query: {},
|
|
17062
|
+
totalCount: Number(countedMatch[2])
|
|
17063
|
+
});
|
|
17064
|
+
continue;
|
|
17065
|
+
}
|
|
17066
|
+
const listedMatch = /^-\s*Listed\s+([A-Za-z0-9_]+).*?->\s*(.+)$/.exec(
|
|
17067
|
+
summary
|
|
17068
|
+
);
|
|
17069
|
+
if (!listedMatch) continue;
|
|
17070
|
+
const outcome = parseSummaryOutcome(listedMatch[2]);
|
|
17071
|
+
const count = typeof outcome.totalCount === "number" ? outcome.totalCount : typeof outcome.count === "number" ? outcome.count : void 0;
|
|
17072
|
+
if (typeof count !== "number") continue;
|
|
17073
|
+
const fact = {
|
|
17074
|
+
entity: listedMatch[1],
|
|
17075
|
+
query: {},
|
|
17076
|
+
totalCount: count
|
|
17077
|
+
};
|
|
17078
|
+
if (typeof outcome.hasMore === "boolean") {
|
|
17079
|
+
fact.lastPageHasMore = outcome.hasMore;
|
|
17080
|
+
fact.loadedAllItems = !outcome.hasMore;
|
|
17081
|
+
} else if (typeof outcome.count === "number" && outcome.count === count) {
|
|
17082
|
+
fact.loadedAllItems = true;
|
|
17083
|
+
}
|
|
17084
|
+
facts.push(fact);
|
|
15937
17085
|
}
|
|
15938
|
-
return
|
|
17086
|
+
return facts.slice(0, 8);
|
|
15939
17087
|
}
|
|
15940
17088
|
function buildGranularAgentSystemPrompt(input) {
|
|
17089
|
+
const outputMode = input.outputMode || "agentMessages";
|
|
17090
|
+
const promptCapabilities = resolvePromptCapabilities(input.capabilities);
|
|
17091
|
+
const domainSections = splitDomainDocumentation(input.domainDocumentation);
|
|
15941
17092
|
const sessionBlock = buildGranularAgentSessionBlock(input.sessionContext);
|
|
15942
|
-
const toolBlock = buildGranularAgentToolBlock(
|
|
15943
|
-
|
|
17093
|
+
const toolBlock = buildGranularAgentToolBlock(
|
|
17094
|
+
input.tools,
|
|
17095
|
+
input.capabilities
|
|
17096
|
+
);
|
|
17097
|
+
const actionIndex = buildGranularAgentActionIndex(input.tools);
|
|
17098
|
+
const domainBlock = buildGranularAgentDomainBlock(domainSections.types);
|
|
15944
17099
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
15945
17100
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
15946
17101
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
15947
17102
|
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
15948
17103
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
17104
|
+
const knownFactsBlock = renderConstBlock(
|
|
17105
|
+
"knownFacts",
|
|
17106
|
+
buildKnownFactsFromCheckpoint(input.checkpoint)
|
|
17107
|
+
);
|
|
17108
|
+
const outputRules = outputMode === "returnValue" ? promptCapabilities.showRecords ? `- End every user-facing job by returning either a short natural-language string or an object like \`{ reply, show }\`.
|
|
17109
|
+
- Use \`{ reply, show }\` when the host UI should render records, heap variables, or lists from session state.
|
|
17110
|
+
- For multi-record display, prefer a saved list/listName so the UI can render a table; use entryPaths for a few individual records.
|
|
17111
|
+
- When the user asks to show, list, display, open, or "show them" for records you found, include those heap-backed records in \`show\`; do not answer only with a count or text summary.
|
|
17112
|
+
- For count-only questions such as "how many", "how many X do I have", or "what is the total number of X", call the entity \`.count(...)\` or use page \`totalCount\` only when a page is already needed for other reasons. Answer with text only. Do not fetch, save, or display sample records just to ground a numeric count.
|
|
17113
|
+
- Do not call \`agent_text_message(...)\` or \`agent_heap_objects(...)\` unless the host explicitly opts into those side-channel message helpers.` : `- End every user-facing job by returning a short natural-language string.` : promptCapabilities.showRecords ? `- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
17114
|
+
- \`agent_text_message(...)\` displays text directly to the user in the host UI. Treat it as the user-facing progress and reply channel, not as a debug log.
|
|
17115
|
+
- For long-running or multi-step jobs, send several short \`agent_text_message(...)\` updates as useful milestones are reached so the user can see what is happening instead of waiting in silence.
|
|
17116
|
+
- Write \`agent_text_message(...)\` content in a friendly, readable product-assistant style: concrete, concise, and natural. Avoid robotic status dumps, raw implementation names, and unexplained IDs unless the ID helps the user.
|
|
17117
|
+
- When \`agent_text_message(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag: \`<granular-object class="class_name" id="stable_id_or_path" label="Visible label" />\`. Use the actual class name and stable id/path from the runtime record or effect result; do not invent ids, field names, or snake/camel-case aliases that are not present in the type declarations or returned object.
|
|
17118
|
+
- Treat \`agent_heap_objects(...)\` as the UI display call for user-visible records, not as a general storage helper. Do not wrap records under an \`items\` key.
|
|
17119
|
+
- When records should remain reusable for follow-ups, first save the runtime record or ordered record array with \`await heap.setVar("stable_selection_name", value)\`, then display that saved selection exactly once with \`await agent_heap_objects({ variableNames: ["stable_selection_name"] })\`.
|
|
17120
|
+
- \`heap.setVar(...)\` only accepts scalar values, runtime records/sandbox instances, or arrays of runtime records/sandbox instances. Do not save plain action/effect result objects. If an action returns an id/path for a created record that should remain referable, fetch the created record first, then save/display that fetched record.
|
|
17121
|
+
- Do not use \`agent_heap_objects({ entries: [...] })\` or \`agent_heap_objects({ saveAs, entries })\` as a shortcut for ordered pages, queues, search results, or ranked lists; those forms can create duplicate or poorly labelled displays. Save the selection with \`heap.setVar(...)\` and display it via \`variableNames\` instead.
|
|
17122
|
+
- Use \`entryPaths\` only for a few already-known individual records and \`listNames\` only for a host-created list that you intentionally want to show. Do not display both an entry/list selection and a heap variable for the same records.
|
|
17123
|
+
- When the user asks to show, list, display, open, or "show them" for records you found, call \`agent_heap_objects(...)\`; do not answer only with a count or text summary.
|
|
17124
|
+
- For count-only questions such as "how many", "how many X do I have", or "what is the total number of X", call the entity \`.count(...)\` or use page \`totalCount\` only when a page is already needed for other reasons. Answer with \`agent_text_message(...)\` only. Do not call \`agent_heap_objects(...)\`, \`saveAs\`, or \`heap.setVar(...)\` unless the user also asked to see records or a later requested action needs a reusable record selection.
|
|
17125
|
+
- Any job that identifies a specific record in the visible answer must also display that grounded record with \`agent_heap_objects(...)\` when the user should see/open it, or save it with \`heap.setVar(...)\` when it is only needed for follow-up resolution.
|
|
17126
|
+
- For ordered record slices, pages, queues, search results, or ranked lists, save the slice with \`heap.setVar(...)\` and then call \`agent_heap_objects({ variableNames: [...] })\` once. Use a stable name that preserves the slice identity and ordering so later references such as "the second item" or "back on the first slice" resolve to the correct earlier slice, not merely the most recent record.
|
|
17127
|
+
- Do not rely on the final return value for UI output. Do not return ad-hoc \`reply\` / \`show\` payloads instead of explicit agent message calls.` : `- Every job that answers the user must emit \`agent_text_message(...)\`.
|
|
17128
|
+
- \`agent_text_message(...)\` displays text directly to the user in the host UI. Treat it as the user-facing progress and reply channel, not as a debug log.
|
|
17129
|
+
- For long-running or multi-step jobs, send several short \`agent_text_message(...)\` updates as useful milestones are reached so the user can see what is happening instead of waiting in silence.
|
|
17130
|
+
- Write \`agent_text_message(...)\` content in a friendly, readable product-assistant style: concrete, concise, and natural. Avoid robotic status dumps, raw implementation names, and unexplained IDs unless the ID helps the user.
|
|
17131
|
+
- When \`agent_text_message(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag: \`<granular-object class="class_name" id="stable_id_or_path" label="Visible label" />\`. Use the actual class name and stable id/path from the runtime record or effect result; do not invent ids, field names, or snake/camel-case aliases that are not present in the type declarations or returned object.`;
|
|
17132
|
+
const codeRules = promptCapabilities.executeCode ? `Code:
|
|
17133
|
+
- Use when the request needs session data, saved data, workflow state, record display, or available actions.
|
|
17134
|
+
- When using code, assistant text must be empty or one brief summary.
|
|
17135
|
+
- Code must be plain runnable JavaScript with top-level await.
|
|
17136
|
+
- Import needed classes and helpers from "./sandbox-tools".
|
|
17137
|
+
- Use static top-level imports such as \`import { Foo, agent_text_message } from "./sandbox-tools";\`. Do not use dynamic \`await import("./sandbox-tools")\`.
|
|
17138
|
+
- Keep generated jobs as straightforward top-level scripts. Small local helper functions are allowed when they make the code clearer, but avoid hiding domain actions, prompts, or relationship traversal inside broad generic helpers.
|
|
17139
|
+
- Do not nest template literals: never put a backtick string inside another template string or inside a \`\${...}\` expression. Build conditional text in variables first, or use simple string concatenation. For multi-line replies, prefer a \`lines\` array and \`.join("\\n")\`.
|
|
17140
|
+
- Do not write an action branch that finds multiple candidates, emits a "please choose" message, and returns. When the current request asks for an action, the same branch must call \`await loop.ask_user(...)\`, resolve the answer, and continue to the requested action before the job finishes.
|
|
17141
|
+
- User-visible output must use the provided message or record-display helpers.
|
|
17142
|
+
- After calling an action or effect, inspect the returned object and base the user-facing answer on its actual fields.
|
|
17143
|
+
- When calling an action, use the exact input property names from the action schema. Do not invent synonym keys for required inputs.
|
|
17144
|
+
- After a mutation succeeds, ground the answer in the affected record by emitting or saving the record for UI display and naming a stable user-visible identifier when one exists. Do not answer only "done" or "sent".
|
|
17145
|
+
- Never call \`process.exit(...)\`; emit a message and use \`return;\` to stop early.
|
|
17146
|
+
- Add short \`//\` planning comments before meaningful blocks. The user will see these comments concatenated as a planning trace while the job is being drafted, so they should read together like a properly written plan.
|
|
17147
|
+
- In \`//\` planning comments, clearly explain the logic of what the job is about to do: the sequence of steps, why each step matters, and any important decision points or branches.
|
|
17148
|
+
- Write \`//\` planning comments for the user, not for engineers: make them friendly, plain-language, and easy to understand.
|
|
17149
|
+
- Keep \`//\` planning comments in future tense, but vary the phrasing so they do not become a repetitive list of sentences that all start the same way.
|
|
17150
|
+
- Make the \`//\` planning trace feel connected: use natural transitions for sequence, dependency, contrast, and branching when useful. If the next step depends on what the job finds, say that in plain language.
|
|
17151
|
+
- Avoid technical terms, implementation names, code concepts, hidden helper names, and complex domain jargon in \`//\` planning comments unless the user already used that wording.
|
|
17152
|
+
- Each \`//\` planning comment should provide valuable feedback about the plan or next visible step. Do not add filler such as "Starting", "Running", or "Processing".
|
|
17153
|
+
${outputRules}` : `Code:
|
|
17154
|
+
- Code execution is unavailable. Use text only, or ask the user for missing information.`;
|
|
17155
|
+
const workflowRules = promptCapabilities.workflowHelpers.length > 0 ? `Workflow:
|
|
17156
|
+
- Use workflow helpers when missing input should pause and resume the workflow.
|
|
17157
|
+
- If code discovers missing required input after a read, use \`await loop.ask_user(...)\`; do not just tell the user to provide it.
|
|
17158
|
+
- Do not ask the user for data the job can discover from grounded records, relationships, saved session state, or visible read-only actions. Ask only when the missing value is truly unavailable, ambiguous, or requires a human decision.
|
|
17159
|
+
- When ambiguity blocks a requested action, import \`loop\` and use \`await loop.ask_user({ type: "choice", ... })\` with grounded options so the same job can resume and complete the action. A plain text request such as "please choose one" is not a workflow and leaves the action unhandled.
|
|
17160
|
+
- If a requested action has 2 to 5 plausible grounded targets, the job is not complete after showing them. Do not stop after \`agent_text_message(...)\` or \`agent_heap_objects(...)\`; import \`loop\`, ask for a grounded choice with \`await loop.ask_user(...)\`, then call the action on the selected record after the job resumes.
|
|
17161
|
+
- If a lookup before a mutation returns multiple plausible target records, do not mutate the first sorted or first returned record. Ask for a grounded choice unless the user supplied a unique identifier, ordinal, or selector that leaves exactly one target.
|
|
17162
|
+
- Use choice only for 2 to 5 short grounded options.
|
|
17163
|
+
- For record choices, set each option value to a stable scalar such as the record \`_graphPath\` or \`id\`, not a label-only value.
|
|
17164
|
+
- After \`await loop.ask_user(...)\` returns from a choice prompt, tolerate either the option value, the option object, or a human-readable label by matching against value, id/path, label, and description before failing. If a returned label is a prefix or substring of exactly one option label, treat it as that option.
|
|
17165
|
+
- Use \`loop.confirm(...)\` for yes/no confirmation only when the user explicitly asks for confirmation, action or permission metadata requires it, policy requires it, or material uncertainty remains after grounding.
|
|
17166
|
+
- Do not add a generic yes/no confirmation after the user has already made a grounded choice, unless one of those confirmation conditions still applies.
|
|
17167
|
+
- Do not add confirmation only because an allowed mutation is visible to other people, customer-facing, or consequential. If the user clearly requested the mutation and the grounded target, action, and condition are unique, perform the mutation unless confirmation is required by the user, policy, action metadata, or remaining material uncertainty.
|
|
17168
|
+
- A conditional request such as "if this is true, do that" is authorization to perform the requested action after you verify the condition. Once the condition, target, and action are grounded uniquely, call the action directly; do not ask "should I perform/post/send this?" unless the user, policy, action metadata, or unresolved material uncertainty requires confirmation. The visibility or impact of an allowed action is not by itself unresolved uncertainty.
|
|
17169
|
+
- If the user explicitly asks you to stop for confirmation, natural-language text such as "please confirm" is not enough: call \`await loop.confirm(...)\` before the mutation, then perform the approved mutation in the same resumed job when it returns true.
|
|
17170
|
+
- Reuse existing task, decision, and closure ids from [State].
|
|
17171
|
+
- If a user request matches both a domain record/action and a workflow helper, prefer the domain capability.` : "";
|
|
17172
|
+
return `[Harness]
|
|
17173
|
+
You are an assistant for a live user session. Use plain, natural language.
|
|
15952
17174
|
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
-
|
|
15956
|
-
-
|
|
15957
|
-
Do not
|
|
15958
|
-
-
|
|
15959
|
-
-
|
|
15960
|
-
- If you can offer a short grounded shortlist, that clarification should usually be \`loop.ask_user({ type: 'choice', ... })\` instead of a plain-text question with bullet options.
|
|
15961
|
-
- Never simulate a live prompt, confirmation, decision, task change, or loop closure in plain text. Plain-text replies are only for conversational answers that do not need to mutate session state.
|
|
17175
|
+
Mode selection:
|
|
17176
|
+
Text only:
|
|
17177
|
+
- Use for general explanations, unsupported requests, or requests that do not need session data.
|
|
17178
|
+
- Do not use text only when the user asks you to check, look up, search, inspect, update, schedule, or otherwise use session data or tools.
|
|
17179
|
+
- Do not answer with a promise like "I'll check" or "I'll do that next"; if the request needs tools, choose a job and run them now.
|
|
17180
|
+
- Do not expose internal names, helper names, file paths, parameter names, or code.
|
|
17181
|
+
- In code jobs, never use \`console.log(JSON.stringify({ action, reply, code }))\` as a user reply. Use the provided message helpers or final return contract.
|
|
15962
17182
|
|
|
15963
|
-
|
|
15964
|
-
- While you are writing code, add short single-line comments with the prefix \`// \` before meaningful blocks.
|
|
15965
|
-
- These comments should explain the intent in friendly product language, not in implementation jargon.
|
|
15966
|
-
- Comments are shown live as a reasoning trace, so keep them brief, concrete, and useful.
|
|
15967
|
-
- Do not mention method names, file paths, or internal identifiers in those comments.
|
|
15968
|
-
- Use only single-line \`//\` comments for this purpose. Do not use block comments.
|
|
15969
|
-
- If you are replying with text only, you may also include a few leading \`// \` comment lines before the final answer.
|
|
15970
|
-
- End text-only replies with the plain user-facing answer on normal lines, without a comment prefix.
|
|
17183
|
+
${codeRules}
|
|
15971
17184
|
|
|
15972
|
-
|
|
15973
|
-
- Use plain, friendly product language.
|
|
15974
|
-
- Never mention internal implementation details in user-facing text:
|
|
15975
|
-
class names, effect names, method names, function names, file paths, parameter names, or code snippets.
|
|
15976
|
-
- Never expose dotted identifiers such as \`Class.method\` in user-facing text.
|
|
15977
|
-
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
15978
|
-
- If you need clarification, ask in everyday language.
|
|
15979
|
-
- If the missing information should pause the live workflow for later continuation, ask through \`loop.ask_user(...)\` in generated code rather than with a plain-text question.
|
|
15980
|
-
- If you are asking the user to pick from explicit options, prefer a live \`loop.ask_user({ type: 'choice', ... })\` prompt over a direct reply that lists those options in text.
|
|
15981
|
-
- Keep replies concise and clear.
|
|
15982
|
-
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
17185
|
+
${workflowRules}
|
|
15983
17186
|
|
|
15984
|
-
|
|
15985
|
-
|
|
17187
|
+
High-priority execution rules:
|
|
17188
|
+
- Treat a human reference as something to ground, not as missing data. When the user names or describes a record, group, queue, parent, relationship, or prior result and asks to inspect, decide, update, schedule, approve, send, or otherwise act on session data, run a code job to ground it before asking the user for more details.
|
|
17189
|
+
- For a human-described primary anchor, a no-match answer is only justified after more than one distinct grounding attempt, such as owner/container grounding, relationship traversal, exact id/path lookup, or shorter target-local search. Before the primary no-match return, retry that same anchor with fewer text constraints or a distinct grounding strategy; do not stop after one zero-result list/find/page call.
|
|
17190
|
+
- A confirmation requirement is not a reason to stay text-only. Do all safe read-only grounding and availability/status checks first, then call \`loop.confirm(...)\` or \`loop.ask_user(...)\` before the mutation.
|
|
17191
|
+
- In any code branch where a requested action or mutation has multiple possible targets, import \`loop\` statically and use \`await loop.ask_user(...)\` in that branch. This includes ambiguity discovered after a query returns several records. A branch that only shows candidates, asks in text, and returns leaves the requested action unfinished.
|
|
17192
|
+
- Before any mutation, know whether the target is one record or several. A singular phrase like "the item" is not proof of uniqueness after a query finds multiple matching records. If the user did not give an exact identifier or explicit selection criterion, call \`loop.ask_user({ type: "choice", ... })\` with grounded choices; do not choose by age, amount, priority, order, or convenience on your own. Resolve the target before any yes/no confirmation.
|
|
17193
|
+
- Treat partial names, first words, aliases, and shorthand labels as partial references. Use search/contains or grounded relationship traversal first; do not report no match after only an exact \`equal_to\` name filter.
|
|
17194
|
+
- When a partial name, alias, or shorthand resolves to a stored record, include that record's stored display value in the visible answer at least once. Prefer exact fields such as name, title, number, label, or other user-facing identifier over the user's shorthand.
|
|
17195
|
+
- If the user says the label/name may be wrong, or gives a nickname/quoted phrase, do not stop after one direct target search. Ground the stable anchor in the request first, such as the named owner, container, parent, account, project, location, or other higher-level record; then traverse its declared relationships, inspect related candidate records, and only then report no match or ask for help.
|
|
17196
|
+
- If the user says "still", "current", "latest", "where", "check", "if", or asks you to decide whether a condition is true, first identify the record that can prove the condition. When that condition names a related object, the code order must be: load the action target or anchor, traverse to the related evidence record, call its visible status/lookup action when available, then decide whether to mutate. Do not branch, return, or reject the condition from parent/action-target status before that evidence step.
|
|
17197
|
+
- When a condition names an anchored noun phrase whose final noun is an entity type, the final entity type is the evidence record to test. Use the earlier words only to ground or traverse to that record; do not test the anchor record as a substitute.
|
|
17198
|
+
- Do not treat prior read-only summaries, cached parent fields, action-target fields, or stored related-record fields as fresh evidence for a later conditional mutation when a related evidence object and visible lookup/status action can be reached.
|
|
17199
|
+
- When the user asks whether a suitable or available candidate exists for assignment, scheduling, routing, or ownership, call the visible availability/matching/search action on the candidate entity when one exists. Existing relationships or current assignments are context, not proof of current availability.
|
|
17200
|
+
- When a request names an owner, parent, account, project, location, or other container plus a target item, plan it as two steps: ground the owner/container, then discover the target through declared relationships, relationship filters, or short target-local search. Do not combine owner/container words with target words in one target-class search, and do not require owner/container words to appear in target-local fields such as title or summary.
|
|
17201
|
+
- When the target or evidence record is reached through relationships, use the declared relationship index/getter list as a graph and walk getters whose target types lead toward the needed entity. If a target has a one-record parent field and the user named the grandparent/owner, start from the grandparent/owner and traverse down through getters; do not put the grandparent condition inside the target's parent filter. If a relationship is documented as one-record or many-to-one, never use \`some\` on it.
|
|
17202
|
+
- After refusing a bypass, external-send, export, or restricted-data request, a follow-up that refers to the same item/case/record inherits that boundary even if no record was saved. Do not perform a different mutation, search for replacement candidates, or ask which restricted referent to use; refuse unless a visible allowed workflow explicitly authorizes the new request.
|
|
17203
|
+
- If the previous answer mentioned, displayed, or contrasted multiple plausible records and the next mutation uses only "it", "that", "that one", "the item", or similar, ask which grounded record to use. Even if one record seems more actionable, the pronoun alone is ambiguous, and a confirmation prompt is not a substitute for a grounded choice prompt.
|
|
17204
|
+
- A saved list, heap object collection, table, or record-display artifact with multiple possible mutation targets counts as multiple plausible records even when the visible text only gave counts. Do not pick the first, last, or most recent item from that collection for a pronoun like "that one"; ask for a grounded choice first.
|
|
17205
|
+
- For "first N", "next N", "top N", queue, slice, newest/oldest, or ranked-list requests, use the runtime paging surface on the target record type when it exists. Relationship getters can help discover context, but a local \`.slice(0, N)\` over a relationship array is not a paged queue result.
|
|
17206
|
+
- When selecting a single "top", "best", "urgent", or "most relevant" record from a broad set, do not rely on lexicographic sorting of label fields or the first page while more results exist. Narrow with grounded filters or gather enough candidates first, then rank from explicit record fields.
|
|
17207
|
+
- Do not remove candidates returned by an availability/search action solely because they are already assigned, current, or previously related, unless the user asked for a different candidate. If the action returned them as available or matching, they remain valid candidates.
|
|
17208
|
+
- In filters, use \`some\` only on relationship fields that are declared as many/collection fields. Singular relationship fields must use \`path\`, \`id\`, or \`is\`; if unsure, follow declared getters from an already grounded record instead.
|
|
15986
17209
|
|
|
15987
|
-
|
|
15988
|
-
|
|
17210
|
+
Intent resolution:
|
|
17211
|
+
- If intent is explicit, act directly.
|
|
17212
|
+
- For pronouns and discourse references like this, it, that, those, them, their, the previous one, or the selected ones, inspect recentReferences first. Do not use recentReferences array order as a selector when several same-type records could satisfy the phrase.
|
|
17213
|
+
- For follow-up phrases like same item, that record, the one you showed, or the previous result, read the single type-compatible recentReference before doing a fresh search. If the follow-up names a related target or evidence type, use the recent record only as the anchor and traverse declared relationships toward that type before searching the target class directly or deciding a condition.
|
|
17214
|
+
- If recentReferences contains an exact entry path for the follow-up target, call the matching class \`.get({ path })\` first only when that entry is the single plausible type-compatible referent or the user identified it with a unique identifier, ordinal, or descriptive selector. A phrase like "that one" is still a bare pronoun when multiple same-type records were displayed or saved together.
|
|
17215
|
+
- recentReferences includes user-mentioned records, assistant inline object references, and assistant heap object messages; prefer the latest type-compatible reference only when it is the single plausible referent for the phrase and not merely the last item from a multi-record display or saved list.
|
|
17216
|
+
- Record paths are opaque ids. Never synthesize a path from a label, name, title, or user phrase; copy an exact path from [State] or discover the record with a query.
|
|
17217
|
+
- If there is exactly one latest type-compatible reference for a phrase like "that same item", use it directly; do not ask the user to restate the item when you can already name or fetch it. This does not apply when the user refers to an earlier slice/list by ordinal wording, or when the prior answer intentionally contrasted several records.
|
|
17218
|
+
- For explicit continuity phrases like "that same item", "same record", or "the previous result", do not ask the user which record they mean. Use the recent reference first; if no saved reference exists, rerun the prior narrow grounding lookup from the conversation text instead of answering text-only that the record is not grounded.
|
|
17219
|
+
- If a follow-up mutation uses only a pronoun such as "it" or "that" after the prior turn mentioned multiple same-type records, ask the user to choose from grounded options before mutating.
|
|
17220
|
+
- If the prior turn displayed or summarized two or more plausible records and the next mutation says only "it", "that", or "on it", do not infer the target from your own ranking; call \`loop.ask_user({ type: "choice", ... })\` with the grounded records first, then mutate only the chosen record.
|
|
17221
|
+
- If the prior turn intentionally contrasted multiple records that could all receive the requested mutation, a lone pronoun is ambiguous even when one record was listed first or looked more urgent.
|
|
17222
|
+
- If a follow-up mutation uses a bare pronoun and recentReferences contains a matching \`group.id\` with \`group.sameTypeSize\` greater than 1, the target is unresolved. The next code must ask for a grounded choice with \`loop.ask_user(...)\`; never call a mutation on one grouped path first.
|
|
17223
|
+
- For follow-up words like "other", "another", or "remaining" after the user selected one candidate from a previous choice, resolve within the active contrast from that choice and the user's answer. Exclude the selected item, preserve descriptors such as larger, smaller, next, older, different, or same status, and do not take the first leftover from a wider saved list when the contrast narrows the intended set.
|
|
17224
|
+
- Before any mutation, prove the target resolves to exactly one grounded record. If the request describes a set, category, relationship, prior result group, or other non-unique scope, gather the candidate records first; when more than one candidate remains, ask the user to choose before calling the action.
|
|
17225
|
+
- For ambiguous choice prompts before a mutation, every option that describes a different candidate must carry a distinct grounded record value/path. After the answer, do not fall back to the first candidate if matching fails; ask again or stop without mutating.
|
|
17226
|
+
- The [State] constants are prompt context, not runtime variables. Never reference \`savedData\`, \`recentReferences\`, \`workflowContext\`, \`workflowState\`, or \`capabilities\` as variables in generated code. When using a recent reference, copy its path string into code and fetch it with \`Class.get({ path: "..." })\`, or call \`heap.getEntry("...")\` when the class is not obvious.
|
|
17227
|
+
- Never write placeholder grounding code such as \`const path = null\`, \`const groundedPath = ""\`, or \`const recordPath = ""\`. If no saved reference is available, delete that branch entirely and execute the fallback lookup directly.
|
|
17228
|
+
- Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
|
|
17229
|
+
- For ordinal references to earlier pages, slices, lists, or ranked results, use the saved list/recent references first. If no saved list is available, rerun the exact same ordered query and select the ordinal index from its returned \`items\`; never invent a record path from a label or ordinal.
|
|
17230
|
+
- \`.get({ path })\` returns \`null\` when a path is not found; it does not throw for normal misses. Check for null before using a search fallback.
|
|
17231
|
+
- If multiple recent references could satisfy the phrase and the action or target would materially differ, ask for a grounded choice before any confirmation or mutation.
|
|
17232
|
+
- If the entity, field, target, scope, ranking, or action is ambiguous, create 2 to 5 plausible interpretations.
|
|
17233
|
+
- Probe plausible interpretations with cheap read-only queries before deciding.
|
|
17234
|
+
- A zero-result first query is not enough to report failure for a human reference; continue in the same job with another grounded strategy such as partial search, owner/container grounding, or relationship traversal before reporting no match.
|
|
17235
|
+
- If a direct target search returns zero and the request contains a stable anchor such as a named related record or higher-level container, ground that anchor and inspect related records before reporting no match.
|
|
17236
|
+
- One strong match means proceed.
|
|
17237
|
+
- Several plausible matches means call \`loop.ask_user({ type: "choice", ... })\` with grounded choices.
|
|
17238
|
+
- No grounded match means ask for missing information.
|
|
17239
|
+
- For consequential changes, resolve first, confirm when needed, then act.
|
|
17240
|
+
- Do not ask the user to resend a request because you need to verify data. If the request needs verification, run a job that verifies it now. If a follow-up reference is not available, rerun the prior narrow grounding lookup or ask a specific grounded question.
|
|
17241
|
+
- If the user asks a read-only advisory question such as "Should we message the team?" and also says not to update/send/act yet, provide the recommendation from grounded data. Do not pause with \`loop.ask_user\` or \`loop.confirm\`.
|
|
17242
|
+
- If the user asks for specific fields, read those fields from the grounded record and include every requested value in the visible answer. If saved state identifies the record but does not include the requested fields, fetch the record before answering. Only say a field is unavailable after checking the documented field/property on the fetched record.
|
|
17243
|
+
- If the user asks for blocked work and sensitive/restricted work as separate things, keep those candidate sets separate. Exclude sensitive or restricted-workflow records from the ordinary blocked operational candidate unless the user explicitly asks for blocked sensitive work.
|
|
15989
17244
|
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
17245
|
+
Use exploratory probing when:
|
|
17246
|
+
- the user gives a human reference instead of an exact id or path
|
|
17247
|
+
- a noun could refer to multiple entity types
|
|
17248
|
+
- a name, number, label, date, or amount is given without a clear field
|
|
17249
|
+
- ranking words are used without a clear metric
|
|
17250
|
+
- a requested change has an unclear target
|
|
17251
|
+
- the first reasonable lookup returns zero results
|
|
17252
|
+
- the first reasonable lookup returns several plausible results
|
|
17253
|
+
|
|
17254
|
+
Do not explore when:
|
|
17255
|
+
- the entity, field, filter, and action are explicit
|
|
17256
|
+
- the request is a general explanation
|
|
17257
|
+
- the request is unsupported by available capabilities
|
|
17258
|
+
- the next step is already a required workflow answer or confirmation
|
|
17259
|
+
|
|
17260
|
+
[Types]
|
|
17261
|
+
Import classes, helpers, and available actions from "./sandbox-tools".
|
|
17262
|
+
Use the domain contract below as the exact code-facing contract. Generated docs, relationship indexes, and action indexes are authoritative for valid fields, getters, actions, and filter shapes.
|
|
15993
17263
|
|
|
15994
17264
|
${domainBlock}
|
|
15995
17265
|
|
|
15996
|
-
|
|
17266
|
+
[Docs]
|
|
17267
|
+
Query policy:
|
|
17268
|
+
- Use filter, search, sort, count, page, list, and iterate on entity classes.
|
|
17269
|
+
- Push filtering and sorting into entity queries. Do not fetch a page only to filter or sort locally.
|
|
17270
|
+
- Valid filter fields are defined by each entity filter type.
|
|
17271
|
+
- Valid sort fields are defined by each entity sort field type.
|
|
17272
|
+
- Search is class-wide text retrieval, not a field-scoped operator.
|
|
17273
|
+
- Entity classes do not have a \`.search(...)\` method. Use \`.find({ search })\`, \`.page({ search, ... })\`, or \`.list({ search, ... })\`.
|
|
17274
|
+
- Entity \`.list(...)\` returns an array of records; use \`matches[0]\`, \`matches.length\`, and direct iteration. Entity \`.page(...)\` returns \`{ items, page, perPage, totalCount, hasMore }\`; only page results have \`.items\`.
|
|
17275
|
+
- For natural queue slices, infer pagination even when the user does not say "page": "first five" means \`page: 1, perPage: 5\`; a follow-up "next five" for the same queue means \`page: 2, perPage: 5\` with the same sort and grounded filter.
|
|
17276
|
+
- For first/next/top queue slices, page the target item class directly with a structured relationship filter. Relationship getters and local \`.slice(0, 5)\` are useful for exploration but do not prove runtime pagination.
|
|
17277
|
+
- Combine search and filter when both free-text matching and exact constraints are needed.
|
|
17278
|
+
- For exact categorical states, prefer positive filters with \`equal_to\` or \`in\`. Do not express a requested state through substring negation of a different state with \`not_contains\`; categorical labels can contain other labels and disappear from the result.
|
|
17279
|
+
- Do not use \`not_in\`; the runtime filter surface does not support it. Use \`in\` with explicit allowed values, or fetch a bounded candidate page and filter excluded values locally before showing the final slice.
|
|
17280
|
+
- Boolean filters use \`equal_to: true\` or \`equal_to: false\`.
|
|
17281
|
+
- Use \`equal_to\` on names only when you know the full stored value. A shortened name, first word, fragment, alias, or nickname is not an exact name; use search/contains first and then ground the exact record. If an exact-name query returns zero for a human-supplied name, retry with search/contains in the same job before reporting that nothing exists.
|
|
17282
|
+
- Keep full-text search strings short and distinctive. Prefer one concrete name/id or 1 to 3 salient terms, then use filters, relationships, or local ranking for the rest.
|
|
17283
|
+
- Do not search a target entity for only a related-record name while also filtering by that relationship. First ground the related record, then use a relationship filter/getter, and use target-entity search only for the target's own identifier, title, label, description, or other target-local fields.
|
|
17284
|
+
- When the user combines a concrete entity name with generic task words like a priority, workflow state, risk, summary, or requested outcome, do not put the whole phrase into one full-text search. Search/filter the concrete name first, then apply status, priority, relationship, amount, date, or ranking constraints.
|
|
17285
|
+
- Treat urgency as priority unless the domain explicitly documents urgent as a status. For an urgent operational item, do not require \`status = "urgent"\`; inspect status/blocker after grounding likely priority matches.
|
|
17286
|
+
- Do not sort a free-text priority, severity, or rank-like label field and assume the first row is most important. Rank candidates locally from explicit field values and continue paging or narrow the query when the result says more records exist.
|
|
17287
|
+
- When looking for blocked or blocking work, treat phrases such as "no blocker", "not blocked", "without blocker", "none", and "clear" as negative evidence. Do not select a record only because its summary/title contains the substring "block"; prefer explicit blocker/status fields and keep scanning for a true blocker.
|
|
17288
|
+
- For broad "open", "active", or "top" operational records, avoid guessing a tiny fixed status list unless the domain documents one. Prefer relationship grounding plus a supported positive filter/list, or locally exclude clearly terminal states such as resolved, closed, complete, completed, paid, canceled, or archived after fetching a bounded sorted candidate page.
|
|
17289
|
+
- When a requested object is normally reached through relationships, follow the declared relationship chain from the grounded parent or related record before giving up on a direct search. If the target entity type is named, prefer chains whose declared return types lead to that target type.
|
|
17290
|
+
- Prefer generated instance relationship getters from a grounded record over hand-written deep nested relationship filters.
|
|
17291
|
+
- When you have grounded a parent record and need its related records, call the declared parent getter such as \`parent.get_related_records()\` instead of writing a nested relationship filter on the target class.
|
|
17292
|
+
- When deciding whether a related object is still in a current state, traverse to the related evidence record and call its visible lookup/status action when available. Do not decide, stop, post, or treat the condition as false from only stored fields, the parent record's status/title/summary, or prior displayed text.
|
|
17293
|
+
- For queues owned by a higher-level record, ground that record and keep the exact query plan: target class, direct relationship path or getter chain, filters, sort, page, and perPage. Save the displayed page and reuse the same plan for follow-up pages instead of inventing a new relationship filter.
|
|
17294
|
+
- Relationship getters return exactly their named entity type. If you call a getter for units/sites, those are not operational items; call the next declared item/work getter on each unit/site, or query the item class directly before reading item fields.
|
|
17295
|
+
- Relationship getters return only their declared related entity type. Do not treat one relationship result as another entity type because the request mentions it; walk the declared relationship chain exactly, or use a grounded direct query for the target entity.
|
|
17296
|
+
- Only call relationship getters that are declared for the class of the record you currently have. If the needed target is not a direct getter on that class, walk through the declared intermediate getter first; never skip a relationship hop by inventing a convenience getter.
|
|
17297
|
+
- Relationship getters are async. Always \`await record.get_related_records()\` before checking whether the result is an array, iterating it, or reading fields from its records.
|
|
17298
|
+
- Relationship filter fields are selectors, not hydrated nested objects. To read a field from a related record, call the declared relationship getter and use the returned record; do not read \`record.relationship.someField\` from the original record.
|
|
17299
|
+
- Do not filter relationship fields with scalar text operators. For example, do not write \`related: { contains: "Example" }\` or pass a parent path into a child filter; ground the related record first, then use the correctly typed relationship getter or \`id\`/\`path\` filter.
|
|
17300
|
+
- Relationship path filters must use a path for the relationship's target type. If a target item has a related parent/container field and the user named a higher-level parent, first follow the parent's declared getter to the correct related record, then use that related record's \`_graphPath\`; never put the wrong record type's path into a child relationship filter.
|
|
17301
|
+
- Do not use a target record's local text fields to prove ownership by a named parent/container. A filter such as \`title/summary contains parentName\` is not a relationship. Ground the parent/container and traverse getters or use the documented relationship field.
|
|
17302
|
+
- Do not write transitive relationship filters such as \`container: { is: { parent: ... } }\` for queue slices. Use a direct relationship path filter from the already grounded related record, such as \`container: { path: container._graphPath }\`.
|
|
17303
|
+
- Do not invent broad relationship filter fields on a target class unless that field is present in the generated filter type. For owned queues, ground the parent first, use declared relationship getters to reach the owned related records, or use the exact documented relationship field.
|
|
17304
|
+
- Do not optional-chain relationship getters to guess at hidden relationships. If a getter is not declared in the TypeScript contract, it does not exist.
|
|
17305
|
+
- Generated relationship getters return arrays of related records, not page objects. Iterate the returned array directly; do not read \`.items\` from a relationship getter result.
|
|
17306
|
+
- If an explicit target entity is not found through an expected relationship chain, try a grounded direct query/search for that target entity before reporting that no target exists.
|
|
17307
|
+
- For operational blocker, risk, status, or "what is happening" questions, inspect the relevant record's scalar fields such as status, priority, blocker, summary, latest update/message, due date, amount, and other domain-specific descriptive fields before answering.
|
|
17308
|
+
- For read-only readiness, risk, health, or status summaries, call any visible read-only assessment/status action on the grounded primary record before ad-hoc aggregation when such an action semantically matches the request. Use the returned fields in the reply and supplement with counts or record reads only when useful.
|
|
17309
|
+
- Do not hide required visible read-only assessment/status actions inside broad try/catch blocks. The runtime action surface should show that the assessment action ran.
|
|
17310
|
+
- Treat action/effect results as structured values, not necessarily arrays. Before indexing, iterating, checking \`.length\`, or calling array methods, normalize the result first: use the result itself only when \`Array.isArray(result)\`; otherwise read the exact array field shown in the output schema, or a documented array field such as \`items\`, \`matches\`, \`results\`, \`records\`, \`entries\`, \`candidates\`, \`options\`, or a domain-specific array field. Never convert a non-array object result to \`[]\` before checking its documented fields.
|
|
17311
|
+
- When a visible search, lookup, availability, or assessment action returns candidates or matches, treat those returned records as already scoped by the action inputs unless the output schema gives reliable fields for further narrowing. When matching returned candidates to grounded records, use the output schema's actual identifier fields, including \`id\`, \`path\`, or fields ending in \`Id\`; do not assume candidates have \`_graphPath\`. Do not discard all returned candidates by re-filtering on guessed property names.
|
|
17312
|
+
- For scheduling actions, convert relative wording into concrete ISO timestamps before mutating records.
|
|
17313
|
+
- When a decision depends on fresh external state and a visible read-only status/lookup action exists on the grounded record, call it before deciding, mutating, or refusing based on stale stored fields.
|
|
17314
|
+
- When deciding whether something is blocked, held, delayed, or still active, combine the fresh lookup result with relevant status, blocker, summary, checkpoint, reason, and latest update fields. Do not use a tiny hand-written status allowlist as the only authority; words such as hold, held, pending, delayed, awaiting, blocked, customs, review, and exception are blocking evidence unless the domain explicitly says otherwise.
|
|
17315
|
+
- One page does not prove there are no more records. For all, every, export, or broad scans, use iteration or page until there are no more results.
|
|
17316
|
+
- When selecting a single "best", "urgent", "top", or "most relevant" record from a broad page, do not assume the first returned page is complete if \`hasMore\` is true. Continue paging, use iteration, or add a stronger grounded filter before selecting.
|
|
17317
|
+
- For exploratory work, use count for totals and page with small perPage for samples; use iteration only after the interpretation is chosen.
|
|
17318
|
+
|
|
17319
|
+
Lookup ladder:
|
|
17320
|
+
1. Check recent references and saved session data.
|
|
17321
|
+
2. Try exact id or path when the user gave an id-like value.
|
|
17322
|
+
3. If the request names a parent/container plus a target, ground the parent/container and traverse declared relationships to target candidates.
|
|
17323
|
+
4. Try exact filters on fields whose names or aliases match the user words.
|
|
17324
|
+
5. Try class-wide search with short target-local terms, not the whole user phrase.
|
|
17325
|
+
6. Try relationship filters when the user mentions connected concepts and the filter shape is documented.
|
|
17326
|
+
7. If the user names a parent/container and says the label may be approximate, inspect related target records before reporting no match.
|
|
17327
|
+
8. If still empty, try one small set of normalized, prefix, or fuzzy variants when search supports it.
|
|
17328
|
+
9. If still empty or ambiguous, ask the user for steering.
|
|
17329
|
+
|
|
17330
|
+
Exploration budget:
|
|
17331
|
+
- For a simple ambiguous reference, try up to 3 strategies.
|
|
17332
|
+
- For a broad ambiguous task, try up to 5 strategies.
|
|
17333
|
+
- Probe with small pages.
|
|
17334
|
+
- Do not run exhaustive scans during probing unless the user explicitly asks for all records or the selected task requires aggregation.
|
|
17335
|
+
- Stop early when a strong unique match is found.
|
|
17336
|
+
|
|
17337
|
+
Strong unique match:
|
|
17338
|
+
- exactly one record matches an exact id or path
|
|
17339
|
+
- exactly one record matches an exact filter on a likely identifier field
|
|
17340
|
+
- exactly one recent reference or saved value fits the request
|
|
17341
|
+
- one interpretation has results and all other reasonable interpretations have none
|
|
17342
|
+
|
|
17343
|
+
Ask the user when:
|
|
17344
|
+
- multiple exact matches exist
|
|
17345
|
+
- several entity types match the same phrase
|
|
17346
|
+
- the best match comes only from broad search and other plausible matches exist
|
|
17347
|
+
- the ranking or metric is unclear
|
|
17348
|
+
- the target is unique but the requested action is unclear
|
|
17349
|
+
|
|
17350
|
+
Relationship filters:
|
|
17351
|
+
- One-record relationships use \`is\`.
|
|
17352
|
+
- Multi-record relationships use \`some\`.
|
|
17353
|
+
- Never guess relationship cardinality from wording. Check the generated TypeScript filter type for the field before writing a relationship filter; if you are not sure, use declared relationship getters from already grounded records instead of a relationship filter.
|
|
17354
|
+
- If a relationship filter type or field is one-record/singular, never use \`some\` on that field. Match by \`id\`, \`path\`, or \`is\`, or fetch the related record and continue through declared getters when you need to traverse farther.
|
|
17355
|
+
- Do not invent nested operators under relationship fields. A one-record relationship filter accepts only its documented operators such as \`id\`, \`path\`, \`is\`, \`null\`, and \`not_null\`; deeper conditions must go under \`is\` or be handled by fetching records and following getters.
|
|
17356
|
+
- Never use \`some\` on one-record fields. If the generated TypeScript type says \`OneRelationFilter\`, valid operators are \`id\`, \`path\`, \`is\`, \`null\`, and \`not_null\`; \`some\` is invalid.
|
|
17357
|
+
- Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
|
|
17358
|
+
- For a singular relationship that points to an intermediate record, nested filters still use \`is\` at the singular hop. Do not use \`some\` because the nested condition names another related record.
|
|
17359
|
+
- Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
|
|
17360
|
+
- When you already fetched the related record, use \`{ relationship: { path: record._graphPath } }\` or \`{ relationship: { id: record.id } }\`; do not wrap a known id/path under \`is\`.
|
|
17361
|
+
- The path used in a relationship filter must be the path of the relationship target. For same-queue follow-ups from an item/batch/ticket, fetch that item's related unit/site/depot first and use the related unit/site/depot path; do not use the item path as a unit/site/depot path.
|
|
17362
|
+
- Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
|
|
17363
|
+
- Do not write \`{ relationship: { some: ... } }\` unless the generated filter type for that exact relationship says it is a many/collection relationship. For one-record, parent, owner, or many-to-one relationships, use \`path\`, \`id\`, \`is\`, or getter traversal.
|
|
17364
|
+
- Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
|
|
17365
|
+
${domainSections.docs ? `
|
|
17366
|
+
Domain notes:
|
|
17367
|
+
${domainSections.docs}
|
|
17368
|
+
` : ""}
|
|
17369
|
+
|
|
17370
|
+
Actions:
|
|
17371
|
+
${actionIndex}
|
|
17372
|
+
- Actions listed under "Record-level" are instance methods. First fetch or find the specific record, then call the action on that instance, e.g. \`const item = await Item.get({ path }); await item.action_name(...)\`.
|
|
17373
|
+
- Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
|
|
17374
|
+
- The action index is the visibility contract. If an action is listed for a class, call it directly on fetched/listed instances of that class; do not use \`typeof record.action_name === "function"\` as a discovery gate. If an action is not listed, do not call it.
|
|
17375
|
+
- Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
|
|
17376
|
+
- For action inputs, use the exact property names from the generated TypeScript method signature or the input schema shown in the action list. Do not invent synonym fields for required inputs.
|
|
17377
|
+
- Match user verbs to visible action names semantically. If a visible action clearly satisfies the user's requested operation, ground the target record and call that action instead of refusing because the wording differs.
|
|
17378
|
+
- When several visible actions or targets plausibly match the request, search/list the plausible grounded candidates, ask for a grounded choice when more than one remains, confirm if needed, then call only the selected visible action.
|
|
17379
|
+
- If the user asks to find records in a workflow state first, do not pre-filter away plausible records with unrelated secondary flags unless the ontology explicitly documents that relationship; gather the grounded candidates, ask when more than one remains, then confirm/action the selected record when appropriate.
|
|
17380
|
+
- If the user asks for an action that is not visible in the action list, refuse explicitly. Do not answer with only a read-only summary, do not ask for confirmation, and do not attempt hidden, guessed, or similarly named methods.
|
|
17381
|
+
- If the user asks to bypass permissions, skip a normal workflow, use a raw HTTP side channel, or export/send restricted data externally, do not ask for confirmation or missing details. Refuse and explain the allowed workflow boundary.
|
|
17382
|
+
- A restricted or denied referent stays restricted in follow-up turns. If the user later says "that same item", "fine then", or similar after a sensitive/bypass request, do not perform a mutation on that referent unless a visible allowed workflow explicitly authorizes it.
|
|
17383
|
+
- When summarizing records found by a query, include or display stable user-visible identifiers such as number, name, title, label, date, amount, or status. Do not answer only with counts when the user asked what you found.
|
|
17384
|
+
|
|
17385
|
+
[State]
|
|
17386
|
+
${toolBlock}
|
|
17387
|
+
|
|
17388
|
+
${sessionBlock}
|
|
17389
|
+
|
|
15997
17390
|
${checkpointBlock}
|
|
15998
17391
|
|
|
15999
|
-
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
16000
17392
|
${workflowBlock}
|
|
16001
17393
|
|
|
16002
|
-
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
16003
17394
|
${referentBlock}
|
|
16004
17395
|
|
|
16005
|
-
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
16006
17396
|
${heapBlock}
|
|
16007
17397
|
|
|
16008
|
-
\u2500\u2500\u2500 AGENT LOOP STATE \u2500\u2500\u2500
|
|
16009
17398
|
${loopBlock}
|
|
16010
17399
|
|
|
16011
|
-
|
|
16012
|
-
- Continue from the latest structured state. Treat WORKFLOW SNAPSHOT, EXECUTION CHECKPOINT, RECENT REFERENTS, SESSION HEAP, and AGENT LOOP STATE as the working memory for this request.
|
|
16013
|
-
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
16014
|
-
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
16015
|
-
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
16016
|
-
- Treat user-provided names, numbers, and labels as human references, not exact keys. Resolve them with code: check recent referents/heap first, then query the graph with the broadest supported \`search\` or \`filter\`, then retry with a few normalized/fuzzy/prefix variants when the first pass is empty or ambiguous. Only say a record does not exist after a reasonable lookup across the relevant class.
|
|
16017
|
-
- If one strong match exists, use it. If several plausible matches remain, use \`loop.ask_user({ type: 'choice', ... })\` with the grounded candidates instead of guessing.
|
|
16018
|
-
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
16019
|
-
- For comparisons, rankings, selections, or summaries, first identify the rule you are using. If that rule is not clear from the user request and DOMAIN REFERENCE, ask the user before choosing anything.
|
|
16020
|
-
- When the ranking, comparison, or selection rule is unclear, the minimum next step is the clarification itself. Do not run a placeholder query for a provisional winner before asking.
|
|
16021
|
-
- If a user request matches both a domain type/effect and a loop helper, prioritize the domain type/effect. For example, if DOMAIN REFERENCE contains a \`Task\` class and the user asks to create a task, create the domain task record; do not call \`loop.create_task(...)\` unless you are only tracking your own workflow.
|
|
16022
|
-
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
16023
|
-
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
16024
|
-
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
16025
|
-
- For an unclear ranking, comparison, or selection rule, prefer \`type: 'choice'\` when you can offer a short grounded list of plausible interpretations from the domain or nearby context.
|
|
16026
|
-
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
16027
|
-
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
16028
|
-
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
16029
|
-
- Use \`loop.open_decision(...)\` to persist grounded candidates, \`loop.close_decision(...)\` to resolve one, and \`loop.close_loop(...)\` when the workflow is completed, canceled, or blocked.
|
|
16030
|
-
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
17400
|
+
${knownFactsBlock}
|
|
16031
17401
|
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
- \`loop.open_decision(...)\`: save explicit candidates that later jobs can revisit; each candidate needs an \`id\`.
|
|
16036
|
-
- \`loop.close_decision(...)\`: resolve an open decision with a stored \`selectedId\` and optional rationale.
|
|
16037
|
-
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short resumable task list for the agent's workflow; these are not domain \`Task\` records.
|
|
16038
|
-
- \`loop.close_loop(...)\`: record the workflow outcome when it is completed, canceled, or blocked.
|
|
17402
|
+
[Request]
|
|
17403
|
+
${input.request?.trim() || "Use the latest user message in the conversation."}`;
|
|
17404
|
+
}
|
|
16039
17405
|
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
-
|
|
16045
|
-
|
|
16046
|
-
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
17406
|
+
// src/openai-usage.ts
|
|
17407
|
+
var OPENAI_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
17408
|
+
var OPENAI_PRICING_EFFECTIVE_DATE = "2026-05-19";
|
|
17409
|
+
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
17410
|
+
"gpt-5.4": {
|
|
17411
|
+
provider: "openai",
|
|
17412
|
+
model: "gpt-5.4",
|
|
17413
|
+
currency: "USD",
|
|
17414
|
+
inputUsdPerMillion: 2.5,
|
|
17415
|
+
cachedInputUsdPerMillion: 0.25,
|
|
17416
|
+
outputUsdPerMillion: 15,
|
|
17417
|
+
sourceUrl: OPENAI_PRICING_SOURCE_URL,
|
|
17418
|
+
effectiveDate: OPENAI_PRICING_EFFECTIVE_DATE
|
|
17419
|
+
}
|
|
17420
|
+
};
|
|
17421
|
+
function asRecord5(value) {
|
|
17422
|
+
return value && typeof value === "object" ? value : null;
|
|
17423
|
+
}
|
|
17424
|
+
function numberField(record, key) {
|
|
17425
|
+
const value = record?.[key];
|
|
17426
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
17427
|
+
}
|
|
17428
|
+
function microsPerMillion(usdPerMillion) {
|
|
17429
|
+
return Math.round(usdPerMillion * 1e6);
|
|
17430
|
+
}
|
|
17431
|
+
function getOpenAIModelPricing(model) {
|
|
17432
|
+
return OPENAI_MODEL_PRICING_USD_PER_MILLION[model] || null;
|
|
17433
|
+
}
|
|
17434
|
+
function normalizeOpenAIUsage(rawUsage) {
|
|
17435
|
+
const usage = asRecord5(rawUsage);
|
|
17436
|
+
if (!usage) {
|
|
17437
|
+
return {
|
|
17438
|
+
inputTokens: 0,
|
|
17439
|
+
cachedInputTokens: 0,
|
|
17440
|
+
uncachedInputTokens: 0,
|
|
17441
|
+
outputTokens: 0,
|
|
17442
|
+
reasoningTokens: 0,
|
|
17443
|
+
totalTokens: 0
|
|
17444
|
+
};
|
|
17445
|
+
}
|
|
17446
|
+
const inputTokens = numberField(usage, "prompt_tokens") || numberField(usage, "input_tokens");
|
|
17447
|
+
const outputTokens = numberField(usage, "completion_tokens") || numberField(usage, "output_tokens");
|
|
17448
|
+
const totalTokens = numberField(usage, "total_tokens") || inputTokens + outputTokens;
|
|
17449
|
+
const inputDetails = asRecord5(usage.prompt_tokens_details) || asRecord5(usage.input_tokens_details);
|
|
17450
|
+
const outputDetails = asRecord5(usage.completion_tokens_details) || asRecord5(usage.output_tokens_details);
|
|
17451
|
+
const cachedInputTokens = Math.min(
|
|
17452
|
+
inputTokens,
|
|
17453
|
+
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
17454
|
+
);
|
|
17455
|
+
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
17456
|
+
return {
|
|
17457
|
+
inputTokens,
|
|
17458
|
+
cachedInputTokens,
|
|
17459
|
+
uncachedInputTokens: Math.max(inputTokens - cachedInputTokens, 0),
|
|
17460
|
+
outputTokens,
|
|
17461
|
+
reasoningTokens,
|
|
17462
|
+
totalTokens
|
|
17463
|
+
};
|
|
17464
|
+
}
|
|
17465
|
+
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
17466
|
+
const pricing = getOpenAIModelPricing(model);
|
|
17467
|
+
if (!pricing) return null;
|
|
17468
|
+
const usage = normalizeOpenAIUsage(rawUsage);
|
|
17469
|
+
const inputPricePerMillionMicros = microsPerMillion(
|
|
17470
|
+
pricing.inputUsdPerMillion
|
|
17471
|
+
);
|
|
17472
|
+
const cachedInputPricePerMillionMicros = microsPerMillion(
|
|
17473
|
+
pricing.cachedInputUsdPerMillion
|
|
17474
|
+
);
|
|
17475
|
+
const outputPricePerMillionMicros = microsPerMillion(
|
|
17476
|
+
pricing.outputUsdPerMillion
|
|
17477
|
+
);
|
|
17478
|
+
const amountMicros = Math.round(
|
|
17479
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
17480
|
+
);
|
|
17481
|
+
return {
|
|
17482
|
+
provider: "openai",
|
|
17483
|
+
model,
|
|
17484
|
+
inputTokens: usage.inputTokens,
|
|
17485
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
17486
|
+
uncachedInputTokens: usage.uncachedInputTokens,
|
|
17487
|
+
outputTokens: usage.outputTokens,
|
|
17488
|
+
reasoningTokens: usage.reasoningTokens,
|
|
17489
|
+
totalTokens: usage.totalTokens,
|
|
17490
|
+
amountMicros,
|
|
17491
|
+
currency: "USD",
|
|
17492
|
+
inputPricePerMillionMicros,
|
|
17493
|
+
cachedInputPricePerMillionMicros,
|
|
17494
|
+
outputPricePerMillionMicros,
|
|
17495
|
+
pricingSource: pricing.sourceUrl,
|
|
17496
|
+
pricingEffectiveAt: pricing.effectiveDate,
|
|
17497
|
+
usage
|
|
17498
|
+
};
|
|
16071
17499
|
}
|
|
16072
17500
|
|
|
16073
17501
|
// src/agent-evals.ts
|
|
@@ -16075,7 +17503,7 @@ var DEFAULT_CONTROLLER_BUDGETS = {
|
|
|
16075
17503
|
maxIterations: 6,
|
|
16076
17504
|
maxNoProgressIterations: 2
|
|
16077
17505
|
};
|
|
16078
|
-
function
|
|
17506
|
+
function asRecord6(value) {
|
|
16079
17507
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
16080
17508
|
return value;
|
|
16081
17509
|
}
|
|
@@ -16127,6 +17555,155 @@ function asArray3(value) {
|
|
|
16127
17555
|
if (!value) return [];
|
|
16128
17556
|
return Array.isArray(value) ? value : [value];
|
|
16129
17557
|
}
|
|
17558
|
+
var GPT_54_TOKEN_PRICING_USD_PER_MILLION = {
|
|
17559
|
+
input: 2.5,
|
|
17560
|
+
cachedInput: 0.25,
|
|
17561
|
+
output: 15
|
|
17562
|
+
};
|
|
17563
|
+
function emptyTokenUsage() {
|
|
17564
|
+
return {
|
|
17565
|
+
calls: 0,
|
|
17566
|
+
inputTokens: 0,
|
|
17567
|
+
cachedInputTokens: 0,
|
|
17568
|
+
uncachedInputTokens: 0,
|
|
17569
|
+
outputTokens: 0,
|
|
17570
|
+
totalTokens: 0,
|
|
17571
|
+
inputCostUsd: 0,
|
|
17572
|
+
cachedInputCostUsd: 0,
|
|
17573
|
+
outputCostUsd: 0,
|
|
17574
|
+
totalCostUsd: 0,
|
|
17575
|
+
missingUsageCalls: 0
|
|
17576
|
+
};
|
|
17577
|
+
}
|
|
17578
|
+
function numberField2(record, key) {
|
|
17579
|
+
const value = record?.[key];
|
|
17580
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
17581
|
+
}
|
|
17582
|
+
function calculateTokenCost(input) {
|
|
17583
|
+
const spend = input.model ? calculateOpenAITokenSpend(input.model, {
|
|
17584
|
+
input_tokens: input.uncachedInputTokens + input.cachedInputTokens,
|
|
17585
|
+
output_tokens: input.outputTokens,
|
|
17586
|
+
input_tokens_details: { cached_tokens: input.cachedInputTokens }
|
|
17587
|
+
}) : null;
|
|
17588
|
+
const pricing = spend ? {
|
|
17589
|
+
input: spend.inputPricePerMillionMicros / 1e6,
|
|
17590
|
+
cachedInput: spend.cachedInputPricePerMillionMicros / 1e6,
|
|
17591
|
+
output: spend.outputPricePerMillionMicros / 1e6
|
|
17592
|
+
} : GPT_54_TOKEN_PRICING_USD_PER_MILLION;
|
|
17593
|
+
const inputCostUsd = input.uncachedInputTokens * pricing.input / 1e6;
|
|
17594
|
+
const cachedInputCostUsd = input.cachedInputTokens * pricing.cachedInput / 1e6;
|
|
17595
|
+
const outputCostUsd = input.outputTokens * pricing.output / 1e6;
|
|
17596
|
+
return {
|
|
17597
|
+
inputCostUsd,
|
|
17598
|
+
cachedInputCostUsd,
|
|
17599
|
+
outputCostUsd,
|
|
17600
|
+
totalCostUsd: inputCostUsd + cachedInputCostUsd + outputCostUsd
|
|
17601
|
+
};
|
|
17602
|
+
}
|
|
17603
|
+
function extractTokenUsageFromRaw(raw) {
|
|
17604
|
+
const usage = asRecord6(asRecord6(raw)?.usage);
|
|
17605
|
+
if (!usage) return null;
|
|
17606
|
+
const model = typeof asRecord6(raw)?.model === "string" ? asRecord6(raw)?.model : void 0;
|
|
17607
|
+
const inputTokens = numberField2(usage, "prompt_tokens") || numberField2(usage, "input_tokens");
|
|
17608
|
+
const outputTokens = numberField2(usage, "completion_tokens") || numberField2(usage, "output_tokens");
|
|
17609
|
+
const details = asRecord6(usage.prompt_tokens_details) || asRecord6(usage.input_tokens_details);
|
|
17610
|
+
const cachedInputTokens = Math.min(
|
|
17611
|
+
inputTokens,
|
|
17612
|
+
numberField2(details, "cached_tokens") || numberField2(details, "cached_input_tokens")
|
|
17613
|
+
);
|
|
17614
|
+
const uncachedInputTokens = Math.max(inputTokens - cachedInputTokens, 0);
|
|
17615
|
+
const totalTokens = numberField2(usage, "total_tokens") || inputTokens + outputTokens;
|
|
17616
|
+
const costs = calculateTokenCost({
|
|
17617
|
+
model,
|
|
17618
|
+
uncachedInputTokens,
|
|
17619
|
+
cachedInputTokens,
|
|
17620
|
+
outputTokens
|
|
17621
|
+
});
|
|
17622
|
+
return {
|
|
17623
|
+
calls: 1,
|
|
17624
|
+
inputTokens,
|
|
17625
|
+
cachedInputTokens,
|
|
17626
|
+
uncachedInputTokens,
|
|
17627
|
+
outputTokens,
|
|
17628
|
+
totalTokens,
|
|
17629
|
+
...costs,
|
|
17630
|
+
missingUsageCalls: 0
|
|
17631
|
+
};
|
|
17632
|
+
}
|
|
17633
|
+
function addTokenUsage(aggregate, usage) {
|
|
17634
|
+
if (!usage) {
|
|
17635
|
+
return {
|
|
17636
|
+
...aggregate,
|
|
17637
|
+
missingUsageCalls: aggregate.missingUsageCalls + 1
|
|
17638
|
+
};
|
|
17639
|
+
}
|
|
17640
|
+
const inputTokens = aggregate.inputTokens + usage.inputTokens;
|
|
17641
|
+
const cachedInputTokens = aggregate.cachedInputTokens + usage.cachedInputTokens;
|
|
17642
|
+
const uncachedInputTokens = aggregate.uncachedInputTokens + usage.uncachedInputTokens;
|
|
17643
|
+
const outputTokens = aggregate.outputTokens + usage.outputTokens;
|
|
17644
|
+
const costs = calculateTokenCost({
|
|
17645
|
+
uncachedInputTokens,
|
|
17646
|
+
cachedInputTokens,
|
|
17647
|
+
outputTokens
|
|
17648
|
+
});
|
|
17649
|
+
return {
|
|
17650
|
+
calls: aggregate.calls + usage.calls,
|
|
17651
|
+
inputTokens,
|
|
17652
|
+
cachedInputTokens,
|
|
17653
|
+
uncachedInputTokens,
|
|
17654
|
+
outputTokens,
|
|
17655
|
+
totalTokens: aggregate.totalTokens + usage.totalTokens,
|
|
17656
|
+
...costs,
|
|
17657
|
+
missingUsageCalls: aggregate.missingUsageCalls + usage.missingUsageCalls
|
|
17658
|
+
};
|
|
17659
|
+
}
|
|
17660
|
+
function aggregateTokenUsage(usages) {
|
|
17661
|
+
return usages.reduce(
|
|
17662
|
+
(aggregate, usage) => addTokenUsage(aggregate, usage),
|
|
17663
|
+
emptyTokenUsage()
|
|
17664
|
+
);
|
|
17665
|
+
}
|
|
17666
|
+
function aggregateConversationTokenUsage(conversation) {
|
|
17667
|
+
return aggregateTokenUsage(
|
|
17668
|
+
(conversation.logTurns || []).flatMap(
|
|
17669
|
+
(turn) => turn.iterations.map((iteration) => iteration.tokenUsage)
|
|
17670
|
+
)
|
|
17671
|
+
);
|
|
17672
|
+
}
|
|
17673
|
+
function tokenUsageForGenerationOutput(generation) {
|
|
17674
|
+
const attempts = generation.generationAttempts?.length ? generation.generationAttempts : [{ raw: generation.raw }];
|
|
17675
|
+
return aggregateTokenUsage(
|
|
17676
|
+
attempts.map((attempt) => extractTokenUsageFromRaw(attempt.raw))
|
|
17677
|
+
);
|
|
17678
|
+
}
|
|
17679
|
+
function formatUsd(value) {
|
|
17680
|
+
return `$${value.toFixed(6)}`;
|
|
17681
|
+
}
|
|
17682
|
+
function formatTokenUsage(usage) {
|
|
17683
|
+
if (!usage || usage.calls === 0 && usage.missingUsageCalls === 0) {
|
|
17684
|
+
return ["- LLM calls with usage data: 0", "- Total cost: $0.000000"];
|
|
17685
|
+
}
|
|
17686
|
+
return [
|
|
17687
|
+
`- LLM calls with usage data: ${usage.calls}`,
|
|
17688
|
+
`- LLM calls missing usage data: ${usage.missingUsageCalls}`,
|
|
17689
|
+
`- Input tokens: ${usage.inputTokens}`,
|
|
17690
|
+
`- Cached input tokens: ${usage.cachedInputTokens}`,
|
|
17691
|
+
`- Uncached input tokens: ${usage.uncachedInputTokens}`,
|
|
17692
|
+
`- Output tokens: ${usage.outputTokens}`,
|
|
17693
|
+
`- Total tokens: ${usage.totalTokens}`,
|
|
17694
|
+
`- Input cost: ${formatUsd(usage.inputCostUsd)}`,
|
|
17695
|
+
`- Cached input cost: ${formatUsd(usage.cachedInputCostUsd)}`,
|
|
17696
|
+
`- Output cost: ${formatUsd(usage.outputCostUsd)}`,
|
|
17697
|
+
`- Total cost: ${formatUsd(usage.totalCostUsd)}`,
|
|
17698
|
+
`- Pricing basis: GPT-5.4 at $${GPT_54_TOKEN_PRICING_USD_PER_MILLION.input}/M input, $${GPT_54_TOKEN_PRICING_USD_PER_MILLION.cachedInput}/M cached input, $${GPT_54_TOKEN_PRICING_USD_PER_MILLION.output}/M output.`
|
|
17699
|
+
];
|
|
17700
|
+
}
|
|
17701
|
+
function getJobAgentMessages(liveDoc, jobId) {
|
|
17702
|
+
const jobsById = asRecord6(asRecord6(liveDoc.jobs)?.byId);
|
|
17703
|
+
const job = asRecord6(jobsById?.[jobId]);
|
|
17704
|
+
const agentMessages = job?.agentMessages;
|
|
17705
|
+
return Array.isArray(agentMessages) ? agentMessages : [];
|
|
17706
|
+
}
|
|
16130
17707
|
function buildScenarioSteps(scenario) {
|
|
16131
17708
|
if (scenario.steps?.length) {
|
|
16132
17709
|
return scenario.steps;
|
|
@@ -16182,12 +17759,12 @@ function buildHistory(entries) {
|
|
|
16182
17759
|
);
|
|
16183
17760
|
}
|
|
16184
17761
|
function getOpenPromptsFromDoc(liveDoc) {
|
|
16185
|
-
const jobsById =
|
|
17762
|
+
const jobsById = asRecord6(asRecord6(liveDoc?.jobs)?.byId) || {};
|
|
16186
17763
|
const prompts = [];
|
|
16187
17764
|
for (const job of Object.values(jobsById)) {
|
|
16188
|
-
const promptRecords =
|
|
17765
|
+
const promptRecords = asRecord6(asRecord6(job)?.prompts) || {};
|
|
16189
17766
|
for (const raw of Object.values(promptRecords)) {
|
|
16190
|
-
const record =
|
|
17767
|
+
const record = asRecord6(raw);
|
|
16191
17768
|
if (!record || record.status !== "open" || typeof record.promptId !== "string")
|
|
16192
17769
|
continue;
|
|
16193
17770
|
const prompt = normalizePrompt({
|
|
@@ -16208,11 +17785,11 @@ function getOpenPromptsFromDoc(liveDoc) {
|
|
|
16208
17785
|
return prompts;
|
|
16209
17786
|
}
|
|
16210
17787
|
function filterPromptsByBoundary(liveDoc, prompts, boundaryTimestamp) {
|
|
16211
|
-
const jobsById =
|
|
17788
|
+
const jobsById = asRecord6(asRecord6(liveDoc?.jobs)?.byId) || {};
|
|
16212
17789
|
return prompts.filter((prompt) => {
|
|
16213
17790
|
for (const jobRecord of Object.values(jobsById)) {
|
|
16214
|
-
const promptsById =
|
|
16215
|
-
const promptRecord =
|
|
17791
|
+
const promptsById = asRecord6(asRecord6(jobRecord)?.prompts) || {};
|
|
17792
|
+
const promptRecord = asRecord6(promptsById[prompt.id]);
|
|
16216
17793
|
const openedAt = Number(promptRecord?.openedAt) || 0;
|
|
16217
17794
|
if (openedAt >= boundaryTimestamp) return true;
|
|
16218
17795
|
}
|
|
@@ -16242,19 +17819,99 @@ function extractJsonObject(text) {
|
|
|
16242
17819
|
const start = text.indexOf("{");
|
|
16243
17820
|
const end = text.lastIndexOf("}");
|
|
16244
17821
|
if (start === -1 || end === -1 || end < start) return null;
|
|
17822
|
+
const candidate = text.slice(start, end + 1);
|
|
16245
17823
|
try {
|
|
16246
|
-
return JSON.parse(
|
|
17824
|
+
return JSON.parse(candidate);
|
|
16247
17825
|
} catch {
|
|
16248
|
-
|
|
17826
|
+
const fallback = {};
|
|
17827
|
+
for (const fieldName of ["action", "reply", "code"]) {
|
|
17828
|
+
const field = extractJsonStringField(candidate, fieldName);
|
|
17829
|
+
if (field?.complete) {
|
|
17830
|
+
fallback[fieldName] = field.value;
|
|
17831
|
+
}
|
|
17832
|
+
}
|
|
17833
|
+
return Object.keys(fallback).length > 0 ? fallback : null;
|
|
17834
|
+
}
|
|
17835
|
+
}
|
|
17836
|
+
function extractJsonStringField(source, fieldName) {
|
|
17837
|
+
const keyIndex = source.indexOf(JSON.stringify(fieldName));
|
|
17838
|
+
if (keyIndex === -1) return null;
|
|
17839
|
+
const colonIndex = source.indexOf(":", keyIndex + fieldName.length + 2);
|
|
17840
|
+
if (colonIndex === -1) return null;
|
|
17841
|
+
let cursor = colonIndex + 1;
|
|
17842
|
+
while (cursor < source.length && /\s/.test(source[cursor] || "")) cursor += 1;
|
|
17843
|
+
if (source[cursor] !== '"') return null;
|
|
17844
|
+
cursor += 1;
|
|
17845
|
+
let value = "";
|
|
17846
|
+
while (cursor < source.length) {
|
|
17847
|
+
const char = source[cursor];
|
|
17848
|
+
if (char === '"') return { value, complete: true };
|
|
17849
|
+
if (char !== "\\") {
|
|
17850
|
+
value += char;
|
|
17851
|
+
cursor += 1;
|
|
17852
|
+
continue;
|
|
17853
|
+
}
|
|
17854
|
+
if (cursor + 1 >= source.length) return { value, complete: false };
|
|
17855
|
+
const escaped = source[cursor + 1];
|
|
17856
|
+
if (escaped === "n") value += "\n";
|
|
17857
|
+
else if (escaped === "r") value += "\r";
|
|
17858
|
+
else if (escaped === "t") value += " ";
|
|
17859
|
+
else if (escaped === "b") value += "\b";
|
|
17860
|
+
else if (escaped === "f") value += "\f";
|
|
17861
|
+
else if (escaped === '"' || escaped === "\\" || escaped === "/") {
|
|
17862
|
+
value += escaped;
|
|
17863
|
+
} else if (escaped === "u") {
|
|
17864
|
+
const hex = source.slice(cursor + 2, cursor + 6);
|
|
17865
|
+
if (hex.length < 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
17866
|
+
return { value, complete: false };
|
|
17867
|
+
}
|
|
17868
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
17869
|
+
cursor += 6;
|
|
17870
|
+
continue;
|
|
17871
|
+
} else {
|
|
17872
|
+
value += escaped;
|
|
17873
|
+
}
|
|
17874
|
+
cursor += 2;
|
|
16249
17875
|
}
|
|
17876
|
+
return { value, complete: false };
|
|
16250
17877
|
}
|
|
16251
17878
|
function modelOutputInstruction() {
|
|
16252
17879
|
return [
|
|
16253
17880
|
"Return only a JSON object with this shape:",
|
|
16254
17881
|
'{ "action": "reply" | "job", "reply": string, "code": string }',
|
|
16255
17882
|
'Use "action":"reply" only when a plain conversational answer is enough and no live session state should change.',
|
|
17883
|
+
'Do not use "action":"reply" to promise future tool work; if the user asks to check, find, look up, inspect, update, post, send, approve, schedule, reschedule, calculate, or confirm around a domain action, use "action":"job".',
|
|
17884
|
+
'Do not use "action":"reply" to say a record is not grounded yet; if the request names or describes a domain record, use "action":"job" and ground it from session state, relationships, searches, or visible read-only actions first.',
|
|
17885
|
+
'Before claiming you lack access, inspect the visible action list. If a visible read-only search, lookup, list, guidance, note, policy, or knowledge action can satisfy a "check", "find", "look up", or "whether we have guidance" request, choose "action":"job" and call it.',
|
|
17886
|
+
"Generated code must not report no matches for the primary human-described anchor after a single zero-result list/find/page call. Before that primary no-match return, retry the primary anchor with fewer text constraints or a distinct fallback such as owner/container grounding, relationship traversal, exact-id/path lookup, or shorter target-local search.",
|
|
16256
17887
|
'Use "action":"job" when the next step should run code or mutate workflow state.',
|
|
16257
17888
|
'When action is "job", include runnable code in "code".',
|
|
17889
|
+
"Generated code must not reference prompt-only symbols such as savedData, recentReferences, workflowContext, workflowState, or capabilities. Copy concrete paths/ids from the prompt into strings, fetch records with imports from ./sandbox-tools, or use documented runtime helpers.",
|
|
17890
|
+
"Generated code must import every class and helper it uses from ./sandbox-tools; do not leave undeclared identifiers in the job.",
|
|
17891
|
+
"Generated action calls must use the exact input property names from the visible action schema. Do not invent synonym keys for required inputs.",
|
|
17892
|
+
"If multiple possible targets or a needed human decision blocks a requested operation, put the pause inside code with loop.ask_user(...) or loop.confirm(...); listing candidates or asking only in reply text and returning is incomplete, including when ambiguity is discovered after a query returns several records.",
|
|
17893
|
+
"When a lookup before a mutation returns multiple plausible target records, generated code must ask for a grounded choice; do not mutate results[0], the earliest sorted record, or any other default pick unless the user supplied a unique identifier, ordinal, or selector.",
|
|
17894
|
+
"A bare pronoun such as it, that, or that one is not a unique mutation target when recentReferences, savedData, or the prior visible answer contains multiple compatible records. Do not let one exact recentReference path override that multi-record ambiguity; generated code must ask for a grounded choice before mutating.",
|
|
17895
|
+
"If a follow-up names the same/previous record and also names a related target or evidence type in a condition, use the same/previous record only as the anchor; traverse to the named related type before deciding or mutating.",
|
|
17896
|
+
"For owner/container plus target requests, generated code must ground the owner/container first, then discover the target through relationships, relationship filters, or short target-local search; do not combine owner/container words with target words in one target-class query or require owner/container words to appear in target-local title/summary fields.",
|
|
17897
|
+
"For requested categorical states, generated code should use positive exact filters or explicit local checks; do not use substring negation of another state as a proxy for the requested state.",
|
|
17898
|
+
"For conditional mutations based on a related evidence record, generated code order must be: load the action target or anchor, traverse to the related evidence record, call any visible status/lookup action, then decide whether to mutate. Do not decide, mutate, return, or reject the condition from parent/action-target fields before that evidence step.",
|
|
17899
|
+
"When a condition names an anchored noun phrase whose final noun is an entity type, the final entity type is the evidence record to test; use the earlier words only to ground or traverse to that record.",
|
|
17900
|
+
"Do not treat prior read-only summaries, cached parent fields, action-target fields, or stored related-record fields as fresh evidence for a later conditional mutation when a related evidence object and visible lookup/status action can be reached.",
|
|
17901
|
+
"Generated code must await declared relationship getters before checking arrays, iterating, or reading related-record fields.",
|
|
17902
|
+
"Generated code must call only relationship getters declared on the current record's class; if the target is not direct, walk the declared intermediate getter first instead of inventing a convenience getter.",
|
|
17903
|
+
"Generated code must use declared relationship getters before reading fields from related records; relationship filter fields are not guaranteed to be hydrated nested objects.",
|
|
17904
|
+
"For suitable/available candidate requests, generated code must call a visible availability, matching, or search action when one exists instead of relying only on current relationships or assignments.",
|
|
17905
|
+
"A follow-up to a refused bypass, export, external-send, or restricted-data request must keep the refusal boundary for the same referent; choose a reply refusal instead of running a mutation job.",
|
|
17906
|
+
"Visible answers for grounded named records should include the stored display name or identifier, not only the user's shorthand.",
|
|
17907
|
+
"When the user asks for specific fields, the reply must include every requested field or explicitly say which grounded field is unavailable after fetching the grounded record if saved state is partial.",
|
|
17908
|
+
"When matching action-returned candidates to grounded records, use the output schema's actual identifier fields, including id, path, or fields ending in Id; do not assume returned candidates have _graphPath.",
|
|
17909
|
+
"When resolving a choice answer, accept an unambiguous prefix or substring of an option label; do not fail just because the returned label is abbreviated.",
|
|
17910
|
+
"Do not discard availability/search results solely because a candidate is already assigned or related, unless the user asked for a different candidate.",
|
|
17911
|
+
"After verifying a user-authorized conditional mutation, call the action directly; do not add loop.confirm(...) solely because the mutation is visible to other people, customer-facing, or consequential. Confirm only when the user, policy, action metadata, or unresolved material uncertainty requires it.",
|
|
17912
|
+
"When a job identifies a specific record in its visible answer, display it with agent_heap_objects(...) when the user should see or open it; otherwise save it with await heap.setVar(...) only when it is needed for follow-up resolution.",
|
|
17913
|
+
"heap.setVar(...) accepts scalars, runtime records, sandbox instances, or arrays of those values; do not save plain action/effect result objects. Fetch a created record by its returned id/path before saving or displaying it.",
|
|
17914
|
+
"For requested record fields, read the documented properties from the fetched record before saying a value is unavailable.",
|
|
16258
17915
|
'When action is "reply", include the user-facing answer in "reply".'
|
|
16259
17916
|
].join("\n");
|
|
16260
17917
|
}
|
|
@@ -16263,7 +17920,30 @@ function createOpenAIChatTurnGenerator(options) {
|
|
|
16263
17920
|
/\/$/,
|
|
16264
17921
|
""
|
|
16265
17922
|
);
|
|
16266
|
-
const model = options.model || "gpt-5
|
|
17923
|
+
const model = options.model || "gpt-5.4";
|
|
17924
|
+
const client = new OpenAI__default.default({
|
|
17925
|
+
apiKey: options.apiKey,
|
|
17926
|
+
baseURL: baseUrl,
|
|
17927
|
+
defaultHeaders: options.headers
|
|
17928
|
+
});
|
|
17929
|
+
const emitUsage = async (rawUsage, requestId, usageContext) => {
|
|
17930
|
+
if (!rawUsage || !options.onUsage) return;
|
|
17931
|
+
const spend = calculateOpenAITokenSpend(model, rawUsage);
|
|
17932
|
+
if (!spend) return;
|
|
17933
|
+
const mergedUsageContext = {
|
|
17934
|
+
...options.usageContext || {},
|
|
17935
|
+
...usageContext || {}
|
|
17936
|
+
};
|
|
17937
|
+
await options.onUsage({
|
|
17938
|
+
...spend,
|
|
17939
|
+
source: "openai",
|
|
17940
|
+
lineItemType: "llm_tokens",
|
|
17941
|
+
operation: "chat.completions",
|
|
17942
|
+
requestId: requestId || null,
|
|
17943
|
+
usageContext: mergedUsageContext,
|
|
17944
|
+
rawUsage
|
|
17945
|
+
});
|
|
17946
|
+
};
|
|
16267
17947
|
return async (input) => {
|
|
16268
17948
|
const messages = [
|
|
16269
17949
|
{
|
|
@@ -16277,7 +17957,8 @@ ${modelOutputInstruction()}`
|
|
|
16277
17957
|
];
|
|
16278
17958
|
const payload = {
|
|
16279
17959
|
model,
|
|
16280
|
-
messages
|
|
17960
|
+
messages,
|
|
17961
|
+
response_format: { type: "json_object" }
|
|
16281
17962
|
};
|
|
16282
17963
|
if (typeof options.temperature === "number") {
|
|
16283
17964
|
payload.temperature = options.temperature;
|
|
@@ -16285,38 +17966,51 @@ ${modelOutputInstruction()}`
|
|
|
16285
17966
|
let lastError = null;
|
|
16286
17967
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
16287
17968
|
try {
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
17969
|
+
let raw;
|
|
17970
|
+
let text = "";
|
|
17971
|
+
let usage = null;
|
|
17972
|
+
let requestId = null;
|
|
17973
|
+
if (input.onTextDelta) {
|
|
17974
|
+
const onTextDelta = input.onTextDelta;
|
|
17975
|
+
const stream = await client.chat.completions.create({
|
|
17976
|
+
...payload,
|
|
17977
|
+
stream: true,
|
|
17978
|
+
stream_options: { include_usage: true }
|
|
17979
|
+
});
|
|
17980
|
+
for await (const event of stream) {
|
|
17981
|
+
requestId = requestId || event.id || event._request_id || null;
|
|
17982
|
+
usage = event.usage || usage;
|
|
17983
|
+
const delta = event.choices?.[0]?.delta?.content;
|
|
17984
|
+
const deltaText = typeof delta === "string" ? delta : Array.isArray(delta) ? delta.map((part) => asRecord6(part)?.text || "").join("") : "";
|
|
17985
|
+
if (!deltaText) continue;
|
|
17986
|
+
text += deltaText;
|
|
17987
|
+
await onTextDelta(deltaText);
|
|
16302
17988
|
}
|
|
16303
|
-
|
|
16304
|
-
|
|
17989
|
+
raw = { streamed: true, model, usage, request_id: requestId };
|
|
17990
|
+
} else {
|
|
17991
|
+
const completion = await client.chat.completions.create(
|
|
17992
|
+
payload
|
|
16305
17993
|
);
|
|
17994
|
+
raw = completion;
|
|
17995
|
+
usage = completion.usage;
|
|
17996
|
+
requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof completion._request_id === "string" ? completion._request_id : null);
|
|
17997
|
+
const content = asRecord6(
|
|
17998
|
+
asRecord6(completion.choices?.[0])?.message
|
|
17999
|
+
)?.content;
|
|
18000
|
+
text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord6(part)?.text || "").join("") : "";
|
|
16306
18001
|
}
|
|
16307
|
-
|
|
16308
|
-
const content = asRecord5(
|
|
16309
|
-
asRecord5(raw.choices?.[0])?.message
|
|
16310
|
-
)?.content;
|
|
16311
|
-
const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord5(part)?.text || "").join("") : "";
|
|
18002
|
+
await emitUsage(usage, requestId, input.usageContext);
|
|
16312
18003
|
const parsed = extractJsonObject(text);
|
|
16313
18004
|
if (!parsed) {
|
|
16314
18005
|
if (attempt < 3) {
|
|
16315
18006
|
await sleep2(300 * attempt);
|
|
16316
18007
|
continue;
|
|
16317
18008
|
}
|
|
16318
|
-
|
|
16319
|
-
|
|
18009
|
+
return {
|
|
18010
|
+
reply: text.trim(),
|
|
18011
|
+
code: void 0,
|
|
18012
|
+
raw
|
|
18013
|
+
};
|
|
16320
18014
|
}
|
|
16321
18015
|
return {
|
|
16322
18016
|
reply: typeof parsed.reply === "string" ? parsed.reply : void 0,
|
|
@@ -16325,9 +18019,10 @@ ${text}`);
|
|
|
16325
18019
|
};
|
|
16326
18020
|
} catch (error) {
|
|
16327
18021
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
16328
|
-
|
|
18022
|
+
const status = Number(error?.status);
|
|
18023
|
+
if (attempt < 3 && (Number.isFinite(status) && (status >= 500 || status === 429) || /socket connection was closed unexpectedly|ECONNRESET|network|timed out/i.test(
|
|
16329
18024
|
lastError.message
|
|
16330
|
-
)) {
|
|
18025
|
+
))) {
|
|
16331
18026
|
await sleep2(500 * attempt);
|
|
16332
18027
|
continue;
|
|
16333
18028
|
}
|
|
@@ -16343,7 +18038,7 @@ async function ensureDir(dir) {
|
|
|
16343
18038
|
async function sleep2(ms) {
|
|
16344
18039
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
16345
18040
|
}
|
|
16346
|
-
async function
|
|
18041
|
+
async function withTimeout2(promise, ms, label) {
|
|
16347
18042
|
let timeoutId;
|
|
16348
18043
|
try {
|
|
16349
18044
|
return await Promise.race([
|
|
@@ -16360,17 +18055,35 @@ async function withTimeout(promise, ms, label) {
|
|
|
16360
18055
|
}
|
|
16361
18056
|
}
|
|
16362
18057
|
function getActionSummary(liveDoc, jobId) {
|
|
16363
|
-
const jobsById =
|
|
16364
|
-
const job =
|
|
16365
|
-
|
|
18058
|
+
const jobsById = asRecord6(asRecord6(liveDoc?.jobs)?.byId) || {};
|
|
18059
|
+
const job = asRecord6(jobsById[jobId]);
|
|
18060
|
+
const summary = Array.isArray(job?.actionSummary) ? job.actionSummary.filter(
|
|
16366
18061
|
(line) => typeof line === "string"
|
|
16367
18062
|
) : [];
|
|
18063
|
+
const trace = Array.isArray(job?.actionTrace) ? job.actionTrace.map((event) => asRecord6(event)).filter((event) => Boolean(event)) : [];
|
|
18064
|
+
const traceLines = trace.map((event) => {
|
|
18065
|
+
const kind = typeof event.kind === "string" ? event.kind : "";
|
|
18066
|
+
const action = typeof event.action === "string" ? event.action : "";
|
|
18067
|
+
const target = typeof event.target === "string" ? event.target : "";
|
|
18068
|
+
if (!action) return "";
|
|
18069
|
+
if (kind === "effect_call") {
|
|
18070
|
+
return `- Called ${target || action}`;
|
|
18071
|
+
}
|
|
18072
|
+
if (kind === "loop_op" && action === "ask_user") {
|
|
18073
|
+
return "- Asked the user for input";
|
|
18074
|
+
}
|
|
18075
|
+
if (kind === "loop_op" && action === "confirm") {
|
|
18076
|
+
return "- Requested confirmation";
|
|
18077
|
+
}
|
|
18078
|
+
return "";
|
|
18079
|
+
}).filter((line) => Boolean(line));
|
|
18080
|
+
return Array.from(/* @__PURE__ */ new Set([...summary, ...traceLines]));
|
|
16368
18081
|
}
|
|
16369
18082
|
function normalizeHeapSnapshot2(heap) {
|
|
16370
18083
|
return {
|
|
16371
|
-
entriesByPath:
|
|
16372
|
-
listsByName:
|
|
16373
|
-
variablesByName:
|
|
18084
|
+
entriesByPath: asRecord6(heap?.entriesByPath) || {},
|
|
18085
|
+
listsByName: asRecord6(heap?.listsByName) || {},
|
|
18086
|
+
variablesByName: asRecord6(heap?.variablesByName) || {},
|
|
16374
18087
|
updatedAt: typeof heap?.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
16375
18088
|
};
|
|
16376
18089
|
}
|
|
@@ -16389,21 +18102,30 @@ ${checkpoint.latestJobResult}` : null
|
|
|
16389
18102
|
async function waitForJobOutcome(input) {
|
|
16390
18103
|
const stdout = [];
|
|
16391
18104
|
const stderr = [];
|
|
18105
|
+
let lastLiveDoc = null;
|
|
18106
|
+
let lastPromptCount = 0;
|
|
18107
|
+
let lastMessageCount = 0;
|
|
18108
|
+
let lastJobSummary = null;
|
|
16392
18109
|
input.job.on("stdout", (line) => stdout.push(String(line)));
|
|
16393
18110
|
input.job.on("stderr", (line) => stderr.push(String(line)));
|
|
16394
18111
|
const startedAt = Date.now();
|
|
16395
18112
|
while (Date.now() - startedAt < input.timeoutMs) {
|
|
16396
18113
|
const liveDoc = cloneJson(input.environment.document);
|
|
18114
|
+
lastLiveDoc = liveDoc;
|
|
16397
18115
|
const prompts = filterPromptsByBoundary(
|
|
16398
18116
|
liveDoc,
|
|
16399
18117
|
getOpenPromptsFromDoc(liveDoc),
|
|
16400
18118
|
input.boundaryTimestamp
|
|
16401
18119
|
);
|
|
18120
|
+
lastPromptCount = prompts.length;
|
|
18121
|
+
const messages = asArray3(asRecord6(liveDoc.conversation)?.messages);
|
|
18122
|
+
lastMessageCount = messages.length;
|
|
18123
|
+
lastJobSummary = asRecord6(asRecord6(liveDoc.jobs)?.byId)?.[input.job.id] || null;
|
|
16402
18124
|
if (prompts.length > 0) {
|
|
16403
18125
|
return { kind: "prompt", prompts, liveDoc, stdout, stderr };
|
|
16404
18126
|
}
|
|
16405
18127
|
try {
|
|
16406
|
-
const result = await
|
|
18128
|
+
const result = await withTimeout2(
|
|
16407
18129
|
input.job.result,
|
|
16408
18130
|
input.pollIntervalMs,
|
|
16409
18131
|
`job ${input.job.id} tick`
|
|
@@ -16414,37 +18136,70 @@ async function waitForJobOutcome(input) {
|
|
|
16414
18136
|
if (!/timed out after/.test(message)) throw error;
|
|
16415
18137
|
}
|
|
16416
18138
|
}
|
|
16417
|
-
|
|
18139
|
+
const diagnostics = {
|
|
18140
|
+
elapsedMs: Date.now() - startedAt,
|
|
18141
|
+
promptCount: lastPromptCount,
|
|
18142
|
+
messageCount: lastMessageCount,
|
|
18143
|
+
stdoutTail: stdout.slice(-5),
|
|
18144
|
+
stderrTail: stderr.slice(-5),
|
|
18145
|
+
job: lastJobSummary,
|
|
18146
|
+
hasLiveDoc: Boolean(lastLiveDoc)
|
|
18147
|
+
};
|
|
18148
|
+
throw new Error(
|
|
18149
|
+
`Job ${input.job.id} timed out after ${input.timeoutMs}ms. Diagnostics: ${JSON.stringify(diagnostics)}`
|
|
18150
|
+
);
|
|
16418
18151
|
}
|
|
16419
18152
|
async function generateTurnWithRepair(generator, input) {
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
"Return the full corrected job code."
|
|
16433
|
-
].join("\n");
|
|
16434
|
-
attempt += 1;
|
|
16435
|
-
output = await generator({
|
|
16436
|
-
...input,
|
|
16437
|
-
attempt,
|
|
16438
|
-
request,
|
|
16439
|
-
repairIssues: issues
|
|
16440
|
-
});
|
|
16441
|
-
}
|
|
16442
|
-
return output;
|
|
18153
|
+
const output = await generator(input);
|
|
18154
|
+
const generationAttempts = [
|
|
18155
|
+
{
|
|
18156
|
+
attempt: input.attempt,
|
|
18157
|
+
request: input.request,
|
|
18158
|
+
repairIssues: input.repairIssues,
|
|
18159
|
+
reply: output.reply,
|
|
18160
|
+
code: output.code,
|
|
18161
|
+
raw: output.raw
|
|
18162
|
+
}
|
|
18163
|
+
];
|
|
18164
|
+
return { ...output, generationAttempts };
|
|
16443
18165
|
}
|
|
16444
18166
|
async function writeJson(filePath, value) {
|
|
16445
18167
|
await promises.writeFile(filePath, `${JSON.stringify(value, null, 2)}
|
|
16446
18168
|
`);
|
|
16447
18169
|
}
|
|
18170
|
+
function describeScenarioBehavior(result) {
|
|
18171
|
+
if (result.scenario.description?.trim()) {
|
|
18172
|
+
return result.scenario.description.trim();
|
|
18173
|
+
}
|
|
18174
|
+
const steps = result.steps?.length ? result.steps : buildScenarioSteps(result.scenario).map((step, index) => ({
|
|
18175
|
+
id: step.id || `step-${index + 1}`,
|
|
18176
|
+
request: step.request
|
|
18177
|
+
}));
|
|
18178
|
+
const stepSummary = steps.map((step, index) => {
|
|
18179
|
+
const request = step.request.replace(/\s+/g, " ").trim();
|
|
18180
|
+
return `${index + 1}. ${step.id}: ${request}`;
|
|
18181
|
+
}).join(" ");
|
|
18182
|
+
return [
|
|
18183
|
+
`This report tests scenario \`${result.scenario.id}\` across ${steps.length} user turn${steps.length === 1 ? "" : "s"}.`,
|
|
18184
|
+
"It verifies that the agent grounds the natural-language request in the current ontology/session context, generates the expected job or refusal, and that the runtime executes or blocks the resulting behavior correctly.",
|
|
18185
|
+
stepSummary
|
|
18186
|
+
].filter(Boolean).join(" ");
|
|
18187
|
+
}
|
|
18188
|
+
function describeJobSource(result) {
|
|
18189
|
+
if (result.scenario.jobSource === "hardcoded") {
|
|
18190
|
+
return "Hardcoded deterministic job code supplied by the test.";
|
|
18191
|
+
}
|
|
18192
|
+
if (result.scenario.jobSource === "mixed") {
|
|
18193
|
+
return "Mixed: LLM-generated agent jobs plus hardcoded setup/inspection jobs supplied by the test.";
|
|
18194
|
+
}
|
|
18195
|
+
const hasGeneratedCode = Boolean(
|
|
18196
|
+
result.finalCode || result.steps?.some((step) => step.finalCode)
|
|
18197
|
+
);
|
|
18198
|
+
if (hasGeneratedCode) {
|
|
18199
|
+
return "LLM-generated agent job code. Setup, direct inspections, and assertions are hardcoded by the test harness.";
|
|
18200
|
+
}
|
|
18201
|
+
return "LLM-generated agent response. Setup, direct inspections, and assertions are hardcoded by the test harness.";
|
|
18202
|
+
}
|
|
16448
18203
|
function buildResultReport(result) {
|
|
16449
18204
|
const stepSection = result.steps?.length ? [
|
|
16450
18205
|
"## Steps",
|
|
@@ -16461,6 +18216,15 @@ function buildResultReport(result) {
|
|
|
16461
18216
|
const lines = [
|
|
16462
18217
|
`# Scenario Report: ${result.scenario.id}`,
|
|
16463
18218
|
"",
|
|
18219
|
+
"## Behavior Under Test",
|
|
18220
|
+
describeScenarioBehavior(result),
|
|
18221
|
+
"",
|
|
18222
|
+
"## Job Source",
|
|
18223
|
+
describeJobSource(result),
|
|
18224
|
+
"",
|
|
18225
|
+
"## Token Usage And Cost",
|
|
18226
|
+
...formatTokenUsage(result.tokenUsage),
|
|
18227
|
+
"",
|
|
16464
18228
|
"## Request",
|
|
16465
18229
|
result.scenario.request || result.steps?.[0]?.request || "_No single request_",
|
|
16466
18230
|
"",
|
|
@@ -16487,9 +18251,21 @@ function buildResultReport(result) {
|
|
|
16487
18251
|
`;
|
|
16488
18252
|
}
|
|
16489
18253
|
function buildSuiteIndex(results) {
|
|
18254
|
+
const totalUsage = aggregateTokenUsage(
|
|
18255
|
+
results.map((result) => result.tokenUsage)
|
|
18256
|
+
);
|
|
16490
18257
|
const lines = [
|
|
16491
18258
|
"# Agent Eval Report Index",
|
|
16492
18259
|
"",
|
|
18260
|
+
"## Token Usage And Cost",
|
|
18261
|
+
...formatTokenUsage(totalUsage),
|
|
18262
|
+
"",
|
|
18263
|
+
"## Session Logs",
|
|
18264
|
+
"",
|
|
18265
|
+
"- [Readable chronological logs](./logs/README.md)",
|
|
18266
|
+
"",
|
|
18267
|
+
"## Scenario Reports",
|
|
18268
|
+
"",
|
|
16493
18269
|
...results.map(
|
|
16494
18270
|
(result) => `- [${result.scenario.id}](./${result.scenario.id}/REPORT.md) - ${result.status}`
|
|
16495
18271
|
)
|
|
@@ -16497,6 +18273,192 @@ function buildSuiteIndex(results) {
|
|
|
16497
18273
|
return `${lines.join("\n")}
|
|
16498
18274
|
`;
|
|
16499
18275
|
}
|
|
18276
|
+
function buildLogsIndex(results) {
|
|
18277
|
+
const totalUsage = aggregateTokenUsage(
|
|
18278
|
+
results.map((result) => result.tokenUsage)
|
|
18279
|
+
);
|
|
18280
|
+
const lines = [
|
|
18281
|
+
"# Agent Eval Session Logs",
|
|
18282
|
+
"",
|
|
18283
|
+
"Each file is a chronological session report with user requests, agent responses, generated code, runtime actions/results, and system prompts at the end.",
|
|
18284
|
+
"",
|
|
18285
|
+
"## Token Usage And Cost",
|
|
18286
|
+
...formatTokenUsage(totalUsage),
|
|
18287
|
+
"",
|
|
18288
|
+
...results.map((result) => {
|
|
18289
|
+
const logName = `${slugify(result.scenario.id)}.md`;
|
|
18290
|
+
return `- [${result.scenario.id}](./${logName}) - ${result.status} - ${formatUsd(result.tokenUsage?.totalCostUsd || 0)}`;
|
|
18291
|
+
})
|
|
18292
|
+
];
|
|
18293
|
+
return `${lines.join("\n")}
|
|
18294
|
+
`;
|
|
18295
|
+
}
|
|
18296
|
+
function fenced(value, language = "") {
|
|
18297
|
+
const fence = value.includes("```") ? "````" : "```";
|
|
18298
|
+
return `${fence}${language}
|
|
18299
|
+
${value}
|
|
18300
|
+
${fence}`;
|
|
18301
|
+
}
|
|
18302
|
+
function jsonBlock(value) {
|
|
18303
|
+
return fenced(JSON.stringify(value, null, 2), "json");
|
|
18304
|
+
}
|
|
18305
|
+
function buildSessionLogReport(input) {
|
|
18306
|
+
const { conversation, result, error } = input;
|
|
18307
|
+
const logTurns = conversation.logTurns || [];
|
|
18308
|
+
const systemPrompts = logTurns.flatMap(
|
|
18309
|
+
(turn) => turn.iterations.map((iteration) => ({
|
|
18310
|
+
turn,
|
|
18311
|
+
iteration
|
|
18312
|
+
}))
|
|
18313
|
+
);
|
|
18314
|
+
const lines = [
|
|
18315
|
+
`# Session Log: ${conversation.label}`,
|
|
18316
|
+
"",
|
|
18317
|
+
"## Behavior Under Test",
|
|
18318
|
+
result ? describeScenarioBehavior(result) : "This session log captures the chronological agent/runtime behavior for a scenario that did not complete a structured result.",
|
|
18319
|
+
"",
|
|
18320
|
+
"## Job Source",
|
|
18321
|
+
result ? describeJobSource(result) : "LLM-generated agent jobs when generation completed; setup and harness assertions are hardcoded by the test harness.",
|
|
18322
|
+
"",
|
|
18323
|
+
"## Token Usage And Cost",
|
|
18324
|
+
...formatTokenUsage(
|
|
18325
|
+
result?.tokenUsage || aggregateConversationTokenUsage(conversation)
|
|
18326
|
+
),
|
|
18327
|
+
"",
|
|
18328
|
+
"## Metadata",
|
|
18329
|
+
`- Session id: \`${conversation.environment.sessionId}\``,
|
|
18330
|
+
`- Environment id: \`${conversation.environment.environmentId}\``,
|
|
18331
|
+
`- Sandbox id: \`${conversation.environment.sandboxId}\``,
|
|
18332
|
+
`- Status: ${result?.status || (error ? "failed" : "unknown")}`,
|
|
18333
|
+
...result?.error || error ? [`- Error: ${result?.error || error}`] : [],
|
|
18334
|
+
"",
|
|
18335
|
+
"## Conversation"
|
|
18336
|
+
];
|
|
18337
|
+
for (const turn of logTurns) {
|
|
18338
|
+
lines.push("", `### Turn ${turn.turnNumber}: ${turn.turnId}`, "");
|
|
18339
|
+
lines.push("**User**", "");
|
|
18340
|
+
lines.push(turn.request, "");
|
|
18341
|
+
for (const iteration of turn.iterations) {
|
|
18342
|
+
lines.push(`#### Agent Generation ${iteration.iteration}`, "");
|
|
18343
|
+
lines.push(
|
|
18344
|
+
"**Token Usage And Cost**",
|
|
18345
|
+
"",
|
|
18346
|
+
...formatTokenUsage(iteration.tokenUsage),
|
|
18347
|
+
""
|
|
18348
|
+
);
|
|
18349
|
+
if ((iteration.generationAttempts?.length || 0) > 1) {
|
|
18350
|
+
lines.push("**Generation Attempts**", "");
|
|
18351
|
+
for (const attempt of iteration.generationAttempts || []) {
|
|
18352
|
+
lines.push(`Attempt ${attempt.attempt}`, "");
|
|
18353
|
+
if (attempt.repairIssues?.length) {
|
|
18354
|
+
lines.push("Repair issues:", "");
|
|
18355
|
+
for (const issue of attempt.repairIssues) {
|
|
18356
|
+
lines.push(`- ${issue.code}: ${issue.message}`);
|
|
18357
|
+
}
|
|
18358
|
+
lines.push("");
|
|
18359
|
+
}
|
|
18360
|
+
lines.push("Request", "", fenced(attempt.request, "text"), "");
|
|
18361
|
+
if (attempt.reply?.trim()) {
|
|
18362
|
+
lines.push("Draft reply", "", attempt.reply.trim(), "");
|
|
18363
|
+
}
|
|
18364
|
+
if (attempt.code?.trim()) {
|
|
18365
|
+
lines.push("Code", "", fenced(attempt.code.trim(), "ts"), "");
|
|
18366
|
+
}
|
|
18367
|
+
}
|
|
18368
|
+
}
|
|
18369
|
+
if (iteration.generationReply?.trim()) {
|
|
18370
|
+
lines.push("**Draft Reply**", "", iteration.generationReply.trim(), "");
|
|
18371
|
+
}
|
|
18372
|
+
if (iteration.generatedCode?.trim()) {
|
|
18373
|
+
lines.push(
|
|
18374
|
+
"**Generated Code**",
|
|
18375
|
+
"",
|
|
18376
|
+
fenced(iteration.generatedCode.trim(), "ts"),
|
|
18377
|
+
""
|
|
18378
|
+
);
|
|
18379
|
+
} else {
|
|
18380
|
+
lines.push("**Generated Code**", "", "_No code generated._", "");
|
|
18381
|
+
}
|
|
18382
|
+
if (iteration.promptInteractions?.length) {
|
|
18383
|
+
lines.push("**Structured User Input**", "");
|
|
18384
|
+
for (const interaction of iteration.promptInteractions) {
|
|
18385
|
+
lines.push(
|
|
18386
|
+
`- ${interaction.type}: ${interaction.message || interaction.title} -> \`${JSON.stringify(interaction.answer)}\``
|
|
18387
|
+
);
|
|
18388
|
+
}
|
|
18389
|
+
lines.push("");
|
|
18390
|
+
}
|
|
18391
|
+
if (iteration.actionSummary?.length) {
|
|
18392
|
+
lines.push("**Runtime Actions**", "");
|
|
18393
|
+
for (const action of iteration.actionSummary) lines.push(`- ${action}`);
|
|
18394
|
+
lines.push("");
|
|
18395
|
+
}
|
|
18396
|
+
if (iteration.responseText?.trim()) {
|
|
18397
|
+
lines.push("**Agent Response**", "", iteration.responseText.trim(), "");
|
|
18398
|
+
}
|
|
18399
|
+
if (iteration.continuation) {
|
|
18400
|
+
lines.push(
|
|
18401
|
+
"**Harness Continuation**",
|
|
18402
|
+
"",
|
|
18403
|
+
jsonBlock(iteration.continuation),
|
|
18404
|
+
""
|
|
18405
|
+
);
|
|
18406
|
+
}
|
|
18407
|
+
if (iteration.result !== void 0) {
|
|
18408
|
+
lines.push("**Runtime Result**", "", jsonBlock(iteration.result), "");
|
|
18409
|
+
}
|
|
18410
|
+
if (iteration.error) {
|
|
18411
|
+
lines.push("**Error**", "", iteration.error, "");
|
|
18412
|
+
}
|
|
18413
|
+
}
|
|
18414
|
+
if (turn.completed) {
|
|
18415
|
+
lines.push(
|
|
18416
|
+
"**Turn Final Response**",
|
|
18417
|
+
"",
|
|
18418
|
+
turn.completed.responseText || "_No reply_",
|
|
18419
|
+
""
|
|
18420
|
+
);
|
|
18421
|
+
if (turn.completed.actionSummary.length) {
|
|
18422
|
+
lines.push("**Turn Final Actions**", "");
|
|
18423
|
+
for (const action of turn.completed.actionSummary)
|
|
18424
|
+
lines.push(`- ${action}`);
|
|
18425
|
+
lines.push("");
|
|
18426
|
+
}
|
|
18427
|
+
}
|
|
18428
|
+
if (turn.error) {
|
|
18429
|
+
lines.push("**Turn Error**", "", turn.error, "");
|
|
18430
|
+
}
|
|
18431
|
+
}
|
|
18432
|
+
lines.push("", "## System Prompts", "");
|
|
18433
|
+
if (!systemPrompts.length) {
|
|
18434
|
+
lines.push("_No system prompts captured._", "");
|
|
18435
|
+
} else {
|
|
18436
|
+
for (const { turn, iteration } of systemPrompts) {
|
|
18437
|
+
lines.push(
|
|
18438
|
+
`### Turn ${turn.turnNumber}, Generation ${iteration.iteration}`,
|
|
18439
|
+
"",
|
|
18440
|
+
fenced(iteration.systemPrompt, "text"),
|
|
18441
|
+
""
|
|
18442
|
+
);
|
|
18443
|
+
}
|
|
18444
|
+
}
|
|
18445
|
+
return `${lines.join("\n")}
|
|
18446
|
+
`;
|
|
18447
|
+
}
|
|
18448
|
+
async function writeSessionLogReport(input) {
|
|
18449
|
+
const logsDir = path__default.default.join(input.artifactDir, "logs");
|
|
18450
|
+
await ensureDir(logsDir);
|
|
18451
|
+
await promises.writeFile(
|
|
18452
|
+
path__default.default.join(logsDir, `${slugify(input.conversation.label)}.md`),
|
|
18453
|
+
buildSessionLogReport(input)
|
|
18454
|
+
);
|
|
18455
|
+
}
|
|
18456
|
+
function findTurnLog(conversation, turnDir) {
|
|
18457
|
+
return conversation.logTurns.find((turn) => turn.turnDir === turnDir);
|
|
18458
|
+
}
|
|
18459
|
+
function latestIterationLog(turn) {
|
|
18460
|
+
return turn?.iterations[turn.iterations.length - 1];
|
|
18461
|
+
}
|
|
16500
18462
|
async function applySetup(setup, context) {
|
|
16501
18463
|
if (!setup) return;
|
|
16502
18464
|
if (setup.manifest) {
|
|
@@ -16570,7 +18532,7 @@ async function runAgentEvalSuite(options) {
|
|
|
16570
18532
|
promptInteractions: completed.promptInteractions,
|
|
16571
18533
|
result: completed.result,
|
|
16572
18534
|
heap: normalizeHeapSnapshot2(
|
|
16573
|
-
|
|
18535
|
+
asRecord6(
|
|
16574
18536
|
cloneJson(conversation.environment.document)?.heap
|
|
16575
18537
|
)
|
|
16576
18538
|
),
|
|
@@ -16581,7 +18543,7 @@ async function runAgentEvalSuite(options) {
|
|
|
16581
18543
|
inspect: async (code) => {
|
|
16582
18544
|
const session = conversation.environment;
|
|
16583
18545
|
const job = await session.submitJob(code);
|
|
16584
|
-
return
|
|
18546
|
+
return withTimeout2(
|
|
16585
18547
|
job.result,
|
|
16586
18548
|
9e4,
|
|
16587
18549
|
`inspection job ${job.id}`
|
|
@@ -16656,6 +18618,7 @@ async function runAgentEvalSuite(options) {
|
|
|
16656
18618
|
actionSummary: lastStep.actionSummary,
|
|
16657
18619
|
promptInteractions: lastStep.promptInteractions,
|
|
16658
18620
|
verification: lastStep.inspectionResults.length <= 1 ? lastStep.inspectionResults[0] ?? null : lastStep.inspectionResults,
|
|
18621
|
+
tokenUsage: aggregateConversationTokenUsage(conversation),
|
|
16659
18622
|
steps: stepResults,
|
|
16660
18623
|
turnDir: conversation.artifactDir
|
|
16661
18624
|
};
|
|
@@ -16671,9 +18634,22 @@ async function runAgentEvalSuite(options) {
|
|
|
16671
18634
|
path__default.default.join(conversation.artifactDir, "REPORT.md"),
|
|
16672
18635
|
buildResultReport(result)
|
|
16673
18636
|
);
|
|
18637
|
+
await writeSessionLogReport({
|
|
18638
|
+
artifactDir: options.harness.artifactDir,
|
|
18639
|
+
conversation,
|
|
18640
|
+
result
|
|
18641
|
+
});
|
|
16674
18642
|
finalResult = result;
|
|
16675
18643
|
} catch (error) {
|
|
16676
18644
|
const failureMessage = error instanceof Error ? error.message : String(error);
|
|
18645
|
+
const failedTurn = conversation.logTurns?.[conversation.logTurns.length - 1];
|
|
18646
|
+
if (failedTurn && !failedTurn.completed) {
|
|
18647
|
+
failedTurn.error = failureMessage;
|
|
18648
|
+
const failedIteration = latestIterationLog(failedTurn);
|
|
18649
|
+
if (failedIteration && !failedIteration.responseText) {
|
|
18650
|
+
failedIteration.error = failureMessage;
|
|
18651
|
+
}
|
|
18652
|
+
}
|
|
16677
18653
|
if (attempt < 2 && isTransientEvalError(error)) {
|
|
16678
18654
|
await options.harness.closeConversation(conversation);
|
|
16679
18655
|
continue;
|
|
@@ -16686,6 +18662,7 @@ async function runAgentEvalSuite(options) {
|
|
|
16686
18662
|
actionSummary: [],
|
|
16687
18663
|
promptInteractions: [],
|
|
16688
18664
|
verification: null,
|
|
18665
|
+
tokenUsage: aggregateConversationTokenUsage(conversation),
|
|
16689
18666
|
turnDir: path__default.default.join(options.harness.artifactDir, scenario.id),
|
|
16690
18667
|
error: failureMessage
|
|
16691
18668
|
};
|
|
@@ -16696,6 +18673,12 @@ async function runAgentEvalSuite(options) {
|
|
|
16696
18673
|
path__default.default.join(failed.turnDir, "REPORT.md"),
|
|
16697
18674
|
buildResultReport(failed)
|
|
16698
18675
|
);
|
|
18676
|
+
await writeSessionLogReport({
|
|
18677
|
+
artifactDir: options.harness.artifactDir,
|
|
18678
|
+
conversation,
|
|
18679
|
+
result: failed,
|
|
18680
|
+
error: failureMessage
|
|
18681
|
+
});
|
|
16699
18682
|
finalResult = failed;
|
|
16700
18683
|
} finally {
|
|
16701
18684
|
await options.harness.closeConversation(conversation);
|
|
@@ -16710,6 +18693,7 @@ async function runAgentEvalSuite(options) {
|
|
|
16710
18693
|
actionSummary: [],
|
|
16711
18694
|
promptInteractions: [],
|
|
16712
18695
|
verification: null,
|
|
18696
|
+
tokenUsage: emptyTokenUsage(),
|
|
16713
18697
|
turnDir: path__default.default.join(options.harness.artifactDir, scenario.id),
|
|
16714
18698
|
error: "Scenario ended without a result."
|
|
16715
18699
|
};
|
|
@@ -16724,6 +18708,11 @@ async function runAgentEvalSuite(options) {
|
|
|
16724
18708
|
path__default.default.join(options.harness.artifactDir, "REPORT_INDEX.md"),
|
|
16725
18709
|
buildSuiteIndex(results)
|
|
16726
18710
|
);
|
|
18711
|
+
await ensureDir(path__default.default.join(options.harness.artifactDir, "logs"));
|
|
18712
|
+
await promises.writeFile(
|
|
18713
|
+
path__default.default.join(options.harness.artifactDir, "logs", "README.md"),
|
|
18714
|
+
buildLogsIndex(results)
|
|
18715
|
+
);
|
|
16727
18716
|
return { artifactDir: options.harness.artifactDir, results };
|
|
16728
18717
|
}
|
|
16729
18718
|
function createAgentEvalHarness(options) {
|
|
@@ -16756,6 +18745,10 @@ function createAgentEvalHarness(options) {
|
|
|
16756
18745
|
promptEvents.push({ prompt, receivedAt: Date.now() });
|
|
16757
18746
|
};
|
|
16758
18747
|
environment.on("prompt", promptHandler);
|
|
18748
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
18749
|
+
if (environment.getEffects().length > 0) break;
|
|
18750
|
+
await sleep2(250);
|
|
18751
|
+
}
|
|
16759
18752
|
await ensureDir(path__default.default.join(artifactDir, slugify(label)));
|
|
16760
18753
|
return {
|
|
16761
18754
|
label,
|
|
@@ -16763,7 +18756,8 @@ function createAgentEvalHarness(options) {
|
|
|
16763
18756
|
history: [],
|
|
16764
18757
|
promptEvents,
|
|
16765
18758
|
artifactDir: path__default.default.join(artifactDir, slugify(label)),
|
|
16766
|
-
turnCount: 0
|
|
18759
|
+
turnCount: 0,
|
|
18760
|
+
logTurns: []
|
|
16767
18761
|
};
|
|
16768
18762
|
}
|
|
16769
18763
|
async function closeConversation(conversation) {
|
|
@@ -16777,11 +18771,11 @@ function createAgentEvalHarness(options) {
|
|
|
16777
18771
|
}
|
|
16778
18772
|
async function runCheckJob(code, session) {
|
|
16779
18773
|
const job = await session.submitJob(code);
|
|
16780
|
-
return
|
|
18774
|
+
return withTimeout2(job.result, jobTimeoutMs, `check job ${job.id}`);
|
|
16781
18775
|
}
|
|
16782
18776
|
function buildCheckContext(conversation, completed, turnDir) {
|
|
16783
18777
|
const liveDoc = cloneJson(conversation.environment.document);
|
|
16784
|
-
const heap = normalizeHeapSnapshot2(
|
|
18778
|
+
const heap = normalizeHeapSnapshot2(asRecord6(liveDoc?.heap));
|
|
16785
18779
|
return {
|
|
16786
18780
|
conversation,
|
|
16787
18781
|
environment: conversation.environment,
|
|
@@ -16801,14 +18795,26 @@ function createAgentEvalHarness(options) {
|
|
|
16801
18795
|
};
|
|
16802
18796
|
}
|
|
16803
18797
|
async function runInspection(conversation, inspection, completed, turnDir) {
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
text,
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
18798
|
+
let result = null;
|
|
18799
|
+
let lastError = null;
|
|
18800
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
18801
|
+
result = await runCheckJob(inspection.code, conversation.environment);
|
|
18802
|
+
const text = JSON.stringify(result, null, 2);
|
|
18803
|
+
try {
|
|
18804
|
+
assertMatches(
|
|
18805
|
+
`Verification for ${conversation.label}`,
|
|
18806
|
+
text,
|
|
18807
|
+
inspection.includes,
|
|
18808
|
+
inspection.excludes
|
|
18809
|
+
);
|
|
18810
|
+
lastError = null;
|
|
18811
|
+
break;
|
|
18812
|
+
} catch (error) {
|
|
18813
|
+
lastError = error;
|
|
18814
|
+
await sleep2(250);
|
|
18815
|
+
}
|
|
18816
|
+
}
|
|
18817
|
+
if (lastError) throw lastError;
|
|
16812
18818
|
if (inspection.check) {
|
|
16813
18819
|
await inspection.check({
|
|
16814
18820
|
...buildCheckContext(conversation, completed, turnDir),
|
|
@@ -16834,6 +18840,11 @@ function createAgentEvalHarness(options) {
|
|
|
16834
18840
|
message: prompt.message,
|
|
16835
18841
|
answer
|
|
16836
18842
|
});
|
|
18843
|
+
const turnLog = findTurnLog(pending.conversation, pending.turnDir);
|
|
18844
|
+
const iterationLog = latestIterationLog(turnLog);
|
|
18845
|
+
if (iterationLog) {
|
|
18846
|
+
iterationLog.promptInteractions = pending.promptInteractions;
|
|
18847
|
+
}
|
|
16837
18848
|
const resumed = await waitForJobOutcome({
|
|
16838
18849
|
environment: pending.conversation.environment,
|
|
16839
18850
|
job: pending.job,
|
|
@@ -16857,7 +18868,8 @@ function createAgentEvalHarness(options) {
|
|
|
16857
18868
|
jobId: pending.job.id,
|
|
16858
18869
|
result: resumed.result,
|
|
16859
18870
|
stdout: [...pending.stdout, ...resumed.stdout],
|
|
16860
|
-
|
|
18871
|
+
agentMessages: getJobAgentMessages(liveDoc, pending.job.id),
|
|
18872
|
+
sessionHeap: normalizeHeapSnapshot2(asRecord6(liveDoc?.heap))
|
|
16861
18873
|
});
|
|
16862
18874
|
const responseText = presentation.responseText || pending.finalReply || "Done.";
|
|
16863
18875
|
pending.conversation.history.push({
|
|
@@ -16874,6 +18886,23 @@ function createAgentEvalHarness(options) {
|
|
|
16874
18886
|
promptInteractions: pending.promptInteractions,
|
|
16875
18887
|
result: resumed.result
|
|
16876
18888
|
});
|
|
18889
|
+
const actionSummary = getActionSummary(liveDoc, pending.job.id);
|
|
18890
|
+
if (iterationLog) {
|
|
18891
|
+
iterationLog.responseText = responseText;
|
|
18892
|
+
iterationLog.terminalKind = getCurrentClosureId(liveDoc) ? "closure" : "reply";
|
|
18893
|
+
iterationLog.actionSummary = actionSummary;
|
|
18894
|
+
iterationLog.promptInteractions = pending.promptInteractions;
|
|
18895
|
+
iterationLog.result = resumed.result;
|
|
18896
|
+
}
|
|
18897
|
+
if (turnLog) {
|
|
18898
|
+
turnLog.completed = {
|
|
18899
|
+
responseText,
|
|
18900
|
+
terminalKind: getCurrentClosureId(liveDoc) ? "closure" : "reply",
|
|
18901
|
+
actionSummary,
|
|
18902
|
+
promptInteractions: pending.promptInteractions,
|
|
18903
|
+
result: resumed.result
|
|
18904
|
+
};
|
|
18905
|
+
}
|
|
16877
18906
|
return {
|
|
16878
18907
|
conversation: pending.conversation,
|
|
16879
18908
|
request: pending.request,
|
|
@@ -16881,7 +18910,7 @@ function createAgentEvalHarness(options) {
|
|
|
16881
18910
|
responseText,
|
|
16882
18911
|
terminalKind: getCurrentClosureId(liveDoc) ? "closure" : "reply",
|
|
16883
18912
|
finalCode: pending.finalCode,
|
|
16884
|
-
actionSummary
|
|
18913
|
+
actionSummary,
|
|
16885
18914
|
promptInteractions: pending.promptInteractions,
|
|
16886
18915
|
verification: null,
|
|
16887
18916
|
result: resumed.result
|
|
@@ -16893,6 +18922,14 @@ function createAgentEvalHarness(options) {
|
|
|
16893
18922
|
const turnId = `turn-${String(turnNumber).padStart(2, "0")}-${slugify(input.request.slice(0, 48))}`;
|
|
16894
18923
|
const turnDir = path__default.default.join(conversation.artifactDir, turnId);
|
|
16895
18924
|
await ensureDir(turnDir);
|
|
18925
|
+
const turnLog = {
|
|
18926
|
+
turnNumber,
|
|
18927
|
+
turnId,
|
|
18928
|
+
request: input.request,
|
|
18929
|
+
turnDir,
|
|
18930
|
+
iterations: []
|
|
18931
|
+
};
|
|
18932
|
+
conversation.logTurns.push(turnLog);
|
|
16896
18933
|
if (input.prepareRecords?.length) {
|
|
16897
18934
|
await conversation.environment.recordObjects(input.prepareRecords);
|
|
16898
18935
|
}
|
|
@@ -16931,6 +18968,24 @@ function createAgentEvalHarness(options) {
|
|
|
16931
18968
|
const workflowFocus = projectWorkflowFocus(liveDoc, pendingPrompts, {
|
|
16932
18969
|
boundaryTimestamp
|
|
16933
18970
|
});
|
|
18971
|
+
const referentFocus = projectConversationReferentFocus(liveDoc);
|
|
18972
|
+
const heapFocus = {
|
|
18973
|
+
variableNames: [
|
|
18974
|
+
...workflowFocus.variableNames,
|
|
18975
|
+
...referentFocus.variableNames
|
|
18976
|
+
],
|
|
18977
|
+
listNames: [...workflowFocus.listNames, ...referentFocus.listNames],
|
|
18978
|
+
entryPaths: [...workflowFocus.entryPaths, ...referentFocus.entryPaths]
|
|
18979
|
+
};
|
|
18980
|
+
const tools = conversation.environment.getEffects().map((tool) => ({
|
|
18981
|
+
name: tool.name,
|
|
18982
|
+
description: tool.description,
|
|
18983
|
+
className: tool.className,
|
|
18984
|
+
static: tool.static,
|
|
18985
|
+
ready: tool.ready,
|
|
18986
|
+
inputSchema: tool.inputSchema,
|
|
18987
|
+
outputSchema: tool.outputSchema
|
|
18988
|
+
}));
|
|
16934
18989
|
const systemPrompt = buildGranularAgentSystemPrompt({
|
|
16935
18990
|
domainDocumentation: await conversation.environment.getDomainDocumentation(),
|
|
16936
18991
|
sessionContext: {
|
|
@@ -16938,37 +18993,51 @@ function createAgentEvalHarness(options) {
|
|
|
16938
18993
|
environmentId: conversation.environment.environmentId,
|
|
16939
18994
|
domainRevision: conversation.environment.domainRevision
|
|
16940
18995
|
},
|
|
16941
|
-
heapSummary: projectHeapSummary(liveDoc, {
|
|
16942
|
-
focus:
|
|
18996
|
+
heapSummary: projectHeapSummary(asRecord6(liveDoc?.heap), {
|
|
18997
|
+
focus: heapFocus
|
|
16943
18998
|
}),
|
|
18999
|
+
referentSummary: projectConversationReferentSummary(liveDoc),
|
|
16944
19000
|
loopSummary: projectLoopSummary(liveDoc, pendingPrompts, {
|
|
16945
19001
|
boundaryTimestamp
|
|
16946
19002
|
}),
|
|
16947
19003
|
workflowSummary: projectWorkflowSummary(liveDoc, pendingPrompts, {
|
|
16948
19004
|
boundaryTimestamp
|
|
16949
19005
|
}),
|
|
16950
|
-
tools
|
|
16951
|
-
name: tool.name,
|
|
16952
|
-
description: tool.description,
|
|
16953
|
-
className: tool.className,
|
|
16954
|
-
static: tool.static,
|
|
16955
|
-
ready: tool.ready
|
|
16956
|
-
})),
|
|
19006
|
+
tools,
|
|
16957
19007
|
checkpoint: latestCheckpoint
|
|
16958
19008
|
});
|
|
16959
19009
|
const request = iteration === 0 ? input.request : buildContinuationInstruction(
|
|
16960
19010
|
buildContinuationPreview(latestCheckpoint, noProgressCount)
|
|
16961
19011
|
);
|
|
16962
|
-
const generation = await
|
|
19012
|
+
const generation = await withTimeout2(
|
|
16963
19013
|
generateTurnWithRepair(options.generator, {
|
|
16964
19014
|
systemPrompt,
|
|
16965
19015
|
history: buildHistory(conversation.history),
|
|
16966
19016
|
request,
|
|
16967
|
-
attempt: 1
|
|
19017
|
+
attempt: 1,
|
|
19018
|
+
tools,
|
|
19019
|
+
usageContext: {
|
|
19020
|
+
sandboxId: conversation.environment.sandboxId,
|
|
19021
|
+
environmentId: conversation.environment.environmentId,
|
|
19022
|
+
sessionId: conversation.environment.sessionId,
|
|
19023
|
+
subjectId: conversation.environment.subjectId,
|
|
19024
|
+
permissionProfileId: conversation.environment.permissionProfileId
|
|
19025
|
+
}
|
|
16968
19026
|
}),
|
|
16969
19027
|
chatTimeoutMs,
|
|
16970
19028
|
`chat generation for ${conversation.label} iteration ${iteration + 1}`
|
|
16971
19029
|
);
|
|
19030
|
+
const iterationLog = {
|
|
19031
|
+
iteration: iteration + 1,
|
|
19032
|
+
request,
|
|
19033
|
+
systemPrompt,
|
|
19034
|
+
generationReply: generation.reply,
|
|
19035
|
+
generatedCode: generation.code,
|
|
19036
|
+
rawGeneration: generation.raw,
|
|
19037
|
+
generationAttempts: generation.generationAttempts,
|
|
19038
|
+
tokenUsage: tokenUsageForGenerationOutput(generation)
|
|
19039
|
+
};
|
|
19040
|
+
turnLog.iterations.push(iterationLog);
|
|
16972
19041
|
await writeJson(
|
|
16973
19042
|
path__default.default.join(turnDir, `iteration-${iteration + 1}-generation.json`),
|
|
16974
19043
|
generation
|
|
@@ -16995,11 +19064,39 @@ function createAgentEvalHarness(options) {
|
|
|
16995
19064
|
turnDir
|
|
16996
19065
|
);
|
|
16997
19066
|
}
|
|
19067
|
+
iterationLog.responseText = responseText2;
|
|
19068
|
+
iterationLog.terminalKind = "reply";
|
|
19069
|
+
iterationLog.actionSummary = [];
|
|
19070
|
+
iterationLog.promptInteractions = [];
|
|
19071
|
+
iterationLog.result = completed.result;
|
|
19072
|
+
turnLog.completed = {
|
|
19073
|
+
responseText: responseText2,
|
|
19074
|
+
terminalKind: "reply",
|
|
19075
|
+
actionSummary: [],
|
|
19076
|
+
promptInteractions: [],
|
|
19077
|
+
result: completed.result
|
|
19078
|
+
};
|
|
16998
19079
|
await writeJson(path__default.default.join(turnDir, "result.json"), completed);
|
|
16999
19080
|
return completed;
|
|
17000
19081
|
}
|
|
17001
19082
|
const session = conversation.environment;
|
|
17002
|
-
const job = await session.submitJob(generation.code
|
|
19083
|
+
const job = await session.submitJob(generation.code, {
|
|
19084
|
+
agent: {
|
|
19085
|
+
userRequest: input.request,
|
|
19086
|
+
generationRequest: request,
|
|
19087
|
+
systemPrompt,
|
|
19088
|
+
history: buildHistory(conversation.history),
|
|
19089
|
+
scenarioLabel: conversation.label,
|
|
19090
|
+
turnId,
|
|
19091
|
+
iteration: iteration + 1,
|
|
19092
|
+
tools,
|
|
19093
|
+
generationReply: generation.reply,
|
|
19094
|
+
rawGeneration: generation.raw,
|
|
19095
|
+
repairIssues: generation.generationAttempts?.flatMap(
|
|
19096
|
+
(attempt) => attempt.repairIssues || []
|
|
19097
|
+
)
|
|
19098
|
+
}
|
|
19099
|
+
});
|
|
17003
19100
|
const outcome = await waitForJobOutcome({
|
|
17004
19101
|
environment: conversation.environment,
|
|
17005
19102
|
job,
|
|
@@ -17055,6 +19152,13 @@ function createAgentEvalHarness(options) {
|
|
|
17055
19152
|
turnDir
|
|
17056
19153
|
);
|
|
17057
19154
|
}
|
|
19155
|
+
turnLog.completed = {
|
|
19156
|
+
responseText: resumed.responseText,
|
|
19157
|
+
terminalKind: resumed.terminalKind,
|
|
19158
|
+
actionSummary: resumed.actionSummary,
|
|
19159
|
+
promptInteractions: resumed.promptInteractions,
|
|
19160
|
+
result: resumed.result
|
|
19161
|
+
};
|
|
17058
19162
|
return resumed;
|
|
17059
19163
|
}
|
|
17060
19164
|
}
|
|
@@ -17067,11 +19171,12 @@ function createAgentEvalHarness(options) {
|
|
|
17067
19171
|
const settledLiveDoc = cloneJson(
|
|
17068
19172
|
conversation.environment.document
|
|
17069
19173
|
);
|
|
17070
|
-
const sessionHeap = normalizeHeapSnapshot2(
|
|
19174
|
+
const sessionHeap = normalizeHeapSnapshot2(asRecord6(settledLiveDoc?.heap));
|
|
17071
19175
|
const presentation = resolveJobPresentation({
|
|
17072
19176
|
jobId: job.id,
|
|
17073
19177
|
result: outcome.result,
|
|
17074
19178
|
stdout: outcome.stdout,
|
|
19179
|
+
agentMessages: getJobAgentMessages(settledLiveDoc, job.id),
|
|
17075
19180
|
sessionHeap
|
|
17076
19181
|
});
|
|
17077
19182
|
const responseText = presentation.responseText || generation.reply?.trim() || "Done.";
|
|
@@ -17125,6 +19230,12 @@ function createAgentEvalHarness(options) {
|
|
|
17125
19230
|
result: outcome.result
|
|
17126
19231
|
}
|
|
17127
19232
|
);
|
|
19233
|
+
iterationLog.responseText = responseText;
|
|
19234
|
+
iterationLog.terminalKind = getCurrentClosureId(settledLiveDoc) ? "closure" : "reply";
|
|
19235
|
+
iterationLog.actionSummary = latestCheckpoint.latestActionSummary || [];
|
|
19236
|
+
iterationLog.promptInteractions = [];
|
|
19237
|
+
iterationLog.continuation = continuation;
|
|
19238
|
+
iterationLog.result = outcome.result;
|
|
17128
19239
|
if (!continuation.shouldContinue) {
|
|
17129
19240
|
const completed = {
|
|
17130
19241
|
conversation,
|
|
@@ -17146,6 +19257,13 @@ function createAgentEvalHarness(options) {
|
|
|
17146
19257
|
turnDir
|
|
17147
19258
|
);
|
|
17148
19259
|
}
|
|
19260
|
+
turnLog.completed = {
|
|
19261
|
+
responseText,
|
|
19262
|
+
terminalKind: completed.terminalKind,
|
|
19263
|
+
actionSummary: completed.actionSummary,
|
|
19264
|
+
promptInteractions: [],
|
|
19265
|
+
result: outcome.result
|
|
19266
|
+
};
|
|
17149
19267
|
await writeJson(path__default.default.join(turnDir, "result.json"), completed);
|
|
17150
19268
|
return completed;
|
|
17151
19269
|
}
|
|
@@ -17177,7 +19295,10 @@ function createAgentTester(options) {
|
|
|
17177
19295
|
model: options.openai?.model || options.model,
|
|
17178
19296
|
baseUrl: options.openai?.baseUrl,
|
|
17179
19297
|
temperature: options.openai?.temperature,
|
|
17180
|
-
headers: options.openai?.headers
|
|
19298
|
+
headers: options.openai?.headers,
|
|
19299
|
+
onUsage: async (usage) => {
|
|
19300
|
+
await granular.recordOpenAIUsageSpend(usage, usage.usageContext);
|
|
19301
|
+
}
|
|
17181
19302
|
});
|
|
17182
19303
|
let resolvedEnvironmentId = "environmentId" in options.target ? options.target.environmentId : null;
|
|
17183
19304
|
let connectSeeded = false;
|