@granular-software/sdk 0.4.29 → 0.4.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -20
- package/dist/agent-evals.d.mts +7 -6
- package/dist/agent-evals.d.ts +7 -6
- package/dist/agent-evals.js +1002 -525
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +1002 -525
- 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 +773 -394
- package/dist/{client-CigjaeO8.d.mts → client-CTH1hfwe.d.mts} +255 -117
- package/dist/{client-CigjaeO8.d.ts → client-CTH1hfwe.d.ts} +255 -117
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +938 -435
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +934 -436
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4647,27 +4647,27 @@ var Session = class {
|
|
|
4647
4647
|
}
|
|
4648
4648
|
async publishTools(tools, revision = "1.0.0") {
|
|
4649
4649
|
throw new Error(
|
|
4650
|
-
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.
|
|
4650
|
+
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
4651
4651
|
);
|
|
4652
4652
|
}
|
|
4653
4653
|
async publishEffect(effect) {
|
|
4654
4654
|
throw new Error(
|
|
4655
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4655
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.register(effect)."
|
|
4656
4656
|
);
|
|
4657
4657
|
}
|
|
4658
4658
|
async publishEffects(effects) {
|
|
4659
4659
|
throw new Error(
|
|
4660
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4660
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
4661
4661
|
);
|
|
4662
4662
|
}
|
|
4663
4663
|
async unpublishEffect(name) {
|
|
4664
4664
|
throw new Error(
|
|
4665
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4665
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.unregister(effectName)."
|
|
4666
4666
|
);
|
|
4667
4667
|
}
|
|
4668
4668
|
async unpublishAllEffects() {
|
|
4669
4669
|
throw new Error(
|
|
4670
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4670
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.clear()."
|
|
4671
4671
|
);
|
|
4672
4672
|
}
|
|
4673
4673
|
/**
|
|
@@ -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();
|
|
@@ -11675,6 +11691,22 @@ function normalizeHeapSnapshot(raw) {
|
|
|
11675
11691
|
updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
11676
11692
|
};
|
|
11677
11693
|
}
|
|
11694
|
+
function deriveRuntimeBaseUrl(apiEndpoint) {
|
|
11695
|
+
try {
|
|
11696
|
+
const endpoint = new URL(apiEndpoint);
|
|
11697
|
+
const graphqlSuffix = "/orchestrator/graphql";
|
|
11698
|
+
if (endpoint.pathname.endsWith(graphqlSuffix)) {
|
|
11699
|
+
endpoint.pathname = endpoint.pathname.slice(0, -graphqlSuffix.length);
|
|
11700
|
+
} else if (endpoint.pathname.endsWith("/graphql")) {
|
|
11701
|
+
endpoint.pathname = endpoint.pathname.slice(0, -"/graphql".length);
|
|
11702
|
+
}
|
|
11703
|
+
endpoint.search = "";
|
|
11704
|
+
endpoint.hash = "";
|
|
11705
|
+
return endpoint.toString().replace(/\/$/, "");
|
|
11706
|
+
} catch {
|
|
11707
|
+
return apiEndpoint.replace(/\/orchestrator\/graphql$/, "").replace(/\/$/, "");
|
|
11708
|
+
}
|
|
11709
|
+
}
|
|
11678
11710
|
function normalizeSubject(subject) {
|
|
11679
11711
|
const granularId = subject.granularId || subject.subjectId;
|
|
11680
11712
|
const userId = subject.userId || subject.identityId || granularId;
|
|
@@ -11699,7 +11731,10 @@ function normalizeUser(user) {
|
|
|
11699
11731
|
};
|
|
11700
11732
|
}
|
|
11701
11733
|
function normalizeEnvironmentData(environment) {
|
|
11702
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11734
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11735
|
+
mode: "pinned",
|
|
11736
|
+
versionId: environment.versionId || environment.buildId
|
|
11737
|
+
});
|
|
11703
11738
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
11704
11739
|
return {
|
|
11705
11740
|
...environment,
|
|
@@ -11711,12 +11746,13 @@ function normalizeEnvironmentData(environment) {
|
|
|
11711
11746
|
tracking: environment.tracking || buildPolicy
|
|
11712
11747
|
};
|
|
11713
11748
|
}
|
|
11714
|
-
var Environment = class
|
|
11749
|
+
var Environment = class {
|
|
11750
|
+
granular;
|
|
11715
11751
|
envData;
|
|
11716
11752
|
_apiKey;
|
|
11717
11753
|
_apiEndpoint;
|
|
11718
|
-
constructor(
|
|
11719
|
-
|
|
11754
|
+
constructor(granular, envData, apiKey, apiEndpoint) {
|
|
11755
|
+
this.granular = granular;
|
|
11720
11756
|
this.envData = envData;
|
|
11721
11757
|
this._apiKey = apiKey;
|
|
11722
11758
|
this._apiEndpoint = apiEndpoint;
|
|
@@ -11757,35 +11793,126 @@ var Environment = class extends Session {
|
|
|
11757
11793
|
get permissionProfileId() {
|
|
11758
11794
|
return this.envData.permissionProfileId;
|
|
11759
11795
|
}
|
|
11796
|
+
/** The current build policy backing this environment */
|
|
11797
|
+
get buildPolicy() {
|
|
11798
|
+
return this.envData.buildPolicy;
|
|
11799
|
+
}
|
|
11800
|
+
/** The current update state relative to the followed tag */
|
|
11801
|
+
get updateState() {
|
|
11802
|
+
return this.envData.updateState;
|
|
11803
|
+
}
|
|
11804
|
+
/** Convenience flag for whether this environment trails the current tag target */
|
|
11805
|
+
get isOutdated() {
|
|
11806
|
+
return this.envData.updateState === "update_available";
|
|
11807
|
+
}
|
|
11808
|
+
/** The followed tag name when this environment is tag-tracked */
|
|
11809
|
+
get tag() {
|
|
11810
|
+
return this.envData.tag?.name || this.envData.buildPolicy.tagName || null;
|
|
11811
|
+
}
|
|
11760
11812
|
/** The GraphQL API endpoint URL */
|
|
11761
11813
|
get apiEndpoint() {
|
|
11762
11814
|
return this._apiEndpoint;
|
|
11763
11815
|
}
|
|
11816
|
+
/** Internal auth token used for control-plane and runtime fallback requests */
|
|
11817
|
+
get authToken() {
|
|
11818
|
+
return this._apiKey;
|
|
11819
|
+
}
|
|
11820
|
+
/** Base runtime URL derived from the GraphQL endpoint */
|
|
11821
|
+
get runtimeBaseUrl() {
|
|
11822
|
+
return this.getRuntimeBaseUrl();
|
|
11823
|
+
}
|
|
11824
|
+
get sessions() {
|
|
11825
|
+
return {
|
|
11826
|
+
list: async (options) => this.listSessions(options?.status || "active"),
|
|
11827
|
+
create: async (options) => this.createSession(options),
|
|
11828
|
+
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
11829
|
+
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
11830
|
+
close: async (sessionId, session) => this.closeSession(sessionId, session)
|
|
11831
|
+
};
|
|
11832
|
+
}
|
|
11833
|
+
get data() {
|
|
11834
|
+
return {
|
|
11835
|
+
record: async (record) => this.recordObject(record),
|
|
11836
|
+
recordMany: async (records, options) => this.recordObjects(records, options),
|
|
11837
|
+
import: async (records, options) => this.enqueueRecordImport(records, options),
|
|
11838
|
+
listImports: async (status) => this.listRecordImports(status),
|
|
11839
|
+
getImport: async (importId) => this.getRecordImport(importId),
|
|
11840
|
+
getImportSummary: async () => this.getRecordImportSummary(),
|
|
11841
|
+
cancelImport: async (importId) => this.cancelRecordImport(importId),
|
|
11842
|
+
getAwaitingCount: async () => this.getAwaitingRecordCount()
|
|
11843
|
+
};
|
|
11844
|
+
}
|
|
11845
|
+
get feedback() {
|
|
11846
|
+
return {
|
|
11847
|
+
list: async () => this.listFeedback()
|
|
11848
|
+
};
|
|
11849
|
+
}
|
|
11764
11850
|
/**
|
|
11765
|
-
*
|
|
11766
|
-
*
|
|
11767
|
-
*
|
|
11768
|
-
*
|
|
11851
|
+
* Sessionless environments do not own a live transport, so disconnecting the
|
|
11852
|
+
* environment handle itself is a no-op. This keeps the public surface
|
|
11853
|
+
* symmetric with `EnvironmentSession.disconnect()` and lets callers always
|
|
11854
|
+
* clean up safely without tracking whether they currently hold an environment
|
|
11855
|
+
* or a session.
|
|
11769
11856
|
*/
|
|
11770
|
-
|
|
11771
|
-
const doc = this.document;
|
|
11772
|
-
return normalizeHeapSnapshot(doc?.heap);
|
|
11857
|
+
async disconnect() {
|
|
11773
11858
|
}
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
const
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11859
|
+
async listSessions(status = "active") {
|
|
11860
|
+
if (status === "all") {
|
|
11861
|
+
const [active, closed] = await Promise.all([
|
|
11862
|
+
this.granular.listOpenSessions({ environmentId: this.environmentId }),
|
|
11863
|
+
this.granular.listClosedSessions({ environmentId: this.environmentId })
|
|
11864
|
+
]);
|
|
11865
|
+
return [...active, ...closed].sort(
|
|
11866
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
11867
|
+
);
|
|
11868
|
+
}
|
|
11869
|
+
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
11870
|
+
}
|
|
11871
|
+
async createSession(options) {
|
|
11872
|
+
return this.granular.createSession({
|
|
11873
|
+
environmentId: this.environmentId,
|
|
11874
|
+
clientId: options?.clientId,
|
|
11875
|
+
initialHeap: options?.initialHeap
|
|
11876
|
+
});
|
|
11877
|
+
}
|
|
11878
|
+
async connectSession(sessionId, options) {
|
|
11879
|
+
const session = await this.granular["connectSession"]({
|
|
11880
|
+
sessionId,
|
|
11881
|
+
clientId: options?.clientId
|
|
11882
|
+
});
|
|
11883
|
+
if (session.environmentId !== this.environmentId) {
|
|
11884
|
+
await session.disconnect().catch(() => {
|
|
11885
|
+
session.disconnectTransport();
|
|
11886
|
+
});
|
|
11887
|
+
throw new Error(
|
|
11888
|
+
`Session ${sessionId} belongs to environment ${session.environmentId}, not ${this.environmentId}.`
|
|
11889
|
+
);
|
|
11890
|
+
}
|
|
11891
|
+
return session;
|
|
11892
|
+
}
|
|
11893
|
+
async reopenSession(sessionId, options) {
|
|
11894
|
+
const session = await this.granular.reopenSession(sessionId, {
|
|
11895
|
+
clientId: options?.clientId
|
|
11896
|
+
});
|
|
11897
|
+
if (session.environmentId !== this.environmentId) {
|
|
11898
|
+
await session.disconnect().catch(() => {
|
|
11899
|
+
session.disconnectTransport();
|
|
11900
|
+
});
|
|
11901
|
+
throw new Error(
|
|
11902
|
+
`Session ${sessionId} belongs to environment ${session.environmentId}, not ${this.environmentId}.`
|
|
11903
|
+
);
|
|
11788
11904
|
}
|
|
11905
|
+
return session;
|
|
11906
|
+
}
|
|
11907
|
+
async closeSession(sessionId, session) {
|
|
11908
|
+
await this.granular.closeSession(sessionId, session);
|
|
11909
|
+
}
|
|
11910
|
+
async listFeedback() {
|
|
11911
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
|
|
11912
|
+
return Array.isArray(response.items) ? response.items : [];
|
|
11913
|
+
}
|
|
11914
|
+
getRuntimeBaseUrl() {
|
|
11915
|
+
return deriveRuntimeBaseUrl(this._apiEndpoint);
|
|
11789
11916
|
}
|
|
11790
11917
|
async controlPlaneRequest(path, options = {}) {
|
|
11791
11918
|
const runtimeBase = this.getRuntimeBaseUrl();
|
|
@@ -11793,94 +11920,19 @@ var Environment = class extends Session {
|
|
|
11793
11920
|
const response = await fetch(url, {
|
|
11794
11921
|
...options,
|
|
11795
11922
|
headers: {
|
|
11796
|
-
|
|
11923
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11797
11924
|
"Content-Type": "application/json",
|
|
11798
|
-
|
|
11925
|
+
Connection: "close",
|
|
11799
11926
|
...options.headers
|
|
11800
11927
|
}
|
|
11801
11928
|
});
|
|
11802
11929
|
if (!response.ok) {
|
|
11803
|
-
throw new Error(
|
|
11930
|
+
throw new Error(
|
|
11931
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
11932
|
+
);
|
|
11804
11933
|
}
|
|
11805
11934
|
return response.json();
|
|
11806
11935
|
}
|
|
11807
|
-
/**
|
|
11808
|
-
* Close the session and disconnect from the sandbox.
|
|
11809
|
-
*
|
|
11810
|
-
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
11811
|
-
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
11812
|
-
* acknowledgement was observed.
|
|
11813
|
-
*/
|
|
11814
|
-
async disconnect() {
|
|
11815
|
-
let wsNotifiedRuntime = false;
|
|
11816
|
-
try {
|
|
11817
|
-
const goodbye = await this.rpc("client.goodbye", {
|
|
11818
|
-
timestamp: Date.now()
|
|
11819
|
-
});
|
|
11820
|
-
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11821
|
-
} catch {
|
|
11822
|
-
wsNotifiedRuntime = false;
|
|
11823
|
-
}
|
|
11824
|
-
if (!wsNotifiedRuntime) {
|
|
11825
|
-
try {
|
|
11826
|
-
const runtimeBase = this.getRuntimeBaseUrl();
|
|
11827
|
-
await fetch(
|
|
11828
|
-
`${runtimeBase}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
11829
|
-
{
|
|
11830
|
-
method: "POST",
|
|
11831
|
-
headers: {
|
|
11832
|
-
"Content-Type": "application/json",
|
|
11833
|
-
"Authorization": `Bearer ${this._apiKey}`,
|
|
11834
|
-
"Connection": "close"
|
|
11835
|
-
},
|
|
11836
|
-
body: JSON.stringify({
|
|
11837
|
-
reason: "sdk_disconnect_http_fallback",
|
|
11838
|
-
sessionId: this.client.currentSessionId
|
|
11839
|
-
})
|
|
11840
|
-
}
|
|
11841
|
-
);
|
|
11842
|
-
} catch {
|
|
11843
|
-
}
|
|
11844
|
-
}
|
|
11845
|
-
this.client.disconnect();
|
|
11846
|
-
}
|
|
11847
|
-
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11848
|
-
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11849
|
-
graphContainerStatus = null;
|
|
11850
|
-
/**
|
|
11851
|
-
* Check if the graph container is ready and warm.
|
|
11852
|
-
*
|
|
11853
|
-
* Sends a lightweight heartbeat RPC to the Session DO which internally
|
|
11854
|
-
* pings the FalkorDB container. The response includes `graphContainerStatus`,
|
|
11855
|
-
* which is stored locally and emitted as a `readiness` event.
|
|
11856
|
-
*
|
|
11857
|
-
* Use this method to proactively warm the graph container before any
|
|
11858
|
-
* GraphQL query that requires it, or to poll the container's state in
|
|
11859
|
-
* the background.
|
|
11860
|
-
*
|
|
11861
|
-
* @returns The current graph container status object
|
|
11862
|
-
*
|
|
11863
|
-
* @example
|
|
11864
|
-
* ```typescript
|
|
11865
|
-
* const status = await env.checkReadiness();
|
|
11866
|
-
* console.log(status.status); // 'hot' | 'warming' | 'unknown'
|
|
11867
|
-
*
|
|
11868
|
-
* // Or listen for live updates
|
|
11869
|
-
* env.on('readiness', (status) => {
|
|
11870
|
-
* console.log('Graph is now:', status.status);
|
|
11871
|
-
* });
|
|
11872
|
-
* ```
|
|
11873
|
-
*/
|
|
11874
|
-
async checkReadiness() {
|
|
11875
|
-
const result = await this.client.call("client.heartbeat", {});
|
|
11876
|
-
const containerStatus = result?.graphContainerStatus ?? {
|
|
11877
|
-
lastKeepAliveAt: Date.now(),
|
|
11878
|
-
status: "unknown"
|
|
11879
|
-
};
|
|
11880
|
-
this.graphContainerStatus = containerStatus;
|
|
11881
|
-
this.emit("readiness", containerStatus);
|
|
11882
|
-
return containerStatus;
|
|
11883
|
-
}
|
|
11884
11936
|
// ==================== ID ↔ GRAPH PATH MAPPING ====================
|
|
11885
11937
|
/**
|
|
11886
11938
|
* Convert a class name + real-world ID into a unique graph path.
|
|
@@ -11909,14 +11961,14 @@ var Environment = class extends Session {
|
|
|
11909
11961
|
}
|
|
11910
11962
|
/**
|
|
11911
11963
|
* Execute a GraphQL query against the environment's graph.
|
|
11912
|
-
*
|
|
11964
|
+
*
|
|
11913
11965
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
11914
11966
|
* Authentication is handled automatically using the SDK's API key.
|
|
11915
|
-
*
|
|
11967
|
+
*
|
|
11916
11968
|
* @param query - The GraphQL query string
|
|
11917
11969
|
* @param variables - Optional variables for the query
|
|
11918
11970
|
* @returns The query result data
|
|
11919
|
-
*
|
|
11971
|
+
*
|
|
11920
11972
|
* @example
|
|
11921
11973
|
* ```typescript
|
|
11922
11974
|
* // Read the workspace
|
|
@@ -11924,7 +11976,7 @@ var Environment = class extends Session {
|
|
|
11924
11976
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
11925
11977
|
* );
|
|
11926
11978
|
* console.log(result.data);
|
|
11927
|
-
*
|
|
11979
|
+
*
|
|
11928
11980
|
* // Create a model
|
|
11929
11981
|
* const created = await env.graphql(
|
|
11930
11982
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -11936,7 +11988,7 @@ var Environment = class extends Session {
|
|
|
11936
11988
|
method: "POST",
|
|
11937
11989
|
headers: {
|
|
11938
11990
|
"Content-Type": "application/json",
|
|
11939
|
-
|
|
11991
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
11940
11992
|
},
|
|
11941
11993
|
body: JSON.stringify({
|
|
11942
11994
|
environmentId: this.environmentId,
|
|
@@ -11953,10 +12005,10 @@ var Environment = class extends Session {
|
|
|
11953
12005
|
// ==================== RELATIONSHIP METHODS ====================
|
|
11954
12006
|
/**
|
|
11955
12007
|
* Define a relationship between two model types.
|
|
11956
|
-
*
|
|
12008
|
+
*
|
|
11957
12009
|
* Creates both submodels (if they don't exist) and links them with
|
|
11958
12010
|
* a RelationshipDef node that encodes cardinality.
|
|
11959
|
-
*
|
|
12011
|
+
*
|
|
11960
12012
|
* @example
|
|
11961
12013
|
* ```typescript
|
|
11962
12014
|
* // Author has many Books, Book has one Author
|
|
@@ -12016,10 +12068,10 @@ var Environment = class extends Session {
|
|
|
12016
12068
|
}
|
|
12017
12069
|
/**
|
|
12018
12070
|
* Get all relationships for a model type.
|
|
12019
|
-
*
|
|
12071
|
+
*
|
|
12020
12072
|
* @param modelPath - The model type path (e.g., "author")
|
|
12021
12073
|
* @returns Array of relationships from this model's perspective
|
|
12022
|
-
*
|
|
12074
|
+
*
|
|
12023
12075
|
* @example
|
|
12024
12076
|
* ```typescript
|
|
12025
12077
|
* const rels = await env.getRelationships('author');
|
|
@@ -12052,18 +12104,18 @@ var Environment = class extends Session {
|
|
|
12052
12104
|
}
|
|
12053
12105
|
/**
|
|
12054
12106
|
* Attach a target model to a relationship submodel.
|
|
12055
|
-
*
|
|
12107
|
+
*
|
|
12056
12108
|
* Handles cardinality automatically:
|
|
12057
12109
|
* - "One" side: sets/replaces the reference
|
|
12058
12110
|
* - "Many" side: adds the target to the collection
|
|
12059
|
-
*
|
|
12111
|
+
*
|
|
12060
12112
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
12061
12113
|
* Bidirectional sync is automatic.
|
|
12062
|
-
*
|
|
12114
|
+
*
|
|
12063
12115
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
12064
12116
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
12065
12117
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
12066
|
-
*
|
|
12118
|
+
*
|
|
12067
12119
|
* @example
|
|
12068
12120
|
* ```typescript
|
|
12069
12121
|
* // Attach a book to an author (many side)
|
|
@@ -12090,18 +12142,18 @@ var Environment = class extends Session {
|
|
|
12090
12142
|
}
|
|
12091
12143
|
/**
|
|
12092
12144
|
* Detach a target model from a relationship submodel.
|
|
12093
|
-
*
|
|
12145
|
+
*
|
|
12094
12146
|
* Handles bidirectional cleanup automatically.
|
|
12095
|
-
*
|
|
12147
|
+
*
|
|
12096
12148
|
* @param modelPath - The model instance path
|
|
12097
12149
|
* @param submodelPath - The relationship submodel
|
|
12098
12150
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
12099
|
-
*
|
|
12151
|
+
*
|
|
12100
12152
|
* @example
|
|
12101
12153
|
* ```typescript
|
|
12102
12154
|
* // Detach a specific book
|
|
12103
12155
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
12104
|
-
*
|
|
12156
|
+
*
|
|
12105
12157
|
* // Detach all books
|
|
12106
12158
|
* await env.detach('tolkien', 'books');
|
|
12107
12159
|
* ```
|
|
@@ -12125,11 +12177,11 @@ var Environment = class extends Session {
|
|
|
12125
12177
|
}
|
|
12126
12178
|
/**
|
|
12127
12179
|
* List all related models through a relationship submodel.
|
|
12128
|
-
*
|
|
12180
|
+
*
|
|
12129
12181
|
* @param modelPath - The model instance path
|
|
12130
12182
|
* @param submodelPath - The relationship submodel
|
|
12131
12183
|
* @returns Array of related model references
|
|
12132
|
-
*
|
|
12184
|
+
*
|
|
12133
12185
|
* @example
|
|
12134
12186
|
* ```typescript
|
|
12135
12187
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -12153,14 +12205,14 @@ var Environment = class extends Session {
|
|
|
12153
12205
|
}
|
|
12154
12206
|
/**
|
|
12155
12207
|
* Apply a manifest to the current environment's graph.
|
|
12156
|
-
*
|
|
12208
|
+
*
|
|
12157
12209
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
12158
12210
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
12159
12211
|
* relationships from a declarative manifest.
|
|
12160
|
-
*
|
|
12212
|
+
*
|
|
12161
12213
|
* @param manifest - The manifest content to apply
|
|
12162
12214
|
* @returns Summary of applied operations
|
|
12163
|
-
*
|
|
12215
|
+
*
|
|
12164
12216
|
* @example
|
|
12165
12217
|
* ```typescript
|
|
12166
12218
|
* await environment.applyManifest({
|
|
@@ -12198,12 +12250,16 @@ var Environment = class extends Session {
|
|
|
12198
12250
|
applied++;
|
|
12199
12251
|
} catch (err) {
|
|
12200
12252
|
if (!err.message?.includes("already exists")) {
|
|
12201
|
-
errors.push(
|
|
12253
|
+
errors.push(
|
|
12254
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
12255
|
+
);
|
|
12202
12256
|
}
|
|
12203
12257
|
}
|
|
12204
12258
|
}
|
|
12205
12259
|
} else {
|
|
12206
|
-
errors.push(
|
|
12260
|
+
errors.push(
|
|
12261
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
12262
|
+
);
|
|
12207
12263
|
}
|
|
12208
12264
|
}
|
|
12209
12265
|
}
|
|
@@ -12287,7 +12343,10 @@ var Environment = class extends Session {
|
|
|
12287
12343
|
}
|
|
12288
12344
|
}
|
|
12289
12345
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
12290
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
12346
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
12347
|
+
toolPath,
|
|
12348
|
+
metamodels
|
|
12349
|
+
)) {
|
|
12291
12350
|
await this._runGraphql(mutation.query, mutation.label);
|
|
12292
12351
|
}
|
|
12293
12352
|
}
|
|
@@ -12336,7 +12395,9 @@ var Environment = class extends Session {
|
|
|
12336
12395
|
}
|
|
12337
12396
|
if (eventType.payloadSchema?.properties) {
|
|
12338
12397
|
const fieldSpecs = {};
|
|
12339
|
-
for (const [propName, propSchema] of Object.entries(
|
|
12398
|
+
for (const [propName, propSchema] of Object.entries(
|
|
12399
|
+
eventType.payloadSchema.properties
|
|
12400
|
+
)) {
|
|
12340
12401
|
const schema = propSchema;
|
|
12341
12402
|
fieldSpecs[propName] = {
|
|
12342
12403
|
type: schema.type ?? "string",
|
|
@@ -12619,7 +12680,9 @@ var Environment = class extends Session {
|
|
|
12619
12680
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12620
12681
|
await Promise.all(
|
|
12621
12682
|
wave.map(async (plan) => {
|
|
12622
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12683
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12684
|
+
plan.slice
|
|
12685
|
+
);
|
|
12623
12686
|
if (items.length !== plan.slice.length) {
|
|
12624
12687
|
throw new Error(
|
|
12625
12688
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -12649,13 +12712,10 @@ var Environment = class extends Session {
|
|
|
12649
12712
|
let lastError;
|
|
12650
12713
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12651
12714
|
try {
|
|
12652
|
-
const response = await this.controlPlaneRequest(
|
|
12653
|
-
|
|
12654
|
-
{
|
|
12655
|
-
|
|
12656
|
-
body: JSON.stringify({ records: chunk })
|
|
12657
|
-
}
|
|
12658
|
-
);
|
|
12715
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
12716
|
+
method: "POST",
|
|
12717
|
+
body: JSON.stringify({ records: chunk })
|
|
12718
|
+
});
|
|
12659
12719
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
12660
12720
|
return { items, durationMs: Date.now() - wallStart };
|
|
12661
12721
|
} catch (error) {
|
|
@@ -12718,7 +12778,9 @@ var Environment = class extends Session {
|
|
|
12718
12778
|
* Fetch a single record import by id.
|
|
12719
12779
|
*/
|
|
12720
12780
|
async getRecordImport(importId) {
|
|
12721
|
-
return this.controlPlaneRequest(
|
|
12781
|
+
return this.controlPlaneRequest(
|
|
12782
|
+
`/control/record-imports/${importId}`
|
|
12783
|
+
);
|
|
12722
12784
|
}
|
|
12723
12785
|
/**
|
|
12724
12786
|
* Cancel a queued/background record import.
|
|
@@ -12731,36 +12793,186 @@ var Environment = class extends Session {
|
|
|
12731
12793
|
}
|
|
12732
12794
|
);
|
|
12733
12795
|
}
|
|
12734
|
-
|
|
12796
|
+
};
|
|
12797
|
+
var EnvironmentSession = class extends Session {
|
|
12798
|
+
environment;
|
|
12799
|
+
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
12800
|
+
graphContainerStatus = null;
|
|
12801
|
+
constructor(client, environment, clientId) {
|
|
12802
|
+
super(client, clientId);
|
|
12803
|
+
this.environment = environment;
|
|
12804
|
+
}
|
|
12805
|
+
get environmentId() {
|
|
12806
|
+
return this.environment.environmentId;
|
|
12807
|
+
}
|
|
12808
|
+
get sandboxId() {
|
|
12809
|
+
return this.environment.sandboxId;
|
|
12810
|
+
}
|
|
12811
|
+
get ontologyId() {
|
|
12812
|
+
return this.environment.ontologyId;
|
|
12813
|
+
}
|
|
12814
|
+
get subjectId() {
|
|
12815
|
+
return this.environment.subjectId;
|
|
12816
|
+
}
|
|
12817
|
+
get envName() {
|
|
12818
|
+
return this.environment.envName;
|
|
12819
|
+
}
|
|
12820
|
+
get versionId() {
|
|
12821
|
+
return this.environment.versionId;
|
|
12822
|
+
}
|
|
12823
|
+
get granularId() {
|
|
12824
|
+
return this.environment.granularId;
|
|
12825
|
+
}
|
|
12826
|
+
get permissionProfileId() {
|
|
12827
|
+
return this.environment.permissionProfileId;
|
|
12828
|
+
}
|
|
12829
|
+
get apiEndpoint() {
|
|
12830
|
+
return this.environment.apiEndpoint;
|
|
12831
|
+
}
|
|
12832
|
+
get data() {
|
|
12833
|
+
return this.environment.data;
|
|
12834
|
+
}
|
|
12835
|
+
get feedback() {
|
|
12836
|
+
return this.environment.feedback;
|
|
12837
|
+
}
|
|
12735
12838
|
/**
|
|
12736
|
-
*
|
|
12839
|
+
* Return a plain JS snapshot of the synced session heap.
|
|
12737
12840
|
*/
|
|
12738
|
-
|
|
12739
|
-
|
|
12841
|
+
getHeap() {
|
|
12842
|
+
const doc = this.document;
|
|
12843
|
+
return normalizeHeapSnapshot(doc?.heap);
|
|
12844
|
+
}
|
|
12845
|
+
async graphql(query, variables) {
|
|
12846
|
+
return this.environment.graphql(query, variables);
|
|
12847
|
+
}
|
|
12848
|
+
async defineRelationship(options) {
|
|
12849
|
+
return this.environment.defineRelationship(options);
|
|
12850
|
+
}
|
|
12851
|
+
async getRelationships(modelPath) {
|
|
12852
|
+
return this.environment.getRelationships(modelPath);
|
|
12853
|
+
}
|
|
12854
|
+
async attach(modelPath, submodelPath, targetPath) {
|
|
12855
|
+
return this.environment.attach(modelPath, submodelPath, targetPath);
|
|
12856
|
+
}
|
|
12857
|
+
async detach(modelPath, submodelPath, targetPath) {
|
|
12858
|
+
return this.environment.detach(modelPath, submodelPath, targetPath);
|
|
12859
|
+
}
|
|
12860
|
+
async listRelated(modelPath, submodelPath) {
|
|
12861
|
+
return this.environment.listRelated(modelPath, submodelPath);
|
|
12862
|
+
}
|
|
12863
|
+
async applyManifest(manifest) {
|
|
12864
|
+
return this.environment.applyManifest(manifest);
|
|
12865
|
+
}
|
|
12866
|
+
async recordObject(options) {
|
|
12867
|
+
return this.environment.recordObject(options);
|
|
12868
|
+
}
|
|
12869
|
+
async recordObjects(records, options) {
|
|
12870
|
+
return this.environment.recordObjects(records, options);
|
|
12871
|
+
}
|
|
12872
|
+
async enqueueRecordImport(records, options = {}) {
|
|
12873
|
+
return this.environment.enqueueRecordImport(records, options);
|
|
12874
|
+
}
|
|
12875
|
+
async listRecordImports(status) {
|
|
12876
|
+
return this.environment.listRecordImports(status);
|
|
12877
|
+
}
|
|
12878
|
+
async getRecordImportSummary() {
|
|
12879
|
+
return this.environment.getRecordImportSummary();
|
|
12880
|
+
}
|
|
12881
|
+
async getAwaitingRecordCount() {
|
|
12882
|
+
return this.environment.getAwaitingRecordCount();
|
|
12883
|
+
}
|
|
12884
|
+
async getRecordImport(importId) {
|
|
12885
|
+
return this.environment.getRecordImport(importId);
|
|
12886
|
+
}
|
|
12887
|
+
async cancelRecordImport(importId) {
|
|
12888
|
+
return this.environment.cancelRecordImport(importId);
|
|
12889
|
+
}
|
|
12890
|
+
async listFeedback() {
|
|
12891
|
+
return this.environment.listFeedback();
|
|
12740
12892
|
}
|
|
12741
12893
|
/**
|
|
12742
|
-
*
|
|
12894
|
+
* Close the session and disconnect from the sandbox.
|
|
12895
|
+
*
|
|
12896
|
+
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
12897
|
+
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
12898
|
+
* acknowledgement was observed.
|
|
12743
12899
|
*/
|
|
12744
|
-
async
|
|
12745
|
-
|
|
12900
|
+
async disconnect() {
|
|
12901
|
+
let wsNotifiedRuntime = false;
|
|
12902
|
+
try {
|
|
12903
|
+
const goodbye = await this.rpc(
|
|
12904
|
+
"client.goodbye",
|
|
12905
|
+
{
|
|
12906
|
+
timestamp: Date.now()
|
|
12907
|
+
}
|
|
12908
|
+
);
|
|
12909
|
+
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
12910
|
+
} catch {
|
|
12911
|
+
wsNotifiedRuntime = false;
|
|
12912
|
+
}
|
|
12913
|
+
if (!wsNotifiedRuntime) {
|
|
12914
|
+
try {
|
|
12915
|
+
await fetch(
|
|
12916
|
+
`${this.environment.runtimeBaseUrl}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
12917
|
+
{
|
|
12918
|
+
method: "POST",
|
|
12919
|
+
headers: {
|
|
12920
|
+
"Content-Type": "application/json",
|
|
12921
|
+
Authorization: `Bearer ${this.environment.authToken}`,
|
|
12922
|
+
Connection: "close"
|
|
12923
|
+
},
|
|
12924
|
+
body: JSON.stringify({
|
|
12925
|
+
reason: "sdk_disconnect_http_fallback",
|
|
12926
|
+
sessionId: this.client.currentSessionId
|
|
12927
|
+
})
|
|
12928
|
+
}
|
|
12929
|
+
);
|
|
12930
|
+
} catch {
|
|
12931
|
+
}
|
|
12932
|
+
}
|
|
12933
|
+
this.client.disconnect();
|
|
12746
12934
|
}
|
|
12747
12935
|
/**
|
|
12748
|
-
*
|
|
12936
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
12749
12937
|
*/
|
|
12750
|
-
|
|
12751
|
-
|
|
12938
|
+
disconnectTransport() {
|
|
12939
|
+
this.client.disconnect();
|
|
12752
12940
|
}
|
|
12753
12941
|
/**
|
|
12754
|
-
*
|
|
12942
|
+
* Backwards-compatible alias for `disconnect()`.
|
|
12755
12943
|
*/
|
|
12756
|
-
async
|
|
12757
|
-
|
|
12944
|
+
async close() {
|
|
12945
|
+
await this.disconnect();
|
|
12758
12946
|
}
|
|
12759
12947
|
/**
|
|
12760
|
-
*
|
|
12948
|
+
* Check if the graph container is ready and warm.
|
|
12761
12949
|
*/
|
|
12762
|
-
async
|
|
12763
|
-
|
|
12950
|
+
async checkReadiness() {
|
|
12951
|
+
const result = await this.client.call("client.heartbeat", {});
|
|
12952
|
+
const containerStatus = result?.graphContainerStatus ?? {
|
|
12953
|
+
lastKeepAliveAt: Date.now(),
|
|
12954
|
+
status: "unknown"
|
|
12955
|
+
};
|
|
12956
|
+
this.graphContainerStatus = containerStatus;
|
|
12957
|
+
this.emit("readiness", containerStatus);
|
|
12958
|
+
return containerStatus;
|
|
12959
|
+
}
|
|
12960
|
+
};
|
|
12961
|
+
var OntologyHandle = class {
|
|
12962
|
+
granular;
|
|
12963
|
+
ontologyNameOrId;
|
|
12964
|
+
constructor(granular, ontologyNameOrId) {
|
|
12965
|
+
this.granular = granular;
|
|
12966
|
+
this.ontologyNameOrId = ontologyNameOrId;
|
|
12967
|
+
}
|
|
12968
|
+
get effects() {
|
|
12969
|
+
return {
|
|
12970
|
+
register: async (effect) => this.granular.registerEffect(this.ontologyNameOrId, effect),
|
|
12971
|
+
registerMany: async (effects) => this.granular.registerEffects(this.ontologyNameOrId, effects),
|
|
12972
|
+
unregister: async (name) => this.granular.unregisterEffect(this.ontologyNameOrId, name),
|
|
12973
|
+
clear: async () => this.granular.unregisterAllEffects(this.ontologyNameOrId),
|
|
12974
|
+
disconnect: async () => this.granular.disconnectEffects(this.ontologyNameOrId)
|
|
12975
|
+
};
|
|
12764
12976
|
}
|
|
12765
12977
|
};
|
|
12766
12978
|
var Granular = class _Granular {
|
|
@@ -12785,7 +12997,9 @@ var Granular = class _Granular {
|
|
|
12785
12997
|
constructor(options) {
|
|
12786
12998
|
const auth = options.token ?? options.apiKey;
|
|
12787
12999
|
if (!auth) {
|
|
12788
|
-
throw new Error(
|
|
13000
|
+
throw new Error(
|
|
13001
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
13002
|
+
);
|
|
12789
13003
|
}
|
|
12790
13004
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
12791
13005
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -12795,12 +13009,18 @@ var Granular = class _Granular {
|
|
|
12795
13009
|
this.onReconnectError = options.onReconnectError;
|
|
12796
13010
|
this.httpUrl = this.apiUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://").replace(/\/ws$/, "");
|
|
12797
13011
|
}
|
|
13012
|
+
/**
|
|
13013
|
+
* Return an ontology-scoped handle for effects and other ontology-level APIs.
|
|
13014
|
+
*/
|
|
13015
|
+
ontology(ontologyNameOrId) {
|
|
13016
|
+
return new OntologyHandle(this, ontologyNameOrId);
|
|
13017
|
+
}
|
|
12798
13018
|
/**
|
|
12799
13019
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12800
|
-
*
|
|
13020
|
+
*
|
|
12801
13021
|
* @param options - User options
|
|
12802
13022
|
* @returns The recorded user with both `userId` and `granularId`
|
|
12803
|
-
*
|
|
13023
|
+
*
|
|
12804
13024
|
* @example
|
|
12805
13025
|
* ```typescript
|
|
12806
13026
|
* const user = await granular.recordUser({
|
|
@@ -12811,14 +13031,16 @@ var Granular = class _Granular {
|
|
|
12811
13031
|
* ```
|
|
12812
13032
|
*/
|
|
12813
13033
|
async recordUser(options) {
|
|
12814
|
-
const subject = normalizeSubject(
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
13034
|
+
const subject = normalizeSubject(
|
|
13035
|
+
await this.request("/control/subjects", {
|
|
13036
|
+
method: "POST",
|
|
13037
|
+
body: JSON.stringify({
|
|
13038
|
+
identityId: options.userId,
|
|
13039
|
+
name: options.name,
|
|
13040
|
+
email: options.email
|
|
13041
|
+
})
|
|
12820
13042
|
})
|
|
12821
|
-
|
|
13043
|
+
);
|
|
12822
13044
|
return normalizeUser({
|
|
12823
13045
|
granularId: subject.granularId,
|
|
12824
13046
|
userId: options.userId,
|
|
@@ -12829,7 +13051,23 @@ var Granular = class _Granular {
|
|
|
12829
13051
|
permissions: options.permissions || []
|
|
12830
13052
|
});
|
|
12831
13053
|
}
|
|
13054
|
+
/**
|
|
13055
|
+
* Alias for `recordUser()` with user-facing naming that matches upsert semantics.
|
|
13056
|
+
*/
|
|
13057
|
+
async upsertUser(options) {
|
|
13058
|
+
return this.recordUser(options);
|
|
13059
|
+
}
|
|
12832
13060
|
async resolveConnectUser(options) {
|
|
13061
|
+
const providedIdentityCount = [
|
|
13062
|
+
Boolean(options.user),
|
|
13063
|
+
Boolean(options.userId),
|
|
13064
|
+
Boolean(options.granularId)
|
|
13065
|
+
].filter(Boolean).length;
|
|
13066
|
+
if (providedIdentityCount !== 1) {
|
|
13067
|
+
throw new Error(
|
|
13068
|
+
"openEnvironment() requires exactly one of userId, granularId, or a user object returned by recordUser()."
|
|
13069
|
+
);
|
|
13070
|
+
}
|
|
12833
13071
|
if (options.user) {
|
|
12834
13072
|
const user = normalizeUser(options.user);
|
|
12835
13073
|
return {
|
|
@@ -12865,76 +13103,141 @@ var Granular = class _Granular {
|
|
|
12865
13103
|
permissions: options.permissions || []
|
|
12866
13104
|
};
|
|
12867
13105
|
}
|
|
12868
|
-
throw new Error(
|
|
13106
|
+
throw new Error(
|
|
13107
|
+
"openEnvironment() requires either userId, granularId, or a user object returned by recordUser()."
|
|
13108
|
+
);
|
|
12869
13109
|
}
|
|
12870
13110
|
/**
|
|
12871
|
-
*
|
|
12872
|
-
*
|
|
12873
|
-
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12874
|
-
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12875
|
-
* the sandbox registry automatically.
|
|
12876
|
-
*
|
|
12877
|
-
* @param options - Connection options
|
|
12878
|
-
* @returns An active environment session
|
|
12879
|
-
*
|
|
13111
|
+
* Open or resolve an ontology environment for one user without opening a session.
|
|
13112
|
+
*
|
|
12880
13113
|
* @example
|
|
12881
13114
|
* ```typescript
|
|
12882
|
-
* const environment = await granular.
|
|
13115
|
+
* const environment = await granular.openEnvironment({
|
|
12883
13116
|
* ontology: 'my-ontology',
|
|
12884
|
-
*
|
|
13117
|
+
* tag: 'dev',
|
|
12885
13118
|
* userId: 'user_123',
|
|
12886
13119
|
* permissions: ['agent'],
|
|
12887
13120
|
* });
|
|
12888
|
-
*
|
|
12889
|
-
* await
|
|
12890
|
-
*
|
|
12891
|
-
*
|
|
12892
|
-
*
|
|
12893
|
-
* handler: async () => 'Hello!',
|
|
13121
|
+
*
|
|
13122
|
+
* await environment.data.record({
|
|
13123
|
+
* className: 'customer',
|
|
13124
|
+
* id: 'acme',
|
|
13125
|
+
* fields: { name: 'Acme' },
|
|
12894
13126
|
* });
|
|
12895
|
-
*
|
|
12896
|
-
*
|
|
12897
|
-
* const job = await
|
|
12898
|
-
*
|
|
12899
|
-
* return await tools.greet({});
|
|
12900
|
-
* `);
|
|
12901
|
-
*
|
|
12902
|
-
* console.log(await job.result); // 'Hello!'
|
|
13127
|
+
*
|
|
13128
|
+
* const session = await environment.sessions.create();
|
|
13129
|
+
* const job = await session.submitJob(`return "hello";`);
|
|
13130
|
+
* console.log(await job.result);
|
|
12903
13131
|
* ```
|
|
12904
|
-
|
|
13132
|
+
*/
|
|
13133
|
+
async openEnvironment(options) {
|
|
13134
|
+
const envData = await this.resolveOpenEnvironmentData(
|
|
13135
|
+
options,
|
|
13136
|
+
"openEnvironment"
|
|
13137
|
+
);
|
|
13138
|
+
return this.bindEnvironmentHandle(envData);
|
|
13139
|
+
}
|
|
13140
|
+
/**
|
|
13141
|
+
* Deprecated compatibility alias for `openEnvironment()`.
|
|
13142
|
+
*
|
|
13143
|
+
* `connect()` no longer opens a runtime session automatically.
|
|
13144
|
+
*/
|
|
12905
13145
|
async connect(options) {
|
|
12906
|
-
|
|
13146
|
+
return this.openEnvironment({
|
|
13147
|
+
...options,
|
|
13148
|
+
tag: this.resolveRequestedTag(options, "connect"),
|
|
13149
|
+
permissions: options.permissions || options.user?.permissions || []
|
|
13150
|
+
});
|
|
13151
|
+
}
|
|
13152
|
+
resolveRequestedTag(options, methodName) {
|
|
13153
|
+
const tag = options.tag?.trim() || options.tagName?.trim() || options.environment?.trim();
|
|
13154
|
+
if (!tag) {
|
|
13155
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
13156
|
+
}
|
|
13157
|
+
return tag;
|
|
13158
|
+
}
|
|
13159
|
+
buildManagedEnvironmentName(tag, versionId) {
|
|
13160
|
+
return `__sdk__${tag}__${versionId}`;
|
|
13161
|
+
}
|
|
13162
|
+
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
13163
|
+
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
13164
|
+
return environment.tagId === tagId || environmentTagName === tagName || environment.environment === tagName || environment.envName === tagName || environment.environment === this.buildManagedEnvironmentName(tagName, environment.versionId) || environment.envName === this.buildManagedEnvironmentName(tagName, environment.versionId);
|
|
13165
|
+
}
|
|
13166
|
+
sortEnvironmentsByRecency(environments) {
|
|
13167
|
+
return [...environments].sort(
|
|
13168
|
+
(left, right) => right.updatedAt - left.updatedAt
|
|
13169
|
+
);
|
|
13170
|
+
}
|
|
13171
|
+
async resolveOpenEnvironmentData(options, methodName) {
|
|
12907
13172
|
const ontology = options.ontology;
|
|
12908
13173
|
if (!ontology) {
|
|
12909
|
-
throw new Error(
|
|
13174
|
+
throw new Error(`${methodName}() requires \`ontology\`.`);
|
|
12910
13175
|
}
|
|
12911
|
-
const
|
|
12912
|
-
if (!
|
|
12913
|
-
throw new Error(
|
|
13176
|
+
const tagName = options.tag?.trim();
|
|
13177
|
+
if (!tagName) {
|
|
13178
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
12914
13179
|
}
|
|
12915
|
-
const tagName = options.tagName?.trim() || void 0;
|
|
12916
13180
|
const user = await this.resolveConnectUser(options);
|
|
13181
|
+
if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
|
|
13182
|
+
throw new Error(
|
|
13183
|
+
`${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
|
|
13184
|
+
);
|
|
13185
|
+
}
|
|
12917
13186
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12918
13187
|
for (const profileName of user.permissions) {
|
|
12919
|
-
const profileId = await this.ensurePermissionProfile(
|
|
12920
|
-
|
|
13188
|
+
const profileId = await this.ensurePermissionProfile(
|
|
13189
|
+
sandbox.sandboxId,
|
|
13190
|
+
profileName
|
|
13191
|
+
);
|
|
13192
|
+
await this.ensureAssignment(
|
|
13193
|
+
user.granularId,
|
|
13194
|
+
sandbox.sandboxId,
|
|
13195
|
+
profileId
|
|
13196
|
+
);
|
|
13197
|
+
}
|
|
13198
|
+
const tags = await this.request(
|
|
13199
|
+
`/control/sandboxes/${sandbox.sandboxId}/tags`
|
|
13200
|
+
);
|
|
13201
|
+
const tag = (tags.items || []).find(
|
|
13202
|
+
(candidate) => Boolean(candidate?.name === tagName)
|
|
13203
|
+
);
|
|
13204
|
+
if (!tag) {
|
|
13205
|
+
throw new Error(
|
|
13206
|
+
`Tag "${tagName}" was not found for ontology ${sandbox.sandboxId}.`
|
|
13207
|
+
);
|
|
13208
|
+
}
|
|
13209
|
+
const targetVersionId = tag.targetVersionId || tag.targetBuildId;
|
|
13210
|
+
if (!targetVersionId) {
|
|
13211
|
+
throw new Error(
|
|
13212
|
+
`Tag "${tagName}" does not currently point to a build/version.`
|
|
13213
|
+
);
|
|
13214
|
+
}
|
|
13215
|
+
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
13216
|
+
const userEnvironments = allEnvironments.filter(
|
|
13217
|
+
(environment) => environment.subjectId === user.granularId
|
|
13218
|
+
);
|
|
13219
|
+
const currentMatches = this.sortEnvironmentsByRecency(
|
|
13220
|
+
userEnvironments.filter(
|
|
13221
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId) && environment.versionId === targetVersionId
|
|
13222
|
+
)
|
|
13223
|
+
);
|
|
13224
|
+
if (currentMatches.length > 0) {
|
|
13225
|
+
return currentMatches[0];
|
|
13226
|
+
}
|
|
13227
|
+
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
13228
|
+
userEnvironments.filter(
|
|
13229
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
|
|
13230
|
+
)
|
|
13231
|
+
);
|
|
13232
|
+
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
13233
|
+
return outdatedMatches[0];
|
|
12921
13234
|
}
|
|
12922
|
-
|
|
13235
|
+
return this.environments.create(sandbox.sandboxId, {
|
|
12923
13236
|
subjectId: user.granularId,
|
|
12924
|
-
environment:
|
|
12925
|
-
|
|
13237
|
+
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
13238
|
+
tagId: tag.tagId,
|
|
12926
13239
|
permissionProfileId: null
|
|
12927
13240
|
});
|
|
12928
|
-
await this.activateEnvironment(envData.environmentId);
|
|
12929
|
-
const session = await this.request("/ws/sessions", {
|
|
12930
|
-
method: "POST",
|
|
12931
|
-
body: JSON.stringify({
|
|
12932
|
-
environmentId: envData.environmentId,
|
|
12933
|
-
clientId,
|
|
12934
|
-
initialHeap: options.initialHeap
|
|
12935
|
-
})
|
|
12936
|
-
});
|
|
12937
|
-
return this.bindWebSocketEnvironment(envData, clientId, session);
|
|
12938
13241
|
}
|
|
12939
13242
|
/**
|
|
12940
13243
|
* List active (open) sessions for an environment — each session is one agent conversation thread.
|
|
@@ -12969,7 +13272,9 @@ var Granular = class _Granular {
|
|
|
12969
13272
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
12970
13273
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
12971
13274
|
summary: row.summary != null ? String(row.summary) : null,
|
|
12972
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13275
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13276
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
13277
|
+
) : null,
|
|
12973
13278
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
12974
13279
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
12975
13280
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -12995,6 +13300,7 @@ var Granular = class _Granular {
|
|
|
12995
13300
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
12996
13301
|
await this.activateEnvironment(options.environmentId);
|
|
12997
13302
|
const envData = await this.environments.get(options.environmentId);
|
|
13303
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
12998
13304
|
const session = await this.request("/ws/sessions", {
|
|
12999
13305
|
method: "POST",
|
|
13000
13306
|
body: JSON.stringify({
|
|
@@ -13003,7 +13309,7 @@ var Granular = class _Granular {
|
|
|
13003
13309
|
initialHeap: options.initialHeap
|
|
13004
13310
|
})
|
|
13005
13311
|
});
|
|
13006
|
-
return this.
|
|
13312
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, session);
|
|
13007
13313
|
}
|
|
13008
13314
|
/**
|
|
13009
13315
|
* Connect to an existing open session (same conversation thread) using a freshly minted WebSocket token.
|
|
@@ -13015,7 +13321,8 @@ var Granular = class _Granular {
|
|
|
13015
13321
|
body: JSON.stringify({})
|
|
13016
13322
|
});
|
|
13017
13323
|
const envData = await this.environments.get(minted.environmentId);
|
|
13018
|
-
|
|
13324
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
13325
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, minted);
|
|
13019
13326
|
}
|
|
13020
13327
|
/**
|
|
13021
13328
|
* Mark a session closed in the control plane. If `environment` is the connected handle for that
|
|
@@ -13047,7 +13354,11 @@ var Granular = class _Granular {
|
|
|
13047
13354
|
});
|
|
13048
13355
|
return this.connectSession({ sessionId, clientId: options?.clientId });
|
|
13049
13356
|
}
|
|
13050
|
-
|
|
13357
|
+
bindEnvironmentHandle(envData) {
|
|
13358
|
+
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13359
|
+
return new Environment(this, envData, this.apiKey, graphqlEndpoint);
|
|
13360
|
+
}
|
|
13361
|
+
async bindWebSocketEnvironmentSession(environment, clientId, session) {
|
|
13051
13362
|
const client = new WSClient({
|
|
13052
13363
|
url: session.wsUrl,
|
|
13053
13364
|
sessionId: session.sessionId,
|
|
@@ -13058,10 +13369,13 @@ var Granular = class _Granular {
|
|
|
13058
13369
|
onReconnectError: this.onReconnectError
|
|
13059
13370
|
});
|
|
13060
13371
|
await client.connect();
|
|
13061
|
-
const
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13372
|
+
const environmentSession = new EnvironmentSession(
|
|
13373
|
+
client,
|
|
13374
|
+
environment,
|
|
13375
|
+
clientId
|
|
13376
|
+
);
|
|
13377
|
+
await environmentSession.hello();
|
|
13378
|
+
return environmentSession;
|
|
13065
13379
|
}
|
|
13066
13380
|
async activateEnvironment(environmentId) {
|
|
13067
13381
|
await this.request(`/orchestrator/runtime/environments/${environmentId}/activate`, {
|
|
@@ -13093,14 +13407,18 @@ var Granular = class _Granular {
|
|
|
13093
13407
|
};
|
|
13094
13408
|
}
|
|
13095
13409
|
async publishSandboxEffectCatalog(host) {
|
|
13096
|
-
const effects = Array.from(
|
|
13097
|
-
(
|
|
13098
|
-
);
|
|
13099
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13410
|
+
const effects = Array.from(
|
|
13411
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
13412
|
+
).map((effect) => this.serializeEffect(effect));
|
|
13413
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13414
|
+
effects
|
|
13415
|
+
});
|
|
13100
13416
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
13101
13417
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
13102
13418
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
13103
|
-
const detail = rejected.map(
|
|
13419
|
+
const detail = rejected.map(
|
|
13420
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
13421
|
+
).join("; ");
|
|
13104
13422
|
throw new Error(
|
|
13105
13423
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
13106
13424
|
);
|
|
@@ -13134,13 +13452,15 @@ var Granular = class _Granular {
|
|
|
13134
13452
|
disconnectError
|
|
13135
13453
|
);
|
|
13136
13454
|
}
|
|
13137
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13455
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13456
|
+
(reconnectError) => {
|
|
13457
|
+
console.error(
|
|
13458
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
13459
|
+
reconnectError
|
|
13460
|
+
);
|
|
13461
|
+
console.error("[Granular] Original heartbeat failure:", error);
|
|
13462
|
+
}
|
|
13463
|
+
);
|
|
13144
13464
|
}
|
|
13145
13465
|
startEffectHostHeartbeat(host) {
|
|
13146
13466
|
if (host.heartbeatTimer) {
|
|
@@ -13161,9 +13481,15 @@ var Granular = class _Granular {
|
|
|
13161
13481
|
host.heartbeatInFlight = false;
|
|
13162
13482
|
});
|
|
13163
13483
|
};
|
|
13164
|
-
sendHeartbeat(
|
|
13484
|
+
sendHeartbeat(
|
|
13485
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
13486
|
+
false
|
|
13487
|
+
);
|
|
13165
13488
|
host.heartbeatTimer = setInterval(() => {
|
|
13166
|
-
sendHeartbeat(
|
|
13489
|
+
sendHeartbeat(
|
|
13490
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
13491
|
+
true
|
|
13492
|
+
);
|
|
13167
13493
|
}, 1e4);
|
|
13168
13494
|
}
|
|
13169
13495
|
stopEffectHostHeartbeat(host) {
|
|
@@ -13195,7 +13521,12 @@ var Granular = class _Granular {
|
|
|
13195
13521
|
const effectClientId = crypto.randomUUID();
|
|
13196
13522
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
13197
13523
|
const wsClient = new WSClient({
|
|
13198
|
-
url: buildEffectHostUrl(
|
|
13524
|
+
url: buildEffectHostUrl(
|
|
13525
|
+
this.apiUrl,
|
|
13526
|
+
sandboxId,
|
|
13527
|
+
effectClientId,
|
|
13528
|
+
clientId
|
|
13529
|
+
),
|
|
13199
13530
|
sessionId: `effect-host:${effectClientId}`,
|
|
13200
13531
|
token: this.apiKey,
|
|
13201
13532
|
tokenProvider: this.tokenProvider,
|
|
@@ -13214,7 +13545,10 @@ var Granular = class _Granular {
|
|
|
13214
13545
|
};
|
|
13215
13546
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
13216
13547
|
const request = params;
|
|
13217
|
-
return invokeRegisteredEffect(
|
|
13548
|
+
return invokeRegisteredEffect(
|
|
13549
|
+
this.getSandboxEffectMap(sandboxId),
|
|
13550
|
+
request
|
|
13551
|
+
);
|
|
13218
13552
|
});
|
|
13219
13553
|
wsClient.on("open", () => {
|
|
13220
13554
|
void this.synchronizeEffectHost(host).catch((error) => {
|
|
@@ -13262,7 +13596,7 @@ var Granular = class _Granular {
|
|
|
13262
13596
|
}
|
|
13263
13597
|
/**
|
|
13264
13598
|
* Register multiple effects (tools) for a specific sandbox.
|
|
13265
|
-
*
|
|
13599
|
+
*
|
|
13266
13600
|
* batch version of `registerEffect`.
|
|
13267
13601
|
*/
|
|
13268
13602
|
async registerEffects(sandboxNameOrId, effects) {
|
|
@@ -13276,7 +13610,7 @@ var Granular = class _Granular {
|
|
|
13276
13610
|
}
|
|
13277
13611
|
/**
|
|
13278
13612
|
* Unregister an effect from a sandbox.
|
|
13279
|
-
*
|
|
13613
|
+
*
|
|
13280
13614
|
* Removes it from the local sandbox registry and updates the
|
|
13281
13615
|
* sandbox-scoped live catalog.
|
|
13282
13616
|
*/
|
|
@@ -13375,27 +13709,31 @@ var Granular = class _Granular {
|
|
|
13375
13709
|
const assignments = await this.request(
|
|
13376
13710
|
`/control/subjects/${subjectId}/assignments`
|
|
13377
13711
|
);
|
|
13378
|
-
const existing = assignments.items.find(
|
|
13379
|
-
(a) => a.sandboxId === sandboxId
|
|
13380
|
-
);
|
|
13712
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
13381
13713
|
if (existing) {
|
|
13382
13714
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
13383
13715
|
return;
|
|
13384
13716
|
}
|
|
13385
|
-
await this.request(
|
|
13386
|
-
|
|
13387
|
-
|
|
13717
|
+
await this.request(
|
|
13718
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
13719
|
+
{
|
|
13720
|
+
method: "DELETE"
|
|
13721
|
+
}
|
|
13722
|
+
);
|
|
13388
13723
|
}
|
|
13389
13724
|
} catch {
|
|
13390
13725
|
}
|
|
13391
|
-
await this.request(
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13726
|
+
await this.request(
|
|
13727
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
13728
|
+
{
|
|
13729
|
+
method: "POST",
|
|
13730
|
+
body: JSON.stringify({
|
|
13731
|
+
sandboxId,
|
|
13732
|
+
subjectId,
|
|
13733
|
+
permissionProfileId
|
|
13734
|
+
})
|
|
13735
|
+
}
|
|
13736
|
+
);
|
|
13399
13737
|
}
|
|
13400
13738
|
/**
|
|
13401
13739
|
* Sandbox management API
|
|
@@ -13475,23 +13813,33 @@ var Granular = class _Granular {
|
|
|
13475
13813
|
},
|
|
13476
13814
|
get: async (environmentId) => {
|
|
13477
13815
|
return normalizeEnvironmentData(
|
|
13478
|
-
await this.request(
|
|
13816
|
+
await this.request(
|
|
13817
|
+
`/control/environments/${environmentId}`
|
|
13818
|
+
)
|
|
13479
13819
|
);
|
|
13480
13820
|
},
|
|
13481
13821
|
create: async (sandboxId, data) => {
|
|
13482
13822
|
const environmentName = data.environment || data.envName;
|
|
13483
|
-
return normalizeEnvironmentData(
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13823
|
+
return normalizeEnvironmentData(
|
|
13824
|
+
await this.request(
|
|
13825
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
13826
|
+
{
|
|
13827
|
+
method: "POST",
|
|
13828
|
+
body: JSON.stringify({
|
|
13829
|
+
...data,
|
|
13830
|
+
envName: environmentName
|
|
13831
|
+
})
|
|
13832
|
+
}
|
|
13833
|
+
)
|
|
13834
|
+
);
|
|
13490
13835
|
},
|
|
13491
13836
|
delete: async (environmentId) => {
|
|
13492
|
-
return this.request(
|
|
13493
|
-
|
|
13494
|
-
|
|
13837
|
+
return this.request(
|
|
13838
|
+
`/control/environments/${environmentId}`,
|
|
13839
|
+
{
|
|
13840
|
+
method: "DELETE"
|
|
13841
|
+
}
|
|
13842
|
+
);
|
|
13495
13843
|
}
|
|
13496
13844
|
};
|
|
13497
13845
|
}
|
|
@@ -13511,10 +13859,13 @@ var Granular = class _Granular {
|
|
|
13511
13859
|
}
|
|
13512
13860
|
if (params.since) query.set("since", params.since.toISOString());
|
|
13513
13861
|
if (params.until) query.set("until", params.until.toISOString());
|
|
13514
|
-
if (params.isAcked !== void 0)
|
|
13862
|
+
if (params.isAcked !== void 0)
|
|
13863
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
13515
13864
|
if (params.limit) query.set("limit", String(params.limit));
|
|
13516
13865
|
if (params.offset) query.set("offset", String(params.offset));
|
|
13517
|
-
const result = await this.request(
|
|
13866
|
+
const result = await this.request(
|
|
13867
|
+
`/control/stream-events?${query.toString()}`
|
|
13868
|
+
);
|
|
13518
13869
|
return (result.items || []).map((row) => ({
|
|
13519
13870
|
eventId: row.event_id,
|
|
13520
13871
|
streamName: row.stream_name,
|
|
@@ -13545,7 +13896,9 @@ var Granular = class _Granular {
|
|
|
13545
13896
|
since: cursor,
|
|
13546
13897
|
limit: 100
|
|
13547
13898
|
});
|
|
13548
|
-
const orderedEvents = [...events].sort(
|
|
13899
|
+
const orderedEvents = [...events].sort(
|
|
13900
|
+
(a, b) => a.createdAt - b.createdAt
|
|
13901
|
+
);
|
|
13549
13902
|
for (const event of orderedEvents) {
|
|
13550
13903
|
if (seenEventIds.has(event.eventId)) {
|
|
13551
13904
|
continue;
|
|
@@ -13558,15 +13911,19 @@ var Granular = class _Granular {
|
|
|
13558
13911
|
params.onEvent(event);
|
|
13559
13912
|
}
|
|
13560
13913
|
} catch (err) {
|
|
13561
|
-
params.onError?.(
|
|
13914
|
+
params.onError?.(
|
|
13915
|
+
err instanceof Error ? err : new Error(String(err))
|
|
13916
|
+
);
|
|
13562
13917
|
}
|
|
13563
13918
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13564
13919
|
}
|
|
13565
13920
|
};
|
|
13566
13921
|
poll();
|
|
13567
|
-
return {
|
|
13568
|
-
|
|
13569
|
-
|
|
13922
|
+
return {
|
|
13923
|
+
unsubscribe: () => {
|
|
13924
|
+
running = false;
|
|
13925
|
+
}
|
|
13926
|
+
};
|
|
13570
13927
|
},
|
|
13571
13928
|
ack: async (eventId) => {
|
|
13572
13929
|
await this.request("/control/stream-events/ack", {
|
|
@@ -13584,7 +13941,9 @@ var Granular = class _Granular {
|
|
|
13584
13941
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13585
13942
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13586
13943
|
if (params.environment) query.set("environmentId", params.environment);
|
|
13587
|
-
const result = await this.request(
|
|
13944
|
+
const result = await this.request(
|
|
13945
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
13946
|
+
);
|
|
13588
13947
|
return (result.items || []).map((row) => ({
|
|
13589
13948
|
streamName: row.stream_name,
|
|
13590
13949
|
eventType: row.event_type,
|
|
@@ -13602,10 +13961,14 @@ var Granular = class _Granular {
|
|
|
13602
13961
|
get subjects() {
|
|
13603
13962
|
return {
|
|
13604
13963
|
get: async (subjectId) => {
|
|
13605
|
-
return normalizeSubject(
|
|
13964
|
+
return normalizeSubject(
|
|
13965
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
13966
|
+
);
|
|
13606
13967
|
},
|
|
13607
13968
|
listAssignments: async (subjectId) => {
|
|
13608
|
-
return this.request(
|
|
13969
|
+
return this.request(
|
|
13970
|
+
`/control/subjects/${subjectId}/assignments`
|
|
13971
|
+
);
|
|
13609
13972
|
}
|
|
13610
13973
|
};
|
|
13611
13974
|
}
|
|
@@ -13615,24 +13978,31 @@ var Granular = class _Granular {
|
|
|
13615
13978
|
get users() {
|
|
13616
13979
|
return {
|
|
13617
13980
|
create: async (data) => {
|
|
13618
|
-
return normalizeSubject(
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13981
|
+
return normalizeSubject(
|
|
13982
|
+
await this.request("/control/subjects", {
|
|
13983
|
+
method: "POST",
|
|
13984
|
+
body: JSON.stringify({
|
|
13985
|
+
identityId: data.id,
|
|
13986
|
+
name: data.name,
|
|
13987
|
+
email: data.email
|
|
13988
|
+
})
|
|
13624
13989
|
})
|
|
13625
|
-
|
|
13990
|
+
);
|
|
13626
13991
|
},
|
|
13627
13992
|
get: async (id) => {
|
|
13628
|
-
return normalizeSubject(
|
|
13993
|
+
return normalizeSubject(
|
|
13994
|
+
await this.request(`/control/subjects/${id}`)
|
|
13995
|
+
);
|
|
13629
13996
|
}
|
|
13630
13997
|
};
|
|
13631
13998
|
}
|
|
13632
13999
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
13633
14000
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13634
|
-
const result = await this.request(
|
|
13635
|
-
|
|
14001
|
+
const result = await this.request(
|
|
14002
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
14003
|
+
);
|
|
14004
|
+
if (result.items.length === 0)
|
|
14005
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13636
14006
|
return result.items[0].sandboxId;
|
|
13637
14007
|
}
|
|
13638
14008
|
/**
|
|
@@ -13647,9 +14017,9 @@ var Granular = class _Granular {
|
|
|
13647
14017
|
const response = await fetch(url, {
|
|
13648
14018
|
...options,
|
|
13649
14019
|
headers: {
|
|
13650
|
-
|
|
14020
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13651
14021
|
"Content-Type": "application/json",
|
|
13652
|
-
|
|
14022
|
+
Connection: "close",
|
|
13653
14023
|
...options.headers
|
|
13654
14024
|
}
|
|
13655
14025
|
});
|
|
@@ -13660,7 +14030,11 @@ var Granular = class _Granular {
|
|
|
13660
14030
|
return response.json();
|
|
13661
14031
|
}
|
|
13662
14032
|
const errorText = await response.text();
|
|
13663
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
14033
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
14034
|
+
response.status,
|
|
14035
|
+
errorText,
|
|
14036
|
+
url
|
|
14037
|
+
);
|
|
13664
14038
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13665
14039
|
if (this.debugHttp) {
|
|
13666
14040
|
console.warn(
|
|
@@ -13799,21 +14173,6 @@ function reviewGeneratedJobCode(code) {
|
|
|
13799
14173
|
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
14174
|
});
|
|
13801
14175
|
}
|
|
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
14176
|
}
|
|
13818
14177
|
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
13819
14178
|
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
@@ -13859,6 +14218,184 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
13859
14218
|
entryPaths: uniqueStrings(entryPaths, 8)
|
|
13860
14219
|
};
|
|
13861
14220
|
}
|
|
14221
|
+
function normalizeActionSummaryForPrompt(line) {
|
|
14222
|
+
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
14223
|
+
}
|
|
14224
|
+
function collectConversationReferents(liveDoc) {
|
|
14225
|
+
const conversation = asRecord2(liveDoc?.conversation);
|
|
14226
|
+
const persistedReferents = asArray(conversation?.referents).map((value) => asRecord2(value)).filter((value) => Boolean(value));
|
|
14227
|
+
if (persistedReferents.length > 0) {
|
|
14228
|
+
return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
|
|
14229
|
+
}
|
|
14230
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14231
|
+
const entriesByPath = asRecord2(heap?.entriesByPath) || {};
|
|
14232
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14233
|
+
const variablesByName = asRecord2(heap?.variablesByName) || {};
|
|
14234
|
+
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));
|
|
14235
|
+
const referents = [];
|
|
14236
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14237
|
+
const pushReferent = (referent) => {
|
|
14238
|
+
if (!referent?.kind || !referent.ref) return;
|
|
14239
|
+
const key = `${referent.kind}:${referent.ref}`;
|
|
14240
|
+
if (seen.has(key)) return;
|
|
14241
|
+
seen.add(key);
|
|
14242
|
+
referents.push(referent);
|
|
14243
|
+
};
|
|
14244
|
+
for (const message of messages) {
|
|
14245
|
+
if (message.role !== "assistant") continue;
|
|
14246
|
+
const show = asRecord2(message.show);
|
|
14247
|
+
if (!show) continue;
|
|
14248
|
+
const ts = Number(message.ts) || 0;
|
|
14249
|
+
const messageId = typeof message.id === "string" ? message.id : void 0;
|
|
14250
|
+
const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
|
|
14251
|
+
for (const entryPath of uniqueStrings(asArray(show.entryPaths))) {
|
|
14252
|
+
const entry = asRecord2(entriesByPath[entryPath]);
|
|
14253
|
+
pushReferent({
|
|
14254
|
+
id: `entry:${entryPath}`,
|
|
14255
|
+
kind: "entry",
|
|
14256
|
+
ref: entryPath,
|
|
14257
|
+
entryPath,
|
|
14258
|
+
className: typeof entry?.className === "string" ? entry.className : void 0,
|
|
14259
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
|
|
14260
|
+
messageId,
|
|
14261
|
+
jobId,
|
|
14262
|
+
ts
|
|
14263
|
+
});
|
|
14264
|
+
}
|
|
14265
|
+
for (const listName of uniqueStrings(asArray(show.listNames))) {
|
|
14266
|
+
const list = asRecord2(listsByName[listName]);
|
|
14267
|
+
pushReferent({
|
|
14268
|
+
id: `list:${listName}`,
|
|
14269
|
+
kind: "list",
|
|
14270
|
+
ref: listName,
|
|
14271
|
+
listName,
|
|
14272
|
+
className: typeof list?.className === "string" ? list.className : void 0,
|
|
14273
|
+
count: Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14274
|
+
messageId,
|
|
14275
|
+
jobId,
|
|
14276
|
+
ts
|
|
14277
|
+
});
|
|
14278
|
+
}
|
|
14279
|
+
for (const variableName of uniqueStrings(
|
|
14280
|
+
asArray(show.variableNames)
|
|
14281
|
+
)) {
|
|
14282
|
+
const variable = asRecord2(variablesByName[variableName]);
|
|
14283
|
+
const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
|
|
14284
|
+
const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
|
|
14285
|
+
const entry = entryPath ? asRecord2(entriesByPath[entryPath]) : null;
|
|
14286
|
+
const list = listName ? asRecord2(listsByName[listName]) : null;
|
|
14287
|
+
pushReferent({
|
|
14288
|
+
id: `variable:${variableName}`,
|
|
14289
|
+
kind: "variable",
|
|
14290
|
+
ref: variableName,
|
|
14291
|
+
variableName,
|
|
14292
|
+
variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
|
|
14293
|
+
entryPath,
|
|
14294
|
+
listName,
|
|
14295
|
+
className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
|
|
14296
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
14297
|
+
count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14298
|
+
scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
|
|
14299
|
+
messageId,
|
|
14300
|
+
jobId,
|
|
14301
|
+
ts
|
|
14302
|
+
});
|
|
14303
|
+
}
|
|
14304
|
+
}
|
|
14305
|
+
return referents;
|
|
14306
|
+
}
|
|
14307
|
+
function projectConversationReferentFocus(liveDoc) {
|
|
14308
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14309
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14310
|
+
const referents = collectConversationReferents(liveDoc);
|
|
14311
|
+
const entryPaths = [];
|
|
14312
|
+
const listNames = [];
|
|
14313
|
+
const variableNames = [];
|
|
14314
|
+
for (const referent of referents.slice(0, 8)) {
|
|
14315
|
+
if (referent.kind === "entry" && typeof referent.entryPath === "string") {
|
|
14316
|
+
entryPaths.push(referent.entryPath);
|
|
14317
|
+
continue;
|
|
14318
|
+
}
|
|
14319
|
+
if (referent.kind === "list" && typeof referent.listName === "string") {
|
|
14320
|
+
listNames.push(referent.listName);
|
|
14321
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14322
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14323
|
+
continue;
|
|
14324
|
+
}
|
|
14325
|
+
if (referent.kind === "variable" && typeof referent.variableName === "string") {
|
|
14326
|
+
variableNames.push(referent.variableName);
|
|
14327
|
+
if (typeof referent.entryPath === "string") {
|
|
14328
|
+
entryPaths.push(referent.entryPath);
|
|
14329
|
+
}
|
|
14330
|
+
if (typeof referent.listName === "string") {
|
|
14331
|
+
listNames.push(referent.listName);
|
|
14332
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14333
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14334
|
+
}
|
|
14335
|
+
}
|
|
14336
|
+
}
|
|
14337
|
+
return {
|
|
14338
|
+
entryPaths: uniqueStrings(entryPaths, 8),
|
|
14339
|
+
listNames: uniqueStrings(listNames, 4),
|
|
14340
|
+
variableNames: uniqueStrings(variableNames, 4)
|
|
14341
|
+
};
|
|
14342
|
+
}
|
|
14343
|
+
function projectConversationReferentSummary(liveDoc) {
|
|
14344
|
+
const referents = collectConversationReferents(liveDoc).slice(0, 8);
|
|
14345
|
+
if (referents.length === 0) {
|
|
14346
|
+
return "No recent referents recorded from prior assistant replies.";
|
|
14347
|
+
}
|
|
14348
|
+
const entryLines = [];
|
|
14349
|
+
const listLines = [];
|
|
14350
|
+
const variableLines = [];
|
|
14351
|
+
for (const referent of referents) {
|
|
14352
|
+
if (referent.kind === "entry" && referent.entryPath) {
|
|
14353
|
+
const label = referent.label || referent.entryPath;
|
|
14354
|
+
const classLabel = referent.className || "unknown";
|
|
14355
|
+
entryLines.push(`- ${label} <${referent.entryPath}> [${classLabel}]`);
|
|
14356
|
+
continue;
|
|
14357
|
+
}
|
|
14358
|
+
if (referent.kind === "list" && referent.listName) {
|
|
14359
|
+
const classLabel = referent.className || "unknown";
|
|
14360
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14361
|
+
listLines.push(
|
|
14362
|
+
`- ${referent.listName}: list<${classLabel}> -> ${countLabel} item(s)`
|
|
14363
|
+
);
|
|
14364
|
+
continue;
|
|
14365
|
+
}
|
|
14366
|
+
if (referent.kind === "variable" && referent.variableName) {
|
|
14367
|
+
if (referent.variableKind === "entry" && referent.entryPath && referent.className) {
|
|
14368
|
+
const label = referent.label || referent.entryPath;
|
|
14369
|
+
variableLines.push(
|
|
14370
|
+
`- ${referent.variableName}: entry<${referent.className}> -> ${label} <${referent.entryPath}>`
|
|
14371
|
+
);
|
|
14372
|
+
continue;
|
|
14373
|
+
}
|
|
14374
|
+
if (referent.variableKind === "list" && referent.listName && referent.className) {
|
|
14375
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14376
|
+
variableLines.push(
|
|
14377
|
+
`- ${referent.variableName}: list<${referent.className}> -> ${countLabel} item(s) via ${referent.listName}`
|
|
14378
|
+
);
|
|
14379
|
+
continue;
|
|
14380
|
+
}
|
|
14381
|
+
if (referent.variableKind === "scalar") {
|
|
14382
|
+
variableLines.push(
|
|
14383
|
+
`- ${referent.variableName}: scalar = ${formatScalar(referent.scalarValue)}`
|
|
14384
|
+
);
|
|
14385
|
+
continue;
|
|
14386
|
+
}
|
|
14387
|
+
variableLines.push(`- ${referent.variableName}`);
|
|
14388
|
+
}
|
|
14389
|
+
}
|
|
14390
|
+
const lines = [];
|
|
14391
|
+
lines.push("Entries:");
|
|
14392
|
+
lines.push(...entryLines.length > 0 ? entryLines : ["- none"]);
|
|
14393
|
+
lines.push("", "Lists:");
|
|
14394
|
+
lines.push(...listLines.length > 0 ? listLines : ["- none"]);
|
|
14395
|
+
lines.push("", "Variables:");
|
|
14396
|
+
lines.push(...variableLines.length > 0 ? variableLines : ["- none"]);
|
|
14397
|
+
return lines.join("\n");
|
|
14398
|
+
}
|
|
13862
14399
|
function getCurrentClosureId(liveDoc) {
|
|
13863
14400
|
const loop = asRecord2(liveDoc?.loop);
|
|
13864
14401
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -14071,7 +14608,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
14071
14608
|
variableNames: uniqueStrings(variableNames, 4),
|
|
14072
14609
|
listNames: uniqueStrings(listNames, 4),
|
|
14073
14610
|
entryPaths: uniqueStrings(entryPaths, 6),
|
|
14074
|
-
recentActionSummary: uniqueStrings(actionSummaryLines, 8)
|
|
14611
|
+
recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
|
|
14612
|
+
normalizeActionSummaryForPrompt
|
|
14613
|
+
)
|
|
14075
14614
|
};
|
|
14076
14615
|
}
|
|
14077
14616
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
@@ -14468,17 +15007,14 @@ ${resultPreview}` : null
|
|
|
14468
15007
|
].filter(Boolean).join("\n\n");
|
|
14469
15008
|
}
|
|
14470
15009
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
14471
|
-
return domainDocumentation?.trim() || "No domain
|
|
15010
|
+
return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
|
|
14472
15011
|
}
|
|
14473
15012
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
14474
15013
|
if (!sessionContext) return "No session metadata available.";
|
|
14475
15014
|
const rows = [
|
|
14476
15015
|
["sandboxId", sessionContext.sandboxId],
|
|
14477
15016
|
["environmentId", sessionContext.environmentId],
|
|
14478
|
-
["
|
|
14479
|
-
["granularId", sessionContext.granularId],
|
|
14480
|
-
["userName", sessionContext.userName],
|
|
14481
|
-
["domainRevision", sessionContext.domainRevision]
|
|
15017
|
+
["userName", sessionContext.userName]
|
|
14482
15018
|
];
|
|
14483
15019
|
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
14484
15020
|
if (activeRows.length === 0) return "No session metadata available.";
|
|
@@ -14487,6 +15023,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
|
|
|
14487
15023
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
14488
15024
|
return heapSummary?.trim() || "Heap is empty for this session.";
|
|
14489
15025
|
}
|
|
15026
|
+
function buildGranularAgentReferentBlock(referentSummary) {
|
|
15027
|
+
return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
|
|
15028
|
+
}
|
|
14490
15029
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
14491
15030
|
return loopSummary?.trim() || "No active loop state recorded for this session.";
|
|
14492
15031
|
}
|
|
@@ -14510,7 +15049,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
14510
15049
|
(tool) => Boolean(tool.className && !tool.static)
|
|
14511
15050
|
);
|
|
14512
15051
|
const lines = [
|
|
14513
|
-
"Treat this block as the planning map. Use DOMAIN
|
|
15052
|
+
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
14514
15053
|
];
|
|
14515
15054
|
const appendGroup = (title, group) => {
|
|
14516
15055
|
lines.push(`- ${title}:`);
|
|
@@ -14558,7 +15097,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
|
14558
15097
|
if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
|
|
14559
15098
|
lines.push("latestActionSummary:");
|
|
14560
15099
|
for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
|
|
14561
|
-
|
|
15100
|
+
const normalizedLine = normalizeActionSummaryForPrompt(line);
|
|
15101
|
+
lines.push(
|
|
15102
|
+
normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
|
|
15103
|
+
);
|
|
14562
15104
|
}
|
|
14563
15105
|
}
|
|
14564
15106
|
if (checkpoint.latestJobResult?.trim()) {
|
|
@@ -14574,9 +15116,10 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
14574
15116
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
14575
15117
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
14576
15118
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
15119
|
+
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
14577
15120
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
14578
15121
|
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
|
|
15122
|
+
You can help the user understand the domain, answer questions, or generate and execute code against the live session.
|
|
14580
15123
|
Your tone must be natural and human-like.
|
|
14581
15124
|
|
|
14582
15125
|
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 +15128,8 @@ When you call \`execute_code\`, additional assistant text must be either:
|
|
|
14585
15128
|
- a brief summary of the actions the generated code will perform.
|
|
14586
15129
|
Do not include any other kind of commentary when calling \`execute_code\`.
|
|
14587
15130
|
- 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(...)\`.
|
|
15131
|
+
- 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.
|
|
15132
|
+
- 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
15133
|
- 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
15134
|
|
|
14590
15135
|
\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
|
|
@@ -14604,6 +15149,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
|
|
|
14604
15149
|
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
14605
15150
|
- If you need clarification, ask in everyday language.
|
|
14606
15151
|
- 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.
|
|
15152
|
+
- 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
15153
|
- Keep replies concise and clear.
|
|
14608
15154
|
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
14609
15155
|
|
|
@@ -14613,9 +15159,9 @@ ${sessionBlock}
|
|
|
14613
15159
|
\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
|
|
14614
15160
|
${toolBlock}
|
|
14615
15161
|
|
|
14616
|
-
\u2500\u2500\u2500 DOMAIN
|
|
15162
|
+
\u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
|
|
14617
15163
|
Import classes and effect functions from \`./sandbox-tools\` in generated code.
|
|
14618
|
-
|
|
15164
|
+
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
|
|
14619
15165
|
|
|
14620
15166
|
${domainBlock}
|
|
14621
15167
|
|
|
@@ -14625,6 +15171,9 @@ ${checkpointBlock}
|
|
|
14625
15171
|
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
14626
15172
|
${workflowBlock}
|
|
14627
15173
|
|
|
15174
|
+
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
15175
|
+
${referentBlock}
|
|
15176
|
+
|
|
14628
15177
|
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
14629
15178
|
${heapBlock}
|
|
14630
15179
|
|
|
@@ -14632,109 +15181,54 @@ ${heapBlock}
|
|
|
14632
15181
|
${loopBlock}
|
|
14633
15182
|
|
|
14634
15183
|
\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.
|
|
15184
|
+
- 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.
|
|
15185
|
+
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
15186
|
+
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
15187
|
+
- 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.
|
|
15188
|
+
- 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.
|
|
15189
|
+
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
15190
|
+
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
15191
|
+
- 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.
|
|
15192
|
+
- 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.
|
|
15193
|
+
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
15194
|
+
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
15195
|
+
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
15196
|
+
- 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.
|
|
15197
|
+
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
15198
|
+
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
15199
|
+
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
15200
|
+
- 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.
|
|
15201
|
+
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
14680
15202
|
|
|
14681
15203
|
\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
|
|
14682
15204
|
- Import from \`./sandbox-tools\`.
|
|
14683
|
-
- If you use \`heap\`, \`loop\`, \`agent_text_message\`,
|
|
15205
|
+
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
|
|
14684
15206
|
- 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.
|
|
15207
|
+
- The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
|
|
15208
|
+
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
|
|
15209
|
+
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
|
|
15210
|
+
- 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.
|
|
15211
|
+
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
|
|
15212
|
+
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
|
|
15213
|
+
- 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.
|
|
15214
|
+
- 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.
|
|
15215
|
+
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
|
|
15216
|
+
- 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(...)\`.
|
|
15217
|
+
- 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.
|
|
15218
|
+
- Call instance methods on instances, static methods on classes, and global effects by name.
|
|
15219
|
+
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
|
|
15220
|
+
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
|
|
15221
|
+
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
|
|
15222
|
+
- 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.
|
|
15223
|
+
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
|
|
15224
|
+
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
|
|
15225
|
+
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
|
|
15226
|
+
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
|
|
15227
|
+
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
15228
|
+
- Use \`agent_text_message(...)\` for user-visible text.
|
|
15229
|
+
- 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.
|
|
15230
|
+
- 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.
|
|
15231
|
+
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
|
|
14738
15232
|
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
|
|
14739
15233
|
}
|
|
14740
15234
|
|
|
@@ -14893,6 +15387,10 @@ function fallbackResponseText(entries, lists) {
|
|
|
14893
15387
|
return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
|
|
14894
15388
|
}
|
|
14895
15389
|
if (lists.length > 0) {
|
|
15390
|
+
const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
|
|
15391
|
+
if (emptyOnly) {
|
|
15392
|
+
return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
|
|
15393
|
+
}
|
|
14896
15394
|
return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
|
|
14897
15395
|
}
|
|
14898
15396
|
return null;
|
|
@@ -15268,6 +15766,6 @@ function buildSessionTranscript(input) {
|
|
|
15268
15766
|
});
|
|
15269
15767
|
}
|
|
15270
15768
|
|
|
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 };
|
|
15769
|
+
export { Environment, EnvironmentSession, Granular, OntologyHandle, 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
15770
|
//# sourceMappingURL=index.mjs.map
|
|
15273
15771
|
//# sourceMappingURL=index.mjs.map
|