@granular-software/sdk 0.4.28 → 0.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-evals.d.mts +1 -1
- package/dist/agent-evals.d.ts +1 -1
- package/dist/agent-evals.js +347 -274
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +347 -274
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.d.mts +11 -2
- package/dist/agent-harness.d.ts +11 -2
- package/dist/agent-harness.js +248 -125
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +246 -126
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +275 -149
- package/dist/{client-CigjaeO8.d.mts → client-BVvOMfln.d.mts} +11 -4
- package/dist/{client-CigjaeO8.d.ts → client-BVvOMfln.d.ts} +11 -4
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +525 -274
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +523 -275
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/agent-evals.mjs
CHANGED
|
@@ -10955,17 +10955,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
10955
10955
|
}
|
|
10956
10956
|
},
|
|
10957
10957
|
domain: {
|
|
10958
|
-
applyToPropertyIR(propertyIR,
|
|
10959
|
-
|
|
10960
|
-
return propertyIR;
|
|
10961
|
-
}
|
|
10962
|
-
return {
|
|
10963
|
-
...propertyIR,
|
|
10964
|
-
docs: [
|
|
10965
|
-
...propertyIR.docs,
|
|
10966
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
10967
|
-
]
|
|
10968
|
-
};
|
|
10958
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
10959
|
+
return propertyIR;
|
|
10969
10960
|
}
|
|
10970
10961
|
}
|
|
10971
10962
|
});
|
|
@@ -11579,17 +11570,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
11579
11570
|
|
|
11580
11571
|
// src/client.ts
|
|
11581
11572
|
var STANDARD_MODULES_OPERATIONS = [
|
|
11582
|
-
{
|
|
11573
|
+
{
|
|
11574
|
+
create: "entity",
|
|
11575
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
11576
|
+
},
|
|
11583
11577
|
{ create: "class", extends: "entity", has: {} },
|
|
11584
|
-
{
|
|
11585
|
-
|
|
11578
|
+
{
|
|
11579
|
+
create: "user",
|
|
11580
|
+
extends: "entity",
|
|
11581
|
+
has: {
|
|
11582
|
+
email: { value: void 0 },
|
|
11583
|
+
firstName: { value: void 0 },
|
|
11584
|
+
lastName: { value: void 0 }
|
|
11585
|
+
}
|
|
11586
|
+
},
|
|
11587
|
+
{
|
|
11588
|
+
create: "company",
|
|
11589
|
+
extends: "entity",
|
|
11590
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
11591
|
+
},
|
|
11586
11592
|
{ create: "string", has: {} },
|
|
11587
11593
|
{ create: "number", has: {} },
|
|
11588
11594
|
{ create: "boolean", has: {} },
|
|
11589
|
-
{
|
|
11595
|
+
{
|
|
11596
|
+
create: "tool_parameter",
|
|
11597
|
+
has: {
|
|
11598
|
+
name: { value: void 0 },
|
|
11599
|
+
type: { value: "string" },
|
|
11600
|
+
description: { value: void 0 },
|
|
11601
|
+
required: { value: false }
|
|
11602
|
+
}
|
|
11603
|
+
}
|
|
11590
11604
|
];
|
|
11591
11605
|
var BUILTIN_MODULES = {
|
|
11592
|
-
|
|
11606
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
11593
11607
|
};
|
|
11594
11608
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11595
11609
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -11624,7 +11638,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
11624
11638
|
}
|
|
11625
11639
|
function isRetryableRecordObjectsError(error) {
|
|
11626
11640
|
const message = error instanceof Error ? error.message : String(error);
|
|
11627
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11641
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11642
|
+
message
|
|
11643
|
+
);
|
|
11628
11644
|
}
|
|
11629
11645
|
function computeEffectKey2(effect) {
|
|
11630
11646
|
const attachedClass = effect.className?.trim();
|
|
@@ -11701,7 +11717,10 @@ function normalizeUser(user) {
|
|
|
11701
11717
|
};
|
|
11702
11718
|
}
|
|
11703
11719
|
function normalizeEnvironmentData(environment) {
|
|
11704
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11720
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11721
|
+
mode: "pinned",
|
|
11722
|
+
versionId: environment.versionId || environment.buildId
|
|
11723
|
+
});
|
|
11705
11724
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
11706
11725
|
return {
|
|
11707
11726
|
...environment,
|
|
@@ -11795,14 +11814,16 @@ var Environment = class extends Session {
|
|
|
11795
11814
|
const response = await fetch(url, {
|
|
11796
11815
|
...options,
|
|
11797
11816
|
headers: {
|
|
11798
|
-
|
|
11817
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11799
11818
|
"Content-Type": "application/json",
|
|
11800
|
-
|
|
11819
|
+
Connection: "close",
|
|
11801
11820
|
...options.headers
|
|
11802
11821
|
}
|
|
11803
11822
|
});
|
|
11804
11823
|
if (!response.ok) {
|
|
11805
|
-
throw new Error(
|
|
11824
|
+
throw new Error(
|
|
11825
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
11826
|
+
);
|
|
11806
11827
|
}
|
|
11807
11828
|
return response.json();
|
|
11808
11829
|
}
|
|
@@ -11816,9 +11837,12 @@ var Environment = class extends Session {
|
|
|
11816
11837
|
async disconnect() {
|
|
11817
11838
|
let wsNotifiedRuntime = false;
|
|
11818
11839
|
try {
|
|
11819
|
-
const goodbye = await this.rpc(
|
|
11820
|
-
|
|
11821
|
-
|
|
11840
|
+
const goodbye = await this.rpc(
|
|
11841
|
+
"client.goodbye",
|
|
11842
|
+
{
|
|
11843
|
+
timestamp: Date.now()
|
|
11844
|
+
}
|
|
11845
|
+
);
|
|
11822
11846
|
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11823
11847
|
} catch {
|
|
11824
11848
|
wsNotifiedRuntime = false;
|
|
@@ -11832,8 +11856,8 @@ var Environment = class extends Session {
|
|
|
11832
11856
|
method: "POST",
|
|
11833
11857
|
headers: {
|
|
11834
11858
|
"Content-Type": "application/json",
|
|
11835
|
-
|
|
11836
|
-
|
|
11859
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11860
|
+
Connection: "close"
|
|
11837
11861
|
},
|
|
11838
11862
|
body: JSON.stringify({
|
|
11839
11863
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -11846,6 +11870,15 @@ var Environment = class extends Session {
|
|
|
11846
11870
|
}
|
|
11847
11871
|
this.client.disconnect();
|
|
11848
11872
|
}
|
|
11873
|
+
/**
|
|
11874
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
11875
|
+
*
|
|
11876
|
+
* Use this when the caller intends to immediately reattach to the same
|
|
11877
|
+
* session after an unexpected disconnect.
|
|
11878
|
+
*/
|
|
11879
|
+
disconnectTransport() {
|
|
11880
|
+
this.client.disconnect();
|
|
11881
|
+
}
|
|
11849
11882
|
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11850
11883
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11851
11884
|
graphContainerStatus = null;
|
|
@@ -11911,14 +11944,14 @@ var Environment = class extends Session {
|
|
|
11911
11944
|
}
|
|
11912
11945
|
/**
|
|
11913
11946
|
* Execute a GraphQL query against the environment's graph.
|
|
11914
|
-
*
|
|
11947
|
+
*
|
|
11915
11948
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
11916
11949
|
* Authentication is handled automatically using the SDK's API key.
|
|
11917
|
-
*
|
|
11950
|
+
*
|
|
11918
11951
|
* @param query - The GraphQL query string
|
|
11919
11952
|
* @param variables - Optional variables for the query
|
|
11920
11953
|
* @returns The query result data
|
|
11921
|
-
*
|
|
11954
|
+
*
|
|
11922
11955
|
* @example
|
|
11923
11956
|
* ```typescript
|
|
11924
11957
|
* // Read the workspace
|
|
@@ -11926,7 +11959,7 @@ var Environment = class extends Session {
|
|
|
11926
11959
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
11927
11960
|
* );
|
|
11928
11961
|
* console.log(result.data);
|
|
11929
|
-
*
|
|
11962
|
+
*
|
|
11930
11963
|
* // Create a model
|
|
11931
11964
|
* const created = await env.graphql(
|
|
11932
11965
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -11938,7 +11971,7 @@ var Environment = class extends Session {
|
|
|
11938
11971
|
method: "POST",
|
|
11939
11972
|
headers: {
|
|
11940
11973
|
"Content-Type": "application/json",
|
|
11941
|
-
|
|
11974
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
11942
11975
|
},
|
|
11943
11976
|
body: JSON.stringify({
|
|
11944
11977
|
environmentId: this.environmentId,
|
|
@@ -11955,10 +11988,10 @@ var Environment = class extends Session {
|
|
|
11955
11988
|
// ==================== RELATIONSHIP METHODS ====================
|
|
11956
11989
|
/**
|
|
11957
11990
|
* Define a relationship between two model types.
|
|
11958
|
-
*
|
|
11991
|
+
*
|
|
11959
11992
|
* Creates both submodels (if they don't exist) and links them with
|
|
11960
11993
|
* a RelationshipDef node that encodes cardinality.
|
|
11961
|
-
*
|
|
11994
|
+
*
|
|
11962
11995
|
* @example
|
|
11963
11996
|
* ```typescript
|
|
11964
11997
|
* // Author has many Books, Book has one Author
|
|
@@ -12018,10 +12051,10 @@ var Environment = class extends Session {
|
|
|
12018
12051
|
}
|
|
12019
12052
|
/**
|
|
12020
12053
|
* Get all relationships for a model type.
|
|
12021
|
-
*
|
|
12054
|
+
*
|
|
12022
12055
|
* @param modelPath - The model type path (e.g., "author")
|
|
12023
12056
|
* @returns Array of relationships from this model's perspective
|
|
12024
|
-
*
|
|
12057
|
+
*
|
|
12025
12058
|
* @example
|
|
12026
12059
|
* ```typescript
|
|
12027
12060
|
* const rels = await env.getRelationships('author');
|
|
@@ -12054,18 +12087,18 @@ var Environment = class extends Session {
|
|
|
12054
12087
|
}
|
|
12055
12088
|
/**
|
|
12056
12089
|
* Attach a target model to a relationship submodel.
|
|
12057
|
-
*
|
|
12090
|
+
*
|
|
12058
12091
|
* Handles cardinality automatically:
|
|
12059
12092
|
* - "One" side: sets/replaces the reference
|
|
12060
12093
|
* - "Many" side: adds the target to the collection
|
|
12061
|
-
*
|
|
12094
|
+
*
|
|
12062
12095
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
12063
12096
|
* Bidirectional sync is automatic.
|
|
12064
|
-
*
|
|
12097
|
+
*
|
|
12065
12098
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
12066
12099
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
12067
12100
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
12068
|
-
*
|
|
12101
|
+
*
|
|
12069
12102
|
* @example
|
|
12070
12103
|
* ```typescript
|
|
12071
12104
|
* // Attach a book to an author (many side)
|
|
@@ -12092,18 +12125,18 @@ var Environment = class extends Session {
|
|
|
12092
12125
|
}
|
|
12093
12126
|
/**
|
|
12094
12127
|
* Detach a target model from a relationship submodel.
|
|
12095
|
-
*
|
|
12128
|
+
*
|
|
12096
12129
|
* Handles bidirectional cleanup automatically.
|
|
12097
|
-
*
|
|
12130
|
+
*
|
|
12098
12131
|
* @param modelPath - The model instance path
|
|
12099
12132
|
* @param submodelPath - The relationship submodel
|
|
12100
12133
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
12101
|
-
*
|
|
12134
|
+
*
|
|
12102
12135
|
* @example
|
|
12103
12136
|
* ```typescript
|
|
12104
12137
|
* // Detach a specific book
|
|
12105
12138
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
12106
|
-
*
|
|
12139
|
+
*
|
|
12107
12140
|
* // Detach all books
|
|
12108
12141
|
* await env.detach('tolkien', 'books');
|
|
12109
12142
|
* ```
|
|
@@ -12127,11 +12160,11 @@ var Environment = class extends Session {
|
|
|
12127
12160
|
}
|
|
12128
12161
|
/**
|
|
12129
12162
|
* List all related models through a relationship submodel.
|
|
12130
|
-
*
|
|
12163
|
+
*
|
|
12131
12164
|
* @param modelPath - The model instance path
|
|
12132
12165
|
* @param submodelPath - The relationship submodel
|
|
12133
12166
|
* @returns Array of related model references
|
|
12134
|
-
*
|
|
12167
|
+
*
|
|
12135
12168
|
* @example
|
|
12136
12169
|
* ```typescript
|
|
12137
12170
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -12155,14 +12188,14 @@ var Environment = class extends Session {
|
|
|
12155
12188
|
}
|
|
12156
12189
|
/**
|
|
12157
12190
|
* Apply a manifest to the current environment's graph.
|
|
12158
|
-
*
|
|
12191
|
+
*
|
|
12159
12192
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
12160
12193
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
12161
12194
|
* relationships from a declarative manifest.
|
|
12162
|
-
*
|
|
12195
|
+
*
|
|
12163
12196
|
* @param manifest - The manifest content to apply
|
|
12164
12197
|
* @returns Summary of applied operations
|
|
12165
|
-
*
|
|
12198
|
+
*
|
|
12166
12199
|
* @example
|
|
12167
12200
|
* ```typescript
|
|
12168
12201
|
* await environment.applyManifest({
|
|
@@ -12200,12 +12233,16 @@ var Environment = class extends Session {
|
|
|
12200
12233
|
applied++;
|
|
12201
12234
|
} catch (err) {
|
|
12202
12235
|
if (!err.message?.includes("already exists")) {
|
|
12203
|
-
errors.push(
|
|
12236
|
+
errors.push(
|
|
12237
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
12238
|
+
);
|
|
12204
12239
|
}
|
|
12205
12240
|
}
|
|
12206
12241
|
}
|
|
12207
12242
|
} else {
|
|
12208
|
-
errors.push(
|
|
12243
|
+
errors.push(
|
|
12244
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
12245
|
+
);
|
|
12209
12246
|
}
|
|
12210
12247
|
}
|
|
12211
12248
|
}
|
|
@@ -12289,7 +12326,10 @@ var Environment = class extends Session {
|
|
|
12289
12326
|
}
|
|
12290
12327
|
}
|
|
12291
12328
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
12292
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
12329
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
12330
|
+
toolPath,
|
|
12331
|
+
metamodels
|
|
12332
|
+
)) {
|
|
12293
12333
|
await this._runGraphql(mutation.query, mutation.label);
|
|
12294
12334
|
}
|
|
12295
12335
|
}
|
|
@@ -12338,7 +12378,9 @@ var Environment = class extends Session {
|
|
|
12338
12378
|
}
|
|
12339
12379
|
if (eventType.payloadSchema?.properties) {
|
|
12340
12380
|
const fieldSpecs = {};
|
|
12341
|
-
for (const [propName, propSchema] of Object.entries(
|
|
12381
|
+
for (const [propName, propSchema] of Object.entries(
|
|
12382
|
+
eventType.payloadSchema.properties
|
|
12383
|
+
)) {
|
|
12342
12384
|
const schema = propSchema;
|
|
12343
12385
|
fieldSpecs[propName] = {
|
|
12344
12386
|
type: schema.type ?? "string",
|
|
@@ -12621,7 +12663,9 @@ var Environment = class extends Session {
|
|
|
12621
12663
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12622
12664
|
await Promise.all(
|
|
12623
12665
|
wave.map(async (plan) => {
|
|
12624
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12666
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12667
|
+
plan.slice
|
|
12668
|
+
);
|
|
12625
12669
|
if (items.length !== plan.slice.length) {
|
|
12626
12670
|
throw new Error(
|
|
12627
12671
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -12651,13 +12695,10 @@ var Environment = class extends Session {
|
|
|
12651
12695
|
let lastError;
|
|
12652
12696
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12653
12697
|
try {
|
|
12654
|
-
const response = await this.controlPlaneRequest(
|
|
12655
|
-
|
|
12656
|
-
{
|
|
12657
|
-
|
|
12658
|
-
body: JSON.stringify({ records: chunk })
|
|
12659
|
-
}
|
|
12660
|
-
);
|
|
12698
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
12699
|
+
method: "POST",
|
|
12700
|
+
body: JSON.stringify({ records: chunk })
|
|
12701
|
+
});
|
|
12661
12702
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
12662
12703
|
return { items, durationMs: Date.now() - wallStart };
|
|
12663
12704
|
} catch (error) {
|
|
@@ -12720,7 +12761,9 @@ var Environment = class extends Session {
|
|
|
12720
12761
|
* Fetch a single record import by id.
|
|
12721
12762
|
*/
|
|
12722
12763
|
async getRecordImport(importId) {
|
|
12723
|
-
return this.controlPlaneRequest(
|
|
12764
|
+
return this.controlPlaneRequest(
|
|
12765
|
+
`/control/record-imports/${importId}`
|
|
12766
|
+
);
|
|
12724
12767
|
}
|
|
12725
12768
|
/**
|
|
12726
12769
|
* Cancel a queued/background record import.
|
|
@@ -12787,7 +12830,9 @@ var Granular = class _Granular {
|
|
|
12787
12830
|
constructor(options) {
|
|
12788
12831
|
const auth = options.token ?? options.apiKey;
|
|
12789
12832
|
if (!auth) {
|
|
12790
|
-
throw new Error(
|
|
12833
|
+
throw new Error(
|
|
12834
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
12835
|
+
);
|
|
12791
12836
|
}
|
|
12792
12837
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
12793
12838
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -12799,10 +12844,10 @@ var Granular = class _Granular {
|
|
|
12799
12844
|
}
|
|
12800
12845
|
/**
|
|
12801
12846
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12802
|
-
*
|
|
12847
|
+
*
|
|
12803
12848
|
* @param options - User options
|
|
12804
12849
|
* @returns The recorded user with both `userId` and `granularId`
|
|
12805
|
-
*
|
|
12850
|
+
*
|
|
12806
12851
|
* @example
|
|
12807
12852
|
* ```typescript
|
|
12808
12853
|
* const user = await granular.recordUser({
|
|
@@ -12813,14 +12858,16 @@ var Granular = class _Granular {
|
|
|
12813
12858
|
* ```
|
|
12814
12859
|
*/
|
|
12815
12860
|
async recordUser(options) {
|
|
12816
|
-
const subject = normalizeSubject(
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12861
|
+
const subject = normalizeSubject(
|
|
12862
|
+
await this.request("/control/subjects", {
|
|
12863
|
+
method: "POST",
|
|
12864
|
+
body: JSON.stringify({
|
|
12865
|
+
identityId: options.userId,
|
|
12866
|
+
name: options.name,
|
|
12867
|
+
email: options.email
|
|
12868
|
+
})
|
|
12822
12869
|
})
|
|
12823
|
-
|
|
12870
|
+
);
|
|
12824
12871
|
return normalizeUser({
|
|
12825
12872
|
granularId: subject.granularId,
|
|
12826
12873
|
userId: options.userId,
|
|
@@ -12867,18 +12914,20 @@ var Granular = class _Granular {
|
|
|
12867
12914
|
permissions: options.permissions || []
|
|
12868
12915
|
};
|
|
12869
12916
|
}
|
|
12870
|
-
throw new Error(
|
|
12917
|
+
throw new Error(
|
|
12918
|
+
"connect() requires either userId, granularId, or a user object returned by recordUser()."
|
|
12919
|
+
);
|
|
12871
12920
|
}
|
|
12872
12921
|
/**
|
|
12873
12922
|
* Connect to an ontology environment and establish a real-time session.
|
|
12874
|
-
*
|
|
12923
|
+
*
|
|
12875
12924
|
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12876
12925
|
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12877
12926
|
* the sandbox registry automatically.
|
|
12878
|
-
*
|
|
12927
|
+
*
|
|
12879
12928
|
* @param options - Connection options
|
|
12880
12929
|
* @returns An active environment session
|
|
12881
|
-
*
|
|
12930
|
+
*
|
|
12882
12931
|
* @example
|
|
12883
12932
|
* ```typescript
|
|
12884
12933
|
* const environment = await granular.connect({
|
|
@@ -12887,23 +12936,23 @@ var Granular = class _Granular {
|
|
|
12887
12936
|
* userId: 'user_123',
|
|
12888
12937
|
* permissions: ['agent'],
|
|
12889
12938
|
* });
|
|
12890
|
-
*
|
|
12939
|
+
*
|
|
12891
12940
|
* await granular.registerEffect('my-sandbox', {
|
|
12892
12941
|
* name: 'greet',
|
|
12893
12942
|
* description: 'Say hello',
|
|
12894
12943
|
* inputSchema: { type: 'object', properties: {} },
|
|
12895
12944
|
* handler: async () => 'Hello!',
|
|
12896
12945
|
* });
|
|
12897
|
-
*
|
|
12946
|
+
*
|
|
12898
12947
|
* // Submit job
|
|
12899
12948
|
* const job = await environment.submitJob(`
|
|
12900
12949
|
* import { tools } from './sandbox-tools';
|
|
12901
12950
|
* return await tools.greet({});
|
|
12902
12951
|
* `);
|
|
12903
|
-
*
|
|
12952
|
+
*
|
|
12904
12953
|
* console.log(await job.result); // 'Hello!'
|
|
12905
12954
|
* ```
|
|
12906
|
-
|
|
12955
|
+
*/
|
|
12907
12956
|
async connect(options) {
|
|
12908
12957
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
12909
12958
|
const ontology = options.ontology;
|
|
@@ -12918,8 +12967,15 @@ var Granular = class _Granular {
|
|
|
12918
12967
|
const user = await this.resolveConnectUser(options);
|
|
12919
12968
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12920
12969
|
for (const profileName of user.permissions) {
|
|
12921
|
-
const profileId = await this.ensurePermissionProfile(
|
|
12922
|
-
|
|
12970
|
+
const profileId = await this.ensurePermissionProfile(
|
|
12971
|
+
sandbox.sandboxId,
|
|
12972
|
+
profileName
|
|
12973
|
+
);
|
|
12974
|
+
await this.ensureAssignment(
|
|
12975
|
+
user.granularId,
|
|
12976
|
+
sandbox.sandboxId,
|
|
12977
|
+
profileId
|
|
12978
|
+
);
|
|
12923
12979
|
}
|
|
12924
12980
|
const envData = await this.environments.create(sandbox.sandboxId, {
|
|
12925
12981
|
subjectId: user.granularId,
|
|
@@ -12971,7 +13027,9 @@ var Granular = class _Granular {
|
|
|
12971
13027
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
12972
13028
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
12973
13029
|
summary: row.summary != null ? String(row.summary) : null,
|
|
12974
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13030
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13031
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
13032
|
+
) : null,
|
|
12975
13033
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
12976
13034
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
12977
13035
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -13061,7 +13119,13 @@ var Granular = class _Granular {
|
|
|
13061
13119
|
});
|
|
13062
13120
|
await client.connect();
|
|
13063
13121
|
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13064
|
-
const environment = new Environment(
|
|
13122
|
+
const environment = new Environment(
|
|
13123
|
+
client,
|
|
13124
|
+
envData,
|
|
13125
|
+
clientId,
|
|
13126
|
+
this.apiKey,
|
|
13127
|
+
graphqlEndpoint
|
|
13128
|
+
);
|
|
13065
13129
|
await environment.hello();
|
|
13066
13130
|
return environment;
|
|
13067
13131
|
}
|
|
@@ -13095,14 +13159,18 @@ var Granular = class _Granular {
|
|
|
13095
13159
|
};
|
|
13096
13160
|
}
|
|
13097
13161
|
async publishSandboxEffectCatalog(host) {
|
|
13098
|
-
const effects = Array.from(
|
|
13099
|
-
(
|
|
13100
|
-
);
|
|
13101
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13162
|
+
const effects = Array.from(
|
|
13163
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
13164
|
+
).map((effect) => this.serializeEffect(effect));
|
|
13165
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13166
|
+
effects
|
|
13167
|
+
});
|
|
13102
13168
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
13103
13169
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
13104
13170
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
13105
|
-
const detail = rejected.map(
|
|
13171
|
+
const detail = rejected.map(
|
|
13172
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
13173
|
+
).join("; ");
|
|
13106
13174
|
throw new Error(
|
|
13107
13175
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
13108
13176
|
);
|
|
@@ -13136,13 +13204,15 @@ var Granular = class _Granular {
|
|
|
13136
13204
|
disconnectError
|
|
13137
13205
|
);
|
|
13138
13206
|
}
|
|
13139
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13207
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13208
|
+
(reconnectError) => {
|
|
13209
|
+
console.error(
|
|
13210
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
13211
|
+
reconnectError
|
|
13212
|
+
);
|
|
13213
|
+
console.error("[Granular] Original heartbeat failure:", error);
|
|
13214
|
+
}
|
|
13215
|
+
);
|
|
13146
13216
|
}
|
|
13147
13217
|
startEffectHostHeartbeat(host) {
|
|
13148
13218
|
if (host.heartbeatTimer) {
|
|
@@ -13163,9 +13233,15 @@ var Granular = class _Granular {
|
|
|
13163
13233
|
host.heartbeatInFlight = false;
|
|
13164
13234
|
});
|
|
13165
13235
|
};
|
|
13166
|
-
sendHeartbeat(
|
|
13236
|
+
sendHeartbeat(
|
|
13237
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
13238
|
+
false
|
|
13239
|
+
);
|
|
13167
13240
|
host.heartbeatTimer = setInterval(() => {
|
|
13168
|
-
sendHeartbeat(
|
|
13241
|
+
sendHeartbeat(
|
|
13242
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
13243
|
+
true
|
|
13244
|
+
);
|
|
13169
13245
|
}, 1e4);
|
|
13170
13246
|
}
|
|
13171
13247
|
stopEffectHostHeartbeat(host) {
|
|
@@ -13197,7 +13273,12 @@ var Granular = class _Granular {
|
|
|
13197
13273
|
const effectClientId = crypto.randomUUID();
|
|
13198
13274
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
13199
13275
|
const wsClient = new WSClient({
|
|
13200
|
-
url: buildEffectHostUrl(
|
|
13276
|
+
url: buildEffectHostUrl(
|
|
13277
|
+
this.apiUrl,
|
|
13278
|
+
sandboxId,
|
|
13279
|
+
effectClientId,
|
|
13280
|
+
clientId
|
|
13281
|
+
),
|
|
13201
13282
|
sessionId: `effect-host:${effectClientId}`,
|
|
13202
13283
|
token: this.apiKey,
|
|
13203
13284
|
tokenProvider: this.tokenProvider,
|
|
@@ -13216,7 +13297,10 @@ var Granular = class _Granular {
|
|
|
13216
13297
|
};
|
|
13217
13298
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
13218
13299
|
const request = params;
|
|
13219
|
-
return invokeRegisteredEffect(
|
|
13300
|
+
return invokeRegisteredEffect(
|
|
13301
|
+
this.getSandboxEffectMap(sandboxId),
|
|
13302
|
+
request
|
|
13303
|
+
);
|
|
13220
13304
|
});
|
|
13221
13305
|
wsClient.on("open", () => {
|
|
13222
13306
|
void this.synchronizeEffectHost(host).catch((error) => {
|
|
@@ -13264,7 +13348,7 @@ var Granular = class _Granular {
|
|
|
13264
13348
|
}
|
|
13265
13349
|
/**
|
|
13266
13350
|
* Register multiple effects (tools) for a specific sandbox.
|
|
13267
|
-
*
|
|
13351
|
+
*
|
|
13268
13352
|
* batch version of `registerEffect`.
|
|
13269
13353
|
*/
|
|
13270
13354
|
async registerEffects(sandboxNameOrId, effects) {
|
|
@@ -13278,7 +13362,7 @@ var Granular = class _Granular {
|
|
|
13278
13362
|
}
|
|
13279
13363
|
/**
|
|
13280
13364
|
* Unregister an effect from a sandbox.
|
|
13281
|
-
*
|
|
13365
|
+
*
|
|
13282
13366
|
* Removes it from the local sandbox registry and updates the
|
|
13283
13367
|
* sandbox-scoped live catalog.
|
|
13284
13368
|
*/
|
|
@@ -13377,27 +13461,31 @@ var Granular = class _Granular {
|
|
|
13377
13461
|
const assignments = await this.request(
|
|
13378
13462
|
`/control/subjects/${subjectId}/assignments`
|
|
13379
13463
|
);
|
|
13380
|
-
const existing = assignments.items.find(
|
|
13381
|
-
(a) => a.sandboxId === sandboxId
|
|
13382
|
-
);
|
|
13464
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
13383
13465
|
if (existing) {
|
|
13384
13466
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
13385
13467
|
return;
|
|
13386
13468
|
}
|
|
13387
|
-
await this.request(
|
|
13388
|
-
|
|
13389
|
-
|
|
13469
|
+
await this.request(
|
|
13470
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
13471
|
+
{
|
|
13472
|
+
method: "DELETE"
|
|
13473
|
+
}
|
|
13474
|
+
);
|
|
13390
13475
|
}
|
|
13391
13476
|
} catch {
|
|
13392
13477
|
}
|
|
13393
|
-
await this.request(
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13478
|
+
await this.request(
|
|
13479
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
13480
|
+
{
|
|
13481
|
+
method: "POST",
|
|
13482
|
+
body: JSON.stringify({
|
|
13483
|
+
sandboxId,
|
|
13484
|
+
subjectId,
|
|
13485
|
+
permissionProfileId
|
|
13486
|
+
})
|
|
13487
|
+
}
|
|
13488
|
+
);
|
|
13401
13489
|
}
|
|
13402
13490
|
/**
|
|
13403
13491
|
* Sandbox management API
|
|
@@ -13477,23 +13565,33 @@ var Granular = class _Granular {
|
|
|
13477
13565
|
},
|
|
13478
13566
|
get: async (environmentId) => {
|
|
13479
13567
|
return normalizeEnvironmentData(
|
|
13480
|
-
await this.request(
|
|
13568
|
+
await this.request(
|
|
13569
|
+
`/control/environments/${environmentId}`
|
|
13570
|
+
)
|
|
13481
13571
|
);
|
|
13482
13572
|
},
|
|
13483
13573
|
create: async (sandboxId, data) => {
|
|
13484
13574
|
const environmentName = data.environment || data.envName;
|
|
13485
|
-
return normalizeEnvironmentData(
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13575
|
+
return normalizeEnvironmentData(
|
|
13576
|
+
await this.request(
|
|
13577
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
13578
|
+
{
|
|
13579
|
+
method: "POST",
|
|
13580
|
+
body: JSON.stringify({
|
|
13581
|
+
...data,
|
|
13582
|
+
envName: environmentName
|
|
13583
|
+
})
|
|
13584
|
+
}
|
|
13585
|
+
)
|
|
13586
|
+
);
|
|
13492
13587
|
},
|
|
13493
13588
|
delete: async (environmentId) => {
|
|
13494
|
-
return this.request(
|
|
13495
|
-
|
|
13496
|
-
|
|
13589
|
+
return this.request(
|
|
13590
|
+
`/control/environments/${environmentId}`,
|
|
13591
|
+
{
|
|
13592
|
+
method: "DELETE"
|
|
13593
|
+
}
|
|
13594
|
+
);
|
|
13497
13595
|
}
|
|
13498
13596
|
};
|
|
13499
13597
|
}
|
|
@@ -13513,10 +13611,13 @@ var Granular = class _Granular {
|
|
|
13513
13611
|
}
|
|
13514
13612
|
if (params.since) query.set("since", params.since.toISOString());
|
|
13515
13613
|
if (params.until) query.set("until", params.until.toISOString());
|
|
13516
|
-
if (params.isAcked !== void 0)
|
|
13614
|
+
if (params.isAcked !== void 0)
|
|
13615
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
13517
13616
|
if (params.limit) query.set("limit", String(params.limit));
|
|
13518
13617
|
if (params.offset) query.set("offset", String(params.offset));
|
|
13519
|
-
const result = await this.request(
|
|
13618
|
+
const result = await this.request(
|
|
13619
|
+
`/control/stream-events?${query.toString()}`
|
|
13620
|
+
);
|
|
13520
13621
|
return (result.items || []).map((row) => ({
|
|
13521
13622
|
eventId: row.event_id,
|
|
13522
13623
|
streamName: row.stream_name,
|
|
@@ -13547,7 +13648,9 @@ var Granular = class _Granular {
|
|
|
13547
13648
|
since: cursor,
|
|
13548
13649
|
limit: 100
|
|
13549
13650
|
});
|
|
13550
|
-
const orderedEvents = [...events].sort(
|
|
13651
|
+
const orderedEvents = [...events].sort(
|
|
13652
|
+
(a, b) => a.createdAt - b.createdAt
|
|
13653
|
+
);
|
|
13551
13654
|
for (const event of orderedEvents) {
|
|
13552
13655
|
if (seenEventIds.has(event.eventId)) {
|
|
13553
13656
|
continue;
|
|
@@ -13560,15 +13663,19 @@ var Granular = class _Granular {
|
|
|
13560
13663
|
params.onEvent(event);
|
|
13561
13664
|
}
|
|
13562
13665
|
} catch (err) {
|
|
13563
|
-
params.onError?.(
|
|
13666
|
+
params.onError?.(
|
|
13667
|
+
err instanceof Error ? err : new Error(String(err))
|
|
13668
|
+
);
|
|
13564
13669
|
}
|
|
13565
13670
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13566
13671
|
}
|
|
13567
13672
|
};
|
|
13568
13673
|
poll();
|
|
13569
|
-
return {
|
|
13570
|
-
|
|
13571
|
-
|
|
13674
|
+
return {
|
|
13675
|
+
unsubscribe: () => {
|
|
13676
|
+
running = false;
|
|
13677
|
+
}
|
|
13678
|
+
};
|
|
13572
13679
|
},
|
|
13573
13680
|
ack: async (eventId) => {
|
|
13574
13681
|
await this.request("/control/stream-events/ack", {
|
|
@@ -13586,7 +13693,9 @@ var Granular = class _Granular {
|
|
|
13586
13693
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13587
13694
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13588
13695
|
if (params.environment) query.set("environmentId", params.environment);
|
|
13589
|
-
const result = await this.request(
|
|
13696
|
+
const result = await this.request(
|
|
13697
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
13698
|
+
);
|
|
13590
13699
|
return (result.items || []).map((row) => ({
|
|
13591
13700
|
streamName: row.stream_name,
|
|
13592
13701
|
eventType: row.event_type,
|
|
@@ -13604,10 +13713,14 @@ var Granular = class _Granular {
|
|
|
13604
13713
|
get subjects() {
|
|
13605
13714
|
return {
|
|
13606
13715
|
get: async (subjectId) => {
|
|
13607
|
-
return normalizeSubject(
|
|
13716
|
+
return normalizeSubject(
|
|
13717
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
13718
|
+
);
|
|
13608
13719
|
},
|
|
13609
13720
|
listAssignments: async (subjectId) => {
|
|
13610
|
-
return this.request(
|
|
13721
|
+
return this.request(
|
|
13722
|
+
`/control/subjects/${subjectId}/assignments`
|
|
13723
|
+
);
|
|
13611
13724
|
}
|
|
13612
13725
|
};
|
|
13613
13726
|
}
|
|
@@ -13617,24 +13730,31 @@ var Granular = class _Granular {
|
|
|
13617
13730
|
get users() {
|
|
13618
13731
|
return {
|
|
13619
13732
|
create: async (data) => {
|
|
13620
|
-
return normalizeSubject(
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13624
|
-
|
|
13625
|
-
|
|
13733
|
+
return normalizeSubject(
|
|
13734
|
+
await this.request("/control/subjects", {
|
|
13735
|
+
method: "POST",
|
|
13736
|
+
body: JSON.stringify({
|
|
13737
|
+
identityId: data.id,
|
|
13738
|
+
name: data.name,
|
|
13739
|
+
email: data.email
|
|
13740
|
+
})
|
|
13626
13741
|
})
|
|
13627
|
-
|
|
13742
|
+
);
|
|
13628
13743
|
},
|
|
13629
13744
|
get: async (id) => {
|
|
13630
|
-
return normalizeSubject(
|
|
13745
|
+
return normalizeSubject(
|
|
13746
|
+
await this.request(`/control/subjects/${id}`)
|
|
13747
|
+
);
|
|
13631
13748
|
}
|
|
13632
13749
|
};
|
|
13633
13750
|
}
|
|
13634
13751
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
13635
13752
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13636
|
-
const result = await this.request(
|
|
13637
|
-
|
|
13753
|
+
const result = await this.request(
|
|
13754
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
13755
|
+
);
|
|
13756
|
+
if (result.items.length === 0)
|
|
13757
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13638
13758
|
return result.items[0].sandboxId;
|
|
13639
13759
|
}
|
|
13640
13760
|
/**
|
|
@@ -13649,9 +13769,9 @@ var Granular = class _Granular {
|
|
|
13649
13769
|
const response = await fetch(url, {
|
|
13650
13770
|
...options,
|
|
13651
13771
|
headers: {
|
|
13652
|
-
|
|
13772
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13653
13773
|
"Content-Type": "application/json",
|
|
13654
|
-
|
|
13774
|
+
Connection: "close",
|
|
13655
13775
|
...options.headers
|
|
13656
13776
|
}
|
|
13657
13777
|
});
|
|
@@ -13662,7 +13782,11 @@ var Granular = class _Granular {
|
|
|
13662
13782
|
return response.json();
|
|
13663
13783
|
}
|
|
13664
13784
|
const errorText = await response.text();
|
|
13665
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
13785
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
13786
|
+
response.status,
|
|
13787
|
+
errorText,
|
|
13788
|
+
url
|
|
13789
|
+
);
|
|
13666
13790
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13667
13791
|
if (this.debugHttp) {
|
|
13668
13792
|
console.warn(
|
|
@@ -13801,21 +13925,6 @@ function reviewGeneratedJobCode(code) {
|
|
|
13801
13925
|
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."
|
|
13802
13926
|
});
|
|
13803
13927
|
}
|
|
13804
|
-
const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
|
|
13805
|
-
for (const call of askUserCalls) {
|
|
13806
|
-
const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
|
|
13807
|
-
const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
|
|
13808
|
-
const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
|
|
13809
|
-
const includesShortlistOptions = /options\s*:\s*\[/.test(call);
|
|
13810
|
-
if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
|
|
13811
|
-
issues.push({
|
|
13812
|
-
code: "disambiguation_requires_choice",
|
|
13813
|
-
severity: "error",
|
|
13814
|
-
message: "When asking the user to choose between known concrete records such as invoices, orders, shipments, requests, cases, or work orders, use loop.ask_user({ type: 'choice', options: [...] }) with a short explicit shortlist instead of a free-text input."
|
|
13815
|
-
});
|
|
13816
|
-
break;
|
|
13817
|
-
}
|
|
13818
|
-
}
|
|
13819
13928
|
}
|
|
13820
13929
|
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
13821
13930
|
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
@@ -13861,6 +13970,9 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
13861
13970
|
entryPaths: uniqueStrings(entryPaths, 8)
|
|
13862
13971
|
};
|
|
13863
13972
|
}
|
|
13973
|
+
function normalizeActionSummaryForPrompt(line) {
|
|
13974
|
+
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
13975
|
+
}
|
|
13864
13976
|
function getCurrentClosureId(liveDoc) {
|
|
13865
13977
|
const loop = asRecord2(liveDoc?.loop);
|
|
13866
13978
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -14073,7 +14185,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
14073
14185
|
variableNames: uniqueStrings(variableNames, 4),
|
|
14074
14186
|
listNames: uniqueStrings(listNames, 4),
|
|
14075
14187
|
entryPaths: uniqueStrings(entryPaths, 6),
|
|
14076
|
-
recentActionSummary: uniqueStrings(actionSummaryLines, 8)
|
|
14188
|
+
recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
|
|
14189
|
+
normalizeActionSummaryForPrompt
|
|
14190
|
+
)
|
|
14077
14191
|
};
|
|
14078
14192
|
}
|
|
14079
14193
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
@@ -14465,17 +14579,14 @@ ${resultPreview}` : null
|
|
|
14465
14579
|
].filter(Boolean).join("\n\n");
|
|
14466
14580
|
}
|
|
14467
14581
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
14468
|
-
return domainDocumentation?.trim() || "No domain
|
|
14582
|
+
return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
|
|
14469
14583
|
}
|
|
14470
14584
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
14471
14585
|
if (!sessionContext) return "No session metadata available.";
|
|
14472
14586
|
const rows = [
|
|
14473
14587
|
["sandboxId", sessionContext.sandboxId],
|
|
14474
14588
|
["environmentId", sessionContext.environmentId],
|
|
14475
|
-
["
|
|
14476
|
-
["granularId", sessionContext.granularId],
|
|
14477
|
-
["userName", sessionContext.userName],
|
|
14478
|
-
["domainRevision", sessionContext.domainRevision]
|
|
14589
|
+
["userName", sessionContext.userName]
|
|
14479
14590
|
];
|
|
14480
14591
|
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
14481
14592
|
if (activeRows.length === 0) return "No session metadata available.";
|
|
@@ -14484,6 +14595,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
|
|
|
14484
14595
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
14485
14596
|
return heapSummary?.trim() || "Heap is empty for this session.";
|
|
14486
14597
|
}
|
|
14598
|
+
function buildGranularAgentReferentBlock(referentSummary) {
|
|
14599
|
+
return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
|
|
14600
|
+
}
|
|
14487
14601
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
14488
14602
|
return loopSummary?.trim() || "No active loop state recorded for this session.";
|
|
14489
14603
|
}
|
|
@@ -14507,7 +14621,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
14507
14621
|
(tool) => Boolean(tool.className && !tool.static)
|
|
14508
14622
|
);
|
|
14509
14623
|
const lines = [
|
|
14510
|
-
"Treat this block as the planning map. Use DOMAIN
|
|
14624
|
+
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
14511
14625
|
];
|
|
14512
14626
|
const appendGroup = (title, group) => {
|
|
14513
14627
|
lines.push(`- ${title}:`);
|
|
@@ -14555,7 +14669,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
|
14555
14669
|
if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
|
|
14556
14670
|
lines.push("latestActionSummary:");
|
|
14557
14671
|
for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
|
|
14558
|
-
|
|
14672
|
+
const normalizedLine = normalizeActionSummaryForPrompt(line);
|
|
14673
|
+
lines.push(
|
|
14674
|
+
normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
|
|
14675
|
+
);
|
|
14559
14676
|
}
|
|
14560
14677
|
}
|
|
14561
14678
|
if (checkpoint.latestJobResult?.trim()) {
|
|
@@ -14571,9 +14688,10 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
14571
14688
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
14572
14689
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
14573
14690
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
14691
|
+
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
14574
14692
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
14575
14693
|
return `You are an AI assistant for a live Granular session.
|
|
14576
|
-
You can help the user understand the domain, answer questions, or generate and execute
|
|
14694
|
+
You can help the user understand the domain, answer questions, or generate and execute code against the live session.
|
|
14577
14695
|
Your tone must be natural and human-like.
|
|
14578
14696
|
|
|
14579
14697
|
Call the \`execute_code\` effect ONLY when the user's intent matches the domain's capabilities and requires executing code against the live session. If the user is just asking a general question or if their request doesn't match the available effects or domain types, respond with text to explain.
|
|
@@ -14582,6 +14700,8 @@ When you call \`execute_code\`, additional assistant text must be either:
|
|
|
14582
14700
|
- a brief summary of the actions the generated code will perform.
|
|
14583
14701
|
Do not include any other kind of commentary when calling \`execute_code\`.
|
|
14584
14702
|
- If the next step needs to create or update workflow state in the live session, you must call \`execute_code\`. This includes \`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`, \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
|
|
14703
|
+
- If the next step is an interactive clarification that should be resumable in the live workflow, you must call \`execute_code\`. A missing preference, rule, metric, target, or option selection is not a plain-text reply when the answer should drive the next live step.
|
|
14704
|
+
- 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.
|
|
14585
14705
|
- 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.
|
|
14586
14706
|
|
|
14587
14707
|
\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
|
|
@@ -14601,6 +14721,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
|
|
|
14601
14721
|
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
14602
14722
|
- If you need clarification, ask in everyday language.
|
|
14603
14723
|
- 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.
|
|
14724
|
+
- 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.
|
|
14604
14725
|
- Keep replies concise and clear.
|
|
14605
14726
|
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
14606
14727
|
|
|
@@ -14610,9 +14731,9 @@ ${sessionBlock}
|
|
|
14610
14731
|
\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
|
|
14611
14732
|
${toolBlock}
|
|
14612
14733
|
|
|
14613
|
-
\u2500\u2500\u2500 DOMAIN
|
|
14734
|
+
\u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
|
|
14614
14735
|
Import classes and effect functions from \`./sandbox-tools\` in generated code.
|
|
14615
|
-
|
|
14736
|
+
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
|
|
14616
14737
|
|
|
14617
14738
|
${domainBlock}
|
|
14618
14739
|
|
|
@@ -14622,6 +14743,9 @@ ${checkpointBlock}
|
|
|
14622
14743
|
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
14623
14744
|
${workflowBlock}
|
|
14624
14745
|
|
|
14746
|
+
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
14747
|
+
${referentBlock}
|
|
14748
|
+
|
|
14625
14749
|
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
14626
14750
|
${heapBlock}
|
|
14627
14751
|
|
|
@@ -14629,109 +14753,54 @@ ${heapBlock}
|
|
|
14629
14753
|
${loopBlock}
|
|
14630
14754
|
|
|
14631
14755
|
\u2500\u2500\u2500 LOOP PLAYBOOK \u2500\u2500\u2500
|
|
14632
|
-
-
|
|
14633
|
-
-
|
|
14634
|
-
-
|
|
14635
|
-
-
|
|
14636
|
-
-
|
|
14637
|
-
-
|
|
14638
|
-
- If
|
|
14639
|
-
-
|
|
14640
|
-
-
|
|
14641
|
-
-
|
|
14642
|
-
-
|
|
14643
|
-
-
|
|
14644
|
-
-
|
|
14645
|
-
-
|
|
14646
|
-
- Use \`loop.
|
|
14647
|
-
-
|
|
14648
|
-
-
|
|
14649
|
-
- If
|
|
14650
|
-
- For disambiguation between concrete known records, prefer \`type: 'choice'\` over \`type: 'input'\`. This is especially important for invoices, orders, shipments, requests, work orders, and cases.
|
|
14651
|
-
- For \`type: 'choice'\` prompts, make the options directly pickable by a human: use a stable value and a readable label that includes the identifier or title they are likely to recognize.
|
|
14652
|
-
- If a shortlist already exists, do not ask the user to type an exact database key or identifier manually. Present the shortlist as clickable choices instead.
|
|
14653
|
-
- When reasoning about free-form status strings, do not use brittle substring checks such as \`status.includes("paid")\` because values like \`"unpaid"\` would be misclassified. Prefer explicit positive matches such as \`unpaid\`, \`open\`, or \`overdue\`, or exact normalized comparisons.
|
|
14654
|
-
- When multiple concrete records match and \`loop.ask_user(...)\` is available, do not stop with a plain-text question like "Which invoice do you mean?". Persist the live workflow and ask through \`await loop.ask_user(...)\` instead.
|
|
14655
|
-
- If the user could reasonably answer with a partial identifier such as \`abcd\` for \`INV-abcd\`, that is another sign the question should be a \`type: 'choice'\` prompt with visible options rather than a free-text input.
|
|
14656
|
-
- Never ask for approval in plain text when \`loop.confirm(...)\` is available. Use \`loop.confirm(...)\` for consequential approval.
|
|
14657
|
-
- When the correct next step is a loop helper action, generate code and call that helper. Do not replace it with a conversational reply.
|
|
14658
|
-
- If you ask the user a new question in the current job, do not also call \`loop.close_loop(...)\` in that same job.
|
|
14659
|
-
- When you need user input or approval, await \`loop.ask_user(...)\` or \`loop.confirm(...)\`. The job will pause until the user answers, then resume from that awaited call.
|
|
14660
|
-
- It is valid to branch on the value returned by \`await loop.ask_user(...)\` or \`await loop.confirm(...)\` after the job resumes.
|
|
14661
|
-
- After \`await loop.ask_user(...)\` returns a concrete choice, continue the workflow in the same resumed job whenever that answer is enough to act. Do not stop with placeholder text such as "I can do that next" or "I'm ready to continue".
|
|
14662
|
-
- After \`await loop.confirm(...)\` returns \`true\`, execute the approved mutation in that same resumed job before returning. Do not end with placeholder text like "Approved, ready to make the change next."
|
|
14663
|
-
- Only stop immediately after a resumed prompt when the user declined, the workflow is now blocked, or you truly still need another missing piece of information.
|
|
14664
|
-
- If the user says stop, enough, or no further action, close the loop and end cleanly without asking another question.
|
|
14665
|
-
- If one clear item is already selected and the next step matters, prefer \`loop.confirm(...)\` over another exploratory question.
|
|
14666
|
-
- If one clear item is already selected and the only missing input is approval to proceed, use \`loop.confirm(...)\` rather than \`loop.ask_user(...)\`.
|
|
14667
|
-
- If the user already gave a usable scheduling window such as "Tuesday morning", treat that as enough to choose a reasonable concrete slot. Do not open another menu just to choose between nearby sub-slots unless a real conflict or hard business rule forces that follow-up.
|
|
14668
|
-
- For schedule changes, prefer one grounded recommendation plus one approval prompt. Avoid a second prompt for optional time-window micro-choices when you can pick a sensible default that still satisfies the user request.
|
|
14669
|
-
- If the user explicitly instructs you to perform a consequential action now, that instruction counts as approval. Do not add an extra confirmation step unless the user expressed hesitation, ambiguity, or asked you not to execute yet.
|
|
14670
|
-
- Direct imperatives such as "cancel this order", "send the reminder now", "approve this refund", or "charge it now" already authorize that exact step. Execute them directly instead of inserting \`loop.confirm(...)\`.
|
|
14671
|
-
- If the user says not to do anything irreversible yet, stop at recommendation, review, or approval. Do not collect checkout-only details like quantity, delivery notes, gift message, or optional preferences unless the user explicitly asks to move closer to purchase.
|
|
14672
|
-
- Once you have one solid recommendation, prefer summarizing it and asking for approval over gathering more optional preferences.
|
|
14673
|
-
- Prefer asking the user for the next missing input over fetching extra related data they did not ask for yet.
|
|
14674
|
-
- Avoid serial menus. After one clarifying choice, prefer acting on it, asking one short text question, or confirming rather than opening another menu.
|
|
14675
|
-
- When the user asks for a summary "including" concrete records such as unpaid invoices, open cases, orders, or shipments, include the actual identifiers or titles of those records in the reply, not just aggregate counts.
|
|
14676
|
-
- Call \`loop.close_loop(...)\` before stopping whenever the current workflow is completed, canceled, or clearly blocked.
|
|
14756
|
+
- 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.
|
|
14757
|
+
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
14758
|
+
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
14759
|
+
- If the user names a record that is not already in the heap, fetch it from the graph instead of saying it is not in context.
|
|
14760
|
+
- Treat user-provided names as human references, not exact keys. If one strong partial match exists, use it. If several plausible matches exist, ask the user to choose.
|
|
14761
|
+
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
14762
|
+
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
14763
|
+
- 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.
|
|
14764
|
+
- 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.
|
|
14765
|
+
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
14766
|
+
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
14767
|
+
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
14768
|
+
- 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.
|
|
14769
|
+
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
14770
|
+
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
14771
|
+
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
14772
|
+
- 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.
|
|
14773
|
+
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
14677
14774
|
|
|
14678
14775
|
\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
|
|
14679
14776
|
- Import from \`./sandbox-tools\`.
|
|
14680
|
-
- If you use \`heap\`, \`loop\`, \`agent_text_message\`,
|
|
14777
|
+
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
|
|
14681
14778
|
- Write top-level executable code with \`await\` at top level.
|
|
14682
|
-
- The generated job body must be plain runnable JavaScript.
|
|
14683
|
-
-
|
|
14684
|
-
-
|
|
14685
|
-
-
|
|
14686
|
-
-
|
|
14687
|
-
-
|
|
14688
|
-
-
|
|
14689
|
-
-
|
|
14690
|
-
-
|
|
14691
|
-
-
|
|
14692
|
-
-
|
|
14693
|
-
-
|
|
14694
|
-
-
|
|
14695
|
-
-
|
|
14696
|
-
-
|
|
14697
|
-
-
|
|
14698
|
-
-
|
|
14699
|
-
-
|
|
14700
|
-
-
|
|
14701
|
-
-
|
|
14702
|
-
-
|
|
14703
|
-
-
|
|
14704
|
-
-
|
|
14705
|
-
-
|
|
14706
|
-
-
|
|
14707
|
-
- If a helper returns plain JSON candidates, keep them local, store only a scalar like the chosen id, or resolve the matching sandbox instance before writing it into the heap.
|
|
14708
|
-
- When reading heap values, prefer generated generic typings such as \`await heap.getVar<Book[]>("my_books")\` or \`await heap.getVar<Book>("selected_book")\`.
|
|
14709
|
-
- If a focused heap variable already points to a known class, read it with that exact generic type and act on it directly. Do not use \`heap.getVar<any>(...)\` or cast through \`any\` when the class is already clear from the prompt. For example, prefer \`await heap.getVar<Order>("selected_order")\` over \`await heap.getVar<any>("selected_order")\`.
|
|
14710
|
-
- Use the injected \`loop\` helpers when you need to manage the workflow itself:
|
|
14711
|
-
\`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`,
|
|
14712
|
-
\`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
|
|
14713
|
-
- Loop helper semantics:
|
|
14714
|
-
- \`loop.open_decision(...)\`: store explicit candidates from the current job so a later job can revisit the same decision. Keep and reuse the returned \`decisionId\`.
|
|
14715
|
-
- \`loop.close_decision(...)\`: resolve an open decision by choosing one stored candidate with \`selectedId\` and recording why. Candidates may be any JSON objects, but each one must have an \`id\`.
|
|
14716
|
-
- \`loop.ask_user(...)\`: pause the job and ask the user for missing input. Default to \`type: 'input'\`; use \`type: 'choice'\` only for a short explicit shortlist. Write \`const answer = await loop.ask_user(...)\`, then continue the same job once the user answers.
|
|
14717
|
-
- \`loop.confirm(...)\`: pause the job for approval before a consequential action. Do not simulate confirmation in plain text. Write \`const approved = await loop.confirm(...)\`, then branch on that approval once the job resumes.
|
|
14718
|
-
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short task list that later jobs can continue and finish.
|
|
14719
|
-
- \`loop.close_loop(...)\`: record the current workflow outcome with a short summary before stopping. Do not call it in the same job that opens a new user prompt unless the workflow is explicitly blocked. This does not end the session forever.
|
|
14720
|
-
- Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
|
|
14721
|
-
- Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
|
|
14722
|
-
- Every job that intends to answer the user must emit at least one explicit UI message with \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
14723
|
-
- Use \`agent_text_message("...")\` for all user-visible text shown in the UI.
|
|
14724
|
-
- Use \`agent_heap_objects(...)\` only when you want the UI to render heap-backed records or lists.
|
|
14725
|
-
- If you want to show both text and records, call \`agent_text_message(...)\` and \`agent_heap_objects(...)\` separately in whatever order fits the interaction.
|
|
14726
|
-
- \`agent_text_message(...)\` should be used with a plain text string in normal generated code.
|
|
14727
|
-
- \`agent_heap_objects(...)\` should point at heap-backed values: explicit \`entryPaths\` / \`listNames\` / \`variableNames\`, a named list saved with \`saveAs\`, or values read back from \`heap.getVar(...)\`.
|
|
14728
|
-
- If you just fetched records and want to show them in the UI, save or reference them through the heap first, then call \`agent_heap_objects(...)\`. Do not try to hand-build UI payloads in job code.
|
|
14729
|
-
- Do not assume heap changes will be displayed automatically. If records should appear in the UI, you must call \`agent_heap_objects(...)\`.
|
|
14730
|
-
- Never write \`return { reply, show }\` or \`return { show: ... }\` for UI. If you want the UI to render records or lists, call \`agent_heap_objects(...)\` instead.
|
|
14731
|
-
- Do not rely on the final return value for user-visible output. A plain return value is not considered a displayed UI answer.
|
|
14732
|
-
- \`agent_message(...)\` remains available as a legacy compatibility alias, but prefer \`agent_text_message(...)\` and \`agent_heap_objects(...)\` in new code.
|
|
14733
|
-
- Do not return bare structured JSON, low-level diagnostics, or database-shaped payloads as the final answer unless the user explicitly asks for them.
|
|
14734
|
-
- Prefer simple executable JavaScript over clever interpolation. Avoid nested template literals or unusually dense inline expressions when a small temporary variable or string concatenation would be clearer and safer.
|
|
14779
|
+
- The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
|
|
14780
|
+
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
|
|
14781
|
+
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
|
|
14782
|
+
- Use \`ClassName.count()\` for totals, \`ClassName.page({ page, perPage, saveAs })\` when you need \`items\` plus \`totalCount\` or \`hasMore\`, \`ClassName.list({ page, perPage, saveAs })\` for one page of records, and \`ClassName.iterate({ perPage, maxItems })\` for large scans.
|
|
14783
|
+
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
|
|
14784
|
+
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
|
|
14785
|
+
- A property appearing on a record does not make it valid in \`filter\` or \`sort\`; only use fields and operators that are explicitly exposed in DOMAIN REFERENCE.
|
|
14786
|
+
- Choose \`sort.field\` verbatim from the sortable fields listed in DOMAIN REFERENCE. Do not sort by relationship names, related-record collections, counts, totals, or other derived metrics unless they are explicitly listed as sortable.
|
|
14787
|
+
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
|
|
14788
|
+
- Do not invent proxy metrics, fallback heuristics, or made-up tie-breakers to resolve ambiguity. If the rule is unclear, ask the user with \`loop.ask_user(...)\`.
|
|
14789
|
+
- Do not fetch, sort, or show a provisional record just to have something to display while the real ranking or selection rule is still ambiguous.
|
|
14790
|
+
- Call instance methods on instances, static methods on classes, and global effects by name.
|
|
14791
|
+
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
|
|
14792
|
+
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
|
|
14793
|
+
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
|
|
14794
|
+
- Only store sandbox instances, typed lists, or scalars in the heap. If a helper returns plain JSON, keep it local or store only the chosen scalar.
|
|
14795
|
+
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
|
|
14796
|
+
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
|
|
14797
|
+
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
|
|
14798
|
+
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
|
|
14799
|
+
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
14800
|
+
- Use \`agent_text_message(...)\` for user-visible text.
|
|
14801
|
+
- Use \`agent_heap_objects(...)\` for user-visible records. You may pass sandbox instances directly, or heap-backed \`entryPaths\`, \`listNames\`, and \`variableNames\` when you already have them. Use \`saveAs\` or \`heap.setVar(...)\` when you need a reusable named selection.
|
|
14802
|
+
- 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.
|
|
14803
|
+
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
|
|
14735
14804
|
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
|
|
14736
14805
|
}
|
|
14737
14806
|
|
|
@@ -14890,6 +14959,10 @@ function fallbackResponseText(entries, lists) {
|
|
|
14890
14959
|
return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
|
|
14891
14960
|
}
|
|
14892
14961
|
if (lists.length > 0) {
|
|
14962
|
+
const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
|
|
14963
|
+
if (emptyOnly) {
|
|
14964
|
+
return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
|
|
14965
|
+
}
|
|
14893
14966
|
return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
|
|
14894
14967
|
}
|
|
14895
14968
|
return null;
|