@granular-software/sdk 0.4.29 → 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 +273 -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/index.js
CHANGED
|
@@ -10975,17 +10975,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
10975
10975
|
}
|
|
10976
10976
|
},
|
|
10977
10977
|
domain: {
|
|
10978
|
-
applyToPropertyIR(propertyIR,
|
|
10979
|
-
|
|
10980
|
-
return propertyIR;
|
|
10981
|
-
}
|
|
10982
|
-
return {
|
|
10983
|
-
...propertyIR,
|
|
10984
|
-
docs: [
|
|
10985
|
-
...propertyIR.docs,
|
|
10986
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
10987
|
-
]
|
|
10988
|
-
};
|
|
10978
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
10979
|
+
return propertyIR;
|
|
10989
10980
|
}
|
|
10990
10981
|
}
|
|
10991
10982
|
});
|
|
@@ -11599,17 +11590,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
11599
11590
|
|
|
11600
11591
|
// src/client.ts
|
|
11601
11592
|
var STANDARD_MODULES_OPERATIONS = [
|
|
11602
|
-
{
|
|
11593
|
+
{
|
|
11594
|
+
create: "entity",
|
|
11595
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
11596
|
+
},
|
|
11603
11597
|
{ create: "class", extends: "entity", has: {} },
|
|
11604
|
-
{
|
|
11605
|
-
|
|
11598
|
+
{
|
|
11599
|
+
create: "user",
|
|
11600
|
+
extends: "entity",
|
|
11601
|
+
has: {
|
|
11602
|
+
email: { value: void 0 },
|
|
11603
|
+
firstName: { value: void 0 },
|
|
11604
|
+
lastName: { value: void 0 }
|
|
11605
|
+
}
|
|
11606
|
+
},
|
|
11607
|
+
{
|
|
11608
|
+
create: "company",
|
|
11609
|
+
extends: "entity",
|
|
11610
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
11611
|
+
},
|
|
11606
11612
|
{ create: "string", has: {} },
|
|
11607
11613
|
{ create: "number", has: {} },
|
|
11608
11614
|
{ create: "boolean", has: {} },
|
|
11609
|
-
{
|
|
11615
|
+
{
|
|
11616
|
+
create: "tool_parameter",
|
|
11617
|
+
has: {
|
|
11618
|
+
name: { value: void 0 },
|
|
11619
|
+
type: { value: "string" },
|
|
11620
|
+
description: { value: void 0 },
|
|
11621
|
+
required: { value: false }
|
|
11622
|
+
}
|
|
11623
|
+
}
|
|
11610
11624
|
];
|
|
11611
11625
|
var BUILTIN_MODULES = {
|
|
11612
|
-
|
|
11626
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
11613
11627
|
};
|
|
11614
11628
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11615
11629
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -11644,7 +11658,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
11644
11658
|
}
|
|
11645
11659
|
function isRetryableRecordObjectsError(error) {
|
|
11646
11660
|
const message = error instanceof Error ? error.message : String(error);
|
|
11647
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11661
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11662
|
+
message
|
|
11663
|
+
);
|
|
11648
11664
|
}
|
|
11649
11665
|
function computeEffectKey2(effect) {
|
|
11650
11666
|
const attachedClass = effect.className?.trim();
|
|
@@ -11721,7 +11737,10 @@ function normalizeUser(user) {
|
|
|
11721
11737
|
};
|
|
11722
11738
|
}
|
|
11723
11739
|
function normalizeEnvironmentData(environment) {
|
|
11724
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11740
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11741
|
+
mode: "pinned",
|
|
11742
|
+
versionId: environment.versionId || environment.buildId
|
|
11743
|
+
});
|
|
11725
11744
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
11726
11745
|
return {
|
|
11727
11746
|
...environment,
|
|
@@ -11815,14 +11834,16 @@ var Environment = class extends Session {
|
|
|
11815
11834
|
const response = await fetch(url, {
|
|
11816
11835
|
...options,
|
|
11817
11836
|
headers: {
|
|
11818
|
-
|
|
11837
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11819
11838
|
"Content-Type": "application/json",
|
|
11820
|
-
|
|
11839
|
+
Connection: "close",
|
|
11821
11840
|
...options.headers
|
|
11822
11841
|
}
|
|
11823
11842
|
});
|
|
11824
11843
|
if (!response.ok) {
|
|
11825
|
-
throw new Error(
|
|
11844
|
+
throw new Error(
|
|
11845
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
11846
|
+
);
|
|
11826
11847
|
}
|
|
11827
11848
|
return response.json();
|
|
11828
11849
|
}
|
|
@@ -11836,9 +11857,12 @@ var Environment = class extends Session {
|
|
|
11836
11857
|
async disconnect() {
|
|
11837
11858
|
let wsNotifiedRuntime = false;
|
|
11838
11859
|
try {
|
|
11839
|
-
const goodbye = await this.rpc(
|
|
11840
|
-
|
|
11841
|
-
|
|
11860
|
+
const goodbye = await this.rpc(
|
|
11861
|
+
"client.goodbye",
|
|
11862
|
+
{
|
|
11863
|
+
timestamp: Date.now()
|
|
11864
|
+
}
|
|
11865
|
+
);
|
|
11842
11866
|
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11843
11867
|
} catch {
|
|
11844
11868
|
wsNotifiedRuntime = false;
|
|
@@ -11852,8 +11876,8 @@ var Environment = class extends Session {
|
|
|
11852
11876
|
method: "POST",
|
|
11853
11877
|
headers: {
|
|
11854
11878
|
"Content-Type": "application/json",
|
|
11855
|
-
|
|
11856
|
-
|
|
11879
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11880
|
+
Connection: "close"
|
|
11857
11881
|
},
|
|
11858
11882
|
body: JSON.stringify({
|
|
11859
11883
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -11866,6 +11890,15 @@ var Environment = class extends Session {
|
|
|
11866
11890
|
}
|
|
11867
11891
|
this.client.disconnect();
|
|
11868
11892
|
}
|
|
11893
|
+
/**
|
|
11894
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
11895
|
+
*
|
|
11896
|
+
* Use this when the caller intends to immediately reattach to the same
|
|
11897
|
+
* session after an unexpected disconnect.
|
|
11898
|
+
*/
|
|
11899
|
+
disconnectTransport() {
|
|
11900
|
+
this.client.disconnect();
|
|
11901
|
+
}
|
|
11869
11902
|
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11870
11903
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11871
11904
|
graphContainerStatus = null;
|
|
@@ -11931,14 +11964,14 @@ var Environment = class extends Session {
|
|
|
11931
11964
|
}
|
|
11932
11965
|
/**
|
|
11933
11966
|
* Execute a GraphQL query against the environment's graph.
|
|
11934
|
-
*
|
|
11967
|
+
*
|
|
11935
11968
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
11936
11969
|
* Authentication is handled automatically using the SDK's API key.
|
|
11937
|
-
*
|
|
11970
|
+
*
|
|
11938
11971
|
* @param query - The GraphQL query string
|
|
11939
11972
|
* @param variables - Optional variables for the query
|
|
11940
11973
|
* @returns The query result data
|
|
11941
|
-
*
|
|
11974
|
+
*
|
|
11942
11975
|
* @example
|
|
11943
11976
|
* ```typescript
|
|
11944
11977
|
* // Read the workspace
|
|
@@ -11946,7 +11979,7 @@ var Environment = class extends Session {
|
|
|
11946
11979
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
11947
11980
|
* );
|
|
11948
11981
|
* console.log(result.data);
|
|
11949
|
-
*
|
|
11982
|
+
*
|
|
11950
11983
|
* // Create a model
|
|
11951
11984
|
* const created = await env.graphql(
|
|
11952
11985
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -11958,7 +11991,7 @@ var Environment = class extends Session {
|
|
|
11958
11991
|
method: "POST",
|
|
11959
11992
|
headers: {
|
|
11960
11993
|
"Content-Type": "application/json",
|
|
11961
|
-
|
|
11994
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
11962
11995
|
},
|
|
11963
11996
|
body: JSON.stringify({
|
|
11964
11997
|
environmentId: this.environmentId,
|
|
@@ -11975,10 +12008,10 @@ var Environment = class extends Session {
|
|
|
11975
12008
|
// ==================== RELATIONSHIP METHODS ====================
|
|
11976
12009
|
/**
|
|
11977
12010
|
* Define a relationship between two model types.
|
|
11978
|
-
*
|
|
12011
|
+
*
|
|
11979
12012
|
* Creates both submodels (if they don't exist) and links them with
|
|
11980
12013
|
* a RelationshipDef node that encodes cardinality.
|
|
11981
|
-
*
|
|
12014
|
+
*
|
|
11982
12015
|
* @example
|
|
11983
12016
|
* ```typescript
|
|
11984
12017
|
* // Author has many Books, Book has one Author
|
|
@@ -12038,10 +12071,10 @@ var Environment = class extends Session {
|
|
|
12038
12071
|
}
|
|
12039
12072
|
/**
|
|
12040
12073
|
* Get all relationships for a model type.
|
|
12041
|
-
*
|
|
12074
|
+
*
|
|
12042
12075
|
* @param modelPath - The model type path (e.g., "author")
|
|
12043
12076
|
* @returns Array of relationships from this model's perspective
|
|
12044
|
-
*
|
|
12077
|
+
*
|
|
12045
12078
|
* @example
|
|
12046
12079
|
* ```typescript
|
|
12047
12080
|
* const rels = await env.getRelationships('author');
|
|
@@ -12074,18 +12107,18 @@ var Environment = class extends Session {
|
|
|
12074
12107
|
}
|
|
12075
12108
|
/**
|
|
12076
12109
|
* Attach a target model to a relationship submodel.
|
|
12077
|
-
*
|
|
12110
|
+
*
|
|
12078
12111
|
* Handles cardinality automatically:
|
|
12079
12112
|
* - "One" side: sets/replaces the reference
|
|
12080
12113
|
* - "Many" side: adds the target to the collection
|
|
12081
|
-
*
|
|
12114
|
+
*
|
|
12082
12115
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
12083
12116
|
* Bidirectional sync is automatic.
|
|
12084
|
-
*
|
|
12117
|
+
*
|
|
12085
12118
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
12086
12119
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
12087
12120
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
12088
|
-
*
|
|
12121
|
+
*
|
|
12089
12122
|
* @example
|
|
12090
12123
|
* ```typescript
|
|
12091
12124
|
* // Attach a book to an author (many side)
|
|
@@ -12112,18 +12145,18 @@ var Environment = class extends Session {
|
|
|
12112
12145
|
}
|
|
12113
12146
|
/**
|
|
12114
12147
|
* Detach a target model from a relationship submodel.
|
|
12115
|
-
*
|
|
12148
|
+
*
|
|
12116
12149
|
* Handles bidirectional cleanup automatically.
|
|
12117
|
-
*
|
|
12150
|
+
*
|
|
12118
12151
|
* @param modelPath - The model instance path
|
|
12119
12152
|
* @param submodelPath - The relationship submodel
|
|
12120
12153
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
12121
|
-
*
|
|
12154
|
+
*
|
|
12122
12155
|
* @example
|
|
12123
12156
|
* ```typescript
|
|
12124
12157
|
* // Detach a specific book
|
|
12125
12158
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
12126
|
-
*
|
|
12159
|
+
*
|
|
12127
12160
|
* // Detach all books
|
|
12128
12161
|
* await env.detach('tolkien', 'books');
|
|
12129
12162
|
* ```
|
|
@@ -12147,11 +12180,11 @@ var Environment = class extends Session {
|
|
|
12147
12180
|
}
|
|
12148
12181
|
/**
|
|
12149
12182
|
* List all related models through a relationship submodel.
|
|
12150
|
-
*
|
|
12183
|
+
*
|
|
12151
12184
|
* @param modelPath - The model instance path
|
|
12152
12185
|
* @param submodelPath - The relationship submodel
|
|
12153
12186
|
* @returns Array of related model references
|
|
12154
|
-
*
|
|
12187
|
+
*
|
|
12155
12188
|
* @example
|
|
12156
12189
|
* ```typescript
|
|
12157
12190
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -12175,14 +12208,14 @@ var Environment = class extends Session {
|
|
|
12175
12208
|
}
|
|
12176
12209
|
/**
|
|
12177
12210
|
* Apply a manifest to the current environment's graph.
|
|
12178
|
-
*
|
|
12211
|
+
*
|
|
12179
12212
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
12180
12213
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
12181
12214
|
* relationships from a declarative manifest.
|
|
12182
|
-
*
|
|
12215
|
+
*
|
|
12183
12216
|
* @param manifest - The manifest content to apply
|
|
12184
12217
|
* @returns Summary of applied operations
|
|
12185
|
-
*
|
|
12218
|
+
*
|
|
12186
12219
|
* @example
|
|
12187
12220
|
* ```typescript
|
|
12188
12221
|
* await environment.applyManifest({
|
|
@@ -12220,12 +12253,16 @@ var Environment = class extends Session {
|
|
|
12220
12253
|
applied++;
|
|
12221
12254
|
} catch (err) {
|
|
12222
12255
|
if (!err.message?.includes("already exists")) {
|
|
12223
|
-
errors.push(
|
|
12256
|
+
errors.push(
|
|
12257
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
12258
|
+
);
|
|
12224
12259
|
}
|
|
12225
12260
|
}
|
|
12226
12261
|
}
|
|
12227
12262
|
} else {
|
|
12228
|
-
errors.push(
|
|
12263
|
+
errors.push(
|
|
12264
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
12265
|
+
);
|
|
12229
12266
|
}
|
|
12230
12267
|
}
|
|
12231
12268
|
}
|
|
@@ -12309,7 +12346,10 @@ var Environment = class extends Session {
|
|
|
12309
12346
|
}
|
|
12310
12347
|
}
|
|
12311
12348
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
12312
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
12349
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
12350
|
+
toolPath,
|
|
12351
|
+
metamodels
|
|
12352
|
+
)) {
|
|
12313
12353
|
await this._runGraphql(mutation.query, mutation.label);
|
|
12314
12354
|
}
|
|
12315
12355
|
}
|
|
@@ -12358,7 +12398,9 @@ var Environment = class extends Session {
|
|
|
12358
12398
|
}
|
|
12359
12399
|
if (eventType.payloadSchema?.properties) {
|
|
12360
12400
|
const fieldSpecs = {};
|
|
12361
|
-
for (const [propName, propSchema] of Object.entries(
|
|
12401
|
+
for (const [propName, propSchema] of Object.entries(
|
|
12402
|
+
eventType.payloadSchema.properties
|
|
12403
|
+
)) {
|
|
12362
12404
|
const schema = propSchema;
|
|
12363
12405
|
fieldSpecs[propName] = {
|
|
12364
12406
|
type: schema.type ?? "string",
|
|
@@ -12641,7 +12683,9 @@ var Environment = class extends Session {
|
|
|
12641
12683
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12642
12684
|
await Promise.all(
|
|
12643
12685
|
wave.map(async (plan) => {
|
|
12644
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12686
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12687
|
+
plan.slice
|
|
12688
|
+
);
|
|
12645
12689
|
if (items.length !== plan.slice.length) {
|
|
12646
12690
|
throw new Error(
|
|
12647
12691
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -12671,13 +12715,10 @@ var Environment = class extends Session {
|
|
|
12671
12715
|
let lastError;
|
|
12672
12716
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12673
12717
|
try {
|
|
12674
|
-
const response = await this.controlPlaneRequest(
|
|
12675
|
-
|
|
12676
|
-
{
|
|
12677
|
-
|
|
12678
|
-
body: JSON.stringify({ records: chunk })
|
|
12679
|
-
}
|
|
12680
|
-
);
|
|
12718
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
12719
|
+
method: "POST",
|
|
12720
|
+
body: JSON.stringify({ records: chunk })
|
|
12721
|
+
});
|
|
12681
12722
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
12682
12723
|
return { items, durationMs: Date.now() - wallStart };
|
|
12683
12724
|
} catch (error) {
|
|
@@ -12740,7 +12781,9 @@ var Environment = class extends Session {
|
|
|
12740
12781
|
* Fetch a single record import by id.
|
|
12741
12782
|
*/
|
|
12742
12783
|
async getRecordImport(importId) {
|
|
12743
|
-
return this.controlPlaneRequest(
|
|
12784
|
+
return this.controlPlaneRequest(
|
|
12785
|
+
`/control/record-imports/${importId}`
|
|
12786
|
+
);
|
|
12744
12787
|
}
|
|
12745
12788
|
/**
|
|
12746
12789
|
* Cancel a queued/background record import.
|
|
@@ -12807,7 +12850,9 @@ var Granular = class _Granular {
|
|
|
12807
12850
|
constructor(options) {
|
|
12808
12851
|
const auth = options.token ?? options.apiKey;
|
|
12809
12852
|
if (!auth) {
|
|
12810
|
-
throw new Error(
|
|
12853
|
+
throw new Error(
|
|
12854
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
12855
|
+
);
|
|
12811
12856
|
}
|
|
12812
12857
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
12813
12858
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -12819,10 +12864,10 @@ var Granular = class _Granular {
|
|
|
12819
12864
|
}
|
|
12820
12865
|
/**
|
|
12821
12866
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12822
|
-
*
|
|
12867
|
+
*
|
|
12823
12868
|
* @param options - User options
|
|
12824
12869
|
* @returns The recorded user with both `userId` and `granularId`
|
|
12825
|
-
*
|
|
12870
|
+
*
|
|
12826
12871
|
* @example
|
|
12827
12872
|
* ```typescript
|
|
12828
12873
|
* const user = await granular.recordUser({
|
|
@@ -12833,14 +12878,16 @@ var Granular = class _Granular {
|
|
|
12833
12878
|
* ```
|
|
12834
12879
|
*/
|
|
12835
12880
|
async recordUser(options) {
|
|
12836
|
-
const subject = normalizeSubject(
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12881
|
+
const subject = normalizeSubject(
|
|
12882
|
+
await this.request("/control/subjects", {
|
|
12883
|
+
method: "POST",
|
|
12884
|
+
body: JSON.stringify({
|
|
12885
|
+
identityId: options.userId,
|
|
12886
|
+
name: options.name,
|
|
12887
|
+
email: options.email
|
|
12888
|
+
})
|
|
12842
12889
|
})
|
|
12843
|
-
|
|
12890
|
+
);
|
|
12844
12891
|
return normalizeUser({
|
|
12845
12892
|
granularId: subject.granularId,
|
|
12846
12893
|
userId: options.userId,
|
|
@@ -12887,18 +12934,20 @@ var Granular = class _Granular {
|
|
|
12887
12934
|
permissions: options.permissions || []
|
|
12888
12935
|
};
|
|
12889
12936
|
}
|
|
12890
|
-
throw new Error(
|
|
12937
|
+
throw new Error(
|
|
12938
|
+
"connect() requires either userId, granularId, or a user object returned by recordUser()."
|
|
12939
|
+
);
|
|
12891
12940
|
}
|
|
12892
12941
|
/**
|
|
12893
12942
|
* Connect to an ontology environment and establish a real-time session.
|
|
12894
|
-
*
|
|
12943
|
+
*
|
|
12895
12944
|
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12896
12945
|
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12897
12946
|
* the sandbox registry automatically.
|
|
12898
|
-
*
|
|
12947
|
+
*
|
|
12899
12948
|
* @param options - Connection options
|
|
12900
12949
|
* @returns An active environment session
|
|
12901
|
-
*
|
|
12950
|
+
*
|
|
12902
12951
|
* @example
|
|
12903
12952
|
* ```typescript
|
|
12904
12953
|
* const environment = await granular.connect({
|
|
@@ -12907,23 +12956,23 @@ var Granular = class _Granular {
|
|
|
12907
12956
|
* userId: 'user_123',
|
|
12908
12957
|
* permissions: ['agent'],
|
|
12909
12958
|
* });
|
|
12910
|
-
*
|
|
12959
|
+
*
|
|
12911
12960
|
* await granular.registerEffect('my-sandbox', {
|
|
12912
12961
|
* name: 'greet',
|
|
12913
12962
|
* description: 'Say hello',
|
|
12914
12963
|
* inputSchema: { type: 'object', properties: {} },
|
|
12915
12964
|
* handler: async () => 'Hello!',
|
|
12916
12965
|
* });
|
|
12917
|
-
*
|
|
12966
|
+
*
|
|
12918
12967
|
* // Submit job
|
|
12919
12968
|
* const job = await environment.submitJob(`
|
|
12920
12969
|
* import { tools } from './sandbox-tools';
|
|
12921
12970
|
* return await tools.greet({});
|
|
12922
12971
|
* `);
|
|
12923
|
-
*
|
|
12972
|
+
*
|
|
12924
12973
|
* console.log(await job.result); // 'Hello!'
|
|
12925
12974
|
* ```
|
|
12926
|
-
|
|
12975
|
+
*/
|
|
12927
12976
|
async connect(options) {
|
|
12928
12977
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
12929
12978
|
const ontology = options.ontology;
|
|
@@ -12938,8 +12987,15 @@ var Granular = class _Granular {
|
|
|
12938
12987
|
const user = await this.resolveConnectUser(options);
|
|
12939
12988
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12940
12989
|
for (const profileName of user.permissions) {
|
|
12941
|
-
const profileId = await this.ensurePermissionProfile(
|
|
12942
|
-
|
|
12990
|
+
const profileId = await this.ensurePermissionProfile(
|
|
12991
|
+
sandbox.sandboxId,
|
|
12992
|
+
profileName
|
|
12993
|
+
);
|
|
12994
|
+
await this.ensureAssignment(
|
|
12995
|
+
user.granularId,
|
|
12996
|
+
sandbox.sandboxId,
|
|
12997
|
+
profileId
|
|
12998
|
+
);
|
|
12943
12999
|
}
|
|
12944
13000
|
const envData = await this.environments.create(sandbox.sandboxId, {
|
|
12945
13001
|
subjectId: user.granularId,
|
|
@@ -12991,7 +13047,9 @@ var Granular = class _Granular {
|
|
|
12991
13047
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
12992
13048
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
12993
13049
|
summary: row.summary != null ? String(row.summary) : null,
|
|
12994
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13050
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13051
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
13052
|
+
) : null,
|
|
12995
13053
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
12996
13054
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
12997
13055
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -13081,7 +13139,13 @@ var Granular = class _Granular {
|
|
|
13081
13139
|
});
|
|
13082
13140
|
await client.connect();
|
|
13083
13141
|
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13084
|
-
const environment = new Environment(
|
|
13142
|
+
const environment = new Environment(
|
|
13143
|
+
client,
|
|
13144
|
+
envData,
|
|
13145
|
+
clientId,
|
|
13146
|
+
this.apiKey,
|
|
13147
|
+
graphqlEndpoint
|
|
13148
|
+
);
|
|
13085
13149
|
await environment.hello();
|
|
13086
13150
|
return environment;
|
|
13087
13151
|
}
|
|
@@ -13115,14 +13179,18 @@ var Granular = class _Granular {
|
|
|
13115
13179
|
};
|
|
13116
13180
|
}
|
|
13117
13181
|
async publishSandboxEffectCatalog(host) {
|
|
13118
|
-
const effects = Array.from(
|
|
13119
|
-
(
|
|
13120
|
-
);
|
|
13121
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13182
|
+
const effects = Array.from(
|
|
13183
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
13184
|
+
).map((effect) => this.serializeEffect(effect));
|
|
13185
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13186
|
+
effects
|
|
13187
|
+
});
|
|
13122
13188
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
13123
13189
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
13124
13190
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
13125
|
-
const detail = rejected.map(
|
|
13191
|
+
const detail = rejected.map(
|
|
13192
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
13193
|
+
).join("; ");
|
|
13126
13194
|
throw new Error(
|
|
13127
13195
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
13128
13196
|
);
|
|
@@ -13156,13 +13224,15 @@ var Granular = class _Granular {
|
|
|
13156
13224
|
disconnectError
|
|
13157
13225
|
);
|
|
13158
13226
|
}
|
|
13159
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13227
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13228
|
+
(reconnectError) => {
|
|
13229
|
+
console.error(
|
|
13230
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
13231
|
+
reconnectError
|
|
13232
|
+
);
|
|
13233
|
+
console.error("[Granular] Original heartbeat failure:", error);
|
|
13234
|
+
}
|
|
13235
|
+
);
|
|
13166
13236
|
}
|
|
13167
13237
|
startEffectHostHeartbeat(host) {
|
|
13168
13238
|
if (host.heartbeatTimer) {
|
|
@@ -13183,9 +13253,15 @@ var Granular = class _Granular {
|
|
|
13183
13253
|
host.heartbeatInFlight = false;
|
|
13184
13254
|
});
|
|
13185
13255
|
};
|
|
13186
|
-
sendHeartbeat(
|
|
13256
|
+
sendHeartbeat(
|
|
13257
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
13258
|
+
false
|
|
13259
|
+
);
|
|
13187
13260
|
host.heartbeatTimer = setInterval(() => {
|
|
13188
|
-
sendHeartbeat(
|
|
13261
|
+
sendHeartbeat(
|
|
13262
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
13263
|
+
true
|
|
13264
|
+
);
|
|
13189
13265
|
}, 1e4);
|
|
13190
13266
|
}
|
|
13191
13267
|
stopEffectHostHeartbeat(host) {
|
|
@@ -13217,7 +13293,12 @@ var Granular = class _Granular {
|
|
|
13217
13293
|
const effectClientId = crypto.randomUUID();
|
|
13218
13294
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
13219
13295
|
const wsClient = new WSClient({
|
|
13220
|
-
url: buildEffectHostUrl(
|
|
13296
|
+
url: buildEffectHostUrl(
|
|
13297
|
+
this.apiUrl,
|
|
13298
|
+
sandboxId,
|
|
13299
|
+
effectClientId,
|
|
13300
|
+
clientId
|
|
13301
|
+
),
|
|
13221
13302
|
sessionId: `effect-host:${effectClientId}`,
|
|
13222
13303
|
token: this.apiKey,
|
|
13223
13304
|
tokenProvider: this.tokenProvider,
|
|
@@ -13236,7 +13317,10 @@ var Granular = class _Granular {
|
|
|
13236
13317
|
};
|
|
13237
13318
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
13238
13319
|
const request = params;
|
|
13239
|
-
return invokeRegisteredEffect(
|
|
13320
|
+
return invokeRegisteredEffect(
|
|
13321
|
+
this.getSandboxEffectMap(sandboxId),
|
|
13322
|
+
request
|
|
13323
|
+
);
|
|
13240
13324
|
});
|
|
13241
13325
|
wsClient.on("open", () => {
|
|
13242
13326
|
void this.synchronizeEffectHost(host).catch((error) => {
|
|
@@ -13284,7 +13368,7 @@ var Granular = class _Granular {
|
|
|
13284
13368
|
}
|
|
13285
13369
|
/**
|
|
13286
13370
|
* Register multiple effects (tools) for a specific sandbox.
|
|
13287
|
-
*
|
|
13371
|
+
*
|
|
13288
13372
|
* batch version of `registerEffect`.
|
|
13289
13373
|
*/
|
|
13290
13374
|
async registerEffects(sandboxNameOrId, effects) {
|
|
@@ -13298,7 +13382,7 @@ var Granular = class _Granular {
|
|
|
13298
13382
|
}
|
|
13299
13383
|
/**
|
|
13300
13384
|
* Unregister an effect from a sandbox.
|
|
13301
|
-
*
|
|
13385
|
+
*
|
|
13302
13386
|
* Removes it from the local sandbox registry and updates the
|
|
13303
13387
|
* sandbox-scoped live catalog.
|
|
13304
13388
|
*/
|
|
@@ -13397,27 +13481,31 @@ var Granular = class _Granular {
|
|
|
13397
13481
|
const assignments = await this.request(
|
|
13398
13482
|
`/control/subjects/${subjectId}/assignments`
|
|
13399
13483
|
);
|
|
13400
|
-
const existing = assignments.items.find(
|
|
13401
|
-
(a) => a.sandboxId === sandboxId
|
|
13402
|
-
);
|
|
13484
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
13403
13485
|
if (existing) {
|
|
13404
13486
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
13405
13487
|
return;
|
|
13406
13488
|
}
|
|
13407
|
-
await this.request(
|
|
13408
|
-
|
|
13409
|
-
|
|
13489
|
+
await this.request(
|
|
13490
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
13491
|
+
{
|
|
13492
|
+
method: "DELETE"
|
|
13493
|
+
}
|
|
13494
|
+
);
|
|
13410
13495
|
}
|
|
13411
13496
|
} catch {
|
|
13412
13497
|
}
|
|
13413
|
-
await this.request(
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13498
|
+
await this.request(
|
|
13499
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
13500
|
+
{
|
|
13501
|
+
method: "POST",
|
|
13502
|
+
body: JSON.stringify({
|
|
13503
|
+
sandboxId,
|
|
13504
|
+
subjectId,
|
|
13505
|
+
permissionProfileId
|
|
13506
|
+
})
|
|
13507
|
+
}
|
|
13508
|
+
);
|
|
13421
13509
|
}
|
|
13422
13510
|
/**
|
|
13423
13511
|
* Sandbox management API
|
|
@@ -13497,23 +13585,33 @@ var Granular = class _Granular {
|
|
|
13497
13585
|
},
|
|
13498
13586
|
get: async (environmentId) => {
|
|
13499
13587
|
return normalizeEnvironmentData(
|
|
13500
|
-
await this.request(
|
|
13588
|
+
await this.request(
|
|
13589
|
+
`/control/environments/${environmentId}`
|
|
13590
|
+
)
|
|
13501
13591
|
);
|
|
13502
13592
|
},
|
|
13503
13593
|
create: async (sandboxId, data) => {
|
|
13504
13594
|
const environmentName = data.environment || data.envName;
|
|
13505
|
-
return normalizeEnvironmentData(
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13595
|
+
return normalizeEnvironmentData(
|
|
13596
|
+
await this.request(
|
|
13597
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
13598
|
+
{
|
|
13599
|
+
method: "POST",
|
|
13600
|
+
body: JSON.stringify({
|
|
13601
|
+
...data,
|
|
13602
|
+
envName: environmentName
|
|
13603
|
+
})
|
|
13604
|
+
}
|
|
13605
|
+
)
|
|
13606
|
+
);
|
|
13512
13607
|
},
|
|
13513
13608
|
delete: async (environmentId) => {
|
|
13514
|
-
return this.request(
|
|
13515
|
-
|
|
13516
|
-
|
|
13609
|
+
return this.request(
|
|
13610
|
+
`/control/environments/${environmentId}`,
|
|
13611
|
+
{
|
|
13612
|
+
method: "DELETE"
|
|
13613
|
+
}
|
|
13614
|
+
);
|
|
13517
13615
|
}
|
|
13518
13616
|
};
|
|
13519
13617
|
}
|
|
@@ -13533,10 +13631,13 @@ var Granular = class _Granular {
|
|
|
13533
13631
|
}
|
|
13534
13632
|
if (params.since) query.set("since", params.since.toISOString());
|
|
13535
13633
|
if (params.until) query.set("until", params.until.toISOString());
|
|
13536
|
-
if (params.isAcked !== void 0)
|
|
13634
|
+
if (params.isAcked !== void 0)
|
|
13635
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
13537
13636
|
if (params.limit) query.set("limit", String(params.limit));
|
|
13538
13637
|
if (params.offset) query.set("offset", String(params.offset));
|
|
13539
|
-
const result = await this.request(
|
|
13638
|
+
const result = await this.request(
|
|
13639
|
+
`/control/stream-events?${query.toString()}`
|
|
13640
|
+
);
|
|
13540
13641
|
return (result.items || []).map((row) => ({
|
|
13541
13642
|
eventId: row.event_id,
|
|
13542
13643
|
streamName: row.stream_name,
|
|
@@ -13567,7 +13668,9 @@ var Granular = class _Granular {
|
|
|
13567
13668
|
since: cursor,
|
|
13568
13669
|
limit: 100
|
|
13569
13670
|
});
|
|
13570
|
-
const orderedEvents = [...events].sort(
|
|
13671
|
+
const orderedEvents = [...events].sort(
|
|
13672
|
+
(a, b) => a.createdAt - b.createdAt
|
|
13673
|
+
);
|
|
13571
13674
|
for (const event of orderedEvents) {
|
|
13572
13675
|
if (seenEventIds.has(event.eventId)) {
|
|
13573
13676
|
continue;
|
|
@@ -13580,15 +13683,19 @@ var Granular = class _Granular {
|
|
|
13580
13683
|
params.onEvent(event);
|
|
13581
13684
|
}
|
|
13582
13685
|
} catch (err) {
|
|
13583
|
-
params.onError?.(
|
|
13686
|
+
params.onError?.(
|
|
13687
|
+
err instanceof Error ? err : new Error(String(err))
|
|
13688
|
+
);
|
|
13584
13689
|
}
|
|
13585
13690
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13586
13691
|
}
|
|
13587
13692
|
};
|
|
13588
13693
|
poll();
|
|
13589
|
-
return {
|
|
13590
|
-
|
|
13591
|
-
|
|
13694
|
+
return {
|
|
13695
|
+
unsubscribe: () => {
|
|
13696
|
+
running = false;
|
|
13697
|
+
}
|
|
13698
|
+
};
|
|
13592
13699
|
},
|
|
13593
13700
|
ack: async (eventId) => {
|
|
13594
13701
|
await this.request("/control/stream-events/ack", {
|
|
@@ -13606,7 +13713,9 @@ var Granular = class _Granular {
|
|
|
13606
13713
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13607
13714
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13608
13715
|
if (params.environment) query.set("environmentId", params.environment);
|
|
13609
|
-
const result = await this.request(
|
|
13716
|
+
const result = await this.request(
|
|
13717
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
13718
|
+
);
|
|
13610
13719
|
return (result.items || []).map((row) => ({
|
|
13611
13720
|
streamName: row.stream_name,
|
|
13612
13721
|
eventType: row.event_type,
|
|
@@ -13624,10 +13733,14 @@ var Granular = class _Granular {
|
|
|
13624
13733
|
get subjects() {
|
|
13625
13734
|
return {
|
|
13626
13735
|
get: async (subjectId) => {
|
|
13627
|
-
return normalizeSubject(
|
|
13736
|
+
return normalizeSubject(
|
|
13737
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
13738
|
+
);
|
|
13628
13739
|
},
|
|
13629
13740
|
listAssignments: async (subjectId) => {
|
|
13630
|
-
return this.request(
|
|
13741
|
+
return this.request(
|
|
13742
|
+
`/control/subjects/${subjectId}/assignments`
|
|
13743
|
+
);
|
|
13631
13744
|
}
|
|
13632
13745
|
};
|
|
13633
13746
|
}
|
|
@@ -13637,24 +13750,31 @@ var Granular = class _Granular {
|
|
|
13637
13750
|
get users() {
|
|
13638
13751
|
return {
|
|
13639
13752
|
create: async (data) => {
|
|
13640
|
-
return normalizeSubject(
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13753
|
+
return normalizeSubject(
|
|
13754
|
+
await this.request("/control/subjects", {
|
|
13755
|
+
method: "POST",
|
|
13756
|
+
body: JSON.stringify({
|
|
13757
|
+
identityId: data.id,
|
|
13758
|
+
name: data.name,
|
|
13759
|
+
email: data.email
|
|
13760
|
+
})
|
|
13646
13761
|
})
|
|
13647
|
-
|
|
13762
|
+
);
|
|
13648
13763
|
},
|
|
13649
13764
|
get: async (id) => {
|
|
13650
|
-
return normalizeSubject(
|
|
13765
|
+
return normalizeSubject(
|
|
13766
|
+
await this.request(`/control/subjects/${id}`)
|
|
13767
|
+
);
|
|
13651
13768
|
}
|
|
13652
13769
|
};
|
|
13653
13770
|
}
|
|
13654
13771
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
13655
13772
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13656
|
-
const result = await this.request(
|
|
13657
|
-
|
|
13773
|
+
const result = await this.request(
|
|
13774
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
13775
|
+
);
|
|
13776
|
+
if (result.items.length === 0)
|
|
13777
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13658
13778
|
return result.items[0].sandboxId;
|
|
13659
13779
|
}
|
|
13660
13780
|
/**
|
|
@@ -13669,9 +13789,9 @@ var Granular = class _Granular {
|
|
|
13669
13789
|
const response = await fetch(url, {
|
|
13670
13790
|
...options,
|
|
13671
13791
|
headers: {
|
|
13672
|
-
|
|
13792
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13673
13793
|
"Content-Type": "application/json",
|
|
13674
|
-
|
|
13794
|
+
Connection: "close",
|
|
13675
13795
|
...options.headers
|
|
13676
13796
|
}
|
|
13677
13797
|
});
|
|
@@ -13682,7 +13802,11 @@ var Granular = class _Granular {
|
|
|
13682
13802
|
return response.json();
|
|
13683
13803
|
}
|
|
13684
13804
|
const errorText = await response.text();
|
|
13685
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
13805
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
13806
|
+
response.status,
|
|
13807
|
+
errorText,
|
|
13808
|
+
url
|
|
13809
|
+
);
|
|
13686
13810
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13687
13811
|
if (this.debugHttp) {
|
|
13688
13812
|
console.warn(
|
|
@@ -13821,21 +13945,6 @@ function reviewGeneratedJobCode(code) {
|
|
|
13821
13945
|
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."
|
|
13822
13946
|
});
|
|
13823
13947
|
}
|
|
13824
|
-
const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
|
|
13825
|
-
for (const call of askUserCalls) {
|
|
13826
|
-
const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
|
|
13827
|
-
const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
|
|
13828
|
-
const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
|
|
13829
|
-
const includesShortlistOptions = /options\s*:\s*\[/.test(call);
|
|
13830
|
-
if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
|
|
13831
|
-
issues.push({
|
|
13832
|
-
code: "disambiguation_requires_choice",
|
|
13833
|
-
severity: "error",
|
|
13834
|
-
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."
|
|
13835
|
-
});
|
|
13836
|
-
break;
|
|
13837
|
-
}
|
|
13838
|
-
}
|
|
13839
13948
|
}
|
|
13840
13949
|
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
13841
13950
|
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
@@ -13881,6 +13990,184 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
13881
13990
|
entryPaths: uniqueStrings(entryPaths, 8)
|
|
13882
13991
|
};
|
|
13883
13992
|
}
|
|
13993
|
+
function normalizeActionSummaryForPrompt(line) {
|
|
13994
|
+
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
13995
|
+
}
|
|
13996
|
+
function collectConversationReferents(liveDoc) {
|
|
13997
|
+
const conversation = asRecord2(liveDoc?.conversation);
|
|
13998
|
+
const persistedReferents = asArray(conversation?.referents).map((value) => asRecord2(value)).filter((value) => Boolean(value));
|
|
13999
|
+
if (persistedReferents.length > 0) {
|
|
14000
|
+
return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
|
|
14001
|
+
}
|
|
14002
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14003
|
+
const entriesByPath = asRecord2(heap?.entriesByPath) || {};
|
|
14004
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14005
|
+
const variablesByName = asRecord2(heap?.variablesByName) || {};
|
|
14006
|
+
const messages = asArray(conversation?.messages).map((value) => asRecord2(value)).filter((value) => Boolean(value)).slice().sort((left, right) => (Number(right.ts) || 0) - (Number(left.ts) || 0));
|
|
14007
|
+
const referents = [];
|
|
14008
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14009
|
+
const pushReferent = (referent) => {
|
|
14010
|
+
if (!referent?.kind || !referent.ref) return;
|
|
14011
|
+
const key = `${referent.kind}:${referent.ref}`;
|
|
14012
|
+
if (seen.has(key)) return;
|
|
14013
|
+
seen.add(key);
|
|
14014
|
+
referents.push(referent);
|
|
14015
|
+
};
|
|
14016
|
+
for (const message of messages) {
|
|
14017
|
+
if (message.role !== "assistant") continue;
|
|
14018
|
+
const show = asRecord2(message.show);
|
|
14019
|
+
if (!show) continue;
|
|
14020
|
+
const ts = Number(message.ts) || 0;
|
|
14021
|
+
const messageId = typeof message.id === "string" ? message.id : void 0;
|
|
14022
|
+
const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
|
|
14023
|
+
for (const entryPath of uniqueStrings(asArray(show.entryPaths))) {
|
|
14024
|
+
const entry = asRecord2(entriesByPath[entryPath]);
|
|
14025
|
+
pushReferent({
|
|
14026
|
+
id: `entry:${entryPath}`,
|
|
14027
|
+
kind: "entry",
|
|
14028
|
+
ref: entryPath,
|
|
14029
|
+
entryPath,
|
|
14030
|
+
className: typeof entry?.className === "string" ? entry.className : void 0,
|
|
14031
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
|
|
14032
|
+
messageId,
|
|
14033
|
+
jobId,
|
|
14034
|
+
ts
|
|
14035
|
+
});
|
|
14036
|
+
}
|
|
14037
|
+
for (const listName of uniqueStrings(asArray(show.listNames))) {
|
|
14038
|
+
const list = asRecord2(listsByName[listName]);
|
|
14039
|
+
pushReferent({
|
|
14040
|
+
id: `list:${listName}`,
|
|
14041
|
+
kind: "list",
|
|
14042
|
+
ref: listName,
|
|
14043
|
+
listName,
|
|
14044
|
+
className: typeof list?.className === "string" ? list.className : void 0,
|
|
14045
|
+
count: Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14046
|
+
messageId,
|
|
14047
|
+
jobId,
|
|
14048
|
+
ts
|
|
14049
|
+
});
|
|
14050
|
+
}
|
|
14051
|
+
for (const variableName of uniqueStrings(
|
|
14052
|
+
asArray(show.variableNames)
|
|
14053
|
+
)) {
|
|
14054
|
+
const variable = asRecord2(variablesByName[variableName]);
|
|
14055
|
+
const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
|
|
14056
|
+
const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
|
|
14057
|
+
const entry = entryPath ? asRecord2(entriesByPath[entryPath]) : null;
|
|
14058
|
+
const list = listName ? asRecord2(listsByName[listName]) : null;
|
|
14059
|
+
pushReferent({
|
|
14060
|
+
id: `variable:${variableName}`,
|
|
14061
|
+
kind: "variable",
|
|
14062
|
+
ref: variableName,
|
|
14063
|
+
variableName,
|
|
14064
|
+
variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
|
|
14065
|
+
entryPath,
|
|
14066
|
+
listName,
|
|
14067
|
+
className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
|
|
14068
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
14069
|
+
count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14070
|
+
scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
|
|
14071
|
+
messageId,
|
|
14072
|
+
jobId,
|
|
14073
|
+
ts
|
|
14074
|
+
});
|
|
14075
|
+
}
|
|
14076
|
+
}
|
|
14077
|
+
return referents;
|
|
14078
|
+
}
|
|
14079
|
+
function projectConversationReferentFocus(liveDoc) {
|
|
14080
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14081
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14082
|
+
const referents = collectConversationReferents(liveDoc);
|
|
14083
|
+
const entryPaths = [];
|
|
14084
|
+
const listNames = [];
|
|
14085
|
+
const variableNames = [];
|
|
14086
|
+
for (const referent of referents.slice(0, 8)) {
|
|
14087
|
+
if (referent.kind === "entry" && typeof referent.entryPath === "string") {
|
|
14088
|
+
entryPaths.push(referent.entryPath);
|
|
14089
|
+
continue;
|
|
14090
|
+
}
|
|
14091
|
+
if (referent.kind === "list" && typeof referent.listName === "string") {
|
|
14092
|
+
listNames.push(referent.listName);
|
|
14093
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14094
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14095
|
+
continue;
|
|
14096
|
+
}
|
|
14097
|
+
if (referent.kind === "variable" && typeof referent.variableName === "string") {
|
|
14098
|
+
variableNames.push(referent.variableName);
|
|
14099
|
+
if (typeof referent.entryPath === "string") {
|
|
14100
|
+
entryPaths.push(referent.entryPath);
|
|
14101
|
+
}
|
|
14102
|
+
if (typeof referent.listName === "string") {
|
|
14103
|
+
listNames.push(referent.listName);
|
|
14104
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14105
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14106
|
+
}
|
|
14107
|
+
}
|
|
14108
|
+
}
|
|
14109
|
+
return {
|
|
14110
|
+
entryPaths: uniqueStrings(entryPaths, 8),
|
|
14111
|
+
listNames: uniqueStrings(listNames, 4),
|
|
14112
|
+
variableNames: uniqueStrings(variableNames, 4)
|
|
14113
|
+
};
|
|
14114
|
+
}
|
|
14115
|
+
function projectConversationReferentSummary(liveDoc) {
|
|
14116
|
+
const referents = collectConversationReferents(liveDoc).slice(0, 8);
|
|
14117
|
+
if (referents.length === 0) {
|
|
14118
|
+
return "No recent referents recorded from prior assistant replies.";
|
|
14119
|
+
}
|
|
14120
|
+
const entryLines = [];
|
|
14121
|
+
const listLines = [];
|
|
14122
|
+
const variableLines = [];
|
|
14123
|
+
for (const referent of referents) {
|
|
14124
|
+
if (referent.kind === "entry" && referent.entryPath) {
|
|
14125
|
+
const label = referent.label || referent.entryPath;
|
|
14126
|
+
const classLabel = referent.className || "unknown";
|
|
14127
|
+
entryLines.push(`- ${label} <${referent.entryPath}> [${classLabel}]`);
|
|
14128
|
+
continue;
|
|
14129
|
+
}
|
|
14130
|
+
if (referent.kind === "list" && referent.listName) {
|
|
14131
|
+
const classLabel = referent.className || "unknown";
|
|
14132
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14133
|
+
listLines.push(
|
|
14134
|
+
`- ${referent.listName}: list<${classLabel}> -> ${countLabel} item(s)`
|
|
14135
|
+
);
|
|
14136
|
+
continue;
|
|
14137
|
+
}
|
|
14138
|
+
if (referent.kind === "variable" && referent.variableName) {
|
|
14139
|
+
if (referent.variableKind === "entry" && referent.entryPath && referent.className) {
|
|
14140
|
+
const label = referent.label || referent.entryPath;
|
|
14141
|
+
variableLines.push(
|
|
14142
|
+
`- ${referent.variableName}: entry<${referent.className}> -> ${label} <${referent.entryPath}>`
|
|
14143
|
+
);
|
|
14144
|
+
continue;
|
|
14145
|
+
}
|
|
14146
|
+
if (referent.variableKind === "list" && referent.listName && referent.className) {
|
|
14147
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14148
|
+
variableLines.push(
|
|
14149
|
+
`- ${referent.variableName}: list<${referent.className}> -> ${countLabel} item(s) via ${referent.listName}`
|
|
14150
|
+
);
|
|
14151
|
+
continue;
|
|
14152
|
+
}
|
|
14153
|
+
if (referent.variableKind === "scalar") {
|
|
14154
|
+
variableLines.push(
|
|
14155
|
+
`- ${referent.variableName}: scalar = ${formatScalar(referent.scalarValue)}`
|
|
14156
|
+
);
|
|
14157
|
+
continue;
|
|
14158
|
+
}
|
|
14159
|
+
variableLines.push(`- ${referent.variableName}`);
|
|
14160
|
+
}
|
|
14161
|
+
}
|
|
14162
|
+
const lines = [];
|
|
14163
|
+
lines.push("Entries:");
|
|
14164
|
+
lines.push(...entryLines.length > 0 ? entryLines : ["- none"]);
|
|
14165
|
+
lines.push("", "Lists:");
|
|
14166
|
+
lines.push(...listLines.length > 0 ? listLines : ["- none"]);
|
|
14167
|
+
lines.push("", "Variables:");
|
|
14168
|
+
lines.push(...variableLines.length > 0 ? variableLines : ["- none"]);
|
|
14169
|
+
return lines.join("\n");
|
|
14170
|
+
}
|
|
13884
14171
|
function getCurrentClosureId(liveDoc) {
|
|
13885
14172
|
const loop = asRecord2(liveDoc?.loop);
|
|
13886
14173
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -14093,7 +14380,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
14093
14380
|
variableNames: uniqueStrings(variableNames, 4),
|
|
14094
14381
|
listNames: uniqueStrings(listNames, 4),
|
|
14095
14382
|
entryPaths: uniqueStrings(entryPaths, 6),
|
|
14096
|
-
recentActionSummary: uniqueStrings(actionSummaryLines, 8)
|
|
14383
|
+
recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
|
|
14384
|
+
normalizeActionSummaryForPrompt
|
|
14385
|
+
)
|
|
14097
14386
|
};
|
|
14098
14387
|
}
|
|
14099
14388
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
@@ -14490,17 +14779,14 @@ ${resultPreview}` : null
|
|
|
14490
14779
|
].filter(Boolean).join("\n\n");
|
|
14491
14780
|
}
|
|
14492
14781
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
14493
|
-
return domainDocumentation?.trim() || "No domain
|
|
14782
|
+
return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
|
|
14494
14783
|
}
|
|
14495
14784
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
14496
14785
|
if (!sessionContext) return "No session metadata available.";
|
|
14497
14786
|
const rows = [
|
|
14498
14787
|
["sandboxId", sessionContext.sandboxId],
|
|
14499
14788
|
["environmentId", sessionContext.environmentId],
|
|
14500
|
-
["
|
|
14501
|
-
["granularId", sessionContext.granularId],
|
|
14502
|
-
["userName", sessionContext.userName],
|
|
14503
|
-
["domainRevision", sessionContext.domainRevision]
|
|
14789
|
+
["userName", sessionContext.userName]
|
|
14504
14790
|
];
|
|
14505
14791
|
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
14506
14792
|
if (activeRows.length === 0) return "No session metadata available.";
|
|
@@ -14509,6 +14795,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
|
|
|
14509
14795
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
14510
14796
|
return heapSummary?.trim() || "Heap is empty for this session.";
|
|
14511
14797
|
}
|
|
14798
|
+
function buildGranularAgentReferentBlock(referentSummary) {
|
|
14799
|
+
return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
|
|
14800
|
+
}
|
|
14512
14801
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
14513
14802
|
return loopSummary?.trim() || "No active loop state recorded for this session.";
|
|
14514
14803
|
}
|
|
@@ -14532,7 +14821,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
14532
14821
|
(tool) => Boolean(tool.className && !tool.static)
|
|
14533
14822
|
);
|
|
14534
14823
|
const lines = [
|
|
14535
|
-
"Treat this block as the planning map. Use DOMAIN
|
|
14824
|
+
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
14536
14825
|
];
|
|
14537
14826
|
const appendGroup = (title, group) => {
|
|
14538
14827
|
lines.push(`- ${title}:`);
|
|
@@ -14580,7 +14869,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
|
14580
14869
|
if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
|
|
14581
14870
|
lines.push("latestActionSummary:");
|
|
14582
14871
|
for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
|
|
14583
|
-
|
|
14872
|
+
const normalizedLine = normalizeActionSummaryForPrompt(line);
|
|
14873
|
+
lines.push(
|
|
14874
|
+
normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
|
|
14875
|
+
);
|
|
14584
14876
|
}
|
|
14585
14877
|
}
|
|
14586
14878
|
if (checkpoint.latestJobResult?.trim()) {
|
|
@@ -14596,9 +14888,10 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
14596
14888
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
14597
14889
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
14598
14890
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
14891
|
+
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
14599
14892
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
14600
14893
|
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
|
|
14894
|
+
You can help the user understand the domain, answer questions, or generate and execute code against the live session.
|
|
14602
14895
|
Your tone must be natural and human-like.
|
|
14603
14896
|
|
|
14604
14897
|
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 +14900,8 @@ When you call \`execute_code\`, additional assistant text must be either:
|
|
|
14607
14900
|
- a brief summary of the actions the generated code will perform.
|
|
14608
14901
|
Do not include any other kind of commentary when calling \`execute_code\`.
|
|
14609
14902
|
- 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(...)\`.
|
|
14903
|
+
- 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.
|
|
14904
|
+
- 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
14905
|
- 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
14906
|
|
|
14612
14907
|
\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
|
|
@@ -14626,6 +14921,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
|
|
|
14626
14921
|
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
14627
14922
|
- If you need clarification, ask in everyday language.
|
|
14628
14923
|
- 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.
|
|
14924
|
+
- 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
14925
|
- Keep replies concise and clear.
|
|
14630
14926
|
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
14631
14927
|
|
|
@@ -14635,9 +14931,9 @@ ${sessionBlock}
|
|
|
14635
14931
|
\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
|
|
14636
14932
|
${toolBlock}
|
|
14637
14933
|
|
|
14638
|
-
\u2500\u2500\u2500 DOMAIN
|
|
14934
|
+
\u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
|
|
14639
14935
|
Import classes and effect functions from \`./sandbox-tools\` in generated code.
|
|
14640
|
-
|
|
14936
|
+
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
|
|
14641
14937
|
|
|
14642
14938
|
${domainBlock}
|
|
14643
14939
|
|
|
@@ -14647,6 +14943,9 @@ ${checkpointBlock}
|
|
|
14647
14943
|
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
14648
14944
|
${workflowBlock}
|
|
14649
14945
|
|
|
14946
|
+
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
14947
|
+
${referentBlock}
|
|
14948
|
+
|
|
14650
14949
|
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
14651
14950
|
${heapBlock}
|
|
14652
14951
|
|
|
@@ -14654,109 +14953,54 @@ ${heapBlock}
|
|
|
14654
14953
|
${loopBlock}
|
|
14655
14954
|
|
|
14656
14955
|
\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.
|
|
14956
|
+
- 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.
|
|
14957
|
+
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
14958
|
+
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
14959
|
+
- 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.
|
|
14960
|
+
- 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.
|
|
14961
|
+
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
14962
|
+
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
14963
|
+
- 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.
|
|
14964
|
+
- 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.
|
|
14965
|
+
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
14966
|
+
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
14967
|
+
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
14968
|
+
- 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.
|
|
14969
|
+
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
14970
|
+
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
14971
|
+
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
14972
|
+
- 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.
|
|
14973
|
+
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
14702
14974
|
|
|
14703
14975
|
\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
|
|
14704
14976
|
- Import from \`./sandbox-tools\`.
|
|
14705
|
-
- If you use \`heap\`, \`loop\`, \`agent_text_message\`,
|
|
14977
|
+
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
|
|
14706
14978
|
- 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.
|
|
14979
|
+
- The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
|
|
14980
|
+
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
|
|
14981
|
+
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
|
|
14982
|
+
- 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.
|
|
14983
|
+
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
|
|
14984
|
+
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
|
|
14985
|
+
- 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.
|
|
14986
|
+
- 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.
|
|
14987
|
+
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
|
|
14988
|
+
- 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(...)\`.
|
|
14989
|
+
- 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.
|
|
14990
|
+
- Call instance methods on instances, static methods on classes, and global effects by name.
|
|
14991
|
+
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
|
|
14992
|
+
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
|
|
14993
|
+
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
|
|
14994
|
+
- 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.
|
|
14995
|
+
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
|
|
14996
|
+
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
|
|
14997
|
+
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
|
|
14998
|
+
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
|
|
14999
|
+
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
15000
|
+
- Use \`agent_text_message(...)\` for user-visible text.
|
|
15001
|
+
- 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.
|
|
15002
|
+
- 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.
|
|
15003
|
+
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
|
|
14760
15004
|
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
|
|
14761
15005
|
}
|
|
14762
15006
|
|
|
@@ -14915,6 +15159,10 @@ function fallbackResponseText(entries, lists) {
|
|
|
14915
15159
|
return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
|
|
14916
15160
|
}
|
|
14917
15161
|
if (lists.length > 0) {
|
|
15162
|
+
const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
|
|
15163
|
+
if (emptyOnly) {
|
|
15164
|
+
return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
|
|
15165
|
+
}
|
|
14918
15166
|
return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
|
|
14919
15167
|
}
|
|
14920
15168
|
return null;
|
|
@@ -15299,6 +15547,7 @@ exports.buildGranularAgentCheckpointBlock = buildGranularAgentCheckpointBlock;
|
|
|
15299
15547
|
exports.buildGranularAgentDomainBlock = buildGranularAgentDomainBlock;
|
|
15300
15548
|
exports.buildGranularAgentHeapBlock = buildGranularAgentHeapBlock;
|
|
15301
15549
|
exports.buildGranularAgentLoopBlock = buildGranularAgentLoopBlock;
|
|
15550
|
+
exports.buildGranularAgentReferentBlock = buildGranularAgentReferentBlock;
|
|
15302
15551
|
exports.buildGranularAgentSessionBlock = buildGranularAgentSessionBlock;
|
|
15303
15552
|
exports.buildGranularAgentSystemPrompt = buildGranularAgentSystemPrompt;
|
|
15304
15553
|
exports.buildGranularAgentToolBlock = buildGranularAgentToolBlock;
|
|
@@ -15316,6 +15565,8 @@ exports.normalizeEffectBehaviors = normalizeEffectBehaviors;
|
|
|
15316
15565
|
exports.normalizePrompt = normalizePrompt;
|
|
15317
15566
|
exports.normalizePromptText = normalizePromptText;
|
|
15318
15567
|
exports.normalizePromptType = normalizePromptType;
|
|
15568
|
+
exports.projectConversationReferentFocus = projectConversationReferentFocus;
|
|
15569
|
+
exports.projectConversationReferentSummary = projectConversationReferentSummary;
|
|
15319
15570
|
exports.projectHeapSummary = projectHeapSummary;
|
|
15320
15571
|
exports.projectLoopSummary = projectLoopSummary;
|
|
15321
15572
|
exports.projectWorkflowFocus = projectWorkflowFocus;
|