@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.js
CHANGED
|
@@ -10980,17 +10980,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
10980
10980
|
}
|
|
10981
10981
|
},
|
|
10982
10982
|
domain: {
|
|
10983
|
-
applyToPropertyIR(propertyIR,
|
|
10984
|
-
|
|
10985
|
-
return propertyIR;
|
|
10986
|
-
}
|
|
10987
|
-
return {
|
|
10988
|
-
...propertyIR,
|
|
10989
|
-
docs: [
|
|
10990
|
-
...propertyIR.docs,
|
|
10991
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
10992
|
-
]
|
|
10993
|
-
};
|
|
10983
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
10984
|
+
return propertyIR;
|
|
10994
10985
|
}
|
|
10995
10986
|
}
|
|
10996
10987
|
});
|
|
@@ -11604,17 +11595,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
11604
11595
|
|
|
11605
11596
|
// src/client.ts
|
|
11606
11597
|
var STANDARD_MODULES_OPERATIONS = [
|
|
11607
|
-
{
|
|
11598
|
+
{
|
|
11599
|
+
create: "entity",
|
|
11600
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
11601
|
+
},
|
|
11608
11602
|
{ create: "class", extends: "entity", has: {} },
|
|
11609
|
-
{
|
|
11610
|
-
|
|
11603
|
+
{
|
|
11604
|
+
create: "user",
|
|
11605
|
+
extends: "entity",
|
|
11606
|
+
has: {
|
|
11607
|
+
email: { value: void 0 },
|
|
11608
|
+
firstName: { value: void 0 },
|
|
11609
|
+
lastName: { value: void 0 }
|
|
11610
|
+
}
|
|
11611
|
+
},
|
|
11612
|
+
{
|
|
11613
|
+
create: "company",
|
|
11614
|
+
extends: "entity",
|
|
11615
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
11616
|
+
},
|
|
11611
11617
|
{ create: "string", has: {} },
|
|
11612
11618
|
{ create: "number", has: {} },
|
|
11613
11619
|
{ create: "boolean", has: {} },
|
|
11614
|
-
{
|
|
11620
|
+
{
|
|
11621
|
+
create: "tool_parameter",
|
|
11622
|
+
has: {
|
|
11623
|
+
name: { value: void 0 },
|
|
11624
|
+
type: { value: "string" },
|
|
11625
|
+
description: { value: void 0 },
|
|
11626
|
+
required: { value: false }
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11615
11629
|
];
|
|
11616
11630
|
var BUILTIN_MODULES = {
|
|
11617
|
-
|
|
11631
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
11618
11632
|
};
|
|
11619
11633
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11620
11634
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -11649,7 +11663,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
11649
11663
|
}
|
|
11650
11664
|
function isRetryableRecordObjectsError(error) {
|
|
11651
11665
|
const message = error instanceof Error ? error.message : String(error);
|
|
11652
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11666
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11667
|
+
message
|
|
11668
|
+
);
|
|
11653
11669
|
}
|
|
11654
11670
|
function computeEffectKey2(effect) {
|
|
11655
11671
|
const attachedClass = effect.className?.trim();
|
|
@@ -11726,7 +11742,10 @@ function normalizeUser(user) {
|
|
|
11726
11742
|
};
|
|
11727
11743
|
}
|
|
11728
11744
|
function normalizeEnvironmentData(environment) {
|
|
11729
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11745
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11746
|
+
mode: "pinned",
|
|
11747
|
+
versionId: environment.versionId || environment.buildId
|
|
11748
|
+
});
|
|
11730
11749
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
11731
11750
|
return {
|
|
11732
11751
|
...environment,
|
|
@@ -11820,14 +11839,16 @@ var Environment = class extends Session {
|
|
|
11820
11839
|
const response = await fetch(url, {
|
|
11821
11840
|
...options,
|
|
11822
11841
|
headers: {
|
|
11823
|
-
|
|
11842
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11824
11843
|
"Content-Type": "application/json",
|
|
11825
|
-
|
|
11844
|
+
Connection: "close",
|
|
11826
11845
|
...options.headers
|
|
11827
11846
|
}
|
|
11828
11847
|
});
|
|
11829
11848
|
if (!response.ok) {
|
|
11830
|
-
throw new Error(
|
|
11849
|
+
throw new Error(
|
|
11850
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
11851
|
+
);
|
|
11831
11852
|
}
|
|
11832
11853
|
return response.json();
|
|
11833
11854
|
}
|
|
@@ -11841,9 +11862,12 @@ var Environment = class extends Session {
|
|
|
11841
11862
|
async disconnect() {
|
|
11842
11863
|
let wsNotifiedRuntime = false;
|
|
11843
11864
|
try {
|
|
11844
|
-
const goodbye = await this.rpc(
|
|
11845
|
-
|
|
11846
|
-
|
|
11865
|
+
const goodbye = await this.rpc(
|
|
11866
|
+
"client.goodbye",
|
|
11867
|
+
{
|
|
11868
|
+
timestamp: Date.now()
|
|
11869
|
+
}
|
|
11870
|
+
);
|
|
11847
11871
|
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11848
11872
|
} catch {
|
|
11849
11873
|
wsNotifiedRuntime = false;
|
|
@@ -11857,8 +11881,8 @@ var Environment = class extends Session {
|
|
|
11857
11881
|
method: "POST",
|
|
11858
11882
|
headers: {
|
|
11859
11883
|
"Content-Type": "application/json",
|
|
11860
|
-
|
|
11861
|
-
|
|
11884
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11885
|
+
Connection: "close"
|
|
11862
11886
|
},
|
|
11863
11887
|
body: JSON.stringify({
|
|
11864
11888
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -11871,6 +11895,15 @@ var Environment = class extends Session {
|
|
|
11871
11895
|
}
|
|
11872
11896
|
this.client.disconnect();
|
|
11873
11897
|
}
|
|
11898
|
+
/**
|
|
11899
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
11900
|
+
*
|
|
11901
|
+
* Use this when the caller intends to immediately reattach to the same
|
|
11902
|
+
* session after an unexpected disconnect.
|
|
11903
|
+
*/
|
|
11904
|
+
disconnectTransport() {
|
|
11905
|
+
this.client.disconnect();
|
|
11906
|
+
}
|
|
11874
11907
|
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11875
11908
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11876
11909
|
graphContainerStatus = null;
|
|
@@ -11936,14 +11969,14 @@ var Environment = class extends Session {
|
|
|
11936
11969
|
}
|
|
11937
11970
|
/**
|
|
11938
11971
|
* Execute a GraphQL query against the environment's graph.
|
|
11939
|
-
*
|
|
11972
|
+
*
|
|
11940
11973
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
11941
11974
|
* Authentication is handled automatically using the SDK's API key.
|
|
11942
|
-
*
|
|
11975
|
+
*
|
|
11943
11976
|
* @param query - The GraphQL query string
|
|
11944
11977
|
* @param variables - Optional variables for the query
|
|
11945
11978
|
* @returns The query result data
|
|
11946
|
-
*
|
|
11979
|
+
*
|
|
11947
11980
|
* @example
|
|
11948
11981
|
* ```typescript
|
|
11949
11982
|
* // Read the workspace
|
|
@@ -11951,7 +11984,7 @@ var Environment = class extends Session {
|
|
|
11951
11984
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
11952
11985
|
* );
|
|
11953
11986
|
* console.log(result.data);
|
|
11954
|
-
*
|
|
11987
|
+
*
|
|
11955
11988
|
* // Create a model
|
|
11956
11989
|
* const created = await env.graphql(
|
|
11957
11990
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -11963,7 +11996,7 @@ var Environment = class extends Session {
|
|
|
11963
11996
|
method: "POST",
|
|
11964
11997
|
headers: {
|
|
11965
11998
|
"Content-Type": "application/json",
|
|
11966
|
-
|
|
11999
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
11967
12000
|
},
|
|
11968
12001
|
body: JSON.stringify({
|
|
11969
12002
|
environmentId: this.environmentId,
|
|
@@ -11980,10 +12013,10 @@ var Environment = class extends Session {
|
|
|
11980
12013
|
// ==================== RELATIONSHIP METHODS ====================
|
|
11981
12014
|
/**
|
|
11982
12015
|
* Define a relationship between two model types.
|
|
11983
|
-
*
|
|
12016
|
+
*
|
|
11984
12017
|
* Creates both submodels (if they don't exist) and links them with
|
|
11985
12018
|
* a RelationshipDef node that encodes cardinality.
|
|
11986
|
-
*
|
|
12019
|
+
*
|
|
11987
12020
|
* @example
|
|
11988
12021
|
* ```typescript
|
|
11989
12022
|
* // Author has many Books, Book has one Author
|
|
@@ -12043,10 +12076,10 @@ var Environment = class extends Session {
|
|
|
12043
12076
|
}
|
|
12044
12077
|
/**
|
|
12045
12078
|
* Get all relationships for a model type.
|
|
12046
|
-
*
|
|
12079
|
+
*
|
|
12047
12080
|
* @param modelPath - The model type path (e.g., "author")
|
|
12048
12081
|
* @returns Array of relationships from this model's perspective
|
|
12049
|
-
*
|
|
12082
|
+
*
|
|
12050
12083
|
* @example
|
|
12051
12084
|
* ```typescript
|
|
12052
12085
|
* const rels = await env.getRelationships('author');
|
|
@@ -12079,18 +12112,18 @@ var Environment = class extends Session {
|
|
|
12079
12112
|
}
|
|
12080
12113
|
/**
|
|
12081
12114
|
* Attach a target model to a relationship submodel.
|
|
12082
|
-
*
|
|
12115
|
+
*
|
|
12083
12116
|
* Handles cardinality automatically:
|
|
12084
12117
|
* - "One" side: sets/replaces the reference
|
|
12085
12118
|
* - "Many" side: adds the target to the collection
|
|
12086
|
-
*
|
|
12119
|
+
*
|
|
12087
12120
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
12088
12121
|
* Bidirectional sync is automatic.
|
|
12089
|
-
*
|
|
12122
|
+
*
|
|
12090
12123
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
12091
12124
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
12092
12125
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
12093
|
-
*
|
|
12126
|
+
*
|
|
12094
12127
|
* @example
|
|
12095
12128
|
* ```typescript
|
|
12096
12129
|
* // Attach a book to an author (many side)
|
|
@@ -12117,18 +12150,18 @@ var Environment = class extends Session {
|
|
|
12117
12150
|
}
|
|
12118
12151
|
/**
|
|
12119
12152
|
* Detach a target model from a relationship submodel.
|
|
12120
|
-
*
|
|
12153
|
+
*
|
|
12121
12154
|
* Handles bidirectional cleanup automatically.
|
|
12122
|
-
*
|
|
12155
|
+
*
|
|
12123
12156
|
* @param modelPath - The model instance path
|
|
12124
12157
|
* @param submodelPath - The relationship submodel
|
|
12125
12158
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
12126
|
-
*
|
|
12159
|
+
*
|
|
12127
12160
|
* @example
|
|
12128
12161
|
* ```typescript
|
|
12129
12162
|
* // Detach a specific book
|
|
12130
12163
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
12131
|
-
*
|
|
12164
|
+
*
|
|
12132
12165
|
* // Detach all books
|
|
12133
12166
|
* await env.detach('tolkien', 'books');
|
|
12134
12167
|
* ```
|
|
@@ -12152,11 +12185,11 @@ var Environment = class extends Session {
|
|
|
12152
12185
|
}
|
|
12153
12186
|
/**
|
|
12154
12187
|
* List all related models through a relationship submodel.
|
|
12155
|
-
*
|
|
12188
|
+
*
|
|
12156
12189
|
* @param modelPath - The model instance path
|
|
12157
12190
|
* @param submodelPath - The relationship submodel
|
|
12158
12191
|
* @returns Array of related model references
|
|
12159
|
-
*
|
|
12192
|
+
*
|
|
12160
12193
|
* @example
|
|
12161
12194
|
* ```typescript
|
|
12162
12195
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -12180,14 +12213,14 @@ var Environment = class extends Session {
|
|
|
12180
12213
|
}
|
|
12181
12214
|
/**
|
|
12182
12215
|
* Apply a manifest to the current environment's graph.
|
|
12183
|
-
*
|
|
12216
|
+
*
|
|
12184
12217
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
12185
12218
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
12186
12219
|
* relationships from a declarative manifest.
|
|
12187
|
-
*
|
|
12220
|
+
*
|
|
12188
12221
|
* @param manifest - The manifest content to apply
|
|
12189
12222
|
* @returns Summary of applied operations
|
|
12190
|
-
*
|
|
12223
|
+
*
|
|
12191
12224
|
* @example
|
|
12192
12225
|
* ```typescript
|
|
12193
12226
|
* await environment.applyManifest({
|
|
@@ -12225,12 +12258,16 @@ var Environment = class extends Session {
|
|
|
12225
12258
|
applied++;
|
|
12226
12259
|
} catch (err) {
|
|
12227
12260
|
if (!err.message?.includes("already exists")) {
|
|
12228
|
-
errors.push(
|
|
12261
|
+
errors.push(
|
|
12262
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
12263
|
+
);
|
|
12229
12264
|
}
|
|
12230
12265
|
}
|
|
12231
12266
|
}
|
|
12232
12267
|
} else {
|
|
12233
|
-
errors.push(
|
|
12268
|
+
errors.push(
|
|
12269
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
12270
|
+
);
|
|
12234
12271
|
}
|
|
12235
12272
|
}
|
|
12236
12273
|
}
|
|
@@ -12314,7 +12351,10 @@ var Environment = class extends Session {
|
|
|
12314
12351
|
}
|
|
12315
12352
|
}
|
|
12316
12353
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
12317
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
12354
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
12355
|
+
toolPath,
|
|
12356
|
+
metamodels
|
|
12357
|
+
)) {
|
|
12318
12358
|
await this._runGraphql(mutation.query, mutation.label);
|
|
12319
12359
|
}
|
|
12320
12360
|
}
|
|
@@ -12363,7 +12403,9 @@ var Environment = class extends Session {
|
|
|
12363
12403
|
}
|
|
12364
12404
|
if (eventType.payloadSchema?.properties) {
|
|
12365
12405
|
const fieldSpecs = {};
|
|
12366
|
-
for (const [propName, propSchema] of Object.entries(
|
|
12406
|
+
for (const [propName, propSchema] of Object.entries(
|
|
12407
|
+
eventType.payloadSchema.properties
|
|
12408
|
+
)) {
|
|
12367
12409
|
const schema = propSchema;
|
|
12368
12410
|
fieldSpecs[propName] = {
|
|
12369
12411
|
type: schema.type ?? "string",
|
|
@@ -12646,7 +12688,9 @@ var Environment = class extends Session {
|
|
|
12646
12688
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12647
12689
|
await Promise.all(
|
|
12648
12690
|
wave.map(async (plan) => {
|
|
12649
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12691
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12692
|
+
plan.slice
|
|
12693
|
+
);
|
|
12650
12694
|
if (items.length !== plan.slice.length) {
|
|
12651
12695
|
throw new Error(
|
|
12652
12696
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -12676,13 +12720,10 @@ var Environment = class extends Session {
|
|
|
12676
12720
|
let lastError;
|
|
12677
12721
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12678
12722
|
try {
|
|
12679
|
-
const response = await this.controlPlaneRequest(
|
|
12680
|
-
|
|
12681
|
-
{
|
|
12682
|
-
|
|
12683
|
-
body: JSON.stringify({ records: chunk })
|
|
12684
|
-
}
|
|
12685
|
-
);
|
|
12723
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
12724
|
+
method: "POST",
|
|
12725
|
+
body: JSON.stringify({ records: chunk })
|
|
12726
|
+
});
|
|
12686
12727
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
12687
12728
|
return { items, durationMs: Date.now() - wallStart };
|
|
12688
12729
|
} catch (error) {
|
|
@@ -12745,7 +12786,9 @@ var Environment = class extends Session {
|
|
|
12745
12786
|
* Fetch a single record import by id.
|
|
12746
12787
|
*/
|
|
12747
12788
|
async getRecordImport(importId) {
|
|
12748
|
-
return this.controlPlaneRequest(
|
|
12789
|
+
return this.controlPlaneRequest(
|
|
12790
|
+
`/control/record-imports/${importId}`
|
|
12791
|
+
);
|
|
12749
12792
|
}
|
|
12750
12793
|
/**
|
|
12751
12794
|
* Cancel a queued/background record import.
|
|
@@ -12812,7 +12855,9 @@ var Granular = class _Granular {
|
|
|
12812
12855
|
constructor(options) {
|
|
12813
12856
|
const auth = options.token ?? options.apiKey;
|
|
12814
12857
|
if (!auth) {
|
|
12815
|
-
throw new Error(
|
|
12858
|
+
throw new Error(
|
|
12859
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
12860
|
+
);
|
|
12816
12861
|
}
|
|
12817
12862
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
12818
12863
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -12824,10 +12869,10 @@ var Granular = class _Granular {
|
|
|
12824
12869
|
}
|
|
12825
12870
|
/**
|
|
12826
12871
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12827
|
-
*
|
|
12872
|
+
*
|
|
12828
12873
|
* @param options - User options
|
|
12829
12874
|
* @returns The recorded user with both `userId` and `granularId`
|
|
12830
|
-
*
|
|
12875
|
+
*
|
|
12831
12876
|
* @example
|
|
12832
12877
|
* ```typescript
|
|
12833
12878
|
* const user = await granular.recordUser({
|
|
@@ -12838,14 +12883,16 @@ var Granular = class _Granular {
|
|
|
12838
12883
|
* ```
|
|
12839
12884
|
*/
|
|
12840
12885
|
async recordUser(options) {
|
|
12841
|
-
const subject = normalizeSubject(
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12886
|
+
const subject = normalizeSubject(
|
|
12887
|
+
await this.request("/control/subjects", {
|
|
12888
|
+
method: "POST",
|
|
12889
|
+
body: JSON.stringify({
|
|
12890
|
+
identityId: options.userId,
|
|
12891
|
+
name: options.name,
|
|
12892
|
+
email: options.email
|
|
12893
|
+
})
|
|
12847
12894
|
})
|
|
12848
|
-
|
|
12895
|
+
);
|
|
12849
12896
|
return normalizeUser({
|
|
12850
12897
|
granularId: subject.granularId,
|
|
12851
12898
|
userId: options.userId,
|
|
@@ -12892,18 +12939,20 @@ var Granular = class _Granular {
|
|
|
12892
12939
|
permissions: options.permissions || []
|
|
12893
12940
|
};
|
|
12894
12941
|
}
|
|
12895
|
-
throw new Error(
|
|
12942
|
+
throw new Error(
|
|
12943
|
+
"connect() requires either userId, granularId, or a user object returned by recordUser()."
|
|
12944
|
+
);
|
|
12896
12945
|
}
|
|
12897
12946
|
/**
|
|
12898
12947
|
* Connect to an ontology environment and establish a real-time session.
|
|
12899
|
-
*
|
|
12948
|
+
*
|
|
12900
12949
|
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12901
12950
|
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12902
12951
|
* the sandbox registry automatically.
|
|
12903
|
-
*
|
|
12952
|
+
*
|
|
12904
12953
|
* @param options - Connection options
|
|
12905
12954
|
* @returns An active environment session
|
|
12906
|
-
*
|
|
12955
|
+
*
|
|
12907
12956
|
* @example
|
|
12908
12957
|
* ```typescript
|
|
12909
12958
|
* const environment = await granular.connect({
|
|
@@ -12912,23 +12961,23 @@ var Granular = class _Granular {
|
|
|
12912
12961
|
* userId: 'user_123',
|
|
12913
12962
|
* permissions: ['agent'],
|
|
12914
12963
|
* });
|
|
12915
|
-
*
|
|
12964
|
+
*
|
|
12916
12965
|
* await granular.registerEffect('my-sandbox', {
|
|
12917
12966
|
* name: 'greet',
|
|
12918
12967
|
* description: 'Say hello',
|
|
12919
12968
|
* inputSchema: { type: 'object', properties: {} },
|
|
12920
12969
|
* handler: async () => 'Hello!',
|
|
12921
12970
|
* });
|
|
12922
|
-
*
|
|
12971
|
+
*
|
|
12923
12972
|
* // Submit job
|
|
12924
12973
|
* const job = await environment.submitJob(`
|
|
12925
12974
|
* import { tools } from './sandbox-tools';
|
|
12926
12975
|
* return await tools.greet({});
|
|
12927
12976
|
* `);
|
|
12928
|
-
*
|
|
12977
|
+
*
|
|
12929
12978
|
* console.log(await job.result); // 'Hello!'
|
|
12930
12979
|
* ```
|
|
12931
|
-
|
|
12980
|
+
*/
|
|
12932
12981
|
async connect(options) {
|
|
12933
12982
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
12934
12983
|
const ontology = options.ontology;
|
|
@@ -12943,8 +12992,15 @@ var Granular = class _Granular {
|
|
|
12943
12992
|
const user = await this.resolveConnectUser(options);
|
|
12944
12993
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12945
12994
|
for (const profileName of user.permissions) {
|
|
12946
|
-
const profileId = await this.ensurePermissionProfile(
|
|
12947
|
-
|
|
12995
|
+
const profileId = await this.ensurePermissionProfile(
|
|
12996
|
+
sandbox.sandboxId,
|
|
12997
|
+
profileName
|
|
12998
|
+
);
|
|
12999
|
+
await this.ensureAssignment(
|
|
13000
|
+
user.granularId,
|
|
13001
|
+
sandbox.sandboxId,
|
|
13002
|
+
profileId
|
|
13003
|
+
);
|
|
12948
13004
|
}
|
|
12949
13005
|
const envData = await this.environments.create(sandbox.sandboxId, {
|
|
12950
13006
|
subjectId: user.granularId,
|
|
@@ -12996,7 +13052,9 @@ var Granular = class _Granular {
|
|
|
12996
13052
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
12997
13053
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
12998
13054
|
summary: row.summary != null ? String(row.summary) : null,
|
|
12999
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13055
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13056
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
13057
|
+
) : null,
|
|
13000
13058
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
13001
13059
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
13002
13060
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -13086,7 +13144,13 @@ var Granular = class _Granular {
|
|
|
13086
13144
|
});
|
|
13087
13145
|
await client.connect();
|
|
13088
13146
|
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13089
|
-
const environment = new Environment(
|
|
13147
|
+
const environment = new Environment(
|
|
13148
|
+
client,
|
|
13149
|
+
envData,
|
|
13150
|
+
clientId,
|
|
13151
|
+
this.apiKey,
|
|
13152
|
+
graphqlEndpoint
|
|
13153
|
+
);
|
|
13090
13154
|
await environment.hello();
|
|
13091
13155
|
return environment;
|
|
13092
13156
|
}
|
|
@@ -13120,14 +13184,18 @@ var Granular = class _Granular {
|
|
|
13120
13184
|
};
|
|
13121
13185
|
}
|
|
13122
13186
|
async publishSandboxEffectCatalog(host) {
|
|
13123
|
-
const effects = Array.from(
|
|
13124
|
-
(
|
|
13125
|
-
);
|
|
13126
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13187
|
+
const effects = Array.from(
|
|
13188
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
13189
|
+
).map((effect) => this.serializeEffect(effect));
|
|
13190
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13191
|
+
effects
|
|
13192
|
+
});
|
|
13127
13193
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
13128
13194
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
13129
13195
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
13130
|
-
const detail = rejected.map(
|
|
13196
|
+
const detail = rejected.map(
|
|
13197
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
13198
|
+
).join("; ");
|
|
13131
13199
|
throw new Error(
|
|
13132
13200
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
13133
13201
|
);
|
|
@@ -13161,13 +13229,15 @@ var Granular = class _Granular {
|
|
|
13161
13229
|
disconnectError
|
|
13162
13230
|
);
|
|
13163
13231
|
}
|
|
13164
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13232
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13233
|
+
(reconnectError) => {
|
|
13234
|
+
console.error(
|
|
13235
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
13236
|
+
reconnectError
|
|
13237
|
+
);
|
|
13238
|
+
console.error("[Granular] Original heartbeat failure:", error);
|
|
13239
|
+
}
|
|
13240
|
+
);
|
|
13171
13241
|
}
|
|
13172
13242
|
startEffectHostHeartbeat(host) {
|
|
13173
13243
|
if (host.heartbeatTimer) {
|
|
@@ -13188,9 +13258,15 @@ var Granular = class _Granular {
|
|
|
13188
13258
|
host.heartbeatInFlight = false;
|
|
13189
13259
|
});
|
|
13190
13260
|
};
|
|
13191
|
-
sendHeartbeat(
|
|
13261
|
+
sendHeartbeat(
|
|
13262
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
13263
|
+
false
|
|
13264
|
+
);
|
|
13192
13265
|
host.heartbeatTimer = setInterval(() => {
|
|
13193
|
-
sendHeartbeat(
|
|
13266
|
+
sendHeartbeat(
|
|
13267
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
13268
|
+
true
|
|
13269
|
+
);
|
|
13194
13270
|
}, 1e4);
|
|
13195
13271
|
}
|
|
13196
13272
|
stopEffectHostHeartbeat(host) {
|
|
@@ -13222,7 +13298,12 @@ var Granular = class _Granular {
|
|
|
13222
13298
|
const effectClientId = crypto.randomUUID();
|
|
13223
13299
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
13224
13300
|
const wsClient = new WSClient({
|
|
13225
|
-
url: buildEffectHostUrl(
|
|
13301
|
+
url: buildEffectHostUrl(
|
|
13302
|
+
this.apiUrl,
|
|
13303
|
+
sandboxId,
|
|
13304
|
+
effectClientId,
|
|
13305
|
+
clientId
|
|
13306
|
+
),
|
|
13226
13307
|
sessionId: `effect-host:${effectClientId}`,
|
|
13227
13308
|
token: this.apiKey,
|
|
13228
13309
|
tokenProvider: this.tokenProvider,
|
|
@@ -13241,7 +13322,10 @@ var Granular = class _Granular {
|
|
|
13241
13322
|
};
|
|
13242
13323
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
13243
13324
|
const request = params;
|
|
13244
|
-
return invokeRegisteredEffect(
|
|
13325
|
+
return invokeRegisteredEffect(
|
|
13326
|
+
this.getSandboxEffectMap(sandboxId),
|
|
13327
|
+
request
|
|
13328
|
+
);
|
|
13245
13329
|
});
|
|
13246
13330
|
wsClient.on("open", () => {
|
|
13247
13331
|
void this.synchronizeEffectHost(host).catch((error) => {
|
|
@@ -13289,7 +13373,7 @@ var Granular = class _Granular {
|
|
|
13289
13373
|
}
|
|
13290
13374
|
/**
|
|
13291
13375
|
* Register multiple effects (tools) for a specific sandbox.
|
|
13292
|
-
*
|
|
13376
|
+
*
|
|
13293
13377
|
* batch version of `registerEffect`.
|
|
13294
13378
|
*/
|
|
13295
13379
|
async registerEffects(sandboxNameOrId, effects) {
|
|
@@ -13303,7 +13387,7 @@ var Granular = class _Granular {
|
|
|
13303
13387
|
}
|
|
13304
13388
|
/**
|
|
13305
13389
|
* Unregister an effect from a sandbox.
|
|
13306
|
-
*
|
|
13390
|
+
*
|
|
13307
13391
|
* Removes it from the local sandbox registry and updates the
|
|
13308
13392
|
* sandbox-scoped live catalog.
|
|
13309
13393
|
*/
|
|
@@ -13402,27 +13486,31 @@ var Granular = class _Granular {
|
|
|
13402
13486
|
const assignments = await this.request(
|
|
13403
13487
|
`/control/subjects/${subjectId}/assignments`
|
|
13404
13488
|
);
|
|
13405
|
-
const existing = assignments.items.find(
|
|
13406
|
-
(a) => a.sandboxId === sandboxId
|
|
13407
|
-
);
|
|
13489
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
13408
13490
|
if (existing) {
|
|
13409
13491
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
13410
13492
|
return;
|
|
13411
13493
|
}
|
|
13412
|
-
await this.request(
|
|
13413
|
-
|
|
13414
|
-
|
|
13494
|
+
await this.request(
|
|
13495
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
13496
|
+
{
|
|
13497
|
+
method: "DELETE"
|
|
13498
|
+
}
|
|
13499
|
+
);
|
|
13415
13500
|
}
|
|
13416
13501
|
} catch {
|
|
13417
13502
|
}
|
|
13418
|
-
await this.request(
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13503
|
+
await this.request(
|
|
13504
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
13505
|
+
{
|
|
13506
|
+
method: "POST",
|
|
13507
|
+
body: JSON.stringify({
|
|
13508
|
+
sandboxId,
|
|
13509
|
+
subjectId,
|
|
13510
|
+
permissionProfileId
|
|
13511
|
+
})
|
|
13512
|
+
}
|
|
13513
|
+
);
|
|
13426
13514
|
}
|
|
13427
13515
|
/**
|
|
13428
13516
|
* Sandbox management API
|
|
@@ -13502,23 +13590,33 @@ var Granular = class _Granular {
|
|
|
13502
13590
|
},
|
|
13503
13591
|
get: async (environmentId) => {
|
|
13504
13592
|
return normalizeEnvironmentData(
|
|
13505
|
-
await this.request(
|
|
13593
|
+
await this.request(
|
|
13594
|
+
`/control/environments/${environmentId}`
|
|
13595
|
+
)
|
|
13506
13596
|
);
|
|
13507
13597
|
},
|
|
13508
13598
|
create: async (sandboxId, data) => {
|
|
13509
13599
|
const environmentName = data.environment || data.envName;
|
|
13510
|
-
return normalizeEnvironmentData(
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13600
|
+
return normalizeEnvironmentData(
|
|
13601
|
+
await this.request(
|
|
13602
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
13603
|
+
{
|
|
13604
|
+
method: "POST",
|
|
13605
|
+
body: JSON.stringify({
|
|
13606
|
+
...data,
|
|
13607
|
+
envName: environmentName
|
|
13608
|
+
})
|
|
13609
|
+
}
|
|
13610
|
+
)
|
|
13611
|
+
);
|
|
13517
13612
|
},
|
|
13518
13613
|
delete: async (environmentId) => {
|
|
13519
|
-
return this.request(
|
|
13520
|
-
|
|
13521
|
-
|
|
13614
|
+
return this.request(
|
|
13615
|
+
`/control/environments/${environmentId}`,
|
|
13616
|
+
{
|
|
13617
|
+
method: "DELETE"
|
|
13618
|
+
}
|
|
13619
|
+
);
|
|
13522
13620
|
}
|
|
13523
13621
|
};
|
|
13524
13622
|
}
|
|
@@ -13538,10 +13636,13 @@ var Granular = class _Granular {
|
|
|
13538
13636
|
}
|
|
13539
13637
|
if (params.since) query.set("since", params.since.toISOString());
|
|
13540
13638
|
if (params.until) query.set("until", params.until.toISOString());
|
|
13541
|
-
if (params.isAcked !== void 0)
|
|
13639
|
+
if (params.isAcked !== void 0)
|
|
13640
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
13542
13641
|
if (params.limit) query.set("limit", String(params.limit));
|
|
13543
13642
|
if (params.offset) query.set("offset", String(params.offset));
|
|
13544
|
-
const result = await this.request(
|
|
13643
|
+
const result = await this.request(
|
|
13644
|
+
`/control/stream-events?${query.toString()}`
|
|
13645
|
+
);
|
|
13545
13646
|
return (result.items || []).map((row) => ({
|
|
13546
13647
|
eventId: row.event_id,
|
|
13547
13648
|
streamName: row.stream_name,
|
|
@@ -13572,7 +13673,9 @@ var Granular = class _Granular {
|
|
|
13572
13673
|
since: cursor,
|
|
13573
13674
|
limit: 100
|
|
13574
13675
|
});
|
|
13575
|
-
const orderedEvents = [...events].sort(
|
|
13676
|
+
const orderedEvents = [...events].sort(
|
|
13677
|
+
(a, b) => a.createdAt - b.createdAt
|
|
13678
|
+
);
|
|
13576
13679
|
for (const event of orderedEvents) {
|
|
13577
13680
|
if (seenEventIds.has(event.eventId)) {
|
|
13578
13681
|
continue;
|
|
@@ -13585,15 +13688,19 @@ var Granular = class _Granular {
|
|
|
13585
13688
|
params.onEvent(event);
|
|
13586
13689
|
}
|
|
13587
13690
|
} catch (err) {
|
|
13588
|
-
params.onError?.(
|
|
13691
|
+
params.onError?.(
|
|
13692
|
+
err instanceof Error ? err : new Error(String(err))
|
|
13693
|
+
);
|
|
13589
13694
|
}
|
|
13590
13695
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13591
13696
|
}
|
|
13592
13697
|
};
|
|
13593
13698
|
poll();
|
|
13594
|
-
return {
|
|
13595
|
-
|
|
13596
|
-
|
|
13699
|
+
return {
|
|
13700
|
+
unsubscribe: () => {
|
|
13701
|
+
running = false;
|
|
13702
|
+
}
|
|
13703
|
+
};
|
|
13597
13704
|
},
|
|
13598
13705
|
ack: async (eventId) => {
|
|
13599
13706
|
await this.request("/control/stream-events/ack", {
|
|
@@ -13611,7 +13718,9 @@ var Granular = class _Granular {
|
|
|
13611
13718
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13612
13719
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13613
13720
|
if (params.environment) query.set("environmentId", params.environment);
|
|
13614
|
-
const result = await this.request(
|
|
13721
|
+
const result = await this.request(
|
|
13722
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
13723
|
+
);
|
|
13615
13724
|
return (result.items || []).map((row) => ({
|
|
13616
13725
|
streamName: row.stream_name,
|
|
13617
13726
|
eventType: row.event_type,
|
|
@@ -13629,10 +13738,14 @@ var Granular = class _Granular {
|
|
|
13629
13738
|
get subjects() {
|
|
13630
13739
|
return {
|
|
13631
13740
|
get: async (subjectId) => {
|
|
13632
|
-
return normalizeSubject(
|
|
13741
|
+
return normalizeSubject(
|
|
13742
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
13743
|
+
);
|
|
13633
13744
|
},
|
|
13634
13745
|
listAssignments: async (subjectId) => {
|
|
13635
|
-
return this.request(
|
|
13746
|
+
return this.request(
|
|
13747
|
+
`/control/subjects/${subjectId}/assignments`
|
|
13748
|
+
);
|
|
13636
13749
|
}
|
|
13637
13750
|
};
|
|
13638
13751
|
}
|
|
@@ -13642,24 +13755,31 @@ var Granular = class _Granular {
|
|
|
13642
13755
|
get users() {
|
|
13643
13756
|
return {
|
|
13644
13757
|
create: async (data) => {
|
|
13645
|
-
return normalizeSubject(
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13758
|
+
return normalizeSubject(
|
|
13759
|
+
await this.request("/control/subjects", {
|
|
13760
|
+
method: "POST",
|
|
13761
|
+
body: JSON.stringify({
|
|
13762
|
+
identityId: data.id,
|
|
13763
|
+
name: data.name,
|
|
13764
|
+
email: data.email
|
|
13765
|
+
})
|
|
13651
13766
|
})
|
|
13652
|
-
|
|
13767
|
+
);
|
|
13653
13768
|
},
|
|
13654
13769
|
get: async (id) => {
|
|
13655
|
-
return normalizeSubject(
|
|
13770
|
+
return normalizeSubject(
|
|
13771
|
+
await this.request(`/control/subjects/${id}`)
|
|
13772
|
+
);
|
|
13656
13773
|
}
|
|
13657
13774
|
};
|
|
13658
13775
|
}
|
|
13659
13776
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
13660
13777
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13661
|
-
const result = await this.request(
|
|
13662
|
-
|
|
13778
|
+
const result = await this.request(
|
|
13779
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
13780
|
+
);
|
|
13781
|
+
if (result.items.length === 0)
|
|
13782
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13663
13783
|
return result.items[0].sandboxId;
|
|
13664
13784
|
}
|
|
13665
13785
|
/**
|
|
@@ -13674,9 +13794,9 @@ var Granular = class _Granular {
|
|
|
13674
13794
|
const response = await fetch(url, {
|
|
13675
13795
|
...options,
|
|
13676
13796
|
headers: {
|
|
13677
|
-
|
|
13797
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13678
13798
|
"Content-Type": "application/json",
|
|
13679
|
-
|
|
13799
|
+
Connection: "close",
|
|
13680
13800
|
...options.headers
|
|
13681
13801
|
}
|
|
13682
13802
|
});
|
|
@@ -13687,7 +13807,11 @@ var Granular = class _Granular {
|
|
|
13687
13807
|
return response.json();
|
|
13688
13808
|
}
|
|
13689
13809
|
const errorText = await response.text();
|
|
13690
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
13810
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
13811
|
+
response.status,
|
|
13812
|
+
errorText,
|
|
13813
|
+
url
|
|
13814
|
+
);
|
|
13691
13815
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13692
13816
|
if (this.debugHttp) {
|
|
13693
13817
|
console.warn(
|
|
@@ -13826,21 +13950,6 @@ function reviewGeneratedJobCode(code) {
|
|
|
13826
13950
|
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."
|
|
13827
13951
|
});
|
|
13828
13952
|
}
|
|
13829
|
-
const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
|
|
13830
|
-
for (const call of askUserCalls) {
|
|
13831
|
-
const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
|
|
13832
|
-
const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
|
|
13833
|
-
const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
|
|
13834
|
-
const includesShortlistOptions = /options\s*:\s*\[/.test(call);
|
|
13835
|
-
if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
|
|
13836
|
-
issues.push({
|
|
13837
|
-
code: "disambiguation_requires_choice",
|
|
13838
|
-
severity: "error",
|
|
13839
|
-
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."
|
|
13840
|
-
});
|
|
13841
|
-
break;
|
|
13842
|
-
}
|
|
13843
|
-
}
|
|
13844
13953
|
}
|
|
13845
13954
|
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
13846
13955
|
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
@@ -13886,6 +13995,9 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
13886
13995
|
entryPaths: uniqueStrings(entryPaths, 8)
|
|
13887
13996
|
};
|
|
13888
13997
|
}
|
|
13998
|
+
function normalizeActionSummaryForPrompt(line) {
|
|
13999
|
+
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
14000
|
+
}
|
|
13889
14001
|
function getCurrentClosureId(liveDoc) {
|
|
13890
14002
|
const loop = asRecord2(liveDoc?.loop);
|
|
13891
14003
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -14098,7 +14210,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
14098
14210
|
variableNames: uniqueStrings(variableNames, 4),
|
|
14099
14211
|
listNames: uniqueStrings(listNames, 4),
|
|
14100
14212
|
entryPaths: uniqueStrings(entryPaths, 6),
|
|
14101
|
-
recentActionSummary: uniqueStrings(actionSummaryLines, 8)
|
|
14213
|
+
recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
|
|
14214
|
+
normalizeActionSummaryForPrompt
|
|
14215
|
+
)
|
|
14102
14216
|
};
|
|
14103
14217
|
}
|
|
14104
14218
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
@@ -14490,17 +14604,14 @@ ${resultPreview}` : null
|
|
|
14490
14604
|
].filter(Boolean).join("\n\n");
|
|
14491
14605
|
}
|
|
14492
14606
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
14493
|
-
return domainDocumentation?.trim() || "No domain
|
|
14607
|
+
return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
|
|
14494
14608
|
}
|
|
14495
14609
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
14496
14610
|
if (!sessionContext) return "No session metadata available.";
|
|
14497
14611
|
const rows = [
|
|
14498
14612
|
["sandboxId", sessionContext.sandboxId],
|
|
14499
14613
|
["environmentId", sessionContext.environmentId],
|
|
14500
|
-
["
|
|
14501
|
-
["granularId", sessionContext.granularId],
|
|
14502
|
-
["userName", sessionContext.userName],
|
|
14503
|
-
["domainRevision", sessionContext.domainRevision]
|
|
14614
|
+
["userName", sessionContext.userName]
|
|
14504
14615
|
];
|
|
14505
14616
|
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
14506
14617
|
if (activeRows.length === 0) return "No session metadata available.";
|
|
@@ -14509,6 +14620,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
|
|
|
14509
14620
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
14510
14621
|
return heapSummary?.trim() || "Heap is empty for this session.";
|
|
14511
14622
|
}
|
|
14623
|
+
function buildGranularAgentReferentBlock(referentSummary) {
|
|
14624
|
+
return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
|
|
14625
|
+
}
|
|
14512
14626
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
14513
14627
|
return loopSummary?.trim() || "No active loop state recorded for this session.";
|
|
14514
14628
|
}
|
|
@@ -14532,7 +14646,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
14532
14646
|
(tool) => Boolean(tool.className && !tool.static)
|
|
14533
14647
|
);
|
|
14534
14648
|
const lines = [
|
|
14535
|
-
"Treat this block as the planning map. Use DOMAIN
|
|
14649
|
+
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
14536
14650
|
];
|
|
14537
14651
|
const appendGroup = (title, group) => {
|
|
14538
14652
|
lines.push(`- ${title}:`);
|
|
@@ -14580,7 +14694,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
|
14580
14694
|
if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
|
|
14581
14695
|
lines.push("latestActionSummary:");
|
|
14582
14696
|
for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
|
|
14583
|
-
|
|
14697
|
+
const normalizedLine = normalizeActionSummaryForPrompt(line);
|
|
14698
|
+
lines.push(
|
|
14699
|
+
normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
|
|
14700
|
+
);
|
|
14584
14701
|
}
|
|
14585
14702
|
}
|
|
14586
14703
|
if (checkpoint.latestJobResult?.trim()) {
|
|
@@ -14596,9 +14713,10 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
14596
14713
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
14597
14714
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
14598
14715
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
14716
|
+
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
14599
14717
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
14600
14718
|
return `You are an AI assistant for a live Granular session.
|
|
14601
|
-
You can help the user understand the domain, answer questions, or generate and execute
|
|
14719
|
+
You can help the user understand the domain, answer questions, or generate and execute code against the live session.
|
|
14602
14720
|
Your tone must be natural and human-like.
|
|
14603
14721
|
|
|
14604
14722
|
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.
|
|
@@ -14607,6 +14725,8 @@ When you call \`execute_code\`, additional assistant text must be either:
|
|
|
14607
14725
|
- a brief summary of the actions the generated code will perform.
|
|
14608
14726
|
Do not include any other kind of commentary when calling \`execute_code\`.
|
|
14609
14727
|
- 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(...)\`.
|
|
14728
|
+
- 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.
|
|
14729
|
+
- 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.
|
|
14610
14730
|
- 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.
|
|
14611
14731
|
|
|
14612
14732
|
\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
|
|
@@ -14626,6 +14746,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
|
|
|
14626
14746
|
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
14627
14747
|
- If you need clarification, ask in everyday language.
|
|
14628
14748
|
- 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.
|
|
14749
|
+
- 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.
|
|
14629
14750
|
- Keep replies concise and clear.
|
|
14630
14751
|
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
14631
14752
|
|
|
@@ -14635,9 +14756,9 @@ ${sessionBlock}
|
|
|
14635
14756
|
\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
|
|
14636
14757
|
${toolBlock}
|
|
14637
14758
|
|
|
14638
|
-
\u2500\u2500\u2500 DOMAIN
|
|
14759
|
+
\u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
|
|
14639
14760
|
Import classes and effect functions from \`./sandbox-tools\` in generated code.
|
|
14640
|
-
|
|
14761
|
+
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
|
|
14641
14762
|
|
|
14642
14763
|
${domainBlock}
|
|
14643
14764
|
|
|
@@ -14647,6 +14768,9 @@ ${checkpointBlock}
|
|
|
14647
14768
|
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
14648
14769
|
${workflowBlock}
|
|
14649
14770
|
|
|
14771
|
+
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
14772
|
+
${referentBlock}
|
|
14773
|
+
|
|
14650
14774
|
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
14651
14775
|
${heapBlock}
|
|
14652
14776
|
|
|
@@ -14654,109 +14778,54 @@ ${heapBlock}
|
|
|
14654
14778
|
${loopBlock}
|
|
14655
14779
|
|
|
14656
14780
|
\u2500\u2500\u2500 LOOP PLAYBOOK \u2500\u2500\u2500
|
|
14657
|
-
-
|
|
14658
|
-
-
|
|
14659
|
-
-
|
|
14660
|
-
-
|
|
14661
|
-
-
|
|
14662
|
-
-
|
|
14663
|
-
- If
|
|
14664
|
-
-
|
|
14665
|
-
-
|
|
14666
|
-
-
|
|
14667
|
-
-
|
|
14668
|
-
-
|
|
14669
|
-
-
|
|
14670
|
-
-
|
|
14671
|
-
- Use \`loop.
|
|
14672
|
-
-
|
|
14673
|
-
-
|
|
14674
|
-
- If
|
|
14675
|
-
- 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.
|
|
14676
|
-
- 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.
|
|
14677
|
-
- 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.
|
|
14678
|
-
- 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.
|
|
14679
|
-
- 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.
|
|
14680
|
-
- 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.
|
|
14681
|
-
- Never ask for approval in plain text when \`loop.confirm(...)\` is available. Use \`loop.confirm(...)\` for consequential approval.
|
|
14682
|
-
- When the correct next step is a loop helper action, generate code and call that helper. Do not replace it with a conversational reply.
|
|
14683
|
-
- If you ask the user a new question in the current job, do not also call \`loop.close_loop(...)\` in that same job.
|
|
14684
|
-
- 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.
|
|
14685
|
-
- It is valid to branch on the value returned by \`await loop.ask_user(...)\` or \`await loop.confirm(...)\` after the job resumes.
|
|
14686
|
-
- 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".
|
|
14687
|
-
- 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."
|
|
14688
|
-
- 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.
|
|
14689
|
-
- If the user says stop, enough, or no further action, close the loop and end cleanly without asking another question.
|
|
14690
|
-
- If one clear item is already selected and the next step matters, prefer \`loop.confirm(...)\` over another exploratory question.
|
|
14691
|
-
- If one clear item is already selected and the only missing input is approval to proceed, use \`loop.confirm(...)\` rather than \`loop.ask_user(...)\`.
|
|
14692
|
-
- 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.
|
|
14693
|
-
- 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.
|
|
14694
|
-
- 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.
|
|
14695
|
-
- 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(...)\`.
|
|
14696
|
-
- 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.
|
|
14697
|
-
- Once you have one solid recommendation, prefer summarizing it and asking for approval over gathering more optional preferences.
|
|
14698
|
-
- Prefer asking the user for the next missing input over fetching extra related data they did not ask for yet.
|
|
14699
|
-
- Avoid serial menus. After one clarifying choice, prefer acting on it, asking one short text question, or confirming rather than opening another menu.
|
|
14700
|
-
- 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.
|
|
14701
|
-
- Call \`loop.close_loop(...)\` before stopping whenever the current workflow is completed, canceled, or clearly blocked.
|
|
14781
|
+
- 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.
|
|
14782
|
+
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
14783
|
+
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
14784
|
+
- 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.
|
|
14785
|
+
- 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.
|
|
14786
|
+
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
14787
|
+
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
14788
|
+
- 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.
|
|
14789
|
+
- 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.
|
|
14790
|
+
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
14791
|
+
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
14792
|
+
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
14793
|
+
- 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.
|
|
14794
|
+
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
14795
|
+
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
14796
|
+
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
14797
|
+
- 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.
|
|
14798
|
+
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
14702
14799
|
|
|
14703
14800
|
\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
|
|
14704
14801
|
- Import from \`./sandbox-tools\`.
|
|
14705
|
-
- If you use \`heap\`, \`loop\`, \`agent_text_message\`,
|
|
14802
|
+
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
|
|
14706
14803
|
- Write top-level executable code with \`await\` at top level.
|
|
14707
|
-
- The generated job body must be plain runnable JavaScript.
|
|
14708
|
-
-
|
|
14709
|
-
-
|
|
14710
|
-
-
|
|
14711
|
-
-
|
|
14712
|
-
-
|
|
14713
|
-
-
|
|
14714
|
-
-
|
|
14715
|
-
-
|
|
14716
|
-
-
|
|
14717
|
-
-
|
|
14718
|
-
-
|
|
14719
|
-
-
|
|
14720
|
-
-
|
|
14721
|
-
-
|
|
14722
|
-
-
|
|
14723
|
-
-
|
|
14724
|
-
-
|
|
14725
|
-
-
|
|
14726
|
-
-
|
|
14727
|
-
-
|
|
14728
|
-
-
|
|
14729
|
-
-
|
|
14730
|
-
-
|
|
14731
|
-
-
|
|
14732
|
-
- 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.
|
|
14733
|
-
- When reading heap values, prefer generated generic typings such as \`await heap.getVar<Book[]>("my_books")\` or \`await heap.getVar<Book>("selected_book")\`.
|
|
14734
|
-
- 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")\`.
|
|
14735
|
-
- Use the injected \`loop\` helpers when you need to manage the workflow itself:
|
|
14736
|
-
\`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`,
|
|
14737
|
-
\`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
|
|
14738
|
-
- Loop helper semantics:
|
|
14739
|
-
- \`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\`.
|
|
14740
|
-
- \`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\`.
|
|
14741
|
-
- \`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.
|
|
14742
|
-
- \`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.
|
|
14743
|
-
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short task list that later jobs can continue and finish.
|
|
14744
|
-
- \`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.
|
|
14745
|
-
- Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
|
|
14746
|
-
- Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
|
|
14747
|
-
- 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(...)\`.
|
|
14748
|
-
- Use \`agent_text_message("...")\` for all user-visible text shown in the UI.
|
|
14749
|
-
- Use \`agent_heap_objects(...)\` only when you want the UI to render heap-backed records or lists.
|
|
14750
|
-
- If you want to show both text and records, call \`agent_text_message(...)\` and \`agent_heap_objects(...)\` separately in whatever order fits the interaction.
|
|
14751
|
-
- \`agent_text_message(...)\` should be used with a plain text string in normal generated code.
|
|
14752
|
-
- \`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(...)\`.
|
|
14753
|
-
- 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.
|
|
14754
|
-
- Do not assume heap changes will be displayed automatically. If records should appear in the UI, you must call \`agent_heap_objects(...)\`.
|
|
14755
|
-
- 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.
|
|
14756
|
-
- Do not rely on the final return value for user-visible output. A plain return value is not considered a displayed UI answer.
|
|
14757
|
-
- \`agent_message(...)\` remains available as a legacy compatibility alias, but prefer \`agent_text_message(...)\` and \`agent_heap_objects(...)\` in new code.
|
|
14758
|
-
- Do not return bare structured JSON, low-level diagnostics, or database-shaped payloads as the final answer unless the user explicitly asks for them.
|
|
14759
|
-
- 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.
|
|
14804
|
+
- The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
|
|
14805
|
+
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
|
|
14806
|
+
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
|
|
14807
|
+
- 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.
|
|
14808
|
+
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
|
|
14809
|
+
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
|
|
14810
|
+
- 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.
|
|
14811
|
+
- 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.
|
|
14812
|
+
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
|
|
14813
|
+
- 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(...)\`.
|
|
14814
|
+
- 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.
|
|
14815
|
+
- Call instance methods on instances, static methods on classes, and global effects by name.
|
|
14816
|
+
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
|
|
14817
|
+
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
|
|
14818
|
+
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
|
|
14819
|
+
- 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.
|
|
14820
|
+
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
|
|
14821
|
+
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
|
|
14822
|
+
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
|
|
14823
|
+
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
|
|
14824
|
+
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
14825
|
+
- Use \`agent_text_message(...)\` for user-visible text.
|
|
14826
|
+
- 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.
|
|
14827
|
+
- 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.
|
|
14828
|
+
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
|
|
14760
14829
|
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
|
|
14761
14830
|
}
|
|
14762
14831
|
|
|
@@ -14915,6 +14984,10 @@ function fallbackResponseText(entries, lists) {
|
|
|
14915
14984
|
return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
|
|
14916
14985
|
}
|
|
14917
14986
|
if (lists.length > 0) {
|
|
14987
|
+
const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
|
|
14988
|
+
if (emptyOnly) {
|
|
14989
|
+
return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
|
|
14990
|
+
}
|
|
14918
14991
|
return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
|
|
14919
14992
|
}
|
|
14920
14993
|
return null;
|