@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/index.mjs
CHANGED
|
@@ -10953,17 +10953,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
10953
10953
|
}
|
|
10954
10954
|
},
|
|
10955
10955
|
domain: {
|
|
10956
|
-
applyToPropertyIR(propertyIR,
|
|
10957
|
-
|
|
10958
|
-
return propertyIR;
|
|
10959
|
-
}
|
|
10960
|
-
return {
|
|
10961
|
-
...propertyIR,
|
|
10962
|
-
docs: [
|
|
10963
|
-
...propertyIR.docs,
|
|
10964
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
10965
|
-
]
|
|
10966
|
-
};
|
|
10956
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
10957
|
+
return propertyIR;
|
|
10967
10958
|
}
|
|
10968
10959
|
}
|
|
10969
10960
|
});
|
|
@@ -11577,17 +11568,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
11577
11568
|
|
|
11578
11569
|
// src/client.ts
|
|
11579
11570
|
var STANDARD_MODULES_OPERATIONS = [
|
|
11580
|
-
{
|
|
11571
|
+
{
|
|
11572
|
+
create: "entity",
|
|
11573
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
11574
|
+
},
|
|
11581
11575
|
{ create: "class", extends: "entity", has: {} },
|
|
11582
|
-
{
|
|
11583
|
-
|
|
11576
|
+
{
|
|
11577
|
+
create: "user",
|
|
11578
|
+
extends: "entity",
|
|
11579
|
+
has: {
|
|
11580
|
+
email: { value: void 0 },
|
|
11581
|
+
firstName: { value: void 0 },
|
|
11582
|
+
lastName: { value: void 0 }
|
|
11583
|
+
}
|
|
11584
|
+
},
|
|
11585
|
+
{
|
|
11586
|
+
create: "company",
|
|
11587
|
+
extends: "entity",
|
|
11588
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
11589
|
+
},
|
|
11584
11590
|
{ create: "string", has: {} },
|
|
11585
11591
|
{ create: "number", has: {} },
|
|
11586
11592
|
{ create: "boolean", has: {} },
|
|
11587
|
-
{
|
|
11593
|
+
{
|
|
11594
|
+
create: "tool_parameter",
|
|
11595
|
+
has: {
|
|
11596
|
+
name: { value: void 0 },
|
|
11597
|
+
type: { value: "string" },
|
|
11598
|
+
description: { value: void 0 },
|
|
11599
|
+
required: { value: false }
|
|
11600
|
+
}
|
|
11601
|
+
}
|
|
11588
11602
|
];
|
|
11589
11603
|
var BUILTIN_MODULES = {
|
|
11590
|
-
|
|
11604
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
11591
11605
|
};
|
|
11592
11606
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11593
11607
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -11622,7 +11636,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
11622
11636
|
}
|
|
11623
11637
|
function isRetryableRecordObjectsError(error) {
|
|
11624
11638
|
const message = error instanceof Error ? error.message : String(error);
|
|
11625
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11639
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11640
|
+
message
|
|
11641
|
+
);
|
|
11626
11642
|
}
|
|
11627
11643
|
function computeEffectKey2(effect) {
|
|
11628
11644
|
const attachedClass = effect.className?.trim();
|
|
@@ -11699,7 +11715,10 @@ function normalizeUser(user) {
|
|
|
11699
11715
|
};
|
|
11700
11716
|
}
|
|
11701
11717
|
function normalizeEnvironmentData(environment) {
|
|
11702
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11718
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11719
|
+
mode: "pinned",
|
|
11720
|
+
versionId: environment.versionId || environment.buildId
|
|
11721
|
+
});
|
|
11703
11722
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
11704
11723
|
return {
|
|
11705
11724
|
...environment,
|
|
@@ -11793,14 +11812,16 @@ var Environment = class extends Session {
|
|
|
11793
11812
|
const response = await fetch(url, {
|
|
11794
11813
|
...options,
|
|
11795
11814
|
headers: {
|
|
11796
|
-
|
|
11815
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11797
11816
|
"Content-Type": "application/json",
|
|
11798
|
-
|
|
11817
|
+
Connection: "close",
|
|
11799
11818
|
...options.headers
|
|
11800
11819
|
}
|
|
11801
11820
|
});
|
|
11802
11821
|
if (!response.ok) {
|
|
11803
|
-
throw new Error(
|
|
11822
|
+
throw new Error(
|
|
11823
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
11824
|
+
);
|
|
11804
11825
|
}
|
|
11805
11826
|
return response.json();
|
|
11806
11827
|
}
|
|
@@ -11814,9 +11835,12 @@ var Environment = class extends Session {
|
|
|
11814
11835
|
async disconnect() {
|
|
11815
11836
|
let wsNotifiedRuntime = false;
|
|
11816
11837
|
try {
|
|
11817
|
-
const goodbye = await this.rpc(
|
|
11818
|
-
|
|
11819
|
-
|
|
11838
|
+
const goodbye = await this.rpc(
|
|
11839
|
+
"client.goodbye",
|
|
11840
|
+
{
|
|
11841
|
+
timestamp: Date.now()
|
|
11842
|
+
}
|
|
11843
|
+
);
|
|
11820
11844
|
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11821
11845
|
} catch {
|
|
11822
11846
|
wsNotifiedRuntime = false;
|
|
@@ -11830,8 +11854,8 @@ var Environment = class extends Session {
|
|
|
11830
11854
|
method: "POST",
|
|
11831
11855
|
headers: {
|
|
11832
11856
|
"Content-Type": "application/json",
|
|
11833
|
-
|
|
11834
|
-
|
|
11857
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11858
|
+
Connection: "close"
|
|
11835
11859
|
},
|
|
11836
11860
|
body: JSON.stringify({
|
|
11837
11861
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -11844,6 +11868,15 @@ var Environment = class extends Session {
|
|
|
11844
11868
|
}
|
|
11845
11869
|
this.client.disconnect();
|
|
11846
11870
|
}
|
|
11871
|
+
/**
|
|
11872
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
11873
|
+
*
|
|
11874
|
+
* Use this when the caller intends to immediately reattach to the same
|
|
11875
|
+
* session after an unexpected disconnect.
|
|
11876
|
+
*/
|
|
11877
|
+
disconnectTransport() {
|
|
11878
|
+
this.client.disconnect();
|
|
11879
|
+
}
|
|
11847
11880
|
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11848
11881
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11849
11882
|
graphContainerStatus = null;
|
|
@@ -11909,14 +11942,14 @@ var Environment = class extends Session {
|
|
|
11909
11942
|
}
|
|
11910
11943
|
/**
|
|
11911
11944
|
* Execute a GraphQL query against the environment's graph.
|
|
11912
|
-
*
|
|
11945
|
+
*
|
|
11913
11946
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
11914
11947
|
* Authentication is handled automatically using the SDK's API key.
|
|
11915
|
-
*
|
|
11948
|
+
*
|
|
11916
11949
|
* @param query - The GraphQL query string
|
|
11917
11950
|
* @param variables - Optional variables for the query
|
|
11918
11951
|
* @returns The query result data
|
|
11919
|
-
*
|
|
11952
|
+
*
|
|
11920
11953
|
* @example
|
|
11921
11954
|
* ```typescript
|
|
11922
11955
|
* // Read the workspace
|
|
@@ -11924,7 +11957,7 @@ var Environment = class extends Session {
|
|
|
11924
11957
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
11925
11958
|
* );
|
|
11926
11959
|
* console.log(result.data);
|
|
11927
|
-
*
|
|
11960
|
+
*
|
|
11928
11961
|
* // Create a model
|
|
11929
11962
|
* const created = await env.graphql(
|
|
11930
11963
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -11936,7 +11969,7 @@ var Environment = class extends Session {
|
|
|
11936
11969
|
method: "POST",
|
|
11937
11970
|
headers: {
|
|
11938
11971
|
"Content-Type": "application/json",
|
|
11939
|
-
|
|
11972
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
11940
11973
|
},
|
|
11941
11974
|
body: JSON.stringify({
|
|
11942
11975
|
environmentId: this.environmentId,
|
|
@@ -11953,10 +11986,10 @@ var Environment = class extends Session {
|
|
|
11953
11986
|
// ==================== RELATIONSHIP METHODS ====================
|
|
11954
11987
|
/**
|
|
11955
11988
|
* Define a relationship between two model types.
|
|
11956
|
-
*
|
|
11989
|
+
*
|
|
11957
11990
|
* Creates both submodels (if they don't exist) and links them with
|
|
11958
11991
|
* a RelationshipDef node that encodes cardinality.
|
|
11959
|
-
*
|
|
11992
|
+
*
|
|
11960
11993
|
* @example
|
|
11961
11994
|
* ```typescript
|
|
11962
11995
|
* // Author has many Books, Book has one Author
|
|
@@ -12016,10 +12049,10 @@ var Environment = class extends Session {
|
|
|
12016
12049
|
}
|
|
12017
12050
|
/**
|
|
12018
12051
|
* Get all relationships for a model type.
|
|
12019
|
-
*
|
|
12052
|
+
*
|
|
12020
12053
|
* @param modelPath - The model type path (e.g., "author")
|
|
12021
12054
|
* @returns Array of relationships from this model's perspective
|
|
12022
|
-
*
|
|
12055
|
+
*
|
|
12023
12056
|
* @example
|
|
12024
12057
|
* ```typescript
|
|
12025
12058
|
* const rels = await env.getRelationships('author');
|
|
@@ -12052,18 +12085,18 @@ var Environment = class extends Session {
|
|
|
12052
12085
|
}
|
|
12053
12086
|
/**
|
|
12054
12087
|
* Attach a target model to a relationship submodel.
|
|
12055
|
-
*
|
|
12088
|
+
*
|
|
12056
12089
|
* Handles cardinality automatically:
|
|
12057
12090
|
* - "One" side: sets/replaces the reference
|
|
12058
12091
|
* - "Many" side: adds the target to the collection
|
|
12059
|
-
*
|
|
12092
|
+
*
|
|
12060
12093
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
12061
12094
|
* Bidirectional sync is automatic.
|
|
12062
|
-
*
|
|
12095
|
+
*
|
|
12063
12096
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
12064
12097
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
12065
12098
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
12066
|
-
*
|
|
12099
|
+
*
|
|
12067
12100
|
* @example
|
|
12068
12101
|
* ```typescript
|
|
12069
12102
|
* // Attach a book to an author (many side)
|
|
@@ -12090,18 +12123,18 @@ var Environment = class extends Session {
|
|
|
12090
12123
|
}
|
|
12091
12124
|
/**
|
|
12092
12125
|
* Detach a target model from a relationship submodel.
|
|
12093
|
-
*
|
|
12126
|
+
*
|
|
12094
12127
|
* Handles bidirectional cleanup automatically.
|
|
12095
|
-
*
|
|
12128
|
+
*
|
|
12096
12129
|
* @param modelPath - The model instance path
|
|
12097
12130
|
* @param submodelPath - The relationship submodel
|
|
12098
12131
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
12099
|
-
*
|
|
12132
|
+
*
|
|
12100
12133
|
* @example
|
|
12101
12134
|
* ```typescript
|
|
12102
12135
|
* // Detach a specific book
|
|
12103
12136
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
12104
|
-
*
|
|
12137
|
+
*
|
|
12105
12138
|
* // Detach all books
|
|
12106
12139
|
* await env.detach('tolkien', 'books');
|
|
12107
12140
|
* ```
|
|
@@ -12125,11 +12158,11 @@ var Environment = class extends Session {
|
|
|
12125
12158
|
}
|
|
12126
12159
|
/**
|
|
12127
12160
|
* List all related models through a relationship submodel.
|
|
12128
|
-
*
|
|
12161
|
+
*
|
|
12129
12162
|
* @param modelPath - The model instance path
|
|
12130
12163
|
* @param submodelPath - The relationship submodel
|
|
12131
12164
|
* @returns Array of related model references
|
|
12132
|
-
*
|
|
12165
|
+
*
|
|
12133
12166
|
* @example
|
|
12134
12167
|
* ```typescript
|
|
12135
12168
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -12153,14 +12186,14 @@ var Environment = class extends Session {
|
|
|
12153
12186
|
}
|
|
12154
12187
|
/**
|
|
12155
12188
|
* Apply a manifest to the current environment's graph.
|
|
12156
|
-
*
|
|
12189
|
+
*
|
|
12157
12190
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
12158
12191
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
12159
12192
|
* relationships from a declarative manifest.
|
|
12160
|
-
*
|
|
12193
|
+
*
|
|
12161
12194
|
* @param manifest - The manifest content to apply
|
|
12162
12195
|
* @returns Summary of applied operations
|
|
12163
|
-
*
|
|
12196
|
+
*
|
|
12164
12197
|
* @example
|
|
12165
12198
|
* ```typescript
|
|
12166
12199
|
* await environment.applyManifest({
|
|
@@ -12198,12 +12231,16 @@ var Environment = class extends Session {
|
|
|
12198
12231
|
applied++;
|
|
12199
12232
|
} catch (err) {
|
|
12200
12233
|
if (!err.message?.includes("already exists")) {
|
|
12201
|
-
errors.push(
|
|
12234
|
+
errors.push(
|
|
12235
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
12236
|
+
);
|
|
12202
12237
|
}
|
|
12203
12238
|
}
|
|
12204
12239
|
}
|
|
12205
12240
|
} else {
|
|
12206
|
-
errors.push(
|
|
12241
|
+
errors.push(
|
|
12242
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
12243
|
+
);
|
|
12207
12244
|
}
|
|
12208
12245
|
}
|
|
12209
12246
|
}
|
|
@@ -12287,7 +12324,10 @@ var Environment = class extends Session {
|
|
|
12287
12324
|
}
|
|
12288
12325
|
}
|
|
12289
12326
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
12290
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
12327
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
12328
|
+
toolPath,
|
|
12329
|
+
metamodels
|
|
12330
|
+
)) {
|
|
12291
12331
|
await this._runGraphql(mutation.query, mutation.label);
|
|
12292
12332
|
}
|
|
12293
12333
|
}
|
|
@@ -12336,7 +12376,9 @@ var Environment = class extends Session {
|
|
|
12336
12376
|
}
|
|
12337
12377
|
if (eventType.payloadSchema?.properties) {
|
|
12338
12378
|
const fieldSpecs = {};
|
|
12339
|
-
for (const [propName, propSchema] of Object.entries(
|
|
12379
|
+
for (const [propName, propSchema] of Object.entries(
|
|
12380
|
+
eventType.payloadSchema.properties
|
|
12381
|
+
)) {
|
|
12340
12382
|
const schema = propSchema;
|
|
12341
12383
|
fieldSpecs[propName] = {
|
|
12342
12384
|
type: schema.type ?? "string",
|
|
@@ -12619,7 +12661,9 @@ var Environment = class extends Session {
|
|
|
12619
12661
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12620
12662
|
await Promise.all(
|
|
12621
12663
|
wave.map(async (plan) => {
|
|
12622
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12664
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12665
|
+
plan.slice
|
|
12666
|
+
);
|
|
12623
12667
|
if (items.length !== plan.slice.length) {
|
|
12624
12668
|
throw new Error(
|
|
12625
12669
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -12649,13 +12693,10 @@ var Environment = class extends Session {
|
|
|
12649
12693
|
let lastError;
|
|
12650
12694
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12651
12695
|
try {
|
|
12652
|
-
const response = await this.controlPlaneRequest(
|
|
12653
|
-
|
|
12654
|
-
{
|
|
12655
|
-
|
|
12656
|
-
body: JSON.stringify({ records: chunk })
|
|
12657
|
-
}
|
|
12658
|
-
);
|
|
12696
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
12697
|
+
method: "POST",
|
|
12698
|
+
body: JSON.stringify({ records: chunk })
|
|
12699
|
+
});
|
|
12659
12700
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
12660
12701
|
return { items, durationMs: Date.now() - wallStart };
|
|
12661
12702
|
} catch (error) {
|
|
@@ -12718,7 +12759,9 @@ var Environment = class extends Session {
|
|
|
12718
12759
|
* Fetch a single record import by id.
|
|
12719
12760
|
*/
|
|
12720
12761
|
async getRecordImport(importId) {
|
|
12721
|
-
return this.controlPlaneRequest(
|
|
12762
|
+
return this.controlPlaneRequest(
|
|
12763
|
+
`/control/record-imports/${importId}`
|
|
12764
|
+
);
|
|
12722
12765
|
}
|
|
12723
12766
|
/**
|
|
12724
12767
|
* Cancel a queued/background record import.
|
|
@@ -12785,7 +12828,9 @@ var Granular = class _Granular {
|
|
|
12785
12828
|
constructor(options) {
|
|
12786
12829
|
const auth = options.token ?? options.apiKey;
|
|
12787
12830
|
if (!auth) {
|
|
12788
|
-
throw new Error(
|
|
12831
|
+
throw new Error(
|
|
12832
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
12833
|
+
);
|
|
12789
12834
|
}
|
|
12790
12835
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
12791
12836
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -12797,10 +12842,10 @@ var Granular = class _Granular {
|
|
|
12797
12842
|
}
|
|
12798
12843
|
/**
|
|
12799
12844
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12800
|
-
*
|
|
12845
|
+
*
|
|
12801
12846
|
* @param options - User options
|
|
12802
12847
|
* @returns The recorded user with both `userId` and `granularId`
|
|
12803
|
-
*
|
|
12848
|
+
*
|
|
12804
12849
|
* @example
|
|
12805
12850
|
* ```typescript
|
|
12806
12851
|
* const user = await granular.recordUser({
|
|
@@ -12811,14 +12856,16 @@ var Granular = class _Granular {
|
|
|
12811
12856
|
* ```
|
|
12812
12857
|
*/
|
|
12813
12858
|
async recordUser(options) {
|
|
12814
|
-
const subject = normalizeSubject(
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12859
|
+
const subject = normalizeSubject(
|
|
12860
|
+
await this.request("/control/subjects", {
|
|
12861
|
+
method: "POST",
|
|
12862
|
+
body: JSON.stringify({
|
|
12863
|
+
identityId: options.userId,
|
|
12864
|
+
name: options.name,
|
|
12865
|
+
email: options.email
|
|
12866
|
+
})
|
|
12820
12867
|
})
|
|
12821
|
-
|
|
12868
|
+
);
|
|
12822
12869
|
return normalizeUser({
|
|
12823
12870
|
granularId: subject.granularId,
|
|
12824
12871
|
userId: options.userId,
|
|
@@ -12865,18 +12912,20 @@ var Granular = class _Granular {
|
|
|
12865
12912
|
permissions: options.permissions || []
|
|
12866
12913
|
};
|
|
12867
12914
|
}
|
|
12868
|
-
throw new Error(
|
|
12915
|
+
throw new Error(
|
|
12916
|
+
"connect() requires either userId, granularId, or a user object returned by recordUser()."
|
|
12917
|
+
);
|
|
12869
12918
|
}
|
|
12870
12919
|
/**
|
|
12871
12920
|
* Connect to an ontology environment and establish a real-time session.
|
|
12872
|
-
*
|
|
12921
|
+
*
|
|
12873
12922
|
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12874
12923
|
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12875
12924
|
* the sandbox registry automatically.
|
|
12876
|
-
*
|
|
12925
|
+
*
|
|
12877
12926
|
* @param options - Connection options
|
|
12878
12927
|
* @returns An active environment session
|
|
12879
|
-
*
|
|
12928
|
+
*
|
|
12880
12929
|
* @example
|
|
12881
12930
|
* ```typescript
|
|
12882
12931
|
* const environment = await granular.connect({
|
|
@@ -12885,23 +12934,23 @@ var Granular = class _Granular {
|
|
|
12885
12934
|
* userId: 'user_123',
|
|
12886
12935
|
* permissions: ['agent'],
|
|
12887
12936
|
* });
|
|
12888
|
-
*
|
|
12937
|
+
*
|
|
12889
12938
|
* await granular.registerEffect('my-sandbox', {
|
|
12890
12939
|
* name: 'greet',
|
|
12891
12940
|
* description: 'Say hello',
|
|
12892
12941
|
* inputSchema: { type: 'object', properties: {} },
|
|
12893
12942
|
* handler: async () => 'Hello!',
|
|
12894
12943
|
* });
|
|
12895
|
-
*
|
|
12944
|
+
*
|
|
12896
12945
|
* // Submit job
|
|
12897
12946
|
* const job = await environment.submitJob(`
|
|
12898
12947
|
* import { tools } from './sandbox-tools';
|
|
12899
12948
|
* return await tools.greet({});
|
|
12900
12949
|
* `);
|
|
12901
|
-
*
|
|
12950
|
+
*
|
|
12902
12951
|
* console.log(await job.result); // 'Hello!'
|
|
12903
12952
|
* ```
|
|
12904
|
-
|
|
12953
|
+
*/
|
|
12905
12954
|
async connect(options) {
|
|
12906
12955
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
12907
12956
|
const ontology = options.ontology;
|
|
@@ -12916,8 +12965,15 @@ var Granular = class _Granular {
|
|
|
12916
12965
|
const user = await this.resolveConnectUser(options);
|
|
12917
12966
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12918
12967
|
for (const profileName of user.permissions) {
|
|
12919
|
-
const profileId = await this.ensurePermissionProfile(
|
|
12920
|
-
|
|
12968
|
+
const profileId = await this.ensurePermissionProfile(
|
|
12969
|
+
sandbox.sandboxId,
|
|
12970
|
+
profileName
|
|
12971
|
+
);
|
|
12972
|
+
await this.ensureAssignment(
|
|
12973
|
+
user.granularId,
|
|
12974
|
+
sandbox.sandboxId,
|
|
12975
|
+
profileId
|
|
12976
|
+
);
|
|
12921
12977
|
}
|
|
12922
12978
|
const envData = await this.environments.create(sandbox.sandboxId, {
|
|
12923
12979
|
subjectId: user.granularId,
|
|
@@ -12969,7 +13025,9 @@ var Granular = class _Granular {
|
|
|
12969
13025
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
12970
13026
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
12971
13027
|
summary: row.summary != null ? String(row.summary) : null,
|
|
12972
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13028
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13029
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
13030
|
+
) : null,
|
|
12973
13031
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
12974
13032
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
12975
13033
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -13059,7 +13117,13 @@ var Granular = class _Granular {
|
|
|
13059
13117
|
});
|
|
13060
13118
|
await client.connect();
|
|
13061
13119
|
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13062
|
-
const environment = new Environment(
|
|
13120
|
+
const environment = new Environment(
|
|
13121
|
+
client,
|
|
13122
|
+
envData,
|
|
13123
|
+
clientId,
|
|
13124
|
+
this.apiKey,
|
|
13125
|
+
graphqlEndpoint
|
|
13126
|
+
);
|
|
13063
13127
|
await environment.hello();
|
|
13064
13128
|
return environment;
|
|
13065
13129
|
}
|
|
@@ -13093,14 +13157,18 @@ var Granular = class _Granular {
|
|
|
13093
13157
|
};
|
|
13094
13158
|
}
|
|
13095
13159
|
async publishSandboxEffectCatalog(host) {
|
|
13096
|
-
const effects = Array.from(
|
|
13097
|
-
(
|
|
13098
|
-
);
|
|
13099
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13160
|
+
const effects = Array.from(
|
|
13161
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
13162
|
+
).map((effect) => this.serializeEffect(effect));
|
|
13163
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13164
|
+
effects
|
|
13165
|
+
});
|
|
13100
13166
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
13101
13167
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
13102
13168
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
13103
|
-
const detail = rejected.map(
|
|
13169
|
+
const detail = rejected.map(
|
|
13170
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
13171
|
+
).join("; ");
|
|
13104
13172
|
throw new Error(
|
|
13105
13173
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
13106
13174
|
);
|
|
@@ -13134,13 +13202,15 @@ var Granular = class _Granular {
|
|
|
13134
13202
|
disconnectError
|
|
13135
13203
|
);
|
|
13136
13204
|
}
|
|
13137
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13205
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13206
|
+
(reconnectError) => {
|
|
13207
|
+
console.error(
|
|
13208
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
13209
|
+
reconnectError
|
|
13210
|
+
);
|
|
13211
|
+
console.error("[Granular] Original heartbeat failure:", error);
|
|
13212
|
+
}
|
|
13213
|
+
);
|
|
13144
13214
|
}
|
|
13145
13215
|
startEffectHostHeartbeat(host) {
|
|
13146
13216
|
if (host.heartbeatTimer) {
|
|
@@ -13161,9 +13231,15 @@ var Granular = class _Granular {
|
|
|
13161
13231
|
host.heartbeatInFlight = false;
|
|
13162
13232
|
});
|
|
13163
13233
|
};
|
|
13164
|
-
sendHeartbeat(
|
|
13234
|
+
sendHeartbeat(
|
|
13235
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
13236
|
+
false
|
|
13237
|
+
);
|
|
13165
13238
|
host.heartbeatTimer = setInterval(() => {
|
|
13166
|
-
sendHeartbeat(
|
|
13239
|
+
sendHeartbeat(
|
|
13240
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
13241
|
+
true
|
|
13242
|
+
);
|
|
13167
13243
|
}, 1e4);
|
|
13168
13244
|
}
|
|
13169
13245
|
stopEffectHostHeartbeat(host) {
|
|
@@ -13195,7 +13271,12 @@ var Granular = class _Granular {
|
|
|
13195
13271
|
const effectClientId = crypto.randomUUID();
|
|
13196
13272
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
13197
13273
|
const wsClient = new WSClient({
|
|
13198
|
-
url: buildEffectHostUrl(
|
|
13274
|
+
url: buildEffectHostUrl(
|
|
13275
|
+
this.apiUrl,
|
|
13276
|
+
sandboxId,
|
|
13277
|
+
effectClientId,
|
|
13278
|
+
clientId
|
|
13279
|
+
),
|
|
13199
13280
|
sessionId: `effect-host:${effectClientId}`,
|
|
13200
13281
|
token: this.apiKey,
|
|
13201
13282
|
tokenProvider: this.tokenProvider,
|
|
@@ -13214,7 +13295,10 @@ var Granular = class _Granular {
|
|
|
13214
13295
|
};
|
|
13215
13296
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
13216
13297
|
const request = params;
|
|
13217
|
-
return invokeRegisteredEffect(
|
|
13298
|
+
return invokeRegisteredEffect(
|
|
13299
|
+
this.getSandboxEffectMap(sandboxId),
|
|
13300
|
+
request
|
|
13301
|
+
);
|
|
13218
13302
|
});
|
|
13219
13303
|
wsClient.on("open", () => {
|
|
13220
13304
|
void this.synchronizeEffectHost(host).catch((error) => {
|
|
@@ -13262,7 +13346,7 @@ var Granular = class _Granular {
|
|
|
13262
13346
|
}
|
|
13263
13347
|
/**
|
|
13264
13348
|
* Register multiple effects (tools) for a specific sandbox.
|
|
13265
|
-
*
|
|
13349
|
+
*
|
|
13266
13350
|
* batch version of `registerEffect`.
|
|
13267
13351
|
*/
|
|
13268
13352
|
async registerEffects(sandboxNameOrId, effects) {
|
|
@@ -13276,7 +13360,7 @@ var Granular = class _Granular {
|
|
|
13276
13360
|
}
|
|
13277
13361
|
/**
|
|
13278
13362
|
* Unregister an effect from a sandbox.
|
|
13279
|
-
*
|
|
13363
|
+
*
|
|
13280
13364
|
* Removes it from the local sandbox registry and updates the
|
|
13281
13365
|
* sandbox-scoped live catalog.
|
|
13282
13366
|
*/
|
|
@@ -13375,27 +13459,31 @@ var Granular = class _Granular {
|
|
|
13375
13459
|
const assignments = await this.request(
|
|
13376
13460
|
`/control/subjects/${subjectId}/assignments`
|
|
13377
13461
|
);
|
|
13378
|
-
const existing = assignments.items.find(
|
|
13379
|
-
(a) => a.sandboxId === sandboxId
|
|
13380
|
-
);
|
|
13462
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
13381
13463
|
if (existing) {
|
|
13382
13464
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
13383
13465
|
return;
|
|
13384
13466
|
}
|
|
13385
|
-
await this.request(
|
|
13386
|
-
|
|
13387
|
-
|
|
13467
|
+
await this.request(
|
|
13468
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
13469
|
+
{
|
|
13470
|
+
method: "DELETE"
|
|
13471
|
+
}
|
|
13472
|
+
);
|
|
13388
13473
|
}
|
|
13389
13474
|
} catch {
|
|
13390
13475
|
}
|
|
13391
|
-
await this.request(
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13476
|
+
await this.request(
|
|
13477
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
13478
|
+
{
|
|
13479
|
+
method: "POST",
|
|
13480
|
+
body: JSON.stringify({
|
|
13481
|
+
sandboxId,
|
|
13482
|
+
subjectId,
|
|
13483
|
+
permissionProfileId
|
|
13484
|
+
})
|
|
13485
|
+
}
|
|
13486
|
+
);
|
|
13399
13487
|
}
|
|
13400
13488
|
/**
|
|
13401
13489
|
* Sandbox management API
|
|
@@ -13475,23 +13563,33 @@ var Granular = class _Granular {
|
|
|
13475
13563
|
},
|
|
13476
13564
|
get: async (environmentId) => {
|
|
13477
13565
|
return normalizeEnvironmentData(
|
|
13478
|
-
await this.request(
|
|
13566
|
+
await this.request(
|
|
13567
|
+
`/control/environments/${environmentId}`
|
|
13568
|
+
)
|
|
13479
13569
|
);
|
|
13480
13570
|
},
|
|
13481
13571
|
create: async (sandboxId, data) => {
|
|
13482
13572
|
const environmentName = data.environment || data.envName;
|
|
13483
|
-
return normalizeEnvironmentData(
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13573
|
+
return normalizeEnvironmentData(
|
|
13574
|
+
await this.request(
|
|
13575
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
13576
|
+
{
|
|
13577
|
+
method: "POST",
|
|
13578
|
+
body: JSON.stringify({
|
|
13579
|
+
...data,
|
|
13580
|
+
envName: environmentName
|
|
13581
|
+
})
|
|
13582
|
+
}
|
|
13583
|
+
)
|
|
13584
|
+
);
|
|
13490
13585
|
},
|
|
13491
13586
|
delete: async (environmentId) => {
|
|
13492
|
-
return this.request(
|
|
13493
|
-
|
|
13494
|
-
|
|
13587
|
+
return this.request(
|
|
13588
|
+
`/control/environments/${environmentId}`,
|
|
13589
|
+
{
|
|
13590
|
+
method: "DELETE"
|
|
13591
|
+
}
|
|
13592
|
+
);
|
|
13495
13593
|
}
|
|
13496
13594
|
};
|
|
13497
13595
|
}
|
|
@@ -13511,10 +13609,13 @@ var Granular = class _Granular {
|
|
|
13511
13609
|
}
|
|
13512
13610
|
if (params.since) query.set("since", params.since.toISOString());
|
|
13513
13611
|
if (params.until) query.set("until", params.until.toISOString());
|
|
13514
|
-
if (params.isAcked !== void 0)
|
|
13612
|
+
if (params.isAcked !== void 0)
|
|
13613
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
13515
13614
|
if (params.limit) query.set("limit", String(params.limit));
|
|
13516
13615
|
if (params.offset) query.set("offset", String(params.offset));
|
|
13517
|
-
const result = await this.request(
|
|
13616
|
+
const result = await this.request(
|
|
13617
|
+
`/control/stream-events?${query.toString()}`
|
|
13618
|
+
);
|
|
13518
13619
|
return (result.items || []).map((row) => ({
|
|
13519
13620
|
eventId: row.event_id,
|
|
13520
13621
|
streamName: row.stream_name,
|
|
@@ -13545,7 +13646,9 @@ var Granular = class _Granular {
|
|
|
13545
13646
|
since: cursor,
|
|
13546
13647
|
limit: 100
|
|
13547
13648
|
});
|
|
13548
|
-
const orderedEvents = [...events].sort(
|
|
13649
|
+
const orderedEvents = [...events].sort(
|
|
13650
|
+
(a, b) => a.createdAt - b.createdAt
|
|
13651
|
+
);
|
|
13549
13652
|
for (const event of orderedEvents) {
|
|
13550
13653
|
if (seenEventIds.has(event.eventId)) {
|
|
13551
13654
|
continue;
|
|
@@ -13558,15 +13661,19 @@ var Granular = class _Granular {
|
|
|
13558
13661
|
params.onEvent(event);
|
|
13559
13662
|
}
|
|
13560
13663
|
} catch (err) {
|
|
13561
|
-
params.onError?.(
|
|
13664
|
+
params.onError?.(
|
|
13665
|
+
err instanceof Error ? err : new Error(String(err))
|
|
13666
|
+
);
|
|
13562
13667
|
}
|
|
13563
13668
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13564
13669
|
}
|
|
13565
13670
|
};
|
|
13566
13671
|
poll();
|
|
13567
|
-
return {
|
|
13568
|
-
|
|
13569
|
-
|
|
13672
|
+
return {
|
|
13673
|
+
unsubscribe: () => {
|
|
13674
|
+
running = false;
|
|
13675
|
+
}
|
|
13676
|
+
};
|
|
13570
13677
|
},
|
|
13571
13678
|
ack: async (eventId) => {
|
|
13572
13679
|
await this.request("/control/stream-events/ack", {
|
|
@@ -13584,7 +13691,9 @@ var Granular = class _Granular {
|
|
|
13584
13691
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13585
13692
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13586
13693
|
if (params.environment) query.set("environmentId", params.environment);
|
|
13587
|
-
const result = await this.request(
|
|
13694
|
+
const result = await this.request(
|
|
13695
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
13696
|
+
);
|
|
13588
13697
|
return (result.items || []).map((row) => ({
|
|
13589
13698
|
streamName: row.stream_name,
|
|
13590
13699
|
eventType: row.event_type,
|
|
@@ -13602,10 +13711,14 @@ var Granular = class _Granular {
|
|
|
13602
13711
|
get subjects() {
|
|
13603
13712
|
return {
|
|
13604
13713
|
get: async (subjectId) => {
|
|
13605
|
-
return normalizeSubject(
|
|
13714
|
+
return normalizeSubject(
|
|
13715
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
13716
|
+
);
|
|
13606
13717
|
},
|
|
13607
13718
|
listAssignments: async (subjectId) => {
|
|
13608
|
-
return this.request(
|
|
13719
|
+
return this.request(
|
|
13720
|
+
`/control/subjects/${subjectId}/assignments`
|
|
13721
|
+
);
|
|
13609
13722
|
}
|
|
13610
13723
|
};
|
|
13611
13724
|
}
|
|
@@ -13615,24 +13728,31 @@ var Granular = class _Granular {
|
|
|
13615
13728
|
get users() {
|
|
13616
13729
|
return {
|
|
13617
13730
|
create: async (data) => {
|
|
13618
|
-
return normalizeSubject(
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13731
|
+
return normalizeSubject(
|
|
13732
|
+
await this.request("/control/subjects", {
|
|
13733
|
+
method: "POST",
|
|
13734
|
+
body: JSON.stringify({
|
|
13735
|
+
identityId: data.id,
|
|
13736
|
+
name: data.name,
|
|
13737
|
+
email: data.email
|
|
13738
|
+
})
|
|
13624
13739
|
})
|
|
13625
|
-
|
|
13740
|
+
);
|
|
13626
13741
|
},
|
|
13627
13742
|
get: async (id) => {
|
|
13628
|
-
return normalizeSubject(
|
|
13743
|
+
return normalizeSubject(
|
|
13744
|
+
await this.request(`/control/subjects/${id}`)
|
|
13745
|
+
);
|
|
13629
13746
|
}
|
|
13630
13747
|
};
|
|
13631
13748
|
}
|
|
13632
13749
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
13633
13750
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13634
|
-
const result = await this.request(
|
|
13635
|
-
|
|
13751
|
+
const result = await this.request(
|
|
13752
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
13753
|
+
);
|
|
13754
|
+
if (result.items.length === 0)
|
|
13755
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13636
13756
|
return result.items[0].sandboxId;
|
|
13637
13757
|
}
|
|
13638
13758
|
/**
|
|
@@ -13647,9 +13767,9 @@ var Granular = class _Granular {
|
|
|
13647
13767
|
const response = await fetch(url, {
|
|
13648
13768
|
...options,
|
|
13649
13769
|
headers: {
|
|
13650
|
-
|
|
13770
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13651
13771
|
"Content-Type": "application/json",
|
|
13652
|
-
|
|
13772
|
+
Connection: "close",
|
|
13653
13773
|
...options.headers
|
|
13654
13774
|
}
|
|
13655
13775
|
});
|
|
@@ -13660,7 +13780,11 @@ var Granular = class _Granular {
|
|
|
13660
13780
|
return response.json();
|
|
13661
13781
|
}
|
|
13662
13782
|
const errorText = await response.text();
|
|
13663
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
13783
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
13784
|
+
response.status,
|
|
13785
|
+
errorText,
|
|
13786
|
+
url
|
|
13787
|
+
);
|
|
13664
13788
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13665
13789
|
if (this.debugHttp) {
|
|
13666
13790
|
console.warn(
|
|
@@ -13799,21 +13923,6 @@ function reviewGeneratedJobCode(code) {
|
|
|
13799
13923
|
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."
|
|
13800
13924
|
});
|
|
13801
13925
|
}
|
|
13802
|
-
const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
|
|
13803
|
-
for (const call of askUserCalls) {
|
|
13804
|
-
const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
|
|
13805
|
-
const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
|
|
13806
|
-
const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
|
|
13807
|
-
const includesShortlistOptions = /options\s*:\s*\[/.test(call);
|
|
13808
|
-
if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
|
|
13809
|
-
issues.push({
|
|
13810
|
-
code: "disambiguation_requires_choice",
|
|
13811
|
-
severity: "error",
|
|
13812
|
-
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."
|
|
13813
|
-
});
|
|
13814
|
-
break;
|
|
13815
|
-
}
|
|
13816
|
-
}
|
|
13817
13926
|
}
|
|
13818
13927
|
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
13819
13928
|
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
@@ -13859,6 +13968,184 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
13859
13968
|
entryPaths: uniqueStrings(entryPaths, 8)
|
|
13860
13969
|
};
|
|
13861
13970
|
}
|
|
13971
|
+
function normalizeActionSummaryForPrompt(line) {
|
|
13972
|
+
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
13973
|
+
}
|
|
13974
|
+
function collectConversationReferents(liveDoc) {
|
|
13975
|
+
const conversation = asRecord2(liveDoc?.conversation);
|
|
13976
|
+
const persistedReferents = asArray(conversation?.referents).map((value) => asRecord2(value)).filter((value) => Boolean(value));
|
|
13977
|
+
if (persistedReferents.length > 0) {
|
|
13978
|
+
return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
|
|
13979
|
+
}
|
|
13980
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
13981
|
+
const entriesByPath = asRecord2(heap?.entriesByPath) || {};
|
|
13982
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
13983
|
+
const variablesByName = asRecord2(heap?.variablesByName) || {};
|
|
13984
|
+
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));
|
|
13985
|
+
const referents = [];
|
|
13986
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13987
|
+
const pushReferent = (referent) => {
|
|
13988
|
+
if (!referent?.kind || !referent.ref) return;
|
|
13989
|
+
const key = `${referent.kind}:${referent.ref}`;
|
|
13990
|
+
if (seen.has(key)) return;
|
|
13991
|
+
seen.add(key);
|
|
13992
|
+
referents.push(referent);
|
|
13993
|
+
};
|
|
13994
|
+
for (const message of messages) {
|
|
13995
|
+
if (message.role !== "assistant") continue;
|
|
13996
|
+
const show = asRecord2(message.show);
|
|
13997
|
+
if (!show) continue;
|
|
13998
|
+
const ts = Number(message.ts) || 0;
|
|
13999
|
+
const messageId = typeof message.id === "string" ? message.id : void 0;
|
|
14000
|
+
const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
|
|
14001
|
+
for (const entryPath of uniqueStrings(asArray(show.entryPaths))) {
|
|
14002
|
+
const entry = asRecord2(entriesByPath[entryPath]);
|
|
14003
|
+
pushReferent({
|
|
14004
|
+
id: `entry:${entryPath}`,
|
|
14005
|
+
kind: "entry",
|
|
14006
|
+
ref: entryPath,
|
|
14007
|
+
entryPath,
|
|
14008
|
+
className: typeof entry?.className === "string" ? entry.className : void 0,
|
|
14009
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
|
|
14010
|
+
messageId,
|
|
14011
|
+
jobId,
|
|
14012
|
+
ts
|
|
14013
|
+
});
|
|
14014
|
+
}
|
|
14015
|
+
for (const listName of uniqueStrings(asArray(show.listNames))) {
|
|
14016
|
+
const list = asRecord2(listsByName[listName]);
|
|
14017
|
+
pushReferent({
|
|
14018
|
+
id: `list:${listName}`,
|
|
14019
|
+
kind: "list",
|
|
14020
|
+
ref: listName,
|
|
14021
|
+
listName,
|
|
14022
|
+
className: typeof list?.className === "string" ? list.className : void 0,
|
|
14023
|
+
count: Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14024
|
+
messageId,
|
|
14025
|
+
jobId,
|
|
14026
|
+
ts
|
|
14027
|
+
});
|
|
14028
|
+
}
|
|
14029
|
+
for (const variableName of uniqueStrings(
|
|
14030
|
+
asArray(show.variableNames)
|
|
14031
|
+
)) {
|
|
14032
|
+
const variable = asRecord2(variablesByName[variableName]);
|
|
14033
|
+
const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
|
|
14034
|
+
const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
|
|
14035
|
+
const entry = entryPath ? asRecord2(entriesByPath[entryPath]) : null;
|
|
14036
|
+
const list = listName ? asRecord2(listsByName[listName]) : null;
|
|
14037
|
+
pushReferent({
|
|
14038
|
+
id: `variable:${variableName}`,
|
|
14039
|
+
kind: "variable",
|
|
14040
|
+
ref: variableName,
|
|
14041
|
+
variableName,
|
|
14042
|
+
variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
|
|
14043
|
+
entryPath,
|
|
14044
|
+
listName,
|
|
14045
|
+
className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
|
|
14046
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
14047
|
+
count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14048
|
+
scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
|
|
14049
|
+
messageId,
|
|
14050
|
+
jobId,
|
|
14051
|
+
ts
|
|
14052
|
+
});
|
|
14053
|
+
}
|
|
14054
|
+
}
|
|
14055
|
+
return referents;
|
|
14056
|
+
}
|
|
14057
|
+
function projectConversationReferentFocus(liveDoc) {
|
|
14058
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14059
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14060
|
+
const referents = collectConversationReferents(liveDoc);
|
|
14061
|
+
const entryPaths = [];
|
|
14062
|
+
const listNames = [];
|
|
14063
|
+
const variableNames = [];
|
|
14064
|
+
for (const referent of referents.slice(0, 8)) {
|
|
14065
|
+
if (referent.kind === "entry" && typeof referent.entryPath === "string") {
|
|
14066
|
+
entryPaths.push(referent.entryPath);
|
|
14067
|
+
continue;
|
|
14068
|
+
}
|
|
14069
|
+
if (referent.kind === "list" && typeof referent.listName === "string") {
|
|
14070
|
+
listNames.push(referent.listName);
|
|
14071
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14072
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14073
|
+
continue;
|
|
14074
|
+
}
|
|
14075
|
+
if (referent.kind === "variable" && typeof referent.variableName === "string") {
|
|
14076
|
+
variableNames.push(referent.variableName);
|
|
14077
|
+
if (typeof referent.entryPath === "string") {
|
|
14078
|
+
entryPaths.push(referent.entryPath);
|
|
14079
|
+
}
|
|
14080
|
+
if (typeof referent.listName === "string") {
|
|
14081
|
+
listNames.push(referent.listName);
|
|
14082
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14083
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14084
|
+
}
|
|
14085
|
+
}
|
|
14086
|
+
}
|
|
14087
|
+
return {
|
|
14088
|
+
entryPaths: uniqueStrings(entryPaths, 8),
|
|
14089
|
+
listNames: uniqueStrings(listNames, 4),
|
|
14090
|
+
variableNames: uniqueStrings(variableNames, 4)
|
|
14091
|
+
};
|
|
14092
|
+
}
|
|
14093
|
+
function projectConversationReferentSummary(liveDoc) {
|
|
14094
|
+
const referents = collectConversationReferents(liveDoc).slice(0, 8);
|
|
14095
|
+
if (referents.length === 0) {
|
|
14096
|
+
return "No recent referents recorded from prior assistant replies.";
|
|
14097
|
+
}
|
|
14098
|
+
const entryLines = [];
|
|
14099
|
+
const listLines = [];
|
|
14100
|
+
const variableLines = [];
|
|
14101
|
+
for (const referent of referents) {
|
|
14102
|
+
if (referent.kind === "entry" && referent.entryPath) {
|
|
14103
|
+
const label = referent.label || referent.entryPath;
|
|
14104
|
+
const classLabel = referent.className || "unknown";
|
|
14105
|
+
entryLines.push(`- ${label} <${referent.entryPath}> [${classLabel}]`);
|
|
14106
|
+
continue;
|
|
14107
|
+
}
|
|
14108
|
+
if (referent.kind === "list" && referent.listName) {
|
|
14109
|
+
const classLabel = referent.className || "unknown";
|
|
14110
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14111
|
+
listLines.push(
|
|
14112
|
+
`- ${referent.listName}: list<${classLabel}> -> ${countLabel} item(s)`
|
|
14113
|
+
);
|
|
14114
|
+
continue;
|
|
14115
|
+
}
|
|
14116
|
+
if (referent.kind === "variable" && referent.variableName) {
|
|
14117
|
+
if (referent.variableKind === "entry" && referent.entryPath && referent.className) {
|
|
14118
|
+
const label = referent.label || referent.entryPath;
|
|
14119
|
+
variableLines.push(
|
|
14120
|
+
`- ${referent.variableName}: entry<${referent.className}> -> ${label} <${referent.entryPath}>`
|
|
14121
|
+
);
|
|
14122
|
+
continue;
|
|
14123
|
+
}
|
|
14124
|
+
if (referent.variableKind === "list" && referent.listName && referent.className) {
|
|
14125
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14126
|
+
variableLines.push(
|
|
14127
|
+
`- ${referent.variableName}: list<${referent.className}> -> ${countLabel} item(s) via ${referent.listName}`
|
|
14128
|
+
);
|
|
14129
|
+
continue;
|
|
14130
|
+
}
|
|
14131
|
+
if (referent.variableKind === "scalar") {
|
|
14132
|
+
variableLines.push(
|
|
14133
|
+
`- ${referent.variableName}: scalar = ${formatScalar(referent.scalarValue)}`
|
|
14134
|
+
);
|
|
14135
|
+
continue;
|
|
14136
|
+
}
|
|
14137
|
+
variableLines.push(`- ${referent.variableName}`);
|
|
14138
|
+
}
|
|
14139
|
+
}
|
|
14140
|
+
const lines = [];
|
|
14141
|
+
lines.push("Entries:");
|
|
14142
|
+
lines.push(...entryLines.length > 0 ? entryLines : ["- none"]);
|
|
14143
|
+
lines.push("", "Lists:");
|
|
14144
|
+
lines.push(...listLines.length > 0 ? listLines : ["- none"]);
|
|
14145
|
+
lines.push("", "Variables:");
|
|
14146
|
+
lines.push(...variableLines.length > 0 ? variableLines : ["- none"]);
|
|
14147
|
+
return lines.join("\n");
|
|
14148
|
+
}
|
|
13862
14149
|
function getCurrentClosureId(liveDoc) {
|
|
13863
14150
|
const loop = asRecord2(liveDoc?.loop);
|
|
13864
14151
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -14071,7 +14358,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
14071
14358
|
variableNames: uniqueStrings(variableNames, 4),
|
|
14072
14359
|
listNames: uniqueStrings(listNames, 4),
|
|
14073
14360
|
entryPaths: uniqueStrings(entryPaths, 6),
|
|
14074
|
-
recentActionSummary: uniqueStrings(actionSummaryLines, 8)
|
|
14361
|
+
recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
|
|
14362
|
+
normalizeActionSummaryForPrompt
|
|
14363
|
+
)
|
|
14075
14364
|
};
|
|
14076
14365
|
}
|
|
14077
14366
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
@@ -14468,17 +14757,14 @@ ${resultPreview}` : null
|
|
|
14468
14757
|
].filter(Boolean).join("\n\n");
|
|
14469
14758
|
}
|
|
14470
14759
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
14471
|
-
return domainDocumentation?.trim() || "No domain
|
|
14760
|
+
return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
|
|
14472
14761
|
}
|
|
14473
14762
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
14474
14763
|
if (!sessionContext) return "No session metadata available.";
|
|
14475
14764
|
const rows = [
|
|
14476
14765
|
["sandboxId", sessionContext.sandboxId],
|
|
14477
14766
|
["environmentId", sessionContext.environmentId],
|
|
14478
|
-
["
|
|
14479
|
-
["granularId", sessionContext.granularId],
|
|
14480
|
-
["userName", sessionContext.userName],
|
|
14481
|
-
["domainRevision", sessionContext.domainRevision]
|
|
14767
|
+
["userName", sessionContext.userName]
|
|
14482
14768
|
];
|
|
14483
14769
|
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
14484
14770
|
if (activeRows.length === 0) return "No session metadata available.";
|
|
@@ -14487,6 +14773,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
|
|
|
14487
14773
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
14488
14774
|
return heapSummary?.trim() || "Heap is empty for this session.";
|
|
14489
14775
|
}
|
|
14776
|
+
function buildGranularAgentReferentBlock(referentSummary) {
|
|
14777
|
+
return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
|
|
14778
|
+
}
|
|
14490
14779
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
14491
14780
|
return loopSummary?.trim() || "No active loop state recorded for this session.";
|
|
14492
14781
|
}
|
|
@@ -14510,7 +14799,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
14510
14799
|
(tool) => Boolean(tool.className && !tool.static)
|
|
14511
14800
|
);
|
|
14512
14801
|
const lines = [
|
|
14513
|
-
"Treat this block as the planning map. Use DOMAIN
|
|
14802
|
+
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
14514
14803
|
];
|
|
14515
14804
|
const appendGroup = (title, group) => {
|
|
14516
14805
|
lines.push(`- ${title}:`);
|
|
@@ -14558,7 +14847,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
|
14558
14847
|
if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
|
|
14559
14848
|
lines.push("latestActionSummary:");
|
|
14560
14849
|
for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
|
|
14561
|
-
|
|
14850
|
+
const normalizedLine = normalizeActionSummaryForPrompt(line);
|
|
14851
|
+
lines.push(
|
|
14852
|
+
normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
|
|
14853
|
+
);
|
|
14562
14854
|
}
|
|
14563
14855
|
}
|
|
14564
14856
|
if (checkpoint.latestJobResult?.trim()) {
|
|
@@ -14574,9 +14866,10 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
14574
14866
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
14575
14867
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
14576
14868
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
14869
|
+
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
14577
14870
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
14578
14871
|
return `You are an AI assistant for a live Granular session.
|
|
14579
|
-
You can help the user understand the domain, answer questions, or generate and execute
|
|
14872
|
+
You can help the user understand the domain, answer questions, or generate and execute code against the live session.
|
|
14580
14873
|
Your tone must be natural and human-like.
|
|
14581
14874
|
|
|
14582
14875
|
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.
|
|
@@ -14585,6 +14878,8 @@ When you call \`execute_code\`, additional assistant text must be either:
|
|
|
14585
14878
|
- a brief summary of the actions the generated code will perform.
|
|
14586
14879
|
Do not include any other kind of commentary when calling \`execute_code\`.
|
|
14587
14880
|
- 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(...)\`.
|
|
14881
|
+
- 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.
|
|
14882
|
+
- 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.
|
|
14588
14883
|
- 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.
|
|
14589
14884
|
|
|
14590
14885
|
\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
|
|
@@ -14604,6 +14899,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
|
|
|
14604
14899
|
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
14605
14900
|
- If you need clarification, ask in everyday language.
|
|
14606
14901
|
- 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.
|
|
14902
|
+
- 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.
|
|
14607
14903
|
- Keep replies concise and clear.
|
|
14608
14904
|
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
14609
14905
|
|
|
@@ -14613,9 +14909,9 @@ ${sessionBlock}
|
|
|
14613
14909
|
\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
|
|
14614
14910
|
${toolBlock}
|
|
14615
14911
|
|
|
14616
|
-
\u2500\u2500\u2500 DOMAIN
|
|
14912
|
+
\u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
|
|
14617
14913
|
Import classes and effect functions from \`./sandbox-tools\` in generated code.
|
|
14618
|
-
|
|
14914
|
+
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
|
|
14619
14915
|
|
|
14620
14916
|
${domainBlock}
|
|
14621
14917
|
|
|
@@ -14625,6 +14921,9 @@ ${checkpointBlock}
|
|
|
14625
14921
|
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
14626
14922
|
${workflowBlock}
|
|
14627
14923
|
|
|
14924
|
+
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
14925
|
+
${referentBlock}
|
|
14926
|
+
|
|
14628
14927
|
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
14629
14928
|
${heapBlock}
|
|
14630
14929
|
|
|
@@ -14632,109 +14931,54 @@ ${heapBlock}
|
|
|
14632
14931
|
${loopBlock}
|
|
14633
14932
|
|
|
14634
14933
|
\u2500\u2500\u2500 LOOP PLAYBOOK \u2500\u2500\u2500
|
|
14635
|
-
-
|
|
14636
|
-
-
|
|
14637
|
-
-
|
|
14638
|
-
-
|
|
14639
|
-
-
|
|
14640
|
-
-
|
|
14641
|
-
- If
|
|
14642
|
-
-
|
|
14643
|
-
-
|
|
14644
|
-
-
|
|
14645
|
-
-
|
|
14646
|
-
-
|
|
14647
|
-
-
|
|
14648
|
-
-
|
|
14649
|
-
- Use \`loop.
|
|
14650
|
-
-
|
|
14651
|
-
-
|
|
14652
|
-
- If
|
|
14653
|
-
- 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.
|
|
14654
|
-
- 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.
|
|
14655
|
-
- 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.
|
|
14656
|
-
- 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.
|
|
14657
|
-
- 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.
|
|
14658
|
-
- 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.
|
|
14659
|
-
- Never ask for approval in plain text when \`loop.confirm(...)\` is available. Use \`loop.confirm(...)\` for consequential approval.
|
|
14660
|
-
- When the correct next step is a loop helper action, generate code and call that helper. Do not replace it with a conversational reply.
|
|
14661
|
-
- If you ask the user a new question in the current job, do not also call \`loop.close_loop(...)\` in that same job.
|
|
14662
|
-
- 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.
|
|
14663
|
-
- It is valid to branch on the value returned by \`await loop.ask_user(...)\` or \`await loop.confirm(...)\` after the job resumes.
|
|
14664
|
-
- 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".
|
|
14665
|
-
- 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."
|
|
14666
|
-
- 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.
|
|
14667
|
-
- If the user says stop, enough, or no further action, close the loop and end cleanly without asking another question.
|
|
14668
|
-
- If one clear item is already selected and the next step matters, prefer \`loop.confirm(...)\` over another exploratory question.
|
|
14669
|
-
- If one clear item is already selected and the only missing input is approval to proceed, use \`loop.confirm(...)\` rather than \`loop.ask_user(...)\`.
|
|
14670
|
-
- 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.
|
|
14671
|
-
- 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.
|
|
14672
|
-
- 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.
|
|
14673
|
-
- 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(...)\`.
|
|
14674
|
-
- 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.
|
|
14675
|
-
- Once you have one solid recommendation, prefer summarizing it and asking for approval over gathering more optional preferences.
|
|
14676
|
-
- Prefer asking the user for the next missing input over fetching extra related data they did not ask for yet.
|
|
14677
|
-
- Avoid serial menus. After one clarifying choice, prefer acting on it, asking one short text question, or confirming rather than opening another menu.
|
|
14678
|
-
- 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.
|
|
14679
|
-
- Call \`loop.close_loop(...)\` before stopping whenever the current workflow is completed, canceled, or clearly blocked.
|
|
14934
|
+
- 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.
|
|
14935
|
+
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
14936
|
+
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
14937
|
+
- 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.
|
|
14938
|
+
- 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.
|
|
14939
|
+
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
14940
|
+
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
14941
|
+
- 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.
|
|
14942
|
+
- 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.
|
|
14943
|
+
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
14944
|
+
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
14945
|
+
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
14946
|
+
- 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.
|
|
14947
|
+
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
14948
|
+
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
14949
|
+
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
14950
|
+
- 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.
|
|
14951
|
+
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
14680
14952
|
|
|
14681
14953
|
\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
|
|
14682
14954
|
- Import from \`./sandbox-tools\`.
|
|
14683
|
-
- If you use \`heap\`, \`loop\`, \`agent_text_message\`,
|
|
14955
|
+
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
|
|
14684
14956
|
- Write top-level executable code with \`await\` at top level.
|
|
14685
|
-
- The generated job body must be plain runnable JavaScript.
|
|
14686
|
-
-
|
|
14687
|
-
-
|
|
14688
|
-
-
|
|
14689
|
-
-
|
|
14690
|
-
-
|
|
14691
|
-
-
|
|
14692
|
-
-
|
|
14693
|
-
-
|
|
14694
|
-
-
|
|
14695
|
-
-
|
|
14696
|
-
-
|
|
14697
|
-
-
|
|
14698
|
-
-
|
|
14699
|
-
-
|
|
14700
|
-
-
|
|
14701
|
-
-
|
|
14702
|
-
-
|
|
14703
|
-
-
|
|
14704
|
-
-
|
|
14705
|
-
-
|
|
14706
|
-
-
|
|
14707
|
-
-
|
|
14708
|
-
-
|
|
14709
|
-
-
|
|
14710
|
-
- 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.
|
|
14711
|
-
- When reading heap values, prefer generated generic typings such as \`await heap.getVar<Book[]>("my_books")\` or \`await heap.getVar<Book>("selected_book")\`.
|
|
14712
|
-
- 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")\`.
|
|
14713
|
-
- Use the injected \`loop\` helpers when you need to manage the workflow itself:
|
|
14714
|
-
\`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`,
|
|
14715
|
-
\`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
|
|
14716
|
-
- Loop helper semantics:
|
|
14717
|
-
- \`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\`.
|
|
14718
|
-
- \`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\`.
|
|
14719
|
-
- \`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.
|
|
14720
|
-
- \`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.
|
|
14721
|
-
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short task list that later jobs can continue and finish.
|
|
14722
|
-
- \`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.
|
|
14723
|
-
- Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
|
|
14724
|
-
- Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
|
|
14725
|
-
- 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(...)\`.
|
|
14726
|
-
- Use \`agent_text_message("...")\` for all user-visible text shown in the UI.
|
|
14727
|
-
- Use \`agent_heap_objects(...)\` only when you want the UI to render heap-backed records or lists.
|
|
14728
|
-
- If you want to show both text and records, call \`agent_text_message(...)\` and \`agent_heap_objects(...)\` separately in whatever order fits the interaction.
|
|
14729
|
-
- \`agent_text_message(...)\` should be used with a plain text string in normal generated code.
|
|
14730
|
-
- \`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(...)\`.
|
|
14731
|
-
- 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.
|
|
14732
|
-
- Do not assume heap changes will be displayed automatically. If records should appear in the UI, you must call \`agent_heap_objects(...)\`.
|
|
14733
|
-
- 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.
|
|
14734
|
-
- Do not rely on the final return value for user-visible output. A plain return value is not considered a displayed UI answer.
|
|
14735
|
-
- \`agent_message(...)\` remains available as a legacy compatibility alias, but prefer \`agent_text_message(...)\` and \`agent_heap_objects(...)\` in new code.
|
|
14736
|
-
- Do not return bare structured JSON, low-level diagnostics, or database-shaped payloads as the final answer unless the user explicitly asks for them.
|
|
14737
|
-
- 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.
|
|
14957
|
+
- The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
|
|
14958
|
+
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
|
|
14959
|
+
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
|
|
14960
|
+
- 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.
|
|
14961
|
+
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
|
|
14962
|
+
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
|
|
14963
|
+
- 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.
|
|
14964
|
+
- 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.
|
|
14965
|
+
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
|
|
14966
|
+
- 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(...)\`.
|
|
14967
|
+
- 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.
|
|
14968
|
+
- Call instance methods on instances, static methods on classes, and global effects by name.
|
|
14969
|
+
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
|
|
14970
|
+
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
|
|
14971
|
+
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
|
|
14972
|
+
- 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.
|
|
14973
|
+
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
|
|
14974
|
+
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
|
|
14975
|
+
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
|
|
14976
|
+
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
|
|
14977
|
+
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
14978
|
+
- Use \`agent_text_message(...)\` for user-visible text.
|
|
14979
|
+
- 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.
|
|
14980
|
+
- 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.
|
|
14981
|
+
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
|
|
14738
14982
|
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
|
|
14739
14983
|
}
|
|
14740
14984
|
|
|
@@ -14893,6 +15137,10 @@ function fallbackResponseText(entries, lists) {
|
|
|
14893
15137
|
return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
|
|
14894
15138
|
}
|
|
14895
15139
|
if (lists.length > 0) {
|
|
15140
|
+
const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
|
|
15141
|
+
if (emptyOnly) {
|
|
15142
|
+
return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
|
|
15143
|
+
}
|
|
14896
15144
|
return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
|
|
14897
15145
|
}
|
|
14898
15146
|
return null;
|
|
@@ -15268,6 +15516,6 @@ function buildSessionTranscript(input) {
|
|
|
15268
15516
|
});
|
|
15269
15517
|
}
|
|
15270
15518
|
|
|
15271
|
-
export { Environment, Granular, Session, WSClient, buildContinuationInstruction, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildSessionTranscript, createHarnessVerifierSnapshot, evaluateContinuation, extractPromptTokens, getCurrentClosureId, getExclusivePromptTarget, hasOpenPrompt, invokeRegisteredEffect, isLocalApiUrl, normalizeEffectBehaviors, normalizePrompt, normalizePromptText, normalizePromptType, projectHeapSummary, projectLoopSummary, projectWorkflowFocus, projectWorkflowSummary, resolveApiUrl, resolveAuthTokenForApiUrl, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch };
|
|
15519
|
+
export { Environment, Granular, Session, WSClient, buildContinuationInstruction, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentReferentBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildSessionTranscript, createHarnessVerifierSnapshot, evaluateContinuation, extractPromptTokens, getCurrentClosureId, getExclusivePromptTarget, hasOpenPrompt, invokeRegisteredEffect, isLocalApiUrl, normalizeEffectBehaviors, normalizePrompt, normalizePromptText, normalizePromptType, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectWorkflowFocus, projectWorkflowSummary, resolveApiUrl, resolveAuthTokenForApiUrl, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch };
|
|
15272
15520
|
//# sourceMappingURL=index.mjs.map
|
|
15273
15521
|
//# sourceMappingURL=index.mjs.map
|