@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.js
CHANGED
|
@@ -4669,27 +4669,27 @@ var Session = class {
|
|
|
4669
4669
|
}
|
|
4670
4670
|
async publishTools(tools, revision = "1.0.0") {
|
|
4671
4671
|
throw new Error(
|
|
4672
|
-
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.
|
|
4672
|
+
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
4673
4673
|
);
|
|
4674
4674
|
}
|
|
4675
4675
|
async publishEffect(effect) {
|
|
4676
4676
|
throw new Error(
|
|
4677
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4677
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.register(effect)."
|
|
4678
4678
|
);
|
|
4679
4679
|
}
|
|
4680
4680
|
async publishEffects(effects) {
|
|
4681
4681
|
throw new Error(
|
|
4682
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4682
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
4683
4683
|
);
|
|
4684
4684
|
}
|
|
4685
4685
|
async unpublishEffect(name) {
|
|
4686
4686
|
throw new Error(
|
|
4687
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4687
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.unregister(effectName)."
|
|
4688
4688
|
);
|
|
4689
4689
|
}
|
|
4690
4690
|
async unpublishAllEffects() {
|
|
4691
4691
|
throw new Error(
|
|
4692
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4692
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.clear()."
|
|
4693
4693
|
);
|
|
4694
4694
|
}
|
|
4695
4695
|
/**
|
|
@@ -10975,17 +10975,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
10975
10975
|
}
|
|
10976
10976
|
},
|
|
10977
10977
|
domain: {
|
|
10978
|
-
applyToPropertyIR(propertyIR,
|
|
10979
|
-
|
|
10980
|
-
return propertyIR;
|
|
10981
|
-
}
|
|
10982
|
-
return {
|
|
10983
|
-
...propertyIR,
|
|
10984
|
-
docs: [
|
|
10985
|
-
...propertyIR.docs,
|
|
10986
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
10987
|
-
]
|
|
10988
|
-
};
|
|
10978
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
10979
|
+
return propertyIR;
|
|
10989
10980
|
}
|
|
10990
10981
|
}
|
|
10991
10982
|
});
|
|
@@ -11599,17 +11590,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
11599
11590
|
|
|
11600
11591
|
// src/client.ts
|
|
11601
11592
|
var STANDARD_MODULES_OPERATIONS = [
|
|
11602
|
-
{
|
|
11593
|
+
{
|
|
11594
|
+
create: "entity",
|
|
11595
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
11596
|
+
},
|
|
11603
11597
|
{ create: "class", extends: "entity", has: {} },
|
|
11604
|
-
{
|
|
11605
|
-
|
|
11598
|
+
{
|
|
11599
|
+
create: "user",
|
|
11600
|
+
extends: "entity",
|
|
11601
|
+
has: {
|
|
11602
|
+
email: { value: void 0 },
|
|
11603
|
+
firstName: { value: void 0 },
|
|
11604
|
+
lastName: { value: void 0 }
|
|
11605
|
+
}
|
|
11606
|
+
},
|
|
11607
|
+
{
|
|
11608
|
+
create: "company",
|
|
11609
|
+
extends: "entity",
|
|
11610
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
11611
|
+
},
|
|
11606
11612
|
{ create: "string", has: {} },
|
|
11607
11613
|
{ create: "number", has: {} },
|
|
11608
11614
|
{ create: "boolean", has: {} },
|
|
11609
|
-
{
|
|
11615
|
+
{
|
|
11616
|
+
create: "tool_parameter",
|
|
11617
|
+
has: {
|
|
11618
|
+
name: { value: void 0 },
|
|
11619
|
+
type: { value: "string" },
|
|
11620
|
+
description: { value: void 0 },
|
|
11621
|
+
required: { value: false }
|
|
11622
|
+
}
|
|
11623
|
+
}
|
|
11610
11624
|
];
|
|
11611
11625
|
var BUILTIN_MODULES = {
|
|
11612
|
-
|
|
11626
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
11613
11627
|
};
|
|
11614
11628
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11615
11629
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -11644,7 +11658,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
11644
11658
|
}
|
|
11645
11659
|
function isRetryableRecordObjectsError(error) {
|
|
11646
11660
|
const message = error instanceof Error ? error.message : String(error);
|
|
11647
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11661
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
11662
|
+
message
|
|
11663
|
+
);
|
|
11648
11664
|
}
|
|
11649
11665
|
function computeEffectKey2(effect) {
|
|
11650
11666
|
const attachedClass = effect.className?.trim();
|
|
@@ -11697,6 +11713,22 @@ function normalizeHeapSnapshot(raw) {
|
|
|
11697
11713
|
updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
11698
11714
|
};
|
|
11699
11715
|
}
|
|
11716
|
+
function deriveRuntimeBaseUrl(apiEndpoint) {
|
|
11717
|
+
try {
|
|
11718
|
+
const endpoint = new URL(apiEndpoint);
|
|
11719
|
+
const graphqlSuffix = "/orchestrator/graphql";
|
|
11720
|
+
if (endpoint.pathname.endsWith(graphqlSuffix)) {
|
|
11721
|
+
endpoint.pathname = endpoint.pathname.slice(0, -graphqlSuffix.length);
|
|
11722
|
+
} else if (endpoint.pathname.endsWith("/graphql")) {
|
|
11723
|
+
endpoint.pathname = endpoint.pathname.slice(0, -"/graphql".length);
|
|
11724
|
+
}
|
|
11725
|
+
endpoint.search = "";
|
|
11726
|
+
endpoint.hash = "";
|
|
11727
|
+
return endpoint.toString().replace(/\/$/, "");
|
|
11728
|
+
} catch {
|
|
11729
|
+
return apiEndpoint.replace(/\/orchestrator\/graphql$/, "").replace(/\/$/, "");
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11700
11732
|
function normalizeSubject(subject) {
|
|
11701
11733
|
const granularId = subject.granularId || subject.subjectId;
|
|
11702
11734
|
const userId = subject.userId || subject.identityId || granularId;
|
|
@@ -11721,7 +11753,10 @@ function normalizeUser(user) {
|
|
|
11721
11753
|
};
|
|
11722
11754
|
}
|
|
11723
11755
|
function normalizeEnvironmentData(environment) {
|
|
11724
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11756
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
11757
|
+
mode: "pinned",
|
|
11758
|
+
versionId: environment.versionId || environment.buildId
|
|
11759
|
+
});
|
|
11725
11760
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
11726
11761
|
return {
|
|
11727
11762
|
...environment,
|
|
@@ -11733,12 +11768,13 @@ function normalizeEnvironmentData(environment) {
|
|
|
11733
11768
|
tracking: environment.tracking || buildPolicy
|
|
11734
11769
|
};
|
|
11735
11770
|
}
|
|
11736
|
-
var Environment = class
|
|
11771
|
+
var Environment = class {
|
|
11772
|
+
granular;
|
|
11737
11773
|
envData;
|
|
11738
11774
|
_apiKey;
|
|
11739
11775
|
_apiEndpoint;
|
|
11740
|
-
constructor(
|
|
11741
|
-
|
|
11776
|
+
constructor(granular, envData, apiKey, apiEndpoint) {
|
|
11777
|
+
this.granular = granular;
|
|
11742
11778
|
this.envData = envData;
|
|
11743
11779
|
this._apiKey = apiKey;
|
|
11744
11780
|
this._apiEndpoint = apiEndpoint;
|
|
@@ -11779,35 +11815,126 @@ var Environment = class extends Session {
|
|
|
11779
11815
|
get permissionProfileId() {
|
|
11780
11816
|
return this.envData.permissionProfileId;
|
|
11781
11817
|
}
|
|
11818
|
+
/** The current build policy backing this environment */
|
|
11819
|
+
get buildPolicy() {
|
|
11820
|
+
return this.envData.buildPolicy;
|
|
11821
|
+
}
|
|
11822
|
+
/** The current update state relative to the followed tag */
|
|
11823
|
+
get updateState() {
|
|
11824
|
+
return this.envData.updateState;
|
|
11825
|
+
}
|
|
11826
|
+
/** Convenience flag for whether this environment trails the current tag target */
|
|
11827
|
+
get isOutdated() {
|
|
11828
|
+
return this.envData.updateState === "update_available";
|
|
11829
|
+
}
|
|
11830
|
+
/** The followed tag name when this environment is tag-tracked */
|
|
11831
|
+
get tag() {
|
|
11832
|
+
return this.envData.tag?.name || this.envData.buildPolicy.tagName || null;
|
|
11833
|
+
}
|
|
11782
11834
|
/** The GraphQL API endpoint URL */
|
|
11783
11835
|
get apiEndpoint() {
|
|
11784
11836
|
return this._apiEndpoint;
|
|
11785
11837
|
}
|
|
11838
|
+
/** Internal auth token used for control-plane and runtime fallback requests */
|
|
11839
|
+
get authToken() {
|
|
11840
|
+
return this._apiKey;
|
|
11841
|
+
}
|
|
11842
|
+
/** Base runtime URL derived from the GraphQL endpoint */
|
|
11843
|
+
get runtimeBaseUrl() {
|
|
11844
|
+
return this.getRuntimeBaseUrl();
|
|
11845
|
+
}
|
|
11846
|
+
get sessions() {
|
|
11847
|
+
return {
|
|
11848
|
+
list: async (options) => this.listSessions(options?.status || "active"),
|
|
11849
|
+
create: async (options) => this.createSession(options),
|
|
11850
|
+
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
11851
|
+
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
11852
|
+
close: async (sessionId, session) => this.closeSession(sessionId, session)
|
|
11853
|
+
};
|
|
11854
|
+
}
|
|
11855
|
+
get data() {
|
|
11856
|
+
return {
|
|
11857
|
+
record: async (record) => this.recordObject(record),
|
|
11858
|
+
recordMany: async (records, options) => this.recordObjects(records, options),
|
|
11859
|
+
import: async (records, options) => this.enqueueRecordImport(records, options),
|
|
11860
|
+
listImports: async (status) => this.listRecordImports(status),
|
|
11861
|
+
getImport: async (importId) => this.getRecordImport(importId),
|
|
11862
|
+
getImportSummary: async () => this.getRecordImportSummary(),
|
|
11863
|
+
cancelImport: async (importId) => this.cancelRecordImport(importId),
|
|
11864
|
+
getAwaitingCount: async () => this.getAwaitingRecordCount()
|
|
11865
|
+
};
|
|
11866
|
+
}
|
|
11867
|
+
get feedback() {
|
|
11868
|
+
return {
|
|
11869
|
+
list: async () => this.listFeedback()
|
|
11870
|
+
};
|
|
11871
|
+
}
|
|
11786
11872
|
/**
|
|
11787
|
-
*
|
|
11788
|
-
*
|
|
11789
|
-
*
|
|
11790
|
-
*
|
|
11873
|
+
* Sessionless environments do not own a live transport, so disconnecting the
|
|
11874
|
+
* environment handle itself is a no-op. This keeps the public surface
|
|
11875
|
+
* symmetric with `EnvironmentSession.disconnect()` and lets callers always
|
|
11876
|
+
* clean up safely without tracking whether they currently hold an environment
|
|
11877
|
+
* or a session.
|
|
11791
11878
|
*/
|
|
11792
|
-
|
|
11793
|
-
const doc = this.document;
|
|
11794
|
-
return normalizeHeapSnapshot(doc?.heap);
|
|
11879
|
+
async disconnect() {
|
|
11795
11880
|
}
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
const
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11881
|
+
async listSessions(status = "active") {
|
|
11882
|
+
if (status === "all") {
|
|
11883
|
+
const [active, closed] = await Promise.all([
|
|
11884
|
+
this.granular.listOpenSessions({ environmentId: this.environmentId }),
|
|
11885
|
+
this.granular.listClosedSessions({ environmentId: this.environmentId })
|
|
11886
|
+
]);
|
|
11887
|
+
return [...active, ...closed].sort(
|
|
11888
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
11889
|
+
);
|
|
11890
|
+
}
|
|
11891
|
+
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
11892
|
+
}
|
|
11893
|
+
async createSession(options) {
|
|
11894
|
+
return this.granular.createSession({
|
|
11895
|
+
environmentId: this.environmentId,
|
|
11896
|
+
clientId: options?.clientId,
|
|
11897
|
+
initialHeap: options?.initialHeap
|
|
11898
|
+
});
|
|
11899
|
+
}
|
|
11900
|
+
async connectSession(sessionId, options) {
|
|
11901
|
+
const session = await this.granular["connectSession"]({
|
|
11902
|
+
sessionId,
|
|
11903
|
+
clientId: options?.clientId
|
|
11904
|
+
});
|
|
11905
|
+
if (session.environmentId !== this.environmentId) {
|
|
11906
|
+
await session.disconnect().catch(() => {
|
|
11907
|
+
session.disconnectTransport();
|
|
11908
|
+
});
|
|
11909
|
+
throw new Error(
|
|
11910
|
+
`Session ${sessionId} belongs to environment ${session.environmentId}, not ${this.environmentId}.`
|
|
11911
|
+
);
|
|
11912
|
+
}
|
|
11913
|
+
return session;
|
|
11914
|
+
}
|
|
11915
|
+
async reopenSession(sessionId, options) {
|
|
11916
|
+
const session = await this.granular.reopenSession(sessionId, {
|
|
11917
|
+
clientId: options?.clientId
|
|
11918
|
+
});
|
|
11919
|
+
if (session.environmentId !== this.environmentId) {
|
|
11920
|
+
await session.disconnect().catch(() => {
|
|
11921
|
+
session.disconnectTransport();
|
|
11922
|
+
});
|
|
11923
|
+
throw new Error(
|
|
11924
|
+
`Session ${sessionId} belongs to environment ${session.environmentId}, not ${this.environmentId}.`
|
|
11925
|
+
);
|
|
11810
11926
|
}
|
|
11927
|
+
return session;
|
|
11928
|
+
}
|
|
11929
|
+
async closeSession(sessionId, session) {
|
|
11930
|
+
await this.granular.closeSession(sessionId, session);
|
|
11931
|
+
}
|
|
11932
|
+
async listFeedback() {
|
|
11933
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
|
|
11934
|
+
return Array.isArray(response.items) ? response.items : [];
|
|
11935
|
+
}
|
|
11936
|
+
getRuntimeBaseUrl() {
|
|
11937
|
+
return deriveRuntimeBaseUrl(this._apiEndpoint);
|
|
11811
11938
|
}
|
|
11812
11939
|
async controlPlaneRequest(path, options = {}) {
|
|
11813
11940
|
const runtimeBase = this.getRuntimeBaseUrl();
|
|
@@ -11815,94 +11942,19 @@ var Environment = class extends Session {
|
|
|
11815
11942
|
const response = await fetch(url, {
|
|
11816
11943
|
...options,
|
|
11817
11944
|
headers: {
|
|
11818
|
-
|
|
11945
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
11819
11946
|
"Content-Type": "application/json",
|
|
11820
|
-
|
|
11947
|
+
Connection: "close",
|
|
11821
11948
|
...options.headers
|
|
11822
11949
|
}
|
|
11823
11950
|
});
|
|
11824
11951
|
if (!response.ok) {
|
|
11825
|
-
throw new Error(
|
|
11952
|
+
throw new Error(
|
|
11953
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
11954
|
+
);
|
|
11826
11955
|
}
|
|
11827
11956
|
return response.json();
|
|
11828
11957
|
}
|
|
11829
|
-
/**
|
|
11830
|
-
* Close the session and disconnect from the sandbox.
|
|
11831
|
-
*
|
|
11832
|
-
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
11833
|
-
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
11834
|
-
* acknowledgement was observed.
|
|
11835
|
-
*/
|
|
11836
|
-
async disconnect() {
|
|
11837
|
-
let wsNotifiedRuntime = false;
|
|
11838
|
-
try {
|
|
11839
|
-
const goodbye = await this.rpc("client.goodbye", {
|
|
11840
|
-
timestamp: Date.now()
|
|
11841
|
-
});
|
|
11842
|
-
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11843
|
-
} catch {
|
|
11844
|
-
wsNotifiedRuntime = false;
|
|
11845
|
-
}
|
|
11846
|
-
if (!wsNotifiedRuntime) {
|
|
11847
|
-
try {
|
|
11848
|
-
const runtimeBase = this.getRuntimeBaseUrl();
|
|
11849
|
-
await fetch(
|
|
11850
|
-
`${runtimeBase}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
11851
|
-
{
|
|
11852
|
-
method: "POST",
|
|
11853
|
-
headers: {
|
|
11854
|
-
"Content-Type": "application/json",
|
|
11855
|
-
"Authorization": `Bearer ${this._apiKey}`,
|
|
11856
|
-
"Connection": "close"
|
|
11857
|
-
},
|
|
11858
|
-
body: JSON.stringify({
|
|
11859
|
-
reason: "sdk_disconnect_http_fallback",
|
|
11860
|
-
sessionId: this.client.currentSessionId
|
|
11861
|
-
})
|
|
11862
|
-
}
|
|
11863
|
-
);
|
|
11864
|
-
} catch {
|
|
11865
|
-
}
|
|
11866
|
-
}
|
|
11867
|
-
this.client.disconnect();
|
|
11868
|
-
}
|
|
11869
|
-
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11870
|
-
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11871
|
-
graphContainerStatus = null;
|
|
11872
|
-
/**
|
|
11873
|
-
* Check if the graph container is ready and warm.
|
|
11874
|
-
*
|
|
11875
|
-
* Sends a lightweight heartbeat RPC to the Session DO which internally
|
|
11876
|
-
* pings the FalkorDB container. The response includes `graphContainerStatus`,
|
|
11877
|
-
* which is stored locally and emitted as a `readiness` event.
|
|
11878
|
-
*
|
|
11879
|
-
* Use this method to proactively warm the graph container before any
|
|
11880
|
-
* GraphQL query that requires it, or to poll the container's state in
|
|
11881
|
-
* the background.
|
|
11882
|
-
*
|
|
11883
|
-
* @returns The current graph container status object
|
|
11884
|
-
*
|
|
11885
|
-
* @example
|
|
11886
|
-
* ```typescript
|
|
11887
|
-
* const status = await env.checkReadiness();
|
|
11888
|
-
* console.log(status.status); // 'hot' | 'warming' | 'unknown'
|
|
11889
|
-
*
|
|
11890
|
-
* // Or listen for live updates
|
|
11891
|
-
* env.on('readiness', (status) => {
|
|
11892
|
-
* console.log('Graph is now:', status.status);
|
|
11893
|
-
* });
|
|
11894
|
-
* ```
|
|
11895
|
-
*/
|
|
11896
|
-
async checkReadiness() {
|
|
11897
|
-
const result = await this.client.call("client.heartbeat", {});
|
|
11898
|
-
const containerStatus = result?.graphContainerStatus ?? {
|
|
11899
|
-
lastKeepAliveAt: Date.now(),
|
|
11900
|
-
status: "unknown"
|
|
11901
|
-
};
|
|
11902
|
-
this.graphContainerStatus = containerStatus;
|
|
11903
|
-
this.emit("readiness", containerStatus);
|
|
11904
|
-
return containerStatus;
|
|
11905
|
-
}
|
|
11906
11958
|
// ==================== ID ↔ GRAPH PATH MAPPING ====================
|
|
11907
11959
|
/**
|
|
11908
11960
|
* Convert a class name + real-world ID into a unique graph path.
|
|
@@ -11931,14 +11983,14 @@ var Environment = class extends Session {
|
|
|
11931
11983
|
}
|
|
11932
11984
|
/**
|
|
11933
11985
|
* Execute a GraphQL query against the environment's graph.
|
|
11934
|
-
*
|
|
11986
|
+
*
|
|
11935
11987
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
11936
11988
|
* Authentication is handled automatically using the SDK's API key.
|
|
11937
|
-
*
|
|
11989
|
+
*
|
|
11938
11990
|
* @param query - The GraphQL query string
|
|
11939
11991
|
* @param variables - Optional variables for the query
|
|
11940
11992
|
* @returns The query result data
|
|
11941
|
-
*
|
|
11993
|
+
*
|
|
11942
11994
|
* @example
|
|
11943
11995
|
* ```typescript
|
|
11944
11996
|
* // Read the workspace
|
|
@@ -11946,7 +11998,7 @@ var Environment = class extends Session {
|
|
|
11946
11998
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
11947
11999
|
* );
|
|
11948
12000
|
* console.log(result.data);
|
|
11949
|
-
*
|
|
12001
|
+
*
|
|
11950
12002
|
* // Create a model
|
|
11951
12003
|
* const created = await env.graphql(
|
|
11952
12004
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -11958,7 +12010,7 @@ var Environment = class extends Session {
|
|
|
11958
12010
|
method: "POST",
|
|
11959
12011
|
headers: {
|
|
11960
12012
|
"Content-Type": "application/json",
|
|
11961
|
-
|
|
12013
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
11962
12014
|
},
|
|
11963
12015
|
body: JSON.stringify({
|
|
11964
12016
|
environmentId: this.environmentId,
|
|
@@ -11975,10 +12027,10 @@ var Environment = class extends Session {
|
|
|
11975
12027
|
// ==================== RELATIONSHIP METHODS ====================
|
|
11976
12028
|
/**
|
|
11977
12029
|
* Define a relationship between two model types.
|
|
11978
|
-
*
|
|
12030
|
+
*
|
|
11979
12031
|
* Creates both submodels (if they don't exist) and links them with
|
|
11980
12032
|
* a RelationshipDef node that encodes cardinality.
|
|
11981
|
-
*
|
|
12033
|
+
*
|
|
11982
12034
|
* @example
|
|
11983
12035
|
* ```typescript
|
|
11984
12036
|
* // Author has many Books, Book has one Author
|
|
@@ -12038,10 +12090,10 @@ var Environment = class extends Session {
|
|
|
12038
12090
|
}
|
|
12039
12091
|
/**
|
|
12040
12092
|
* Get all relationships for a model type.
|
|
12041
|
-
*
|
|
12093
|
+
*
|
|
12042
12094
|
* @param modelPath - The model type path (e.g., "author")
|
|
12043
12095
|
* @returns Array of relationships from this model's perspective
|
|
12044
|
-
*
|
|
12096
|
+
*
|
|
12045
12097
|
* @example
|
|
12046
12098
|
* ```typescript
|
|
12047
12099
|
* const rels = await env.getRelationships('author');
|
|
@@ -12074,18 +12126,18 @@ var Environment = class extends Session {
|
|
|
12074
12126
|
}
|
|
12075
12127
|
/**
|
|
12076
12128
|
* Attach a target model to a relationship submodel.
|
|
12077
|
-
*
|
|
12129
|
+
*
|
|
12078
12130
|
* Handles cardinality automatically:
|
|
12079
12131
|
* - "One" side: sets/replaces the reference
|
|
12080
12132
|
* - "Many" side: adds the target to the collection
|
|
12081
|
-
*
|
|
12133
|
+
*
|
|
12082
12134
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
12083
12135
|
* Bidirectional sync is automatic.
|
|
12084
|
-
*
|
|
12136
|
+
*
|
|
12085
12137
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
12086
12138
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
12087
12139
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
12088
|
-
*
|
|
12140
|
+
*
|
|
12089
12141
|
* @example
|
|
12090
12142
|
* ```typescript
|
|
12091
12143
|
* // Attach a book to an author (many side)
|
|
@@ -12112,18 +12164,18 @@ var Environment = class extends Session {
|
|
|
12112
12164
|
}
|
|
12113
12165
|
/**
|
|
12114
12166
|
* Detach a target model from a relationship submodel.
|
|
12115
|
-
*
|
|
12167
|
+
*
|
|
12116
12168
|
* Handles bidirectional cleanup automatically.
|
|
12117
|
-
*
|
|
12169
|
+
*
|
|
12118
12170
|
* @param modelPath - The model instance path
|
|
12119
12171
|
* @param submodelPath - The relationship submodel
|
|
12120
12172
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
12121
|
-
*
|
|
12173
|
+
*
|
|
12122
12174
|
* @example
|
|
12123
12175
|
* ```typescript
|
|
12124
12176
|
* // Detach a specific book
|
|
12125
12177
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
12126
|
-
*
|
|
12178
|
+
*
|
|
12127
12179
|
* // Detach all books
|
|
12128
12180
|
* await env.detach('tolkien', 'books');
|
|
12129
12181
|
* ```
|
|
@@ -12147,11 +12199,11 @@ var Environment = class extends Session {
|
|
|
12147
12199
|
}
|
|
12148
12200
|
/**
|
|
12149
12201
|
* List all related models through a relationship submodel.
|
|
12150
|
-
*
|
|
12202
|
+
*
|
|
12151
12203
|
* @param modelPath - The model instance path
|
|
12152
12204
|
* @param submodelPath - The relationship submodel
|
|
12153
12205
|
* @returns Array of related model references
|
|
12154
|
-
*
|
|
12206
|
+
*
|
|
12155
12207
|
* @example
|
|
12156
12208
|
* ```typescript
|
|
12157
12209
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -12175,14 +12227,14 @@ var Environment = class extends Session {
|
|
|
12175
12227
|
}
|
|
12176
12228
|
/**
|
|
12177
12229
|
* Apply a manifest to the current environment's graph.
|
|
12178
|
-
*
|
|
12230
|
+
*
|
|
12179
12231
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
12180
12232
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
12181
12233
|
* relationships from a declarative manifest.
|
|
12182
|
-
*
|
|
12234
|
+
*
|
|
12183
12235
|
* @param manifest - The manifest content to apply
|
|
12184
12236
|
* @returns Summary of applied operations
|
|
12185
|
-
*
|
|
12237
|
+
*
|
|
12186
12238
|
* @example
|
|
12187
12239
|
* ```typescript
|
|
12188
12240
|
* await environment.applyManifest({
|
|
@@ -12220,12 +12272,16 @@ var Environment = class extends Session {
|
|
|
12220
12272
|
applied++;
|
|
12221
12273
|
} catch (err) {
|
|
12222
12274
|
if (!err.message?.includes("already exists")) {
|
|
12223
|
-
errors.push(
|
|
12275
|
+
errors.push(
|
|
12276
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
12277
|
+
);
|
|
12224
12278
|
}
|
|
12225
12279
|
}
|
|
12226
12280
|
}
|
|
12227
12281
|
} else {
|
|
12228
|
-
errors.push(
|
|
12282
|
+
errors.push(
|
|
12283
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
12284
|
+
);
|
|
12229
12285
|
}
|
|
12230
12286
|
}
|
|
12231
12287
|
}
|
|
@@ -12309,7 +12365,10 @@ var Environment = class extends Session {
|
|
|
12309
12365
|
}
|
|
12310
12366
|
}
|
|
12311
12367
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
12312
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
12368
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
12369
|
+
toolPath,
|
|
12370
|
+
metamodels
|
|
12371
|
+
)) {
|
|
12313
12372
|
await this._runGraphql(mutation.query, mutation.label);
|
|
12314
12373
|
}
|
|
12315
12374
|
}
|
|
@@ -12358,7 +12417,9 @@ var Environment = class extends Session {
|
|
|
12358
12417
|
}
|
|
12359
12418
|
if (eventType.payloadSchema?.properties) {
|
|
12360
12419
|
const fieldSpecs = {};
|
|
12361
|
-
for (const [propName, propSchema] of Object.entries(
|
|
12420
|
+
for (const [propName, propSchema] of Object.entries(
|
|
12421
|
+
eventType.payloadSchema.properties
|
|
12422
|
+
)) {
|
|
12362
12423
|
const schema = propSchema;
|
|
12363
12424
|
fieldSpecs[propName] = {
|
|
12364
12425
|
type: schema.type ?? "string",
|
|
@@ -12641,7 +12702,9 @@ var Environment = class extends Session {
|
|
|
12641
12702
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12642
12703
|
await Promise.all(
|
|
12643
12704
|
wave.map(async (plan) => {
|
|
12644
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12705
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
12706
|
+
plan.slice
|
|
12707
|
+
);
|
|
12645
12708
|
if (items.length !== plan.slice.length) {
|
|
12646
12709
|
throw new Error(
|
|
12647
12710
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -12671,13 +12734,10 @@ var Environment = class extends Session {
|
|
|
12671
12734
|
let lastError;
|
|
12672
12735
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12673
12736
|
try {
|
|
12674
|
-
const response = await this.controlPlaneRequest(
|
|
12675
|
-
|
|
12676
|
-
{
|
|
12677
|
-
|
|
12678
|
-
body: JSON.stringify({ records: chunk })
|
|
12679
|
-
}
|
|
12680
|
-
);
|
|
12737
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
12738
|
+
method: "POST",
|
|
12739
|
+
body: JSON.stringify({ records: chunk })
|
|
12740
|
+
});
|
|
12681
12741
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
12682
12742
|
return { items, durationMs: Date.now() - wallStart };
|
|
12683
12743
|
} catch (error) {
|
|
@@ -12740,7 +12800,9 @@ var Environment = class extends Session {
|
|
|
12740
12800
|
* Fetch a single record import by id.
|
|
12741
12801
|
*/
|
|
12742
12802
|
async getRecordImport(importId) {
|
|
12743
|
-
return this.controlPlaneRequest(
|
|
12803
|
+
return this.controlPlaneRequest(
|
|
12804
|
+
`/control/record-imports/${importId}`
|
|
12805
|
+
);
|
|
12744
12806
|
}
|
|
12745
12807
|
/**
|
|
12746
12808
|
* Cancel a queued/background record import.
|
|
@@ -12753,36 +12815,186 @@ var Environment = class extends Session {
|
|
|
12753
12815
|
}
|
|
12754
12816
|
);
|
|
12755
12817
|
}
|
|
12756
|
-
|
|
12818
|
+
};
|
|
12819
|
+
var EnvironmentSession = class extends Session {
|
|
12820
|
+
environment;
|
|
12821
|
+
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
12822
|
+
graphContainerStatus = null;
|
|
12823
|
+
constructor(client, environment, clientId) {
|
|
12824
|
+
super(client, clientId);
|
|
12825
|
+
this.environment = environment;
|
|
12826
|
+
}
|
|
12827
|
+
get environmentId() {
|
|
12828
|
+
return this.environment.environmentId;
|
|
12829
|
+
}
|
|
12830
|
+
get sandboxId() {
|
|
12831
|
+
return this.environment.sandboxId;
|
|
12832
|
+
}
|
|
12833
|
+
get ontologyId() {
|
|
12834
|
+
return this.environment.ontologyId;
|
|
12835
|
+
}
|
|
12836
|
+
get subjectId() {
|
|
12837
|
+
return this.environment.subjectId;
|
|
12838
|
+
}
|
|
12839
|
+
get envName() {
|
|
12840
|
+
return this.environment.envName;
|
|
12841
|
+
}
|
|
12842
|
+
get versionId() {
|
|
12843
|
+
return this.environment.versionId;
|
|
12844
|
+
}
|
|
12845
|
+
get granularId() {
|
|
12846
|
+
return this.environment.granularId;
|
|
12847
|
+
}
|
|
12848
|
+
get permissionProfileId() {
|
|
12849
|
+
return this.environment.permissionProfileId;
|
|
12850
|
+
}
|
|
12851
|
+
get apiEndpoint() {
|
|
12852
|
+
return this.environment.apiEndpoint;
|
|
12853
|
+
}
|
|
12854
|
+
get data() {
|
|
12855
|
+
return this.environment.data;
|
|
12856
|
+
}
|
|
12857
|
+
get feedback() {
|
|
12858
|
+
return this.environment.feedback;
|
|
12859
|
+
}
|
|
12757
12860
|
/**
|
|
12758
|
-
*
|
|
12861
|
+
* Return a plain JS snapshot of the synced session heap.
|
|
12759
12862
|
*/
|
|
12760
|
-
|
|
12761
|
-
|
|
12863
|
+
getHeap() {
|
|
12864
|
+
const doc = this.document;
|
|
12865
|
+
return normalizeHeapSnapshot(doc?.heap);
|
|
12866
|
+
}
|
|
12867
|
+
async graphql(query, variables) {
|
|
12868
|
+
return this.environment.graphql(query, variables);
|
|
12869
|
+
}
|
|
12870
|
+
async defineRelationship(options) {
|
|
12871
|
+
return this.environment.defineRelationship(options);
|
|
12872
|
+
}
|
|
12873
|
+
async getRelationships(modelPath) {
|
|
12874
|
+
return this.environment.getRelationships(modelPath);
|
|
12875
|
+
}
|
|
12876
|
+
async attach(modelPath, submodelPath, targetPath) {
|
|
12877
|
+
return this.environment.attach(modelPath, submodelPath, targetPath);
|
|
12878
|
+
}
|
|
12879
|
+
async detach(modelPath, submodelPath, targetPath) {
|
|
12880
|
+
return this.environment.detach(modelPath, submodelPath, targetPath);
|
|
12881
|
+
}
|
|
12882
|
+
async listRelated(modelPath, submodelPath) {
|
|
12883
|
+
return this.environment.listRelated(modelPath, submodelPath);
|
|
12884
|
+
}
|
|
12885
|
+
async applyManifest(manifest) {
|
|
12886
|
+
return this.environment.applyManifest(manifest);
|
|
12887
|
+
}
|
|
12888
|
+
async recordObject(options) {
|
|
12889
|
+
return this.environment.recordObject(options);
|
|
12890
|
+
}
|
|
12891
|
+
async recordObjects(records, options) {
|
|
12892
|
+
return this.environment.recordObjects(records, options);
|
|
12893
|
+
}
|
|
12894
|
+
async enqueueRecordImport(records, options = {}) {
|
|
12895
|
+
return this.environment.enqueueRecordImport(records, options);
|
|
12896
|
+
}
|
|
12897
|
+
async listRecordImports(status) {
|
|
12898
|
+
return this.environment.listRecordImports(status);
|
|
12899
|
+
}
|
|
12900
|
+
async getRecordImportSummary() {
|
|
12901
|
+
return this.environment.getRecordImportSummary();
|
|
12902
|
+
}
|
|
12903
|
+
async getAwaitingRecordCount() {
|
|
12904
|
+
return this.environment.getAwaitingRecordCount();
|
|
12905
|
+
}
|
|
12906
|
+
async getRecordImport(importId) {
|
|
12907
|
+
return this.environment.getRecordImport(importId);
|
|
12908
|
+
}
|
|
12909
|
+
async cancelRecordImport(importId) {
|
|
12910
|
+
return this.environment.cancelRecordImport(importId);
|
|
12911
|
+
}
|
|
12912
|
+
async listFeedback() {
|
|
12913
|
+
return this.environment.listFeedback();
|
|
12762
12914
|
}
|
|
12763
12915
|
/**
|
|
12764
|
-
*
|
|
12916
|
+
* Close the session and disconnect from the sandbox.
|
|
12917
|
+
*
|
|
12918
|
+
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
12919
|
+
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
12920
|
+
* acknowledgement was observed.
|
|
12765
12921
|
*/
|
|
12766
|
-
async
|
|
12767
|
-
|
|
12922
|
+
async disconnect() {
|
|
12923
|
+
let wsNotifiedRuntime = false;
|
|
12924
|
+
try {
|
|
12925
|
+
const goodbye = await this.rpc(
|
|
12926
|
+
"client.goodbye",
|
|
12927
|
+
{
|
|
12928
|
+
timestamp: Date.now()
|
|
12929
|
+
}
|
|
12930
|
+
);
|
|
12931
|
+
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
12932
|
+
} catch {
|
|
12933
|
+
wsNotifiedRuntime = false;
|
|
12934
|
+
}
|
|
12935
|
+
if (!wsNotifiedRuntime) {
|
|
12936
|
+
try {
|
|
12937
|
+
await fetch(
|
|
12938
|
+
`${this.environment.runtimeBaseUrl}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
12939
|
+
{
|
|
12940
|
+
method: "POST",
|
|
12941
|
+
headers: {
|
|
12942
|
+
"Content-Type": "application/json",
|
|
12943
|
+
Authorization: `Bearer ${this.environment.authToken}`,
|
|
12944
|
+
Connection: "close"
|
|
12945
|
+
},
|
|
12946
|
+
body: JSON.stringify({
|
|
12947
|
+
reason: "sdk_disconnect_http_fallback",
|
|
12948
|
+
sessionId: this.client.currentSessionId
|
|
12949
|
+
})
|
|
12950
|
+
}
|
|
12951
|
+
);
|
|
12952
|
+
} catch {
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
this.client.disconnect();
|
|
12768
12956
|
}
|
|
12769
12957
|
/**
|
|
12770
|
-
*
|
|
12958
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
12771
12959
|
*/
|
|
12772
|
-
|
|
12773
|
-
|
|
12960
|
+
disconnectTransport() {
|
|
12961
|
+
this.client.disconnect();
|
|
12774
12962
|
}
|
|
12775
12963
|
/**
|
|
12776
|
-
*
|
|
12964
|
+
* Backwards-compatible alias for `disconnect()`.
|
|
12777
12965
|
*/
|
|
12778
|
-
async
|
|
12779
|
-
|
|
12966
|
+
async close() {
|
|
12967
|
+
await this.disconnect();
|
|
12780
12968
|
}
|
|
12781
12969
|
/**
|
|
12782
|
-
*
|
|
12970
|
+
* Check if the graph container is ready and warm.
|
|
12783
12971
|
*/
|
|
12784
|
-
async
|
|
12785
|
-
|
|
12972
|
+
async checkReadiness() {
|
|
12973
|
+
const result = await this.client.call("client.heartbeat", {});
|
|
12974
|
+
const containerStatus = result?.graphContainerStatus ?? {
|
|
12975
|
+
lastKeepAliveAt: Date.now(),
|
|
12976
|
+
status: "unknown"
|
|
12977
|
+
};
|
|
12978
|
+
this.graphContainerStatus = containerStatus;
|
|
12979
|
+
this.emit("readiness", containerStatus);
|
|
12980
|
+
return containerStatus;
|
|
12981
|
+
}
|
|
12982
|
+
};
|
|
12983
|
+
var OntologyHandle = class {
|
|
12984
|
+
granular;
|
|
12985
|
+
ontologyNameOrId;
|
|
12986
|
+
constructor(granular, ontologyNameOrId) {
|
|
12987
|
+
this.granular = granular;
|
|
12988
|
+
this.ontologyNameOrId = ontologyNameOrId;
|
|
12989
|
+
}
|
|
12990
|
+
get effects() {
|
|
12991
|
+
return {
|
|
12992
|
+
register: async (effect) => this.granular.registerEffect(this.ontologyNameOrId, effect),
|
|
12993
|
+
registerMany: async (effects) => this.granular.registerEffects(this.ontologyNameOrId, effects),
|
|
12994
|
+
unregister: async (name) => this.granular.unregisterEffect(this.ontologyNameOrId, name),
|
|
12995
|
+
clear: async () => this.granular.unregisterAllEffects(this.ontologyNameOrId),
|
|
12996
|
+
disconnect: async () => this.granular.disconnectEffects(this.ontologyNameOrId)
|
|
12997
|
+
};
|
|
12786
12998
|
}
|
|
12787
12999
|
};
|
|
12788
13000
|
var Granular = class _Granular {
|
|
@@ -12807,7 +13019,9 @@ var Granular = class _Granular {
|
|
|
12807
13019
|
constructor(options) {
|
|
12808
13020
|
const auth = options.token ?? options.apiKey;
|
|
12809
13021
|
if (!auth) {
|
|
12810
|
-
throw new Error(
|
|
13022
|
+
throw new Error(
|
|
13023
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
13024
|
+
);
|
|
12811
13025
|
}
|
|
12812
13026
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
12813
13027
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -12817,12 +13031,18 @@ var Granular = class _Granular {
|
|
|
12817
13031
|
this.onReconnectError = options.onReconnectError;
|
|
12818
13032
|
this.httpUrl = this.apiUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://").replace(/\/ws$/, "");
|
|
12819
13033
|
}
|
|
13034
|
+
/**
|
|
13035
|
+
* Return an ontology-scoped handle for effects and other ontology-level APIs.
|
|
13036
|
+
*/
|
|
13037
|
+
ontology(ontologyNameOrId) {
|
|
13038
|
+
return new OntologyHandle(this, ontologyNameOrId);
|
|
13039
|
+
}
|
|
12820
13040
|
/**
|
|
12821
13041
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12822
|
-
*
|
|
13042
|
+
*
|
|
12823
13043
|
* @param options - User options
|
|
12824
13044
|
* @returns The recorded user with both `userId` and `granularId`
|
|
12825
|
-
*
|
|
13045
|
+
*
|
|
12826
13046
|
* @example
|
|
12827
13047
|
* ```typescript
|
|
12828
13048
|
* const user = await granular.recordUser({
|
|
@@ -12833,14 +13053,16 @@ var Granular = class _Granular {
|
|
|
12833
13053
|
* ```
|
|
12834
13054
|
*/
|
|
12835
13055
|
async recordUser(options) {
|
|
12836
|
-
const subject = normalizeSubject(
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
13056
|
+
const subject = normalizeSubject(
|
|
13057
|
+
await this.request("/control/subjects", {
|
|
13058
|
+
method: "POST",
|
|
13059
|
+
body: JSON.stringify({
|
|
13060
|
+
identityId: options.userId,
|
|
13061
|
+
name: options.name,
|
|
13062
|
+
email: options.email
|
|
13063
|
+
})
|
|
12842
13064
|
})
|
|
12843
|
-
|
|
13065
|
+
);
|
|
12844
13066
|
return normalizeUser({
|
|
12845
13067
|
granularId: subject.granularId,
|
|
12846
13068
|
userId: options.userId,
|
|
@@ -12851,7 +13073,23 @@ var Granular = class _Granular {
|
|
|
12851
13073
|
permissions: options.permissions || []
|
|
12852
13074
|
});
|
|
12853
13075
|
}
|
|
13076
|
+
/**
|
|
13077
|
+
* Alias for `recordUser()` with user-facing naming that matches upsert semantics.
|
|
13078
|
+
*/
|
|
13079
|
+
async upsertUser(options) {
|
|
13080
|
+
return this.recordUser(options);
|
|
13081
|
+
}
|
|
12854
13082
|
async resolveConnectUser(options) {
|
|
13083
|
+
const providedIdentityCount = [
|
|
13084
|
+
Boolean(options.user),
|
|
13085
|
+
Boolean(options.userId),
|
|
13086
|
+
Boolean(options.granularId)
|
|
13087
|
+
].filter(Boolean).length;
|
|
13088
|
+
if (providedIdentityCount !== 1) {
|
|
13089
|
+
throw new Error(
|
|
13090
|
+
"openEnvironment() requires exactly one of userId, granularId, or a user object returned by recordUser()."
|
|
13091
|
+
);
|
|
13092
|
+
}
|
|
12855
13093
|
if (options.user) {
|
|
12856
13094
|
const user = normalizeUser(options.user);
|
|
12857
13095
|
return {
|
|
@@ -12887,76 +13125,141 @@ var Granular = class _Granular {
|
|
|
12887
13125
|
permissions: options.permissions || []
|
|
12888
13126
|
};
|
|
12889
13127
|
}
|
|
12890
|
-
throw new Error(
|
|
13128
|
+
throw new Error(
|
|
13129
|
+
"openEnvironment() requires either userId, granularId, or a user object returned by recordUser()."
|
|
13130
|
+
);
|
|
12891
13131
|
}
|
|
12892
13132
|
/**
|
|
12893
|
-
*
|
|
12894
|
-
*
|
|
12895
|
-
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12896
|
-
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12897
|
-
* the sandbox registry automatically.
|
|
12898
|
-
*
|
|
12899
|
-
* @param options - Connection options
|
|
12900
|
-
* @returns An active environment session
|
|
12901
|
-
*
|
|
13133
|
+
* Open or resolve an ontology environment for one user without opening a session.
|
|
13134
|
+
*
|
|
12902
13135
|
* @example
|
|
12903
13136
|
* ```typescript
|
|
12904
|
-
* const environment = await granular.
|
|
13137
|
+
* const environment = await granular.openEnvironment({
|
|
12905
13138
|
* ontology: 'my-ontology',
|
|
12906
|
-
*
|
|
13139
|
+
* tag: 'dev',
|
|
12907
13140
|
* userId: 'user_123',
|
|
12908
13141
|
* permissions: ['agent'],
|
|
12909
13142
|
* });
|
|
12910
|
-
*
|
|
12911
|
-
* await
|
|
12912
|
-
*
|
|
12913
|
-
*
|
|
12914
|
-
*
|
|
12915
|
-
* handler: async () => 'Hello!',
|
|
13143
|
+
*
|
|
13144
|
+
* await environment.data.record({
|
|
13145
|
+
* className: 'customer',
|
|
13146
|
+
* id: 'acme',
|
|
13147
|
+
* fields: { name: 'Acme' },
|
|
12916
13148
|
* });
|
|
12917
|
-
*
|
|
12918
|
-
*
|
|
12919
|
-
* const job = await
|
|
12920
|
-
*
|
|
12921
|
-
* return await tools.greet({});
|
|
12922
|
-
* `);
|
|
12923
|
-
*
|
|
12924
|
-
* console.log(await job.result); // 'Hello!'
|
|
13149
|
+
*
|
|
13150
|
+
* const session = await environment.sessions.create();
|
|
13151
|
+
* const job = await session.submitJob(`return "hello";`);
|
|
13152
|
+
* console.log(await job.result);
|
|
12925
13153
|
* ```
|
|
12926
|
-
|
|
13154
|
+
*/
|
|
13155
|
+
async openEnvironment(options) {
|
|
13156
|
+
const envData = await this.resolveOpenEnvironmentData(
|
|
13157
|
+
options,
|
|
13158
|
+
"openEnvironment"
|
|
13159
|
+
);
|
|
13160
|
+
return this.bindEnvironmentHandle(envData);
|
|
13161
|
+
}
|
|
13162
|
+
/**
|
|
13163
|
+
* Deprecated compatibility alias for `openEnvironment()`.
|
|
13164
|
+
*
|
|
13165
|
+
* `connect()` no longer opens a runtime session automatically.
|
|
13166
|
+
*/
|
|
12927
13167
|
async connect(options) {
|
|
12928
|
-
|
|
13168
|
+
return this.openEnvironment({
|
|
13169
|
+
...options,
|
|
13170
|
+
tag: this.resolveRequestedTag(options, "connect"),
|
|
13171
|
+
permissions: options.permissions || options.user?.permissions || []
|
|
13172
|
+
});
|
|
13173
|
+
}
|
|
13174
|
+
resolveRequestedTag(options, methodName) {
|
|
13175
|
+
const tag = options.tag?.trim() || options.tagName?.trim() || options.environment?.trim();
|
|
13176
|
+
if (!tag) {
|
|
13177
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
13178
|
+
}
|
|
13179
|
+
return tag;
|
|
13180
|
+
}
|
|
13181
|
+
buildManagedEnvironmentName(tag, versionId) {
|
|
13182
|
+
return `__sdk__${tag}__${versionId}`;
|
|
13183
|
+
}
|
|
13184
|
+
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
13185
|
+
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
13186
|
+
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);
|
|
13187
|
+
}
|
|
13188
|
+
sortEnvironmentsByRecency(environments) {
|
|
13189
|
+
return [...environments].sort(
|
|
13190
|
+
(left, right) => right.updatedAt - left.updatedAt
|
|
13191
|
+
);
|
|
13192
|
+
}
|
|
13193
|
+
async resolveOpenEnvironmentData(options, methodName) {
|
|
12929
13194
|
const ontology = options.ontology;
|
|
12930
13195
|
if (!ontology) {
|
|
12931
|
-
throw new Error(
|
|
13196
|
+
throw new Error(`${methodName}() requires \`ontology\`.`);
|
|
12932
13197
|
}
|
|
12933
|
-
const
|
|
12934
|
-
if (!
|
|
12935
|
-
throw new Error(
|
|
13198
|
+
const tagName = options.tag?.trim();
|
|
13199
|
+
if (!tagName) {
|
|
13200
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
12936
13201
|
}
|
|
12937
|
-
const tagName = options.tagName?.trim() || void 0;
|
|
12938
13202
|
const user = await this.resolveConnectUser(options);
|
|
13203
|
+
if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
|
|
13204
|
+
throw new Error(
|
|
13205
|
+
`${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
|
|
13206
|
+
);
|
|
13207
|
+
}
|
|
12939
13208
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12940
13209
|
for (const profileName of user.permissions) {
|
|
12941
|
-
const profileId = await this.ensurePermissionProfile(
|
|
12942
|
-
|
|
13210
|
+
const profileId = await this.ensurePermissionProfile(
|
|
13211
|
+
sandbox.sandboxId,
|
|
13212
|
+
profileName
|
|
13213
|
+
);
|
|
13214
|
+
await this.ensureAssignment(
|
|
13215
|
+
user.granularId,
|
|
13216
|
+
sandbox.sandboxId,
|
|
13217
|
+
profileId
|
|
13218
|
+
);
|
|
13219
|
+
}
|
|
13220
|
+
const tags = await this.request(
|
|
13221
|
+
`/control/sandboxes/${sandbox.sandboxId}/tags`
|
|
13222
|
+
);
|
|
13223
|
+
const tag = (tags.items || []).find(
|
|
13224
|
+
(candidate) => Boolean(candidate?.name === tagName)
|
|
13225
|
+
);
|
|
13226
|
+
if (!tag) {
|
|
13227
|
+
throw new Error(
|
|
13228
|
+
`Tag "${tagName}" was not found for ontology ${sandbox.sandboxId}.`
|
|
13229
|
+
);
|
|
13230
|
+
}
|
|
13231
|
+
const targetVersionId = tag.targetVersionId || tag.targetBuildId;
|
|
13232
|
+
if (!targetVersionId) {
|
|
13233
|
+
throw new Error(
|
|
13234
|
+
`Tag "${tagName}" does not currently point to a build/version.`
|
|
13235
|
+
);
|
|
13236
|
+
}
|
|
13237
|
+
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
13238
|
+
const userEnvironments = allEnvironments.filter(
|
|
13239
|
+
(environment) => environment.subjectId === user.granularId
|
|
13240
|
+
);
|
|
13241
|
+
const currentMatches = this.sortEnvironmentsByRecency(
|
|
13242
|
+
userEnvironments.filter(
|
|
13243
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId) && environment.versionId === targetVersionId
|
|
13244
|
+
)
|
|
13245
|
+
);
|
|
13246
|
+
if (currentMatches.length > 0) {
|
|
13247
|
+
return currentMatches[0];
|
|
13248
|
+
}
|
|
13249
|
+
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
13250
|
+
userEnvironments.filter(
|
|
13251
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
|
|
13252
|
+
)
|
|
13253
|
+
);
|
|
13254
|
+
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
13255
|
+
return outdatedMatches[0];
|
|
12943
13256
|
}
|
|
12944
|
-
|
|
13257
|
+
return this.environments.create(sandbox.sandboxId, {
|
|
12945
13258
|
subjectId: user.granularId,
|
|
12946
|
-
environment:
|
|
12947
|
-
|
|
13259
|
+
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
13260
|
+
tagId: tag.tagId,
|
|
12948
13261
|
permissionProfileId: null
|
|
12949
13262
|
});
|
|
12950
|
-
await this.activateEnvironment(envData.environmentId);
|
|
12951
|
-
const session = await this.request("/ws/sessions", {
|
|
12952
|
-
method: "POST",
|
|
12953
|
-
body: JSON.stringify({
|
|
12954
|
-
environmentId: envData.environmentId,
|
|
12955
|
-
clientId,
|
|
12956
|
-
initialHeap: options.initialHeap
|
|
12957
|
-
})
|
|
12958
|
-
});
|
|
12959
|
-
return this.bindWebSocketEnvironment(envData, clientId, session);
|
|
12960
13263
|
}
|
|
12961
13264
|
/**
|
|
12962
13265
|
* List active (open) sessions for an environment — each session is one agent conversation thread.
|
|
@@ -12991,7 +13294,9 @@ var Granular = class _Granular {
|
|
|
12991
13294
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
12992
13295
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
12993
13296
|
summary: row.summary != null ? String(row.summary) : null,
|
|
12994
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13297
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
13298
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
13299
|
+
) : null,
|
|
12995
13300
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
12996
13301
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
12997
13302
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -13017,6 +13322,7 @@ var Granular = class _Granular {
|
|
|
13017
13322
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
13018
13323
|
await this.activateEnvironment(options.environmentId);
|
|
13019
13324
|
const envData = await this.environments.get(options.environmentId);
|
|
13325
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
13020
13326
|
const session = await this.request("/ws/sessions", {
|
|
13021
13327
|
method: "POST",
|
|
13022
13328
|
body: JSON.stringify({
|
|
@@ -13025,7 +13331,7 @@ var Granular = class _Granular {
|
|
|
13025
13331
|
initialHeap: options.initialHeap
|
|
13026
13332
|
})
|
|
13027
13333
|
});
|
|
13028
|
-
return this.
|
|
13334
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, session);
|
|
13029
13335
|
}
|
|
13030
13336
|
/**
|
|
13031
13337
|
* Connect to an existing open session (same conversation thread) using a freshly minted WebSocket token.
|
|
@@ -13037,7 +13343,8 @@ var Granular = class _Granular {
|
|
|
13037
13343
|
body: JSON.stringify({})
|
|
13038
13344
|
});
|
|
13039
13345
|
const envData = await this.environments.get(minted.environmentId);
|
|
13040
|
-
|
|
13346
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
13347
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, minted);
|
|
13041
13348
|
}
|
|
13042
13349
|
/**
|
|
13043
13350
|
* Mark a session closed in the control plane. If `environment` is the connected handle for that
|
|
@@ -13069,7 +13376,11 @@ var Granular = class _Granular {
|
|
|
13069
13376
|
});
|
|
13070
13377
|
return this.connectSession({ sessionId, clientId: options?.clientId });
|
|
13071
13378
|
}
|
|
13072
|
-
|
|
13379
|
+
bindEnvironmentHandle(envData) {
|
|
13380
|
+
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13381
|
+
return new Environment(this, envData, this.apiKey, graphqlEndpoint);
|
|
13382
|
+
}
|
|
13383
|
+
async bindWebSocketEnvironmentSession(environment, clientId, session) {
|
|
13073
13384
|
const client = new WSClient({
|
|
13074
13385
|
url: session.wsUrl,
|
|
13075
13386
|
sessionId: session.sessionId,
|
|
@@ -13080,10 +13391,13 @@ var Granular = class _Granular {
|
|
|
13080
13391
|
onReconnectError: this.onReconnectError
|
|
13081
13392
|
});
|
|
13082
13393
|
await client.connect();
|
|
13083
|
-
const
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13394
|
+
const environmentSession = new EnvironmentSession(
|
|
13395
|
+
client,
|
|
13396
|
+
environment,
|
|
13397
|
+
clientId
|
|
13398
|
+
);
|
|
13399
|
+
await environmentSession.hello();
|
|
13400
|
+
return environmentSession;
|
|
13087
13401
|
}
|
|
13088
13402
|
async activateEnvironment(environmentId) {
|
|
13089
13403
|
await this.request(`/orchestrator/runtime/environments/${environmentId}/activate`, {
|
|
@@ -13115,14 +13429,18 @@ var Granular = class _Granular {
|
|
|
13115
13429
|
};
|
|
13116
13430
|
}
|
|
13117
13431
|
async publishSandboxEffectCatalog(host) {
|
|
13118
|
-
const effects = Array.from(
|
|
13119
|
-
(
|
|
13120
|
-
);
|
|
13121
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13432
|
+
const effects = Array.from(
|
|
13433
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
13434
|
+
).map((effect) => this.serializeEffect(effect));
|
|
13435
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
13436
|
+
effects
|
|
13437
|
+
});
|
|
13122
13438
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
13123
13439
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
13124
13440
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
13125
|
-
const detail = rejected.map(
|
|
13441
|
+
const detail = rejected.map(
|
|
13442
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
13443
|
+
).join("; ");
|
|
13126
13444
|
throw new Error(
|
|
13127
13445
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
13128
13446
|
);
|
|
@@ -13156,13 +13474,15 @@ var Granular = class _Granular {
|
|
|
13156
13474
|
disconnectError
|
|
13157
13475
|
);
|
|
13158
13476
|
}
|
|
13159
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13477
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
13478
|
+
(reconnectError) => {
|
|
13479
|
+
console.error(
|
|
13480
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
13481
|
+
reconnectError
|
|
13482
|
+
);
|
|
13483
|
+
console.error("[Granular] Original heartbeat failure:", error);
|
|
13484
|
+
}
|
|
13485
|
+
);
|
|
13166
13486
|
}
|
|
13167
13487
|
startEffectHostHeartbeat(host) {
|
|
13168
13488
|
if (host.heartbeatTimer) {
|
|
@@ -13183,9 +13503,15 @@ var Granular = class _Granular {
|
|
|
13183
13503
|
host.heartbeatInFlight = false;
|
|
13184
13504
|
});
|
|
13185
13505
|
};
|
|
13186
|
-
sendHeartbeat(
|
|
13506
|
+
sendHeartbeat(
|
|
13507
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
13508
|
+
false
|
|
13509
|
+
);
|
|
13187
13510
|
host.heartbeatTimer = setInterval(() => {
|
|
13188
|
-
sendHeartbeat(
|
|
13511
|
+
sendHeartbeat(
|
|
13512
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
13513
|
+
true
|
|
13514
|
+
);
|
|
13189
13515
|
}, 1e4);
|
|
13190
13516
|
}
|
|
13191
13517
|
stopEffectHostHeartbeat(host) {
|
|
@@ -13217,7 +13543,12 @@ var Granular = class _Granular {
|
|
|
13217
13543
|
const effectClientId = crypto.randomUUID();
|
|
13218
13544
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
13219
13545
|
const wsClient = new WSClient({
|
|
13220
|
-
url: buildEffectHostUrl(
|
|
13546
|
+
url: buildEffectHostUrl(
|
|
13547
|
+
this.apiUrl,
|
|
13548
|
+
sandboxId,
|
|
13549
|
+
effectClientId,
|
|
13550
|
+
clientId
|
|
13551
|
+
),
|
|
13221
13552
|
sessionId: `effect-host:${effectClientId}`,
|
|
13222
13553
|
token: this.apiKey,
|
|
13223
13554
|
tokenProvider: this.tokenProvider,
|
|
@@ -13236,7 +13567,10 @@ var Granular = class _Granular {
|
|
|
13236
13567
|
};
|
|
13237
13568
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
13238
13569
|
const request = params;
|
|
13239
|
-
return invokeRegisteredEffect(
|
|
13570
|
+
return invokeRegisteredEffect(
|
|
13571
|
+
this.getSandboxEffectMap(sandboxId),
|
|
13572
|
+
request
|
|
13573
|
+
);
|
|
13240
13574
|
});
|
|
13241
13575
|
wsClient.on("open", () => {
|
|
13242
13576
|
void this.synchronizeEffectHost(host).catch((error) => {
|
|
@@ -13284,7 +13618,7 @@ var Granular = class _Granular {
|
|
|
13284
13618
|
}
|
|
13285
13619
|
/**
|
|
13286
13620
|
* Register multiple effects (tools) for a specific sandbox.
|
|
13287
|
-
*
|
|
13621
|
+
*
|
|
13288
13622
|
* batch version of `registerEffect`.
|
|
13289
13623
|
*/
|
|
13290
13624
|
async registerEffects(sandboxNameOrId, effects) {
|
|
@@ -13298,7 +13632,7 @@ var Granular = class _Granular {
|
|
|
13298
13632
|
}
|
|
13299
13633
|
/**
|
|
13300
13634
|
* Unregister an effect from a sandbox.
|
|
13301
|
-
*
|
|
13635
|
+
*
|
|
13302
13636
|
* Removes it from the local sandbox registry and updates the
|
|
13303
13637
|
* sandbox-scoped live catalog.
|
|
13304
13638
|
*/
|
|
@@ -13397,27 +13731,31 @@ var Granular = class _Granular {
|
|
|
13397
13731
|
const assignments = await this.request(
|
|
13398
13732
|
`/control/subjects/${subjectId}/assignments`
|
|
13399
13733
|
);
|
|
13400
|
-
const existing = assignments.items.find(
|
|
13401
|
-
(a) => a.sandboxId === sandboxId
|
|
13402
|
-
);
|
|
13734
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
13403
13735
|
if (existing) {
|
|
13404
13736
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
13405
13737
|
return;
|
|
13406
13738
|
}
|
|
13407
|
-
await this.request(
|
|
13408
|
-
|
|
13409
|
-
|
|
13739
|
+
await this.request(
|
|
13740
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
13741
|
+
{
|
|
13742
|
+
method: "DELETE"
|
|
13743
|
+
}
|
|
13744
|
+
);
|
|
13410
13745
|
}
|
|
13411
13746
|
} catch {
|
|
13412
13747
|
}
|
|
13413
|
-
await this.request(
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13748
|
+
await this.request(
|
|
13749
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
13750
|
+
{
|
|
13751
|
+
method: "POST",
|
|
13752
|
+
body: JSON.stringify({
|
|
13753
|
+
sandboxId,
|
|
13754
|
+
subjectId,
|
|
13755
|
+
permissionProfileId
|
|
13756
|
+
})
|
|
13757
|
+
}
|
|
13758
|
+
);
|
|
13421
13759
|
}
|
|
13422
13760
|
/**
|
|
13423
13761
|
* Sandbox management API
|
|
@@ -13497,23 +13835,33 @@ var Granular = class _Granular {
|
|
|
13497
13835
|
},
|
|
13498
13836
|
get: async (environmentId) => {
|
|
13499
13837
|
return normalizeEnvironmentData(
|
|
13500
|
-
await this.request(
|
|
13838
|
+
await this.request(
|
|
13839
|
+
`/control/environments/${environmentId}`
|
|
13840
|
+
)
|
|
13501
13841
|
);
|
|
13502
13842
|
},
|
|
13503
13843
|
create: async (sandboxId, data) => {
|
|
13504
13844
|
const environmentName = data.environment || data.envName;
|
|
13505
|
-
return normalizeEnvironmentData(
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13845
|
+
return normalizeEnvironmentData(
|
|
13846
|
+
await this.request(
|
|
13847
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
13848
|
+
{
|
|
13849
|
+
method: "POST",
|
|
13850
|
+
body: JSON.stringify({
|
|
13851
|
+
...data,
|
|
13852
|
+
envName: environmentName
|
|
13853
|
+
})
|
|
13854
|
+
}
|
|
13855
|
+
)
|
|
13856
|
+
);
|
|
13512
13857
|
},
|
|
13513
13858
|
delete: async (environmentId) => {
|
|
13514
|
-
return this.request(
|
|
13515
|
-
|
|
13516
|
-
|
|
13859
|
+
return this.request(
|
|
13860
|
+
`/control/environments/${environmentId}`,
|
|
13861
|
+
{
|
|
13862
|
+
method: "DELETE"
|
|
13863
|
+
}
|
|
13864
|
+
);
|
|
13517
13865
|
}
|
|
13518
13866
|
};
|
|
13519
13867
|
}
|
|
@@ -13533,10 +13881,13 @@ var Granular = class _Granular {
|
|
|
13533
13881
|
}
|
|
13534
13882
|
if (params.since) query.set("since", params.since.toISOString());
|
|
13535
13883
|
if (params.until) query.set("until", params.until.toISOString());
|
|
13536
|
-
if (params.isAcked !== void 0)
|
|
13884
|
+
if (params.isAcked !== void 0)
|
|
13885
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
13537
13886
|
if (params.limit) query.set("limit", String(params.limit));
|
|
13538
13887
|
if (params.offset) query.set("offset", String(params.offset));
|
|
13539
|
-
const result = await this.request(
|
|
13888
|
+
const result = await this.request(
|
|
13889
|
+
`/control/stream-events?${query.toString()}`
|
|
13890
|
+
);
|
|
13540
13891
|
return (result.items || []).map((row) => ({
|
|
13541
13892
|
eventId: row.event_id,
|
|
13542
13893
|
streamName: row.stream_name,
|
|
@@ -13567,7 +13918,9 @@ var Granular = class _Granular {
|
|
|
13567
13918
|
since: cursor,
|
|
13568
13919
|
limit: 100
|
|
13569
13920
|
});
|
|
13570
|
-
const orderedEvents = [...events].sort(
|
|
13921
|
+
const orderedEvents = [...events].sort(
|
|
13922
|
+
(a, b) => a.createdAt - b.createdAt
|
|
13923
|
+
);
|
|
13571
13924
|
for (const event of orderedEvents) {
|
|
13572
13925
|
if (seenEventIds.has(event.eventId)) {
|
|
13573
13926
|
continue;
|
|
@@ -13580,15 +13933,19 @@ var Granular = class _Granular {
|
|
|
13580
13933
|
params.onEvent(event);
|
|
13581
13934
|
}
|
|
13582
13935
|
} catch (err) {
|
|
13583
|
-
params.onError?.(
|
|
13936
|
+
params.onError?.(
|
|
13937
|
+
err instanceof Error ? err : new Error(String(err))
|
|
13938
|
+
);
|
|
13584
13939
|
}
|
|
13585
13940
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13586
13941
|
}
|
|
13587
13942
|
};
|
|
13588
13943
|
poll();
|
|
13589
|
-
return {
|
|
13590
|
-
|
|
13591
|
-
|
|
13944
|
+
return {
|
|
13945
|
+
unsubscribe: () => {
|
|
13946
|
+
running = false;
|
|
13947
|
+
}
|
|
13948
|
+
};
|
|
13592
13949
|
},
|
|
13593
13950
|
ack: async (eventId) => {
|
|
13594
13951
|
await this.request("/control/stream-events/ack", {
|
|
@@ -13606,7 +13963,9 @@ var Granular = class _Granular {
|
|
|
13606
13963
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13607
13964
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13608
13965
|
if (params.environment) query.set("environmentId", params.environment);
|
|
13609
|
-
const result = await this.request(
|
|
13966
|
+
const result = await this.request(
|
|
13967
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
13968
|
+
);
|
|
13610
13969
|
return (result.items || []).map((row) => ({
|
|
13611
13970
|
streamName: row.stream_name,
|
|
13612
13971
|
eventType: row.event_type,
|
|
@@ -13624,10 +13983,14 @@ var Granular = class _Granular {
|
|
|
13624
13983
|
get subjects() {
|
|
13625
13984
|
return {
|
|
13626
13985
|
get: async (subjectId) => {
|
|
13627
|
-
return normalizeSubject(
|
|
13986
|
+
return normalizeSubject(
|
|
13987
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
13988
|
+
);
|
|
13628
13989
|
},
|
|
13629
13990
|
listAssignments: async (subjectId) => {
|
|
13630
|
-
return this.request(
|
|
13991
|
+
return this.request(
|
|
13992
|
+
`/control/subjects/${subjectId}/assignments`
|
|
13993
|
+
);
|
|
13631
13994
|
}
|
|
13632
13995
|
};
|
|
13633
13996
|
}
|
|
@@ -13637,24 +14000,31 @@ var Granular = class _Granular {
|
|
|
13637
14000
|
get users() {
|
|
13638
14001
|
return {
|
|
13639
14002
|
create: async (data) => {
|
|
13640
|
-
return normalizeSubject(
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
14003
|
+
return normalizeSubject(
|
|
14004
|
+
await this.request("/control/subjects", {
|
|
14005
|
+
method: "POST",
|
|
14006
|
+
body: JSON.stringify({
|
|
14007
|
+
identityId: data.id,
|
|
14008
|
+
name: data.name,
|
|
14009
|
+
email: data.email
|
|
14010
|
+
})
|
|
13646
14011
|
})
|
|
13647
|
-
|
|
14012
|
+
);
|
|
13648
14013
|
},
|
|
13649
14014
|
get: async (id) => {
|
|
13650
|
-
return normalizeSubject(
|
|
14015
|
+
return normalizeSubject(
|
|
14016
|
+
await this.request(`/control/subjects/${id}`)
|
|
14017
|
+
);
|
|
13651
14018
|
}
|
|
13652
14019
|
};
|
|
13653
14020
|
}
|
|
13654
14021
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
13655
14022
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13656
|
-
const result = await this.request(
|
|
13657
|
-
|
|
14023
|
+
const result = await this.request(
|
|
14024
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
14025
|
+
);
|
|
14026
|
+
if (result.items.length === 0)
|
|
14027
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13658
14028
|
return result.items[0].sandboxId;
|
|
13659
14029
|
}
|
|
13660
14030
|
/**
|
|
@@ -13669,9 +14039,9 @@ var Granular = class _Granular {
|
|
|
13669
14039
|
const response = await fetch(url, {
|
|
13670
14040
|
...options,
|
|
13671
14041
|
headers: {
|
|
13672
|
-
|
|
14042
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13673
14043
|
"Content-Type": "application/json",
|
|
13674
|
-
|
|
14044
|
+
Connection: "close",
|
|
13675
14045
|
...options.headers
|
|
13676
14046
|
}
|
|
13677
14047
|
});
|
|
@@ -13682,7 +14052,11 @@ var Granular = class _Granular {
|
|
|
13682
14052
|
return response.json();
|
|
13683
14053
|
}
|
|
13684
14054
|
const errorText = await response.text();
|
|
13685
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
14055
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
14056
|
+
response.status,
|
|
14057
|
+
errorText,
|
|
14058
|
+
url
|
|
14059
|
+
);
|
|
13686
14060
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13687
14061
|
if (this.debugHttp) {
|
|
13688
14062
|
console.warn(
|
|
@@ -13821,21 +14195,6 @@ function reviewGeneratedJobCode(code) {
|
|
|
13821
14195
|
message: "After await loop.ask_user(...) returns a usable answer, continue the workflow in the same resumed run instead of stopping with placeholder text about doing the work later."
|
|
13822
14196
|
});
|
|
13823
14197
|
}
|
|
13824
|
-
const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
|
|
13825
|
-
for (const call of askUserCalls) {
|
|
13826
|
-
const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
|
|
13827
|
-
const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
|
|
13828
|
-
const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
|
|
13829
|
-
const includesShortlistOptions = /options\s*:\s*\[/.test(call);
|
|
13830
|
-
if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
|
|
13831
|
-
issues.push({
|
|
13832
|
-
code: "disambiguation_requires_choice",
|
|
13833
|
-
severity: "error",
|
|
13834
|
-
message: "When asking the user to choose between known concrete records such as invoices, orders, shipments, requests, cases, or work orders, use loop.ask_user({ type: 'choice', options: [...] }) with a short explicit shortlist instead of a free-text input."
|
|
13835
|
-
});
|
|
13836
|
-
break;
|
|
13837
|
-
}
|
|
13838
|
-
}
|
|
13839
14198
|
}
|
|
13840
14199
|
const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
|
|
13841
14200
|
const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
|
|
@@ -13881,6 +14240,184 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
|
|
|
13881
14240
|
entryPaths: uniqueStrings(entryPaths, 8)
|
|
13882
14241
|
};
|
|
13883
14242
|
}
|
|
14243
|
+
function normalizeActionSummaryForPrompt(line) {
|
|
14244
|
+
return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
|
|
14245
|
+
}
|
|
14246
|
+
function collectConversationReferents(liveDoc) {
|
|
14247
|
+
const conversation = asRecord2(liveDoc?.conversation);
|
|
14248
|
+
const persistedReferents = asArray(conversation?.referents).map((value) => asRecord2(value)).filter((value) => Boolean(value));
|
|
14249
|
+
if (persistedReferents.length > 0) {
|
|
14250
|
+
return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
|
|
14251
|
+
}
|
|
14252
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14253
|
+
const entriesByPath = asRecord2(heap?.entriesByPath) || {};
|
|
14254
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14255
|
+
const variablesByName = asRecord2(heap?.variablesByName) || {};
|
|
14256
|
+
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));
|
|
14257
|
+
const referents = [];
|
|
14258
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14259
|
+
const pushReferent = (referent) => {
|
|
14260
|
+
if (!referent?.kind || !referent.ref) return;
|
|
14261
|
+
const key = `${referent.kind}:${referent.ref}`;
|
|
14262
|
+
if (seen.has(key)) return;
|
|
14263
|
+
seen.add(key);
|
|
14264
|
+
referents.push(referent);
|
|
14265
|
+
};
|
|
14266
|
+
for (const message of messages) {
|
|
14267
|
+
if (message.role !== "assistant") continue;
|
|
14268
|
+
const show = asRecord2(message.show);
|
|
14269
|
+
if (!show) continue;
|
|
14270
|
+
const ts = Number(message.ts) || 0;
|
|
14271
|
+
const messageId = typeof message.id === "string" ? message.id : void 0;
|
|
14272
|
+
const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
|
|
14273
|
+
for (const entryPath of uniqueStrings(asArray(show.entryPaths))) {
|
|
14274
|
+
const entry = asRecord2(entriesByPath[entryPath]);
|
|
14275
|
+
pushReferent({
|
|
14276
|
+
id: `entry:${entryPath}`,
|
|
14277
|
+
kind: "entry",
|
|
14278
|
+
ref: entryPath,
|
|
14279
|
+
entryPath,
|
|
14280
|
+
className: typeof entry?.className === "string" ? entry.className : void 0,
|
|
14281
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
|
|
14282
|
+
messageId,
|
|
14283
|
+
jobId,
|
|
14284
|
+
ts
|
|
14285
|
+
});
|
|
14286
|
+
}
|
|
14287
|
+
for (const listName of uniqueStrings(asArray(show.listNames))) {
|
|
14288
|
+
const list = asRecord2(listsByName[listName]);
|
|
14289
|
+
pushReferent({
|
|
14290
|
+
id: `list:${listName}`,
|
|
14291
|
+
kind: "list",
|
|
14292
|
+
ref: listName,
|
|
14293
|
+
listName,
|
|
14294
|
+
className: typeof list?.className === "string" ? list.className : void 0,
|
|
14295
|
+
count: Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14296
|
+
messageId,
|
|
14297
|
+
jobId,
|
|
14298
|
+
ts
|
|
14299
|
+
});
|
|
14300
|
+
}
|
|
14301
|
+
for (const variableName of uniqueStrings(
|
|
14302
|
+
asArray(show.variableNames)
|
|
14303
|
+
)) {
|
|
14304
|
+
const variable = asRecord2(variablesByName[variableName]);
|
|
14305
|
+
const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
|
|
14306
|
+
const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
|
|
14307
|
+
const entry = entryPath ? asRecord2(entriesByPath[entryPath]) : null;
|
|
14308
|
+
const list = listName ? asRecord2(listsByName[listName]) : null;
|
|
14309
|
+
pushReferent({
|
|
14310
|
+
id: `variable:${variableName}`,
|
|
14311
|
+
kind: "variable",
|
|
14312
|
+
ref: variableName,
|
|
14313
|
+
variableName,
|
|
14314
|
+
variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
|
|
14315
|
+
entryPath,
|
|
14316
|
+
listName,
|
|
14317
|
+
className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
|
|
14318
|
+
label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
14319
|
+
count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
|
|
14320
|
+
scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
|
|
14321
|
+
messageId,
|
|
14322
|
+
jobId,
|
|
14323
|
+
ts
|
|
14324
|
+
});
|
|
14325
|
+
}
|
|
14326
|
+
}
|
|
14327
|
+
return referents;
|
|
14328
|
+
}
|
|
14329
|
+
function projectConversationReferentFocus(liveDoc) {
|
|
14330
|
+
const heap = asRecord2(liveDoc?.heap);
|
|
14331
|
+
const listsByName = asRecord2(heap?.listsByName) || {};
|
|
14332
|
+
const referents = collectConversationReferents(liveDoc);
|
|
14333
|
+
const entryPaths = [];
|
|
14334
|
+
const listNames = [];
|
|
14335
|
+
const variableNames = [];
|
|
14336
|
+
for (const referent of referents.slice(0, 8)) {
|
|
14337
|
+
if (referent.kind === "entry" && typeof referent.entryPath === "string") {
|
|
14338
|
+
entryPaths.push(referent.entryPath);
|
|
14339
|
+
continue;
|
|
14340
|
+
}
|
|
14341
|
+
if (referent.kind === "list" && typeof referent.listName === "string") {
|
|
14342
|
+
listNames.push(referent.listName);
|
|
14343
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14344
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14345
|
+
continue;
|
|
14346
|
+
}
|
|
14347
|
+
if (referent.kind === "variable" && typeof referent.variableName === "string") {
|
|
14348
|
+
variableNames.push(referent.variableName);
|
|
14349
|
+
if (typeof referent.entryPath === "string") {
|
|
14350
|
+
entryPaths.push(referent.entryPath);
|
|
14351
|
+
}
|
|
14352
|
+
if (typeof referent.listName === "string") {
|
|
14353
|
+
listNames.push(referent.listName);
|
|
14354
|
+
const list = asRecord2(listsByName[referent.listName]);
|
|
14355
|
+
entryPaths.push(...asArray(list?.paths).slice(0, 4));
|
|
14356
|
+
}
|
|
14357
|
+
}
|
|
14358
|
+
}
|
|
14359
|
+
return {
|
|
14360
|
+
entryPaths: uniqueStrings(entryPaths, 8),
|
|
14361
|
+
listNames: uniqueStrings(listNames, 4),
|
|
14362
|
+
variableNames: uniqueStrings(variableNames, 4)
|
|
14363
|
+
};
|
|
14364
|
+
}
|
|
14365
|
+
function projectConversationReferentSummary(liveDoc) {
|
|
14366
|
+
const referents = collectConversationReferents(liveDoc).slice(0, 8);
|
|
14367
|
+
if (referents.length === 0) {
|
|
14368
|
+
return "No recent referents recorded from prior assistant replies.";
|
|
14369
|
+
}
|
|
14370
|
+
const entryLines = [];
|
|
14371
|
+
const listLines = [];
|
|
14372
|
+
const variableLines = [];
|
|
14373
|
+
for (const referent of referents) {
|
|
14374
|
+
if (referent.kind === "entry" && referent.entryPath) {
|
|
14375
|
+
const label = referent.label || referent.entryPath;
|
|
14376
|
+
const classLabel = referent.className || "unknown";
|
|
14377
|
+
entryLines.push(`- ${label} <${referent.entryPath}> [${classLabel}]`);
|
|
14378
|
+
continue;
|
|
14379
|
+
}
|
|
14380
|
+
if (referent.kind === "list" && referent.listName) {
|
|
14381
|
+
const classLabel = referent.className || "unknown";
|
|
14382
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14383
|
+
listLines.push(
|
|
14384
|
+
`- ${referent.listName}: list<${classLabel}> -> ${countLabel} item(s)`
|
|
14385
|
+
);
|
|
14386
|
+
continue;
|
|
14387
|
+
}
|
|
14388
|
+
if (referent.kind === "variable" && referent.variableName) {
|
|
14389
|
+
if (referent.variableKind === "entry" && referent.entryPath && referent.className) {
|
|
14390
|
+
const label = referent.label || referent.entryPath;
|
|
14391
|
+
variableLines.push(
|
|
14392
|
+
`- ${referent.variableName}: entry<${referent.className}> -> ${label} <${referent.entryPath}>`
|
|
14393
|
+
);
|
|
14394
|
+
continue;
|
|
14395
|
+
}
|
|
14396
|
+
if (referent.variableKind === "list" && referent.listName && referent.className) {
|
|
14397
|
+
const countLabel = typeof referent.count === "number" ? referent.count : "?";
|
|
14398
|
+
variableLines.push(
|
|
14399
|
+
`- ${referent.variableName}: list<${referent.className}> -> ${countLabel} item(s) via ${referent.listName}`
|
|
14400
|
+
);
|
|
14401
|
+
continue;
|
|
14402
|
+
}
|
|
14403
|
+
if (referent.variableKind === "scalar") {
|
|
14404
|
+
variableLines.push(
|
|
14405
|
+
`- ${referent.variableName}: scalar = ${formatScalar(referent.scalarValue)}`
|
|
14406
|
+
);
|
|
14407
|
+
continue;
|
|
14408
|
+
}
|
|
14409
|
+
variableLines.push(`- ${referent.variableName}`);
|
|
14410
|
+
}
|
|
14411
|
+
}
|
|
14412
|
+
const lines = [];
|
|
14413
|
+
lines.push("Entries:");
|
|
14414
|
+
lines.push(...entryLines.length > 0 ? entryLines : ["- none"]);
|
|
14415
|
+
lines.push("", "Lists:");
|
|
14416
|
+
lines.push(...listLines.length > 0 ? listLines : ["- none"]);
|
|
14417
|
+
lines.push("", "Variables:");
|
|
14418
|
+
lines.push(...variableLines.length > 0 ? variableLines : ["- none"]);
|
|
14419
|
+
return lines.join("\n");
|
|
14420
|
+
}
|
|
13884
14421
|
function getCurrentClosureId(liveDoc) {
|
|
13885
14422
|
const loop = asRecord2(liveDoc?.loop);
|
|
13886
14423
|
return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
|
|
@@ -14093,7 +14630,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
14093
14630
|
variableNames: uniqueStrings(variableNames, 4),
|
|
14094
14631
|
listNames: uniqueStrings(listNames, 4),
|
|
14095
14632
|
entryPaths: uniqueStrings(entryPaths, 6),
|
|
14096
|
-
recentActionSummary: uniqueStrings(actionSummaryLines, 8)
|
|
14633
|
+
recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
|
|
14634
|
+
normalizeActionSummaryForPrompt
|
|
14635
|
+
)
|
|
14097
14636
|
};
|
|
14098
14637
|
}
|
|
14099
14638
|
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
@@ -14490,17 +15029,14 @@ ${resultPreview}` : null
|
|
|
14490
15029
|
].filter(Boolean).join("\n\n");
|
|
14491
15030
|
}
|
|
14492
15031
|
function buildGranularAgentDomainBlock(domainDocumentation) {
|
|
14493
|
-
return domainDocumentation?.trim() || "No domain
|
|
15032
|
+
return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
|
|
14494
15033
|
}
|
|
14495
15034
|
function buildGranularAgentSessionBlock(sessionContext) {
|
|
14496
15035
|
if (!sessionContext) return "No session metadata available.";
|
|
14497
15036
|
const rows = [
|
|
14498
15037
|
["sandboxId", sessionContext.sandboxId],
|
|
14499
15038
|
["environmentId", sessionContext.environmentId],
|
|
14500
|
-
["
|
|
14501
|
-
["granularId", sessionContext.granularId],
|
|
14502
|
-
["userName", sessionContext.userName],
|
|
14503
|
-
["domainRevision", sessionContext.domainRevision]
|
|
15039
|
+
["userName", sessionContext.userName]
|
|
14504
15040
|
];
|
|
14505
15041
|
const activeRows = rows.filter(([, value]) => Boolean(value));
|
|
14506
15042
|
if (activeRows.length === 0) return "No session metadata available.";
|
|
@@ -14509,6 +15045,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
|
|
|
14509
15045
|
function buildGranularAgentHeapBlock(heapSummary) {
|
|
14510
15046
|
return heapSummary?.trim() || "Heap is empty for this session.";
|
|
14511
15047
|
}
|
|
15048
|
+
function buildGranularAgentReferentBlock(referentSummary) {
|
|
15049
|
+
return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
|
|
15050
|
+
}
|
|
14512
15051
|
function buildGranularAgentLoopBlock(loopSummary) {
|
|
14513
15052
|
return loopSummary?.trim() || "No active loop state recorded for this session.";
|
|
14514
15053
|
}
|
|
@@ -14532,7 +15071,7 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
14532
15071
|
(tool) => Boolean(tool.className && !tool.static)
|
|
14533
15072
|
);
|
|
14534
15073
|
const lines = [
|
|
14535
|
-
"Treat this block as the planning map. Use DOMAIN
|
|
15074
|
+
"Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
|
|
14536
15075
|
];
|
|
14537
15076
|
const appendGroup = (title, group) => {
|
|
14538
15077
|
lines.push(`- ${title}:`);
|
|
@@ -14580,7 +15119,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
|
|
|
14580
15119
|
if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
|
|
14581
15120
|
lines.push("latestActionSummary:");
|
|
14582
15121
|
for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
|
|
14583
|
-
|
|
15122
|
+
const normalizedLine = normalizeActionSummaryForPrompt(line);
|
|
15123
|
+
lines.push(
|
|
15124
|
+
normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
|
|
15125
|
+
);
|
|
14584
15126
|
}
|
|
14585
15127
|
}
|
|
14586
15128
|
if (checkpoint.latestJobResult?.trim()) {
|
|
@@ -14596,9 +15138,10 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
14596
15138
|
const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
|
|
14597
15139
|
const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
|
|
14598
15140
|
const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
|
|
15141
|
+
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
14599
15142
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
14600
15143
|
return `You are an AI assistant for a live Granular session.
|
|
14601
|
-
You can help the user understand the domain, answer questions, or generate and execute
|
|
15144
|
+
You can help the user understand the domain, answer questions, or generate and execute code against the live session.
|
|
14602
15145
|
Your tone must be natural and human-like.
|
|
14603
15146
|
|
|
14604
15147
|
Call the \`execute_code\` effect ONLY when the user's intent matches the domain's capabilities and requires executing code against the live session. If the user is just asking a general question or if their request doesn't match the available effects or domain types, respond with text to explain.
|
|
@@ -14607,6 +15150,8 @@ When you call \`execute_code\`, additional assistant text must be either:
|
|
|
14607
15150
|
- a brief summary of the actions the generated code will perform.
|
|
14608
15151
|
Do not include any other kind of commentary when calling \`execute_code\`.
|
|
14609
15152
|
- 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(...)\`.
|
|
15153
|
+
- 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.
|
|
15154
|
+
- If you can offer a short grounded shortlist, that clarification should usually be \`loop.ask_user({ type: 'choice', ... })\` instead of a plain-text question with bullet options.
|
|
14610
15155
|
- Never simulate a live prompt, confirmation, decision, task change, or loop closure in plain text. Plain-text replies are only for conversational answers that do not need to mutate session state.
|
|
14611
15156
|
|
|
14612
15157
|
\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
|
|
@@ -14626,6 +15171,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
|
|
|
14626
15171
|
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
|
|
14627
15172
|
- If you need clarification, ask in everyday language.
|
|
14628
15173
|
- 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.
|
|
15174
|
+
- If you are asking the user to pick from explicit options, prefer a live \`loop.ask_user({ type: 'choice', ... })\` prompt over a direct reply that lists those options in text.
|
|
14629
15175
|
- Keep replies concise and clear.
|
|
14630
15176
|
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
|
|
14631
15177
|
|
|
@@ -14635,9 +15181,9 @@ ${sessionBlock}
|
|
|
14635
15181
|
\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
|
|
14636
15182
|
${toolBlock}
|
|
14637
15183
|
|
|
14638
|
-
\u2500\u2500\u2500 DOMAIN
|
|
15184
|
+
\u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
|
|
14639
15185
|
Import classes and effect functions from \`./sandbox-tools\` in generated code.
|
|
14640
|
-
|
|
15186
|
+
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
|
|
14641
15187
|
|
|
14642
15188
|
${domainBlock}
|
|
14643
15189
|
|
|
@@ -14647,6 +15193,9 @@ ${checkpointBlock}
|
|
|
14647
15193
|
\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
|
|
14648
15194
|
${workflowBlock}
|
|
14649
15195
|
|
|
15196
|
+
\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
|
|
15197
|
+
${referentBlock}
|
|
15198
|
+
|
|
14650
15199
|
\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
|
|
14651
15200
|
${heapBlock}
|
|
14652
15201
|
|
|
@@ -14654,109 +15203,54 @@ ${heapBlock}
|
|
|
14654
15203
|
${loopBlock}
|
|
14655
15204
|
|
|
14656
15205
|
\u2500\u2500\u2500 LOOP PLAYBOOK \u2500\u2500\u2500
|
|
14657
|
-
-
|
|
14658
|
-
-
|
|
14659
|
-
-
|
|
14660
|
-
-
|
|
14661
|
-
-
|
|
14662
|
-
-
|
|
14663
|
-
- If
|
|
14664
|
-
-
|
|
14665
|
-
-
|
|
14666
|
-
-
|
|
14667
|
-
-
|
|
14668
|
-
-
|
|
14669
|
-
-
|
|
14670
|
-
-
|
|
14671
|
-
- Use \`loop.
|
|
14672
|
-
-
|
|
14673
|
-
-
|
|
14674
|
-
- If
|
|
14675
|
-
- For disambiguation between concrete known records, prefer \`type: 'choice'\` over \`type: 'input'\`. This is especially important for invoices, orders, shipments, requests, work orders, and cases.
|
|
14676
|
-
- For \`type: 'choice'\` prompts, make the options directly pickable by a human: use a stable value and a readable label that includes the identifier or title they are likely to recognize.
|
|
14677
|
-
- If a shortlist already exists, do not ask the user to type an exact database key or identifier manually. Present the shortlist as clickable choices instead.
|
|
14678
|
-
- When reasoning about free-form status strings, do not use brittle substring checks such as \`status.includes("paid")\` because values like \`"unpaid"\` would be misclassified. Prefer explicit positive matches such as \`unpaid\`, \`open\`, or \`overdue\`, or exact normalized comparisons.
|
|
14679
|
-
- When multiple concrete records match and \`loop.ask_user(...)\` is available, do not stop with a plain-text question like "Which invoice do you mean?". Persist the live workflow and ask through \`await loop.ask_user(...)\` instead.
|
|
14680
|
-
- If the user could reasonably answer with a partial identifier such as \`abcd\` for \`INV-abcd\`, that is another sign the question should be a \`type: 'choice'\` prompt with visible options rather than a free-text input.
|
|
14681
|
-
- Never ask for approval in plain text when \`loop.confirm(...)\` is available. Use \`loop.confirm(...)\` for consequential approval.
|
|
14682
|
-
- When the correct next step is a loop helper action, generate code and call that helper. Do not replace it with a conversational reply.
|
|
14683
|
-
- If you ask the user a new question in the current job, do not also call \`loop.close_loop(...)\` in that same job.
|
|
14684
|
-
- When you need user input or approval, await \`loop.ask_user(...)\` or \`loop.confirm(...)\`. The job will pause until the user answers, then resume from that awaited call.
|
|
14685
|
-
- It is valid to branch on the value returned by \`await loop.ask_user(...)\` or \`await loop.confirm(...)\` after the job resumes.
|
|
14686
|
-
- After \`await loop.ask_user(...)\` returns a concrete choice, continue the workflow in the same resumed job whenever that answer is enough to act. Do not stop with placeholder text such as "I can do that next" or "I'm ready to continue".
|
|
14687
|
-
- After \`await loop.confirm(...)\` returns \`true\`, execute the approved mutation in that same resumed job before returning. Do not end with placeholder text like "Approved, ready to make the change next."
|
|
14688
|
-
- Only stop immediately after a resumed prompt when the user declined, the workflow is now blocked, or you truly still need another missing piece of information.
|
|
14689
|
-
- If the user says stop, enough, or no further action, close the loop and end cleanly without asking another question.
|
|
14690
|
-
- If one clear item is already selected and the next step matters, prefer \`loop.confirm(...)\` over another exploratory question.
|
|
14691
|
-
- If one clear item is already selected and the only missing input is approval to proceed, use \`loop.confirm(...)\` rather than \`loop.ask_user(...)\`.
|
|
14692
|
-
- If the user already gave a usable scheduling window such as "Tuesday morning", treat that as enough to choose a reasonable concrete slot. Do not open another menu just to choose between nearby sub-slots unless a real conflict or hard business rule forces that follow-up.
|
|
14693
|
-
- For schedule changes, prefer one grounded recommendation plus one approval prompt. Avoid a second prompt for optional time-window micro-choices when you can pick a sensible default that still satisfies the user request.
|
|
14694
|
-
- If the user explicitly instructs you to perform a consequential action now, that instruction counts as approval. Do not add an extra confirmation step unless the user expressed hesitation, ambiguity, or asked you not to execute yet.
|
|
14695
|
-
- Direct imperatives such as "cancel this order", "send the reminder now", "approve this refund", or "charge it now" already authorize that exact step. Execute them directly instead of inserting \`loop.confirm(...)\`.
|
|
14696
|
-
- If the user says not to do anything irreversible yet, stop at recommendation, review, or approval. Do not collect checkout-only details like quantity, delivery notes, gift message, or optional preferences unless the user explicitly asks to move closer to purchase.
|
|
14697
|
-
- Once you have one solid recommendation, prefer summarizing it and asking for approval over gathering more optional preferences.
|
|
14698
|
-
- Prefer asking the user for the next missing input over fetching extra related data they did not ask for yet.
|
|
14699
|
-
- Avoid serial menus. After one clarifying choice, prefer acting on it, asking one short text question, or confirming rather than opening another menu.
|
|
14700
|
-
- When the user asks for a summary "including" concrete records such as unpaid invoices, open cases, orders, or shipments, include the actual identifiers or titles of those records in the reply, not just aggregate counts.
|
|
14701
|
-
- Call \`loop.close_loop(...)\` before stopping whenever the current workflow is completed, canceled, or clearly blocked.
|
|
15206
|
+
- 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.
|
|
15207
|
+
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
|
|
15208
|
+
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
|
|
15209
|
+
- 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.
|
|
15210
|
+
- 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.
|
|
15211
|
+
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
|
|
15212
|
+
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
|
|
15213
|
+
- 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.
|
|
15214
|
+
- 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.
|
|
15215
|
+
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
|
|
15216
|
+
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
|
|
15217
|
+
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
|
|
15218
|
+
- 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.
|
|
15219
|
+
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
|
|
15220
|
+
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
|
|
15221
|
+
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
|
|
15222
|
+
- 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.
|
|
15223
|
+
- If you ask a new question in the current job, do not also close the loop in that same job.
|
|
14702
15224
|
|
|
14703
15225
|
\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
|
|
14704
15226
|
- Import from \`./sandbox-tools\`.
|
|
14705
|
-
- If you use \`heap\`, \`loop\`, \`agent_text_message\`,
|
|
15227
|
+
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
|
|
14706
15228
|
- Write top-level executable code with \`await\` at top level.
|
|
14707
|
-
- The generated job body must be plain runnable JavaScript.
|
|
14708
|
-
-
|
|
14709
|
-
-
|
|
14710
|
-
-
|
|
14711
|
-
-
|
|
14712
|
-
-
|
|
14713
|
-
-
|
|
14714
|
-
-
|
|
14715
|
-
-
|
|
14716
|
-
-
|
|
14717
|
-
-
|
|
14718
|
-
-
|
|
14719
|
-
-
|
|
14720
|
-
-
|
|
14721
|
-
-
|
|
14722
|
-
-
|
|
14723
|
-
-
|
|
14724
|
-
-
|
|
14725
|
-
-
|
|
14726
|
-
-
|
|
14727
|
-
-
|
|
14728
|
-
-
|
|
14729
|
-
-
|
|
14730
|
-
-
|
|
14731
|
-
-
|
|
14732
|
-
- If a helper returns plain JSON candidates, keep them local, store only a scalar like the chosen id, or resolve the matching sandbox instance before writing it into the heap.
|
|
14733
|
-
- When reading heap values, prefer generated generic typings such as \`await heap.getVar<Book[]>("my_books")\` or \`await heap.getVar<Book>("selected_book")\`.
|
|
14734
|
-
- If a focused heap variable already points to a known class, read it with that exact generic type and act on it directly. Do not use \`heap.getVar<any>(...)\` or cast through \`any\` when the class is already clear from the prompt. For example, prefer \`await heap.getVar<Order>("selected_order")\` over \`await heap.getVar<any>("selected_order")\`.
|
|
14735
|
-
- Use the injected \`loop\` helpers when you need to manage the workflow itself:
|
|
14736
|
-
\`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`,
|
|
14737
|
-
\`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
|
|
14738
|
-
- Loop helper semantics:
|
|
14739
|
-
- \`loop.open_decision(...)\`: store explicit candidates from the current job so a later job can revisit the same decision. Keep and reuse the returned \`decisionId\`.
|
|
14740
|
-
- \`loop.close_decision(...)\`: resolve an open decision by choosing one stored candidate with \`selectedId\` and recording why. Candidates may be any JSON objects, but each one must have an \`id\`.
|
|
14741
|
-
- \`loop.ask_user(...)\`: pause the job and ask the user for missing input. Default to \`type: 'input'\`; use \`type: 'choice'\` only for a short explicit shortlist. Write \`const answer = await loop.ask_user(...)\`, then continue the same job once the user answers.
|
|
14742
|
-
- \`loop.confirm(...)\`: pause the job for approval before a consequential action. Do not simulate confirmation in plain text. Write \`const approved = await loop.confirm(...)\`, then branch on that approval once the job resumes.
|
|
14743
|
-
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short task list that later jobs can continue and finish.
|
|
14744
|
-
- \`loop.close_loop(...)\`: record the current workflow outcome with a short summary before stopping. Do not call it in the same job that opens a new user prompt unless the workflow is explicitly blocked. This does not end the session forever.
|
|
14745
|
-
- Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
|
|
14746
|
-
- Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
|
|
14747
|
-
- Every job that intends to answer the user must emit at least one explicit UI message with \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
14748
|
-
- Use \`agent_text_message("...")\` for all user-visible text shown in the UI.
|
|
14749
|
-
- Use \`agent_heap_objects(...)\` only when you want the UI to render heap-backed records or lists.
|
|
14750
|
-
- If you want to show both text and records, call \`agent_text_message(...)\` and \`agent_heap_objects(...)\` separately in whatever order fits the interaction.
|
|
14751
|
-
- \`agent_text_message(...)\` should be used with a plain text string in normal generated code.
|
|
14752
|
-
- \`agent_heap_objects(...)\` should point at heap-backed values: explicit \`entryPaths\` / \`listNames\` / \`variableNames\`, a named list saved with \`saveAs\`, or values read back from \`heap.getVar(...)\`.
|
|
14753
|
-
- If you just fetched records and want to show them in the UI, save or reference them through the heap first, then call \`agent_heap_objects(...)\`. Do not try to hand-build UI payloads in job code.
|
|
14754
|
-
- Do not assume heap changes will be displayed automatically. If records should appear in the UI, you must call \`agent_heap_objects(...)\`.
|
|
14755
|
-
- Never write \`return { reply, show }\` or \`return { show: ... }\` for UI. If you want the UI to render records or lists, call \`agent_heap_objects(...)\` instead.
|
|
14756
|
-
- Do not rely on the final return value for user-visible output. A plain return value is not considered a displayed UI answer.
|
|
14757
|
-
- \`agent_message(...)\` remains available as a legacy compatibility alias, but prefer \`agent_text_message(...)\` and \`agent_heap_objects(...)\` in new code.
|
|
14758
|
-
- Do not return bare structured JSON, low-level diagnostics, or database-shaped payloads as the final answer unless the user explicitly asks for them.
|
|
14759
|
-
- Prefer simple executable JavaScript over clever interpolation. Avoid nested template literals or unusually dense inline expressions when a small temporary variable or string concatenation would be clearer and safer.
|
|
15229
|
+
- The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
|
|
15230
|
+
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
|
|
15231
|
+
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
|
|
15232
|
+
- 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.
|
|
15233
|
+
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
|
|
15234
|
+
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
|
|
15235
|
+
- 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.
|
|
15236
|
+
- 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.
|
|
15237
|
+
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
|
|
15238
|
+
- 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(...)\`.
|
|
15239
|
+
- 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.
|
|
15240
|
+
- Call instance methods on instances, static methods on classes, and global effects by name.
|
|
15241
|
+
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
|
|
15242
|
+
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
|
|
15243
|
+
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
|
|
15244
|
+
- 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.
|
|
15245
|
+
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
|
|
15246
|
+
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
|
|
15247
|
+
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
|
|
15248
|
+
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
|
|
15249
|
+
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
|
|
15250
|
+
- Use \`agent_text_message(...)\` for user-visible text.
|
|
15251
|
+
- 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.
|
|
15252
|
+
- 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.
|
|
15253
|
+
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
|
|
14760
15254
|
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
|
|
14761
15255
|
}
|
|
14762
15256
|
|
|
@@ -14915,6 +15409,10 @@ function fallbackResponseText(entries, lists) {
|
|
|
14915
15409
|
return entries.length === 1 ? "I found one relevant record." : `I found ${entries.length} relevant records.`;
|
|
14916
15410
|
}
|
|
14917
15411
|
if (lists.length > 0) {
|
|
15412
|
+
const emptyOnly = lists.every((list) => (list.paths || []).length === 0);
|
|
15413
|
+
if (emptyOnly) {
|
|
15414
|
+
return lists.length === 1 ? "I saved one empty result set." : `I saved ${lists.length} empty result sets.`;
|
|
15415
|
+
}
|
|
14918
15416
|
return lists.length === 1 ? "I saved one result set." : `I saved ${lists.length} result sets.`;
|
|
14919
15417
|
}
|
|
14920
15418
|
return null;
|
|
@@ -15291,7 +15789,9 @@ function buildSessionTranscript(input) {
|
|
|
15291
15789
|
}
|
|
15292
15790
|
|
|
15293
15791
|
exports.Environment = Environment;
|
|
15792
|
+
exports.EnvironmentSession = EnvironmentSession;
|
|
15294
15793
|
exports.Granular = Granular;
|
|
15794
|
+
exports.OntologyHandle = OntologyHandle;
|
|
15295
15795
|
exports.Session = Session;
|
|
15296
15796
|
exports.WSClient = WSClient;
|
|
15297
15797
|
exports.buildContinuationInstruction = buildContinuationInstruction;
|
|
@@ -15299,6 +15799,7 @@ exports.buildGranularAgentCheckpointBlock = buildGranularAgentCheckpointBlock;
|
|
|
15299
15799
|
exports.buildGranularAgentDomainBlock = buildGranularAgentDomainBlock;
|
|
15300
15800
|
exports.buildGranularAgentHeapBlock = buildGranularAgentHeapBlock;
|
|
15301
15801
|
exports.buildGranularAgentLoopBlock = buildGranularAgentLoopBlock;
|
|
15802
|
+
exports.buildGranularAgentReferentBlock = buildGranularAgentReferentBlock;
|
|
15302
15803
|
exports.buildGranularAgentSessionBlock = buildGranularAgentSessionBlock;
|
|
15303
15804
|
exports.buildGranularAgentSystemPrompt = buildGranularAgentSystemPrompt;
|
|
15304
15805
|
exports.buildGranularAgentToolBlock = buildGranularAgentToolBlock;
|
|
@@ -15316,6 +15817,8 @@ exports.normalizeEffectBehaviors = normalizeEffectBehaviors;
|
|
|
15316
15817
|
exports.normalizePrompt = normalizePrompt;
|
|
15317
15818
|
exports.normalizePromptText = normalizePromptText;
|
|
15318
15819
|
exports.normalizePromptType = normalizePromptType;
|
|
15820
|
+
exports.projectConversationReferentFocus = projectConversationReferentFocus;
|
|
15821
|
+
exports.projectConversationReferentSummary = projectConversationReferentSummary;
|
|
15319
15822
|
exports.projectHeapSummary = projectHeapSummary;
|
|
15320
15823
|
exports.projectLoopSummary = projectLoopSummary;
|
|
15321
15824
|
exports.projectWorkflowFocus = projectWorkflowFocus;
|