@granular-software/sdk 0.4.50 → 0.4.51
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 +53 -0
- package/dist/agent-evals.d.mts +2 -2
- package/dist/agent-evals.d.ts +2 -2
- package/dist/agent-evals.js +449 -179
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +449 -179
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.js +4 -2
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +4 -2
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +487 -203
- package/dist/{client-BbI7ThzU.d.ts → client-NSH-tpNU.d.ts} +27 -11
- package/dist/{client-DaYFTHG8.d.mts → client-djorlOpn.d.mts} +27 -11
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +449 -179
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +449 -179
- package/dist/index.mjs.map +1 -1
- package/dist/{spend-RpJikX9w.d.mts → spend-BA-jZwZ0.d.mts} +125 -4
- package/dist/{spend-RpJikX9w.d.ts → spend-BA-jZwZ0.d.ts} +125 -4
- package/dist/spend.d.mts +1 -1
- package/dist/spend.d.ts +1 -1
- package/dist/spend.js +6 -1
- package/dist/spend.js.map +1 -1
- package/dist/spend.mjs +6 -1
- package/dist/spend.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -60,11 +60,11 @@ var __export = (target, all) => {
|
|
|
60
60
|
for (var name in all)
|
|
61
61
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
62
62
|
};
|
|
63
|
-
var __copyProps = (to,
|
|
64
|
-
if (
|
|
65
|
-
for (let key of __getOwnPropNames(
|
|
63
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
64
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
65
|
+
for (let key of __getOwnPropNames(from2))
|
|
66
66
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
67
|
-
__defProp(to, key, { get: () =>
|
|
67
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
68
68
|
}
|
|
69
69
|
return to;
|
|
70
70
|
};
|
|
@@ -8889,7 +8889,7 @@ createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
|
8889
8889
|
var source_default = chalk;
|
|
8890
8890
|
|
|
8891
8891
|
// ../../node_modules/mimic-function/index.js
|
|
8892
|
-
var copyProperty = (to,
|
|
8892
|
+
var copyProperty = (to, from2, property, ignoreNonConfigurable) => {
|
|
8893
8893
|
if (property === "length" || property === "prototype") {
|
|
8894
8894
|
return;
|
|
8895
8895
|
}
|
|
@@ -8897,7 +8897,7 @@ var copyProperty = (to, from, property, ignoreNonConfigurable) => {
|
|
|
8897
8897
|
return;
|
|
8898
8898
|
}
|
|
8899
8899
|
const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
|
|
8900
|
-
const fromDescriptor = Object.getOwnPropertyDescriptor(
|
|
8900
|
+
const fromDescriptor = Object.getOwnPropertyDescriptor(from2, property);
|
|
8901
8901
|
if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
|
|
8902
8902
|
return;
|
|
8903
8903
|
}
|
|
@@ -8906,8 +8906,8 @@ var copyProperty = (to, from, property, ignoreNonConfigurable) => {
|
|
|
8906
8906
|
var canCopyProperty = function(toDescriptor, fromDescriptor) {
|
|
8907
8907
|
return toDescriptor === void 0 || toDescriptor.configurable || toDescriptor.writable === fromDescriptor.writable && toDescriptor.enumerable === fromDescriptor.enumerable && toDescriptor.configurable === fromDescriptor.configurable && (toDescriptor.writable || toDescriptor.value === fromDescriptor.value);
|
|
8908
8908
|
};
|
|
8909
|
-
var changePrototype = (to,
|
|
8910
|
-
const fromPrototype = Object.getPrototypeOf(
|
|
8909
|
+
var changePrototype = (to, from2) => {
|
|
8910
|
+
const fromPrototype = Object.getPrototypeOf(from2);
|
|
8911
8911
|
if (fromPrototype === Object.getPrototypeOf(to)) {
|
|
8912
8912
|
return;
|
|
8913
8913
|
}
|
|
@@ -8917,20 +8917,20 @@ var wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/
|
|
|
8917
8917
|
${fromBody}`;
|
|
8918
8918
|
var toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, "toString");
|
|
8919
8919
|
var toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, "name");
|
|
8920
|
-
var changeToString = (to,
|
|
8920
|
+
var changeToString = (to, from2, name) => {
|
|
8921
8921
|
const withName = name === "" ? "" : `with ${name.trim()}() `;
|
|
8922
|
-
const newToString = wrappedToString.bind(null, withName,
|
|
8922
|
+
const newToString = wrappedToString.bind(null, withName, from2.toString());
|
|
8923
8923
|
Object.defineProperty(newToString, "name", toStringName);
|
|
8924
8924
|
const { writable, enumerable, configurable } = toStringDescriptor;
|
|
8925
8925
|
Object.defineProperty(to, "toString", { value: newToString, writable, enumerable, configurable });
|
|
8926
8926
|
};
|
|
8927
|
-
function mimicFunction(to,
|
|
8927
|
+
function mimicFunction(to, from2, { ignoreNonConfigurable = false } = {}) {
|
|
8928
8928
|
const { name } = to;
|
|
8929
|
-
for (const property of Reflect.ownKeys(
|
|
8930
|
-
copyProperty(to,
|
|
8929
|
+
for (const property of Reflect.ownKeys(from2)) {
|
|
8930
|
+
copyProperty(to, from2, property, ignoreNonConfigurable);
|
|
8931
8931
|
}
|
|
8932
|
-
changePrototype(to,
|
|
8933
|
-
changeToString(to,
|
|
8932
|
+
changePrototype(to, from2);
|
|
8933
|
+
changeToString(to, from2, name);
|
|
8934
8934
|
return to;
|
|
8935
8935
|
}
|
|
8936
8936
|
|
|
@@ -15624,11 +15624,20 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
15624
15624
|
return mutations;
|
|
15625
15625
|
}
|
|
15626
15626
|
function buildMachineTypes(classSummary, machine) {
|
|
15627
|
+
const stateGlossary = machine.states.map((state) => {
|
|
15628
|
+
const label = state.label && state.label !== state.name ? state.label : null;
|
|
15629
|
+
const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
|
|
15630
|
+
const finalMarker = state.isFinal ? " Final state." : "";
|
|
15631
|
+
return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
|
|
15632
|
+
});
|
|
15627
15633
|
return [
|
|
15628
15634
|
{
|
|
15629
15635
|
kind: "union",
|
|
15630
15636
|
name: stateTypeName(classSummary.name, machine.name),
|
|
15631
|
-
docs: [
|
|
15637
|
+
docs: [
|
|
15638
|
+
`Allowed states for ${classSummary.name}.${machine.name}.`,
|
|
15639
|
+
...stateGlossary
|
|
15640
|
+
],
|
|
15632
15641
|
members: machine.states.map((state) => state.name)
|
|
15633
15642
|
},
|
|
15634
15643
|
{
|
|
@@ -15976,7 +15985,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
15976
15985
|
},
|
|
15977
15986
|
add_transition: async (value, {
|
|
15978
15987
|
name,
|
|
15979
|
-
from,
|
|
15988
|
+
from: from2,
|
|
15980
15989
|
to,
|
|
15981
15990
|
label,
|
|
15982
15991
|
description,
|
|
@@ -15991,7 +16000,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
15991
16000
|
value.target.add_state_machine_transition(
|
|
15992
16001
|
value.name,
|
|
15993
16002
|
name,
|
|
15994
|
-
|
|
16003
|
+
from2,
|
|
15995
16004
|
to,
|
|
15996
16005
|
{
|
|
15997
16006
|
label,
|
|
@@ -16814,7 +16823,7 @@ ${effectMetamodelTable}
|
|
|
16814
16823
|
| \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImport\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge, returning an \`importId\` is enough up front, and background processing is acceptable. |
|
|
16815
16824
|
| \`graphql(query, variables?)\` | **GraphQL** \u2014 see dedicated subsection below. |
|
|
16816
16825
|
| \`defineRelationship\`, \`getRelationships\`, \`attach\`, \`detach\`, \`listRelated\` | Imperative relationship operations (same ideas as manifest \`defineRelationship\`). |
|
|
16817
|
-
| \`sessions.list()\`, \`sessions.create()\`, \`sessions.connect()\`, \`sessions.reopen()\`, \`sessions.close()\` |
|
|
16826
|
+
| \`sessions.list({ status?, sessionScope?, limit?, offset? })\`, \`sessions.create({ sessionScope? })\`, \`sessions.connect()\`, \`sessions.reopen()\`, \`sessions.close()\` | Scoped, bounded session history and lifecycle for this environment. |
|
|
16818
16827
|
|
|
16819
16828
|
### \`Session\` (live runtime connection)
|
|
16820
16829
|
|
|
@@ -16862,7 +16871,7 @@ Use \`environment.graphql(query, variables?)\` when you need **query/mutation ac
|
|
|
16862
16871
|
| \`granular simulate\` | Open simulator in browser. |
|
|
16863
16872
|
| \`granular simulate --print-url\` | Print a deep-linkable simulator URL without opening the browser. |
|
|
16864
16873
|
| \`granular connect test --json\` | Verify auth and environment connectivity with a real session. |
|
|
16865
|
-
| \`granular session create/list/heap/doc --json\` | Create, enumerate, and inspect real session state. |
|
|
16874
|
+
| \`granular session create/list/heap/doc --json\` | Create, enumerate, and inspect real session state. \`session list\` defaults to 25 rows; use \`--session-scope\`, \`--subject-id\`, \`--limit\`, and \`--offset\` for deterministic paging. |
|
|
16866
16875
|
| \`granular graphql --query '...' --json\` | Run a GraphQL query or mutation against the live graph. |
|
|
16867
16876
|
| \`granular effects list/diff --json\` | Inspect declared versus live ready effects. |
|
|
16868
16877
|
| \`granular job run --file ./job.ts --json\` | Execute a real runtime job from the terminal or CI. |
|
|
@@ -17542,8 +17551,8 @@ Use this for command execution, environment setup, and shipping flows.
|
|
|
17542
17551
|
| \`granular graphql --query '...' --json\` | Query the live graph through the environment GraphQL API |
|
|
17543
17552
|
| \`granular effects list --json\` | Inspect declared and live effects for an environment |
|
|
17544
17553
|
| \`granular effects diff --json\` | Compare declared effects to live ready handlers |
|
|
17545
|
-
| \`granular session create --json\` | Create a fresh session for an environment |
|
|
17546
|
-
| \`granular session list --json\` | List
|
|
17554
|
+
| \`granular session create --session-scope <scope> --json\` | Create a fresh, application-scoped session for an environment |
|
|
17555
|
+
| \`granular session list --session-scope <scope> --limit 25 --offset 0 --json\` | List one bounded page of indexed sessions |
|
|
17547
17556
|
| \`granular session heap --json\` | Inspect session heap |
|
|
17548
17557
|
| \`granular session doc --json\` | Inspect the Automerge-backed session document |
|
|
17549
17558
|
| \`granular job run --file ./job.ts\` | Execute a real job against the ontology runtime |
|
|
@@ -17626,8 +17635,8 @@ Use this for runtime debugging after the ontology builds but behavior does not m
|
|
|
17626
17635
|
|
|
17627
17636
|
| Goal | Preferred path |
|
|
17628
17637
|
| --- | --- |
|
|
17629
|
-
| Create or rotate a session | \`granular session create --json\` |
|
|
17630
|
-
| List known sessions | \`granular session list --json\` |
|
|
17638
|
+
| Create or rotate a session | \`granular session create --session-scope <scope> --json\` |
|
|
17639
|
+
| List known sessions | \`granular session list --session-scope <scope> --limit 25 --offset 0 --json\` |
|
|
17631
17640
|
| Inspect heap | \`granular session heap --json\` |
|
|
17632
17641
|
| Inspect full document | \`granular session doc --json\` |
|
|
17633
17642
|
| Verify connectivity | \`granular connect test\` |
|
|
@@ -19560,16 +19569,37 @@ var WSClient = class {
|
|
|
19560
19569
|
tokenRefreshTimer = null;
|
|
19561
19570
|
isExplicitlyDisconnected = false;
|
|
19562
19571
|
reconnectAttempts = 0;
|
|
19572
|
+
connectPromise = null;
|
|
19573
|
+
connectionEpoch = 0;
|
|
19574
|
+
cancelConnectAttempt = null;
|
|
19563
19575
|
options;
|
|
19564
19576
|
constructor(options) {
|
|
19565
19577
|
this.options = options;
|
|
19566
19578
|
this.url = options.url;
|
|
19567
19579
|
this.sessionId = options.sessionId;
|
|
19568
19580
|
this.token = options.token;
|
|
19581
|
+
if (options.initialDocumentSnapshot) {
|
|
19582
|
+
this.seedDocumentSnapshot(options.initialDocumentSnapshot);
|
|
19583
|
+
}
|
|
19569
19584
|
}
|
|
19570
19585
|
get currentSessionId() {
|
|
19571
19586
|
return this.sessionId;
|
|
19572
19587
|
}
|
|
19588
|
+
seedDocumentSnapshot(document) {
|
|
19589
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
19590
|
+
return;
|
|
19591
|
+
}
|
|
19592
|
+
try {
|
|
19593
|
+
this.doc = document instanceof Uint8Array ? Automerge__namespace.load(document) : Automerge__namespace.from(document);
|
|
19594
|
+
this.syncState = Automerge__namespace.initSyncState();
|
|
19595
|
+
this.emit("sync", this.doc);
|
|
19596
|
+
} catch (error2) {
|
|
19597
|
+
console.warn("[Granular] Failed to seed cached session document", error2);
|
|
19598
|
+
}
|
|
19599
|
+
}
|
|
19600
|
+
saveDocumentSnapshot() {
|
|
19601
|
+
return Automerge__namespace.save(this.doc);
|
|
19602
|
+
}
|
|
19573
19603
|
clearTokenRefreshTimer() {
|
|
19574
19604
|
if (this.tokenRefreshTimer) {
|
|
19575
19605
|
clearTimeout(this.tokenRefreshTimer);
|
|
@@ -19679,8 +19709,23 @@ var WSClient = class {
|
|
|
19679
19709
|
* Connect to the WebSocket server
|
|
19680
19710
|
* @returns {Promise<void>} Resolves when connection is open
|
|
19681
19711
|
*/
|
|
19682
|
-
async connect() {
|
|
19712
|
+
async connect(options = {}) {
|
|
19713
|
+
if (this.ws?.readyState === READY_STATE_OPEN) return;
|
|
19714
|
+
if (this.connectPromise) return this.connectPromise;
|
|
19715
|
+
const connectPromise = this.connectAttempt(options.signal);
|
|
19716
|
+
this.connectPromise = connectPromise;
|
|
19717
|
+
try {
|
|
19718
|
+
await connectPromise;
|
|
19719
|
+
} finally {
|
|
19720
|
+
if (this.connectPromise === connectPromise) {
|
|
19721
|
+
this.connectPromise = null;
|
|
19722
|
+
}
|
|
19723
|
+
}
|
|
19724
|
+
}
|
|
19725
|
+
async connectAttempt(signal) {
|
|
19726
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
19683
19727
|
const token = await this.resolveTokenForConnect();
|
|
19728
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
19684
19729
|
this.isExplicitlyDisconnected = false;
|
|
19685
19730
|
this.scheduleTokenRefresh();
|
|
19686
19731
|
if (this.reconnectTimer) {
|
|
@@ -19692,7 +19737,7 @@ var WSClient = class {
|
|
|
19692
19737
|
try {
|
|
19693
19738
|
const wsModule = await import('ws');
|
|
19694
19739
|
WebSocketClass = wsModule.default || wsModule;
|
|
19695
|
-
} catch
|
|
19740
|
+
} catch {
|
|
19696
19741
|
}
|
|
19697
19742
|
}
|
|
19698
19743
|
if (!WebSocketClass) {
|
|
@@ -19700,83 +19745,97 @@ var WSClient = class {
|
|
|
19700
19745
|
'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
|
|
19701
19746
|
);
|
|
19702
19747
|
}
|
|
19748
|
+
const epoch = ++this.connectionEpoch;
|
|
19749
|
+
const wsUrl = new URL(this.url);
|
|
19750
|
+
wsUrl.searchParams.set("sessionId", this.sessionId);
|
|
19751
|
+
wsUrl.searchParams.set("token", token);
|
|
19752
|
+
const socket = new WebSocketClass(wsUrl.toString());
|
|
19753
|
+
this.ws = socket;
|
|
19703
19754
|
return new Promise((resolve2, reject) => {
|
|
19704
|
-
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
if (
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
this.reconnectTimer = null;
|
|
19716
|
-
}
|
|
19717
|
-
this.reconnectAttempts = 0;
|
|
19718
|
-
this.emit("open", {});
|
|
19719
|
-
resolve2();
|
|
19720
|
-
});
|
|
19721
|
-
socket.on("message", (data) => {
|
|
19722
|
-
try {
|
|
19723
|
-
const message = JSON.parse(data.toString());
|
|
19724
|
-
this.handleMessage(message);
|
|
19725
|
-
} catch (error2) {
|
|
19726
|
-
console.error("[Granular] Failed to parse message:", error2);
|
|
19727
|
-
}
|
|
19728
|
-
});
|
|
19729
|
-
socket.on("error", (error2) => {
|
|
19730
|
-
this.emit("error", error2);
|
|
19731
|
-
if (socket.readyState !== READY_STATE_OPEN) {
|
|
19732
|
-
reject(error2);
|
|
19733
|
-
}
|
|
19734
|
-
});
|
|
19735
|
-
socket.on("close", (code, reason) => {
|
|
19736
|
-
this.handleDisconnect({
|
|
19737
|
-
code,
|
|
19738
|
-
reason: this.normalizeReason(reason),
|
|
19739
|
-
// ws does not provide wasClean on Node-style close callback
|
|
19740
|
-
wasClean: code === 1e3
|
|
19741
|
-
});
|
|
19742
|
-
});
|
|
19755
|
+
let settled = false;
|
|
19756
|
+
const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
|
|
19757
|
+
const finish = (error2) => {
|
|
19758
|
+
if (settled) return;
|
|
19759
|
+
settled = true;
|
|
19760
|
+
if (this.cancelConnectAttempt === handleAbort) {
|
|
19761
|
+
this.cancelConnectAttempt = null;
|
|
19762
|
+
}
|
|
19763
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
19764
|
+
if (error2) {
|
|
19765
|
+
reject(error2 instanceof Error ? error2 : new Error(String(error2)));
|
|
19743
19766
|
} else {
|
|
19744
|
-
|
|
19745
|
-
if (this.reconnectTimer) {
|
|
19746
|
-
clearTimeout(this.reconnectTimer);
|
|
19747
|
-
this.reconnectTimer = null;
|
|
19748
|
-
}
|
|
19749
|
-
this.reconnectAttempts = 0;
|
|
19750
|
-
this.emit("open", {});
|
|
19751
|
-
resolve2();
|
|
19752
|
-
};
|
|
19753
|
-
this.ws.onmessage = (event) => {
|
|
19754
|
-
try {
|
|
19755
|
-
const data = event.data;
|
|
19756
|
-
const message = JSON.parse(data.toString());
|
|
19757
|
-
this.handleMessage(message);
|
|
19758
|
-
} catch (error2) {
|
|
19759
|
-
console.error("[Granular] Failed to parse message:", error2);
|
|
19760
|
-
}
|
|
19761
|
-
};
|
|
19762
|
-
this.ws.onerror = (event) => {
|
|
19763
|
-
const error2 = new Error("WebSocket error");
|
|
19764
|
-
error2.event = event;
|
|
19765
|
-
this.emit("error", error2);
|
|
19766
|
-
if (this.ws?.readyState !== READY_STATE_OPEN) {
|
|
19767
|
-
reject(error2);
|
|
19768
|
-
}
|
|
19769
|
-
};
|
|
19770
|
-
this.ws.onclose = (event) => {
|
|
19771
|
-
this.handleDisconnect({
|
|
19772
|
-
code: event.code,
|
|
19773
|
-
reason: event.reason,
|
|
19774
|
-
wasClean: event.wasClean
|
|
19775
|
-
});
|
|
19776
|
-
};
|
|
19767
|
+
resolve2();
|
|
19777
19768
|
}
|
|
19778
|
-
}
|
|
19779
|
-
|
|
19769
|
+
};
|
|
19770
|
+
const closeStaleSocket = () => {
|
|
19771
|
+
try {
|
|
19772
|
+
socket.close(1e3, "Stale connection attempt");
|
|
19773
|
+
} catch {
|
|
19774
|
+
}
|
|
19775
|
+
};
|
|
19776
|
+
const handleAbort = () => {
|
|
19777
|
+
if (isCurrent()) {
|
|
19778
|
+
this.connectionEpoch += 1;
|
|
19779
|
+
this.ws = null;
|
|
19780
|
+
}
|
|
19781
|
+
closeStaleSocket();
|
|
19782
|
+
finish(new Error("WebSocket connect aborted"));
|
|
19783
|
+
};
|
|
19784
|
+
this.cancelConnectAttempt = handleAbort;
|
|
19785
|
+
const handleOpen = () => {
|
|
19786
|
+
if (!isCurrent()) {
|
|
19787
|
+
closeStaleSocket();
|
|
19788
|
+
return;
|
|
19789
|
+
}
|
|
19790
|
+
this.reconnectAttempts = 0;
|
|
19791
|
+
this.emit("open", {});
|
|
19792
|
+
finish();
|
|
19793
|
+
};
|
|
19794
|
+
const handleMessage = (data) => {
|
|
19795
|
+
if (!isCurrent()) return;
|
|
19796
|
+
try {
|
|
19797
|
+
const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
|
|
19798
|
+
this.handleMessage(JSON.parse(text));
|
|
19799
|
+
} catch (error2) {
|
|
19800
|
+
console.error("[Granular] Failed to parse message:", error2);
|
|
19801
|
+
}
|
|
19802
|
+
};
|
|
19803
|
+
const handleError = (error2) => {
|
|
19804
|
+
if (!isCurrent()) return;
|
|
19805
|
+
const typedError = error2 instanceof Error ? error2 : new Error("WebSocket error");
|
|
19806
|
+
this.emit("error", typedError);
|
|
19807
|
+
if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
|
|
19808
|
+
};
|
|
19809
|
+
const handleClose = (close) => {
|
|
19810
|
+
if (!isCurrent()) return;
|
|
19811
|
+
if (!settled) {
|
|
19812
|
+
finish(
|
|
19813
|
+
new Error(
|
|
19814
|
+
`WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
|
|
19815
|
+
)
|
|
19816
|
+
);
|
|
19817
|
+
}
|
|
19818
|
+
this.handleDisconnect({
|
|
19819
|
+
code: close.code,
|
|
19820
|
+
reason: this.normalizeReason(close.reason),
|
|
19821
|
+
wasClean: close.wasClean
|
|
19822
|
+
});
|
|
19823
|
+
};
|
|
19824
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
19825
|
+
const nodeSocket = socket;
|
|
19826
|
+
if (typeof nodeSocket.on === "function") {
|
|
19827
|
+
nodeSocket.on("open", handleOpen);
|
|
19828
|
+
nodeSocket.on("message", handleMessage);
|
|
19829
|
+
nodeSocket.on("error", handleError);
|
|
19830
|
+
nodeSocket.on(
|
|
19831
|
+
"close",
|
|
19832
|
+
(code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
|
|
19833
|
+
);
|
|
19834
|
+
} else {
|
|
19835
|
+
socket.onopen = handleOpen;
|
|
19836
|
+
socket.onmessage = (event) => handleMessage(event.data);
|
|
19837
|
+
socket.onerror = handleError;
|
|
19838
|
+
socket.onclose = (event) => handleClose(event);
|
|
19780
19839
|
}
|
|
19781
19840
|
});
|
|
19782
19841
|
}
|
|
@@ -19794,9 +19853,58 @@ var WSClient = class {
|
|
|
19794
19853
|
return void 0;
|
|
19795
19854
|
}
|
|
19796
19855
|
rejectPending(error2) {
|
|
19797
|
-
this.messageQueue.forEach((pending) =>
|
|
19856
|
+
this.messageQueue.forEach((pending) => {
|
|
19857
|
+
clearTimeout(pending.timeout);
|
|
19858
|
+
pending.reject(error2);
|
|
19859
|
+
});
|
|
19798
19860
|
this.messageQueue = [];
|
|
19799
19861
|
}
|
|
19862
|
+
emitReconnectErrorMessage(error2) {
|
|
19863
|
+
const reconnectInfo = {
|
|
19864
|
+
error: error2,
|
|
19865
|
+
sessionId: this.sessionId,
|
|
19866
|
+
timestamp: Date.now()
|
|
19867
|
+
};
|
|
19868
|
+
this.emit("reconnect_error", reconnectInfo);
|
|
19869
|
+
if (this.options.onReconnectError) {
|
|
19870
|
+
try {
|
|
19871
|
+
this.options.onReconnectError(reconnectInfo);
|
|
19872
|
+
} catch (callbackError) {
|
|
19873
|
+
console.error(
|
|
19874
|
+
"[Granular] onReconnectError callback failed:",
|
|
19875
|
+
callbackError
|
|
19876
|
+
);
|
|
19877
|
+
}
|
|
19878
|
+
}
|
|
19879
|
+
}
|
|
19880
|
+
scheduleReconnectAttempt() {
|
|
19881
|
+
if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
|
|
19882
|
+
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
19883
|
+
const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
19884
|
+
if (this.reconnectAttempts >= maxReconnectAttempts) {
|
|
19885
|
+
this.emitReconnectErrorMessage(
|
|
19886
|
+
`WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
|
|
19887
|
+
);
|
|
19888
|
+
return null;
|
|
19889
|
+
}
|
|
19890
|
+
this.reconnectAttempts += 1;
|
|
19891
|
+
const reconnectDelayMs = Math.min(
|
|
19892
|
+
3e4,
|
|
19893
|
+
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
19894
|
+
);
|
|
19895
|
+
this.reconnectTimer = setTimeout(() => {
|
|
19896
|
+
this.reconnectTimer = null;
|
|
19897
|
+
console.log("[Granular] Attempting reconnect...");
|
|
19898
|
+
this.connect().catch((error2) => {
|
|
19899
|
+
console.error("[Granular] Reconnect failed:", error2);
|
|
19900
|
+
this.emitReconnectErrorMessage(
|
|
19901
|
+
error2 instanceof Error ? error2.message : String(error2)
|
|
19902
|
+
);
|
|
19903
|
+
this.scheduleReconnectAttempt();
|
|
19904
|
+
});
|
|
19905
|
+
}, reconnectDelayMs);
|
|
19906
|
+
return reconnectDelayMs;
|
|
19907
|
+
}
|
|
19800
19908
|
buildDisconnectError(info2) {
|
|
19801
19909
|
const details = [
|
|
19802
19910
|
info2.code !== void 0 ? `code=${info2.code}` : void 0,
|
|
@@ -19806,8 +19914,6 @@ var WSClient = class {
|
|
|
19806
19914
|
return new Error(`WebSocket disconnected${suffix}`);
|
|
19807
19915
|
}
|
|
19808
19916
|
handleDisconnect(close = {}) {
|
|
19809
|
-
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
19810
|
-
const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
19811
19917
|
const unexpected = !this.isExplicitlyDisconnected;
|
|
19812
19918
|
const info2 = {
|
|
19813
19919
|
code: close.code,
|
|
@@ -19827,32 +19933,9 @@ var WSClient = class {
|
|
|
19827
19933
|
const disconnectError = this.buildDisconnectError(info2);
|
|
19828
19934
|
this.rejectPending(disconnectError);
|
|
19829
19935
|
this.emit("disconnect", info2);
|
|
19830
|
-
|
|
19831
|
-
|
|
19832
|
-
|
|
19833
|
-
sessionId: this.sessionId,
|
|
19834
|
-
timestamp: Date.now()
|
|
19835
|
-
};
|
|
19836
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
19837
|
-
if (this.options.onReconnectError) {
|
|
19838
|
-
try {
|
|
19839
|
-
this.options.onReconnectError(reconnectInfo);
|
|
19840
|
-
} catch (callbackError) {
|
|
19841
|
-
console.error(
|
|
19842
|
-
"[Granular] onReconnectError callback failed:",
|
|
19843
|
-
callbackError
|
|
19844
|
-
);
|
|
19845
|
-
}
|
|
19846
|
-
}
|
|
19847
|
-
return;
|
|
19848
|
-
}
|
|
19849
|
-
this.reconnectAttempts += 1;
|
|
19850
|
-
const reconnectDelayMs = Math.min(
|
|
19851
|
-
3e4,
|
|
19852
|
-
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
19853
|
-
);
|
|
19854
|
-
info2.reconnectScheduled = true;
|
|
19855
|
-
info2.reconnectDelayMs = reconnectDelayMs;
|
|
19936
|
+
const reconnectDelayMs = this.scheduleReconnectAttempt();
|
|
19937
|
+
info2.reconnectScheduled = reconnectDelayMs !== null;
|
|
19938
|
+
if (reconnectDelayMs !== null) info2.reconnectDelayMs = reconnectDelayMs;
|
|
19856
19939
|
if (this.options.onUnexpectedClose) {
|
|
19857
19940
|
try {
|
|
19858
19941
|
this.options.onUnexpectedClose(info2);
|
|
@@ -19863,28 +19946,6 @@ var WSClient = class {
|
|
|
19863
19946
|
);
|
|
19864
19947
|
}
|
|
19865
19948
|
}
|
|
19866
|
-
this.reconnectTimer = setTimeout(() => {
|
|
19867
|
-
console.log("[Granular] Attempting reconnect...");
|
|
19868
|
-
this.connect().catch((error2) => {
|
|
19869
|
-
console.error("[Granular] Reconnect failed:", error2);
|
|
19870
|
-
const reconnectInfo = {
|
|
19871
|
-
error: error2 instanceof Error ? error2.message : String(error2),
|
|
19872
|
-
sessionId: this.sessionId,
|
|
19873
|
-
timestamp: Date.now()
|
|
19874
|
-
};
|
|
19875
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
19876
|
-
if (this.options.onReconnectError) {
|
|
19877
|
-
try {
|
|
19878
|
-
this.options.onReconnectError(reconnectInfo);
|
|
19879
|
-
} catch (callbackError) {
|
|
19880
|
-
console.error(
|
|
19881
|
-
"[Granular] onReconnectError callback failed:",
|
|
19882
|
-
callbackError
|
|
19883
|
-
);
|
|
19884
|
-
}
|
|
19885
|
-
}
|
|
19886
|
-
});
|
|
19887
|
-
}, reconnectDelayMs);
|
|
19888
19949
|
}
|
|
19889
19950
|
}
|
|
19890
19951
|
handleMessage(message) {
|
|
@@ -19995,6 +20056,7 @@ var WSClient = class {
|
|
|
19995
20056
|
const response = message;
|
|
19996
20057
|
const pending = this.messageQueue.find((q) => q.id === response.id);
|
|
19997
20058
|
if (pending) {
|
|
20059
|
+
clearTimeout(pending.timeout);
|
|
19998
20060
|
if (response.type === "rpc_error") {
|
|
19999
20061
|
pending.reject(
|
|
20000
20062
|
new Error(
|
|
@@ -20040,16 +20102,22 @@ var WSClient = class {
|
|
|
20040
20102
|
id
|
|
20041
20103
|
};
|
|
20042
20104
|
return new Promise((resolve2, reject) => {
|
|
20043
|
-
this.messageQueue.push({ resolve: resolve2, reject, id });
|
|
20044
|
-
this.ws.send(JSON.stringify(request));
|
|
20045
20105
|
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
20046
|
-
setTimeout(() => {
|
|
20106
|
+
const timeout = setTimeout(() => {
|
|
20047
20107
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
20048
20108
|
if (pending) {
|
|
20049
20109
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
20050
20110
|
reject(new Error(`RPC timeout: ${method}`));
|
|
20051
20111
|
}
|
|
20052
20112
|
}, timeoutMs);
|
|
20113
|
+
this.messageQueue.push({ resolve: resolve2, reject, id, timeout });
|
|
20114
|
+
try {
|
|
20115
|
+
this.ws.send(JSON.stringify(request));
|
|
20116
|
+
} catch (error2) {
|
|
20117
|
+
clearTimeout(timeout);
|
|
20118
|
+
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
20119
|
+
reject(error2 instanceof Error ? error2 : new Error(String(error2)));
|
|
20120
|
+
}
|
|
20053
20121
|
});
|
|
20054
20122
|
}
|
|
20055
20123
|
async handleIncomingRpc(request) {
|
|
@@ -20135,15 +20203,18 @@ var WSClient = class {
|
|
|
20135
20203
|
/**
|
|
20136
20204
|
* Disconnect the WebSocket and clear state
|
|
20137
20205
|
*/
|
|
20138
|
-
disconnect() {
|
|
20206
|
+
disconnect(options = {}) {
|
|
20139
20207
|
this.isExplicitlyDisconnected = true;
|
|
20208
|
+
this.cancelConnectAttempt?.();
|
|
20209
|
+
this.cancelConnectAttempt = null;
|
|
20210
|
+
this.connectionEpoch += 1;
|
|
20140
20211
|
if (this.reconnectTimer) {
|
|
20141
20212
|
clearTimeout(this.reconnectTimer);
|
|
20142
20213
|
this.reconnectTimer = null;
|
|
20143
20214
|
}
|
|
20144
20215
|
this.clearTokenRefreshTimer();
|
|
20145
20216
|
if (this.ws) {
|
|
20146
|
-
this.ws.close(1e3, "Client disconnect");
|
|
20217
|
+
this.ws.close(1e3, options.reason || "Client disconnect");
|
|
20147
20218
|
this.ws = null;
|
|
20148
20219
|
}
|
|
20149
20220
|
this.rejectPending(new Error("Client explicitly disconnected"));
|
|
@@ -20239,8 +20310,12 @@ function normalizePrompt(rawValue) {
|
|
|
20239
20310
|
const source = promptRecord || raw;
|
|
20240
20311
|
const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
|
|
20241
20312
|
if (!id) return null;
|
|
20313
|
+
const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
|
|
20314
|
+
const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
|
|
20242
20315
|
return {
|
|
20243
20316
|
id,
|
|
20317
|
+
...jobId ? { jobId } : {},
|
|
20318
|
+
...turnId ? { turnId } : {},
|
|
20244
20319
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
20245
20320
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
20246
20321
|
message: typeof source.message === "string" ? source.message : "",
|
|
@@ -20327,6 +20402,9 @@ var Session = class {
|
|
|
20327
20402
|
this.initialQuota = options.initialQuota || null;
|
|
20328
20403
|
this.setupEventHandlers();
|
|
20329
20404
|
this.setupToolInvokeHandler();
|
|
20405
|
+
this.currentDomainRevision = this.extractDomainRevisionFromDoc(
|
|
20406
|
+
this.client.doc
|
|
20407
|
+
);
|
|
20330
20408
|
}
|
|
20331
20409
|
extractDomainRevisionFromDoc(doc) {
|
|
20332
20410
|
const domain = doc?.domain;
|
|
@@ -21232,6 +21310,7 @@ function normalizeJobAgentMessageEnvelope(data) {
|
|
|
21232
21310
|
}
|
|
21233
21311
|
return {
|
|
21234
21312
|
jobId: d.jobId,
|
|
21313
|
+
...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
|
|
21235
21314
|
message: {
|
|
21236
21315
|
messageId: d.messageId,
|
|
21237
21316
|
kind: d.kind === "artifacts" ? "artifacts" : "text",
|
|
@@ -21890,9 +21969,14 @@ function normalizeShowRefs(value) {
|
|
|
21890
21969
|
variableNames: normalizeRefs(record.variableNames),
|
|
21891
21970
|
fileIds: normalizeRefs(record.fileIds),
|
|
21892
21971
|
sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
|
|
21893
|
-
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
|
|
21972
|
+
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
|
|
21973
|
+
tables: Array.isArray(record.tables) ? record.tables.filter(
|
|
21974
|
+
(table2) => Boolean(
|
|
21975
|
+
table2 && typeof table2 === "object" && !Array.isArray(table2) && Array.isArray(table2.columns) && Array.isArray(table2.rows)
|
|
21976
|
+
)
|
|
21977
|
+
) : void 0
|
|
21894
21978
|
};
|
|
21895
|
-
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
|
|
21979
|
+
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
|
|
21896
21980
|
}
|
|
21897
21981
|
function normalizeActionSuggestions(value) {
|
|
21898
21982
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -21914,6 +21998,76 @@ function normalizeActionSuggestions(value) {
|
|
|
21914
21998
|
}
|
|
21915
21999
|
return suggestions.length ? suggestions : void 0;
|
|
21916
22000
|
}
|
|
22001
|
+
var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
|
|
22002
|
+
var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
|
|
22003
|
+
var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
|
|
22004
|
+
var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
|
|
22005
|
+
function normalizeConversationMessageActions(value) {
|
|
22006
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
22007
|
+
const actions = [];
|
|
22008
|
+
for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
|
|
22009
|
+
const record = asRecord3(item);
|
|
22010
|
+
const kind = record?.kind;
|
|
22011
|
+
const label = trimString(record?.label ?? record?.title);
|
|
22012
|
+
const status = record?.status;
|
|
22013
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
22014
|
+
continue;
|
|
22015
|
+
}
|
|
22016
|
+
actions.push({
|
|
22017
|
+
kind,
|
|
22018
|
+
label,
|
|
22019
|
+
...status === "done" || status === "queued" || status === "failed" ? { status } : {}
|
|
22020
|
+
});
|
|
22021
|
+
}
|
|
22022
|
+
return actions.length ? actions : void 0;
|
|
22023
|
+
}
|
|
22024
|
+
function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
|
|
22025
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
|
|
22026
|
+
return void 0;
|
|
22027
|
+
}
|
|
22028
|
+
const parts = [];
|
|
22029
|
+
const canonicalActionsById = new Map(
|
|
22030
|
+
(canonicalActions || []).map((action) => [
|
|
22031
|
+
`${action.kind}:${action.label}`,
|
|
22032
|
+
action
|
|
22033
|
+
])
|
|
22034
|
+
);
|
|
22035
|
+
const seenActionIds = /* @__PURE__ */ new Set();
|
|
22036
|
+
let textLength = 0;
|
|
22037
|
+
for (const item of value) {
|
|
22038
|
+
const record = asRecord3(item);
|
|
22039
|
+
if (!record) return void 0;
|
|
22040
|
+
if (record.type === "text") {
|
|
22041
|
+
if (typeof record.text !== "string" || record.text.length === 0) {
|
|
22042
|
+
return void 0;
|
|
22043
|
+
}
|
|
22044
|
+
textLength += record.text.length;
|
|
22045
|
+
if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
|
|
22046
|
+
parts.push({ type: "text", text: record.text });
|
|
22047
|
+
continue;
|
|
22048
|
+
}
|
|
22049
|
+
if (record.type !== "action") return void 0;
|
|
22050
|
+
const action = asRecord3(record.action);
|
|
22051
|
+
const kind = action?.kind;
|
|
22052
|
+
const label = trimString(action?.label);
|
|
22053
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
22054
|
+
return void 0;
|
|
22055
|
+
}
|
|
22056
|
+
const actionId = `${kind}:${label}`;
|
|
22057
|
+
const canonicalAction = canonicalActionsById.get(actionId);
|
|
22058
|
+
if (!canonicalAction) return void 0;
|
|
22059
|
+
if (seenActionIds.has(actionId)) continue;
|
|
22060
|
+
seenActionIds.add(actionId);
|
|
22061
|
+
parts.push({
|
|
22062
|
+
type: "action",
|
|
22063
|
+
action: canonicalAction
|
|
22064
|
+
});
|
|
22065
|
+
}
|
|
22066
|
+
const orderedText = parts.filter(
|
|
22067
|
+
(part) => part.type === "text"
|
|
22068
|
+
).map((part) => part.text).join("");
|
|
22069
|
+
return orderedText === canonicalContent ? parts : void 0;
|
|
22070
|
+
}
|
|
21917
22071
|
function stringifyTranscriptValue(value, fallback2 = "") {
|
|
21918
22072
|
if (typeof value === "string") {
|
|
21919
22073
|
return value.trim() || fallback2;
|
|
@@ -22072,10 +22226,12 @@ function normalizeConversationMessage(raw, artifactsById) {
|
|
|
22072
22226
|
const content = trimString(
|
|
22073
22227
|
record.content ?? record.reply ?? record.message ?? record.text
|
|
22074
22228
|
);
|
|
22229
|
+
const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
|
|
22230
|
+
const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
|
|
22075
22231
|
const show = normalizeShowRefs(record.show);
|
|
22076
22232
|
const id = asString(record.id) || crypto.randomUUID();
|
|
22077
22233
|
const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
|
|
22078
|
-
if (!content && !show) return null;
|
|
22234
|
+
if (!content && !show && !actions?.length) return null;
|
|
22079
22235
|
const artifactHistory = buildArtifactHistory(show, artifactsById);
|
|
22080
22236
|
const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
|
|
22081
22237
|
${content}
|
|
@@ -22090,6 +22246,8 @@ ${content}` : artifactHistory : void 0;
|
|
|
22090
22246
|
jobId: asString(record.jobId),
|
|
22091
22247
|
promptId: asString(record.promptId),
|
|
22092
22248
|
show,
|
|
22249
|
+
actions,
|
|
22250
|
+
parts,
|
|
22093
22251
|
historyContent,
|
|
22094
22252
|
source: "conversation"
|
|
22095
22253
|
};
|
|
@@ -22738,7 +22896,12 @@ async function recordOpenAIUsageSpend(options) {
|
|
|
22738
22896
|
const metadata = {
|
|
22739
22897
|
...options.metadata || {},
|
|
22740
22898
|
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
22741
|
-
usageContext: context
|
|
22899
|
+
usageContext: context,
|
|
22900
|
+
pricingContextTier: options.usage.pricingContextTier,
|
|
22901
|
+
cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
|
|
22902
|
+
cacheWriteTokens: options.usage.cacheWriteTokens,
|
|
22903
|
+
cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
|
|
22904
|
+
longContextThresholdTokens: options.usage.longContextThresholdTokens
|
|
22742
22905
|
};
|
|
22743
22906
|
const response = await fetch(
|
|
22744
22907
|
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
@@ -22809,6 +22972,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
22809
22972
|
}
|
|
22810
22973
|
|
|
22811
22974
|
// src/client.ts
|
|
22975
|
+
var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
|
|
22976
|
+
var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
|
|
22977
|
+
var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
|
|
22978
|
+
function boundedSessionListInteger(value, name, fallback2, minimum, maximum) {
|
|
22979
|
+
if (value === void 0) return fallback2;
|
|
22980
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
22981
|
+
throw new RangeError(
|
|
22982
|
+
`Session list ${name} must be an integer between ${minimum} and ${maximum}.`
|
|
22983
|
+
);
|
|
22984
|
+
}
|
|
22985
|
+
return value;
|
|
22986
|
+
}
|
|
22812
22987
|
var STANDARD_MODULES_OPERATIONS = [
|
|
22813
22988
|
{
|
|
22814
22989
|
create: "entity",
|
|
@@ -23147,7 +23322,7 @@ var Environment = class _Environment {
|
|
|
23147
23322
|
}
|
|
23148
23323
|
get sessions() {
|
|
23149
23324
|
return {
|
|
23150
|
-
list: async (options) => this.listSessions(options
|
|
23325
|
+
list: async (options = {}) => this.listSessions(options),
|
|
23151
23326
|
create: async (options) => this.createSession(options),
|
|
23152
23327
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
23153
23328
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -23282,17 +23457,12 @@ var Environment = class _Environment {
|
|
|
23282
23457
|
*/
|
|
23283
23458
|
async disconnect() {
|
|
23284
23459
|
}
|
|
23285
|
-
async listSessions(
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23291
|
-
return [...active, ...closed].sort(
|
|
23292
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
23293
|
-
);
|
|
23294
|
-
}
|
|
23295
|
-
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
23460
|
+
async listSessions(optionsOrStatus = {}) {
|
|
23461
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
23462
|
+
return this.granular.listSessions({
|
|
23463
|
+
...options,
|
|
23464
|
+
environmentId: this.environmentId
|
|
23465
|
+
});
|
|
23296
23466
|
}
|
|
23297
23467
|
async getUserEnvironmentState(options = {}) {
|
|
23298
23468
|
return this.granular.getUserEnvironmentState({
|
|
@@ -23310,6 +23480,7 @@ var Environment = class _Environment {
|
|
|
23310
23480
|
return this.granular.createSession({
|
|
23311
23481
|
environmentId: this.environmentId,
|
|
23312
23482
|
clientId: options?.clientId,
|
|
23483
|
+
sessionScope: options?.sessionScope,
|
|
23313
23484
|
initialHeap: options?.initialHeap
|
|
23314
23485
|
});
|
|
23315
23486
|
}
|
|
@@ -24872,7 +25043,7 @@ var EnvironmentSession = class extends Session {
|
|
|
24872
25043
|
* Close only the socket transport without sending `client.goodbye`.
|
|
24873
25044
|
*/
|
|
24874
25045
|
disconnectTransport() {
|
|
24875
|
-
this.client.disconnect();
|
|
25046
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
24876
25047
|
}
|
|
24877
25048
|
/**
|
|
24878
25049
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -25267,16 +25438,71 @@ var Granular = class _Granular {
|
|
|
25267
25438
|
};
|
|
25268
25439
|
}
|
|
25269
25440
|
/**
|
|
25270
|
-
* List
|
|
25441
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
25442
|
+
*/
|
|
25443
|
+
async listSessions(options) {
|
|
25444
|
+
const environmentId = options.environmentId?.trim();
|
|
25445
|
+
const sandboxId = options.sandboxId?.trim();
|
|
25446
|
+
const subjectId = options.subjectId?.trim();
|
|
25447
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
25448
|
+
throw new Error(
|
|
25449
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
25450
|
+
);
|
|
25451
|
+
}
|
|
25452
|
+
const status = options.status || "active";
|
|
25453
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
25454
|
+
"active",
|
|
25455
|
+
"closed",
|
|
25456
|
+
"expired",
|
|
25457
|
+
"failed",
|
|
25458
|
+
"timeout",
|
|
25459
|
+
"all"
|
|
25460
|
+
]);
|
|
25461
|
+
if (!allowedStatuses.has(status)) {
|
|
25462
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
25463
|
+
}
|
|
25464
|
+
const limit = boundedSessionListInteger(
|
|
25465
|
+
options.limit,
|
|
25466
|
+
"limit",
|
|
25467
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
25468
|
+
1,
|
|
25469
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
25470
|
+
);
|
|
25471
|
+
const offset = boundedSessionListInteger(
|
|
25472
|
+
options.offset,
|
|
25473
|
+
"offset",
|
|
25474
|
+
0,
|
|
25475
|
+
0,
|
|
25476
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
25477
|
+
);
|
|
25478
|
+
const query = new URLSearchParams({
|
|
25479
|
+
limit: String(limit),
|
|
25480
|
+
offset: String(offset)
|
|
25481
|
+
});
|
|
25482
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
25483
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
25484
|
+
if (subjectId) query.set("userId", subjectId);
|
|
25485
|
+
if (options.sessionScope?.trim()) {
|
|
25486
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
25487
|
+
}
|
|
25488
|
+
if (status !== "all") query.set("status", status);
|
|
25489
|
+
const res = await this.request(
|
|
25490
|
+
`/control/sessions?${query.toString()}`
|
|
25491
|
+
);
|
|
25492
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
25493
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
25494
|
+
}
|
|
25495
|
+
/**
|
|
25496
|
+
* List active (open) sessions for an environment.
|
|
25271
25497
|
*/
|
|
25272
25498
|
async listOpenSessions(filters) {
|
|
25273
|
-
return this.
|
|
25499
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
25274
25500
|
}
|
|
25275
25501
|
/**
|
|
25276
25502
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
25277
25503
|
*/
|
|
25278
25504
|
async listClosedSessions(filters) {
|
|
25279
|
-
return this.
|
|
25505
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
25280
25506
|
}
|
|
25281
25507
|
async getUserEnvironmentState(options) {
|
|
25282
25508
|
const query = new URLSearchParams({
|
|
@@ -25311,14 +25537,6 @@ var Granular = class _Granular {
|
|
|
25311
25537
|
});
|
|
25312
25538
|
return result.readAtBySessionId || {};
|
|
25313
25539
|
}
|
|
25314
|
-
async listSessionsForEnvironment(environmentId, status) {
|
|
25315
|
-
const query = new URLSearchParams({ environmentId, status });
|
|
25316
|
-
const res = await this.request(
|
|
25317
|
-
`/control/sessions?${query.toString()}`
|
|
25318
|
-
);
|
|
25319
|
-
const items = Array.isArray(res.items) ? res.items : [];
|
|
25320
|
-
return items.map((row) => this.normalizeConversationSession(row));
|
|
25321
|
-
}
|
|
25322
25540
|
normalizeConversationSession(row) {
|
|
25323
25541
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
25324
25542
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -25377,6 +25595,7 @@ var Granular = class _Granular {
|
|
|
25377
25595
|
*/
|
|
25378
25596
|
async createSession(options) {
|
|
25379
25597
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
25598
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
25380
25599
|
await this.activateEnvironment(options.environmentId);
|
|
25381
25600
|
const envData = await this.environments.get(options.environmentId);
|
|
25382
25601
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -25385,6 +25604,8 @@ var Granular = class _Granular {
|
|
|
25385
25604
|
body: JSON.stringify({
|
|
25386
25605
|
environmentId: options.environmentId,
|
|
25387
25606
|
clientId,
|
|
25607
|
+
sessionScope,
|
|
25608
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
25388
25609
|
initialHeap: options.initialHeap
|
|
25389
25610
|
})
|
|
25390
25611
|
});
|
|
@@ -26706,15 +26927,22 @@ async function resolveEnvironmentData(granular, options) {
|
|
|
26706
26927
|
const ontologyId = await resolveOntologyId(granular, options.ontology);
|
|
26707
26928
|
const environmentName = options.environment ?? "dev";
|
|
26708
26929
|
const environments = await granular.environments.list(ontologyId);
|
|
26709
|
-
const
|
|
26710
|
-
(environment) => matchesEnvironmentName(environment, environmentName)
|
|
26930
|
+
const matchingEnvironments = environments.filter(
|
|
26931
|
+
(environment) => matchesEnvironmentName(environment, environmentName) && (!options.subjectId || environment.subjectId === options.subjectId)
|
|
26711
26932
|
);
|
|
26933
|
+
if (matchingEnvironments.length > 1) {
|
|
26934
|
+
throw new Error(
|
|
26935
|
+
`Environment slot \`${environmentName}\` matches ${matchingEnvironments.length} subject environments. Pass --environment-id or --subject-id so session history cannot resolve to an arbitrary user.`
|
|
26936
|
+
);
|
|
26937
|
+
}
|
|
26938
|
+
const existing = matchingEnvironments[0];
|
|
26712
26939
|
if (existing) {
|
|
26713
26940
|
return existing;
|
|
26714
26941
|
}
|
|
26715
26942
|
if (!options.createIfMissing) {
|
|
26943
|
+
const subjectSuffix = options.subjectId ? ` for subject \`${options.subjectId}\`` : "";
|
|
26716
26944
|
throw new Error(
|
|
26717
|
-
`No environment named \`${environmentName}
|
|
26945
|
+
`No environment named \`${environmentName}\`${subjectSuffix} found for ontology \`${ontologyId}\`. Run \`granular connect test\` or \`granular session create\` first, or pass \`--environment-id\`.`
|
|
26718
26946
|
);
|
|
26719
26947
|
}
|
|
26720
26948
|
const connection = await granular.connect({
|
|
@@ -26725,17 +26953,15 @@ async function resolveEnvironmentData(granular, options) {
|
|
|
26725
26953
|
});
|
|
26726
26954
|
return await granular.environments.get(connection.environmentId);
|
|
26727
26955
|
}
|
|
26728
|
-
async function listSessionsForEnvironment(granular, environmentId,
|
|
26729
|
-
|
|
26730
|
-
|
|
26731
|
-
|
|
26732
|
-
|
|
26733
|
-
|
|
26734
|
-
|
|
26735
|
-
|
|
26736
|
-
|
|
26737
|
-
}
|
|
26738
|
-
return status === "closed" ? granular.listClosedSessions({ environmentId }) : granular.listOpenSessions({ environmentId });
|
|
26956
|
+
async function listSessionsForEnvironment(granular, environmentId, options) {
|
|
26957
|
+
return granular.listSessions({
|
|
26958
|
+
environmentId,
|
|
26959
|
+
status: options.status,
|
|
26960
|
+
sessionScope: options.sessionScope,
|
|
26961
|
+
subjectId: options.subjectId,
|
|
26962
|
+
limit: options.limit,
|
|
26963
|
+
offset: options.offset
|
|
26964
|
+
});
|
|
26739
26965
|
}
|
|
26740
26966
|
async function connectRuntime(options) {
|
|
26741
26967
|
if (options.sessionId) {
|
|
@@ -26848,6 +27074,19 @@ async function connectTestCommand(options) {
|
|
|
26848
27074
|
}
|
|
26849
27075
|
|
|
26850
27076
|
// src/cli/commands/session.ts
|
|
27077
|
+
function parseSessionListInteger(value, name) {
|
|
27078
|
+
const fallback2 = name === "limit" ? 25 : 0;
|
|
27079
|
+
const minimum = name === "limit" ? 1 : 0;
|
|
27080
|
+
const maximum = name === "limit" ? 500 : 1e5;
|
|
27081
|
+
if (value === void 0 || value === "") return fallback2;
|
|
27082
|
+
const parsed = typeof value === "number" ? value : Number.parseInt(value.trim(), 10);
|
|
27083
|
+
if (!Number.isInteger(parsed) || String(parsed) !== String(value).trim() || parsed < minimum || parsed > maximum) {
|
|
27084
|
+
throw new Error(
|
|
27085
|
+
`--${name} must be an integer between ${minimum} and ${maximum}.`
|
|
27086
|
+
);
|
|
27087
|
+
}
|
|
27088
|
+
return parsed;
|
|
27089
|
+
}
|
|
26851
27090
|
function printValue(value, emitJson) {
|
|
26852
27091
|
const json = JSON.stringify(value, null, 2);
|
|
26853
27092
|
if (emitJson) {
|
|
@@ -26932,7 +27171,8 @@ async function sessionCreateCommand(options) {
|
|
|
26932
27171
|
createIfMissing: true
|
|
26933
27172
|
});
|
|
26934
27173
|
const environment = await granular.createSession({
|
|
26935
|
-
environmentId: envData.environmentId
|
|
27174
|
+
environmentId: envData.environmentId,
|
|
27175
|
+
sessionScope: options.sessionScope
|
|
26936
27176
|
});
|
|
26937
27177
|
try {
|
|
26938
27178
|
const payload = {
|
|
@@ -26941,6 +27181,7 @@ async function sessionCreateCommand(options) {
|
|
|
26941
27181
|
environmentId: environment.environmentId,
|
|
26942
27182
|
environment: environment.tag || envData.tag?.name || envData.buildPolicy.tagName || requestedEnvironment,
|
|
26943
27183
|
sessionId: environment.sessionId,
|
|
27184
|
+
sessionScope: options.sessionScope?.trim() || null,
|
|
26944
27185
|
subjectId: environment.subjectId,
|
|
26945
27186
|
versionId: environment.versionId
|
|
26946
27187
|
};
|
|
@@ -26955,6 +27196,7 @@ async function sessionCreateCommand(options) {
|
|
|
26955
27196
|
Environment: payload.environment,
|
|
26956
27197
|
"Environment ID": payload.environmentId,
|
|
26957
27198
|
"Session ID": payload.sessionId,
|
|
27199
|
+
"Session scope": payload.sessionScope || "unscoped",
|
|
26958
27200
|
"Subject ID": payload.subjectId,
|
|
26959
27201
|
"Version ID": payload.versionId
|
|
26960
27202
|
});
|
|
@@ -26970,6 +27212,21 @@ async function sessionCreateCommand(options) {
|
|
|
26970
27212
|
async function sessionListCommand(options) {
|
|
26971
27213
|
const emitJson = options.json === true;
|
|
26972
27214
|
const status = options.status ?? "active";
|
|
27215
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
27216
|
+
"active",
|
|
27217
|
+
"closed",
|
|
27218
|
+
"expired",
|
|
27219
|
+
"failed",
|
|
27220
|
+
"timeout",
|
|
27221
|
+
"all"
|
|
27222
|
+
]);
|
|
27223
|
+
if (!allowedStatuses.has(status)) {
|
|
27224
|
+
throw new Error(
|
|
27225
|
+
"--status must be one of active, closed, expired, failed, timeout, or all."
|
|
27226
|
+
);
|
|
27227
|
+
}
|
|
27228
|
+
const limit = parseSessionListInteger(options.limit, "limit");
|
|
27229
|
+
const offset = parseSessionListInteger(options.offset, "offset");
|
|
26973
27230
|
const requestedEnvironment = options.environment ?? "dev";
|
|
26974
27231
|
if (!emitJson) {
|
|
26975
27232
|
printHeader();
|
|
@@ -26979,18 +27236,33 @@ async function sessionListCommand(options) {
|
|
|
26979
27236
|
ontology: options.ontology,
|
|
26980
27237
|
environment: requestedEnvironment,
|
|
26981
27238
|
environmentId: options.environmentId,
|
|
27239
|
+
subjectId: options.subjectId,
|
|
26982
27240
|
createIfMissing: false
|
|
26983
27241
|
});
|
|
26984
27242
|
const items = await listSessionsForEnvironment(
|
|
26985
27243
|
granular,
|
|
26986
27244
|
environmentData.environmentId,
|
|
26987
|
-
|
|
27245
|
+
{
|
|
27246
|
+
status,
|
|
27247
|
+
sessionScope: options.sessionScope?.trim() || void 0,
|
|
27248
|
+
subjectId: options.subjectId?.trim() || void 0,
|
|
27249
|
+
limit,
|
|
27250
|
+
offset
|
|
27251
|
+
}
|
|
26988
27252
|
);
|
|
26989
27253
|
const payload = {
|
|
26990
27254
|
ontologyId: environmentData.sandboxId,
|
|
26991
27255
|
environmentId: environmentData.environmentId,
|
|
26992
27256
|
environment: environmentData.tag?.name || environmentData.buildPolicy.tagName || requestedEnvironment,
|
|
26993
27257
|
status,
|
|
27258
|
+
sessionScope: options.sessionScope?.trim() || null,
|
|
27259
|
+
page: {
|
|
27260
|
+
limit,
|
|
27261
|
+
offset,
|
|
27262
|
+
returned: items.length,
|
|
27263
|
+
mayHaveMore: items.length === limit,
|
|
27264
|
+
nextOffset: items.length === limit ? offset + items.length : null
|
|
27265
|
+
},
|
|
26994
27266
|
items
|
|
26995
27267
|
};
|
|
26996
27268
|
if (emitJson) {
|
|
@@ -27013,6 +27285,9 @@ async function sessionListCommand(options) {
|
|
|
27013
27285
|
item.lastSeenAt
|
|
27014
27286
|
])
|
|
27015
27287
|
);
|
|
27288
|
+
info(
|
|
27289
|
+
`Showing ${items.length} session${items.length === 1 ? "" : "s"} from offset ${offset}.` + (items.length === limit ? ` Use --offset ${offset + items.length} for the next page.` : "")
|
|
27290
|
+
);
|
|
27016
27291
|
console.log();
|
|
27017
27292
|
}
|
|
27018
27293
|
|
|
@@ -27919,6 +28194,9 @@ session.command("create").description(
|
|
|
27919
28194
|
"--permissions <list>",
|
|
27920
28195
|
"Comma-separated permission profile to ensure when a named environment needs to be created",
|
|
27921
28196
|
"allow-all"
|
|
28197
|
+
).option(
|
|
28198
|
+
"--session-scope <scope>",
|
|
28199
|
+
"Application-owned history scope to persist on the new session"
|
|
27922
28200
|
).option("--json", "Print machine-readable JSON").action(
|
|
27923
28201
|
async (options) => {
|
|
27924
28202
|
try {
|
|
@@ -27929,7 +28207,7 @@ session.command("create").description(
|
|
|
27929
28207
|
}
|
|
27930
28208
|
}
|
|
27931
28209
|
);
|
|
27932
|
-
session.command("list").description("List indexed sessions for an environment").option(
|
|
28210
|
+
session.command("list").description("List one bounded page of indexed sessions for an environment").option(
|
|
27933
28211
|
"--ontology <ontologyId>",
|
|
27934
28212
|
"Override the ontology id from .granularrc"
|
|
27935
28213
|
).option(
|
|
@@ -27939,11 +28217,17 @@ session.command("list").description("List indexed sessions for an environment").
|
|
|
27939
28217
|
).option(
|
|
27940
28218
|
"--environment-id <environmentId>",
|
|
27941
28219
|
"List sessions for this exact environment id"
|
|
28220
|
+
).option(
|
|
28221
|
+
"--subject-id <subjectId>",
|
|
28222
|
+
"Resolve and constrain history to this internal Granular subject id"
|
|
28223
|
+
).option(
|
|
28224
|
+
"--session-scope <scope>",
|
|
28225
|
+
"Return only sessions owned by this application history scope"
|
|
27942
28226
|
).option(
|
|
27943
28227
|
"--status <status>",
|
|
27944
|
-
"Session status
|
|
28228
|
+
"Session status: active|closed|expired|failed|timeout|all",
|
|
27945
28229
|
"active"
|
|
27946
|
-
).option("--json", "Print machine-readable JSON").action(
|
|
28230
|
+
).option("--limit <count>", "Rows to return (1-500)", "25").option("--offset <count>", "Rows to skip (0-100000)", "0").option("--json", "Print machine-readable JSON").action(
|
|
27947
28231
|
async (options) => {
|
|
27948
28232
|
try {
|
|
27949
28233
|
await sessionListCommand(options);
|