@granular-software/sdk 0.4.49 → 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 +1551 -277
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +1551 -277
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.d.mts +19 -2
- package/dist/agent-harness.d.ts +19 -2
- package/dist/agent-harness.js +81 -26
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +80 -27
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +1492 -266
- package/dist/{client-B-MPVvDr.d.mts → client-NSH-tpNU.d.ts} +88 -11
- package/dist/{client-zihxkDDs.d.ts → client-djorlOpn.d.mts} +88 -11
- package/dist/index.d.mts +104 -5
- package/dist/index.d.ts +104 -5
- package/dist/index.js +1633 -268
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1630 -269
- package/dist/index.mjs.map +1 -1
- package/dist/{spend-CStuOBXb.d.mts → spend-BA-jZwZ0.d.mts} +505 -12
- package/dist/{spend-CStuOBXb.d.ts → spend-BA-jZwZ0.d.ts} +505 -12
- 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 +2 -1
package/dist/agent-evals.js
CHANGED
|
@@ -51,11 +51,11 @@ var __export = (target, all) => {
|
|
|
51
51
|
for (var name in all)
|
|
52
52
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
53
53
|
};
|
|
54
|
-
var __copyProps = (to,
|
|
55
|
-
if (
|
|
56
|
-
for (let key of __getOwnPropNames(
|
|
54
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
55
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
56
|
+
for (let key of __getOwnPropNames(from2))
|
|
57
57
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
58
|
-
__defProp(to, key, { get: () =>
|
|
58
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
59
59
|
}
|
|
60
60
|
return to;
|
|
61
61
|
};
|
|
@@ -4045,6 +4045,9 @@ function rpcTimeoutMsForMethod(method) {
|
|
|
4045
4045
|
return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
|
|
4046
4046
|
case "client.heartbeat":
|
|
4047
4047
|
case "effects.publishCatalog":
|
|
4048
|
+
case "effects.resetCatalog":
|
|
4049
|
+
case "effects.addCatalog":
|
|
4050
|
+
case "effects.removeCatalog":
|
|
4048
4051
|
case "effects.refresh":
|
|
4049
4052
|
return EFFECT_CONTROL_RPC_TIMEOUT_MS;
|
|
4050
4053
|
case "harness.run":
|
|
@@ -4069,16 +4072,37 @@ var WSClient = class {
|
|
|
4069
4072
|
tokenRefreshTimer = null;
|
|
4070
4073
|
isExplicitlyDisconnected = false;
|
|
4071
4074
|
reconnectAttempts = 0;
|
|
4075
|
+
connectPromise = null;
|
|
4076
|
+
connectionEpoch = 0;
|
|
4077
|
+
cancelConnectAttempt = null;
|
|
4072
4078
|
options;
|
|
4073
4079
|
constructor(options) {
|
|
4074
4080
|
this.options = options;
|
|
4075
4081
|
this.url = options.url;
|
|
4076
4082
|
this.sessionId = options.sessionId;
|
|
4077
4083
|
this.token = options.token;
|
|
4084
|
+
if (options.initialDocumentSnapshot) {
|
|
4085
|
+
this.seedDocumentSnapshot(options.initialDocumentSnapshot);
|
|
4086
|
+
}
|
|
4078
4087
|
}
|
|
4079
4088
|
get currentSessionId() {
|
|
4080
4089
|
return this.sessionId;
|
|
4081
4090
|
}
|
|
4091
|
+
seedDocumentSnapshot(document) {
|
|
4092
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
try {
|
|
4096
|
+
this.doc = document instanceof Uint8Array ? Automerge__namespace.load(document) : Automerge__namespace.from(document);
|
|
4097
|
+
this.syncState = Automerge__namespace.initSyncState();
|
|
4098
|
+
this.emit("sync", this.doc);
|
|
4099
|
+
} catch (error) {
|
|
4100
|
+
console.warn("[Granular] Failed to seed cached session document", error);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
saveDocumentSnapshot() {
|
|
4104
|
+
return Automerge__namespace.save(this.doc);
|
|
4105
|
+
}
|
|
4082
4106
|
clearTokenRefreshTimer() {
|
|
4083
4107
|
if (this.tokenRefreshTimer) {
|
|
4084
4108
|
clearTimeout(this.tokenRefreshTimer);
|
|
@@ -4188,8 +4212,23 @@ var WSClient = class {
|
|
|
4188
4212
|
* Connect to the WebSocket server
|
|
4189
4213
|
* @returns {Promise<void>} Resolves when connection is open
|
|
4190
4214
|
*/
|
|
4191
|
-
async connect() {
|
|
4215
|
+
async connect(options = {}) {
|
|
4216
|
+
if (this.ws?.readyState === READY_STATE_OPEN) return;
|
|
4217
|
+
if (this.connectPromise) return this.connectPromise;
|
|
4218
|
+
const connectPromise = this.connectAttempt(options.signal);
|
|
4219
|
+
this.connectPromise = connectPromise;
|
|
4220
|
+
try {
|
|
4221
|
+
await connectPromise;
|
|
4222
|
+
} finally {
|
|
4223
|
+
if (this.connectPromise === connectPromise) {
|
|
4224
|
+
this.connectPromise = null;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
async connectAttempt(signal) {
|
|
4229
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4192
4230
|
const token = await this.resolveTokenForConnect();
|
|
4231
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4193
4232
|
this.isExplicitlyDisconnected = false;
|
|
4194
4233
|
this.scheduleTokenRefresh();
|
|
4195
4234
|
if (this.reconnectTimer) {
|
|
@@ -4201,7 +4240,7 @@ var WSClient = class {
|
|
|
4201
4240
|
try {
|
|
4202
4241
|
const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
4203
4242
|
WebSocketClass = wsModule.default || wsModule;
|
|
4204
|
-
} catch
|
|
4243
|
+
} catch {
|
|
4205
4244
|
}
|
|
4206
4245
|
}
|
|
4207
4246
|
if (!WebSocketClass) {
|
|
@@ -4209,83 +4248,97 @@ var WSClient = class {
|
|
|
4209
4248
|
'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
|
|
4210
4249
|
);
|
|
4211
4250
|
}
|
|
4251
|
+
const epoch = ++this.connectionEpoch;
|
|
4252
|
+
const wsUrl = new URL(this.url);
|
|
4253
|
+
wsUrl.searchParams.set("sessionId", this.sessionId);
|
|
4254
|
+
wsUrl.searchParams.set("token", token);
|
|
4255
|
+
const socket = new WebSocketClass(wsUrl.toString());
|
|
4256
|
+
this.ws = socket;
|
|
4212
4257
|
return new Promise((resolve, reject) => {
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
if (
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
this.reconnectTimer = null;
|
|
4225
|
-
}
|
|
4226
|
-
this.reconnectAttempts = 0;
|
|
4227
|
-
this.emit("open", {});
|
|
4228
|
-
resolve();
|
|
4229
|
-
});
|
|
4230
|
-
socket.on("message", (data) => {
|
|
4231
|
-
try {
|
|
4232
|
-
const message = JSON.parse(data.toString());
|
|
4233
|
-
this.handleMessage(message);
|
|
4234
|
-
} catch (error) {
|
|
4235
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4236
|
-
}
|
|
4237
|
-
});
|
|
4238
|
-
socket.on("error", (error) => {
|
|
4239
|
-
this.emit("error", error);
|
|
4240
|
-
if (socket.readyState !== READY_STATE_OPEN) {
|
|
4241
|
-
reject(error);
|
|
4242
|
-
}
|
|
4243
|
-
});
|
|
4244
|
-
socket.on("close", (code, reason) => {
|
|
4245
|
-
this.handleDisconnect({
|
|
4246
|
-
code,
|
|
4247
|
-
reason: this.normalizeReason(reason),
|
|
4248
|
-
// ws does not provide wasClean on Node-style close callback
|
|
4249
|
-
wasClean: code === 1e3
|
|
4250
|
-
});
|
|
4251
|
-
});
|
|
4258
|
+
let settled = false;
|
|
4259
|
+
const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
|
|
4260
|
+
const finish = (error) => {
|
|
4261
|
+
if (settled) return;
|
|
4262
|
+
settled = true;
|
|
4263
|
+
if (this.cancelConnectAttempt === handleAbort) {
|
|
4264
|
+
this.cancelConnectAttempt = null;
|
|
4265
|
+
}
|
|
4266
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
4267
|
+
if (error) {
|
|
4268
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4252
4269
|
} else {
|
|
4253
|
-
|
|
4254
|
-
if (this.reconnectTimer) {
|
|
4255
|
-
clearTimeout(this.reconnectTimer);
|
|
4256
|
-
this.reconnectTimer = null;
|
|
4257
|
-
}
|
|
4258
|
-
this.reconnectAttempts = 0;
|
|
4259
|
-
this.emit("open", {});
|
|
4260
|
-
resolve();
|
|
4261
|
-
};
|
|
4262
|
-
this.ws.onmessage = (event) => {
|
|
4263
|
-
try {
|
|
4264
|
-
const data = event.data;
|
|
4265
|
-
const message = JSON.parse(data.toString());
|
|
4266
|
-
this.handleMessage(message);
|
|
4267
|
-
} catch (error) {
|
|
4268
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4269
|
-
}
|
|
4270
|
-
};
|
|
4271
|
-
this.ws.onerror = (event) => {
|
|
4272
|
-
const error = new Error("WebSocket error");
|
|
4273
|
-
error.event = event;
|
|
4274
|
-
this.emit("error", error);
|
|
4275
|
-
if (this.ws?.readyState !== READY_STATE_OPEN) {
|
|
4276
|
-
reject(error);
|
|
4277
|
-
}
|
|
4278
|
-
};
|
|
4279
|
-
this.ws.onclose = (event) => {
|
|
4280
|
-
this.handleDisconnect({
|
|
4281
|
-
code: event.code,
|
|
4282
|
-
reason: event.reason,
|
|
4283
|
-
wasClean: event.wasClean
|
|
4284
|
-
});
|
|
4285
|
-
};
|
|
4270
|
+
resolve();
|
|
4286
4271
|
}
|
|
4287
|
-
}
|
|
4288
|
-
|
|
4272
|
+
};
|
|
4273
|
+
const closeStaleSocket = () => {
|
|
4274
|
+
try {
|
|
4275
|
+
socket.close(1e3, "Stale connection attempt");
|
|
4276
|
+
} catch {
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
const handleAbort = () => {
|
|
4280
|
+
if (isCurrent()) {
|
|
4281
|
+
this.connectionEpoch += 1;
|
|
4282
|
+
this.ws = null;
|
|
4283
|
+
}
|
|
4284
|
+
closeStaleSocket();
|
|
4285
|
+
finish(new Error("WebSocket connect aborted"));
|
|
4286
|
+
};
|
|
4287
|
+
this.cancelConnectAttempt = handleAbort;
|
|
4288
|
+
const handleOpen = () => {
|
|
4289
|
+
if (!isCurrent()) {
|
|
4290
|
+
closeStaleSocket();
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
this.reconnectAttempts = 0;
|
|
4294
|
+
this.emit("open", {});
|
|
4295
|
+
finish();
|
|
4296
|
+
};
|
|
4297
|
+
const handleMessage = (data) => {
|
|
4298
|
+
if (!isCurrent()) return;
|
|
4299
|
+
try {
|
|
4300
|
+
const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
|
|
4301
|
+
this.handleMessage(JSON.parse(text));
|
|
4302
|
+
} catch (error) {
|
|
4303
|
+
console.error("[Granular] Failed to parse message:", error);
|
|
4304
|
+
}
|
|
4305
|
+
};
|
|
4306
|
+
const handleError = (error) => {
|
|
4307
|
+
if (!isCurrent()) return;
|
|
4308
|
+
const typedError = error instanceof Error ? error : new Error("WebSocket error");
|
|
4309
|
+
this.emit("error", typedError);
|
|
4310
|
+
if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
|
|
4311
|
+
};
|
|
4312
|
+
const handleClose = (close) => {
|
|
4313
|
+
if (!isCurrent()) return;
|
|
4314
|
+
if (!settled) {
|
|
4315
|
+
finish(
|
|
4316
|
+
new Error(
|
|
4317
|
+
`WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
|
|
4318
|
+
)
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
this.handleDisconnect({
|
|
4322
|
+
code: close.code,
|
|
4323
|
+
reason: this.normalizeReason(close.reason),
|
|
4324
|
+
wasClean: close.wasClean
|
|
4325
|
+
});
|
|
4326
|
+
};
|
|
4327
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
4328
|
+
const nodeSocket = socket;
|
|
4329
|
+
if (typeof nodeSocket.on === "function") {
|
|
4330
|
+
nodeSocket.on("open", handleOpen);
|
|
4331
|
+
nodeSocket.on("message", handleMessage);
|
|
4332
|
+
nodeSocket.on("error", handleError);
|
|
4333
|
+
nodeSocket.on(
|
|
4334
|
+
"close",
|
|
4335
|
+
(code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
|
|
4336
|
+
);
|
|
4337
|
+
} else {
|
|
4338
|
+
socket.onopen = handleOpen;
|
|
4339
|
+
socket.onmessage = (event) => handleMessage(event.data);
|
|
4340
|
+
socket.onerror = handleError;
|
|
4341
|
+
socket.onclose = (event) => handleClose(event);
|
|
4289
4342
|
}
|
|
4290
4343
|
});
|
|
4291
4344
|
}
|
|
@@ -4303,9 +4356,58 @@ var WSClient = class {
|
|
|
4303
4356
|
return void 0;
|
|
4304
4357
|
}
|
|
4305
4358
|
rejectPending(error) {
|
|
4306
|
-
this.messageQueue.forEach((pending) =>
|
|
4359
|
+
this.messageQueue.forEach((pending) => {
|
|
4360
|
+
clearTimeout(pending.timeout);
|
|
4361
|
+
pending.reject(error);
|
|
4362
|
+
});
|
|
4307
4363
|
this.messageQueue = [];
|
|
4308
4364
|
}
|
|
4365
|
+
emitReconnectErrorMessage(error) {
|
|
4366
|
+
const reconnectInfo = {
|
|
4367
|
+
error,
|
|
4368
|
+
sessionId: this.sessionId,
|
|
4369
|
+
timestamp: Date.now()
|
|
4370
|
+
};
|
|
4371
|
+
this.emit("reconnect_error", reconnectInfo);
|
|
4372
|
+
if (this.options.onReconnectError) {
|
|
4373
|
+
try {
|
|
4374
|
+
this.options.onReconnectError(reconnectInfo);
|
|
4375
|
+
} catch (callbackError) {
|
|
4376
|
+
console.error(
|
|
4377
|
+
"[Granular] onReconnectError callback failed:",
|
|
4378
|
+
callbackError
|
|
4379
|
+
);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
scheduleReconnectAttempt() {
|
|
4384
|
+
if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
|
|
4385
|
+
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4386
|
+
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;
|
|
4387
|
+
if (this.reconnectAttempts >= maxReconnectAttempts) {
|
|
4388
|
+
this.emitReconnectErrorMessage(
|
|
4389
|
+
`WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
|
|
4390
|
+
);
|
|
4391
|
+
return null;
|
|
4392
|
+
}
|
|
4393
|
+
this.reconnectAttempts += 1;
|
|
4394
|
+
const reconnectDelayMs = Math.min(
|
|
4395
|
+
3e4,
|
|
4396
|
+
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4397
|
+
);
|
|
4398
|
+
this.reconnectTimer = setTimeout(() => {
|
|
4399
|
+
this.reconnectTimer = null;
|
|
4400
|
+
console.log("[Granular] Attempting reconnect...");
|
|
4401
|
+
this.connect().catch((error) => {
|
|
4402
|
+
console.error("[Granular] Reconnect failed:", error);
|
|
4403
|
+
this.emitReconnectErrorMessage(
|
|
4404
|
+
error instanceof Error ? error.message : String(error)
|
|
4405
|
+
);
|
|
4406
|
+
this.scheduleReconnectAttempt();
|
|
4407
|
+
});
|
|
4408
|
+
}, reconnectDelayMs);
|
|
4409
|
+
return reconnectDelayMs;
|
|
4410
|
+
}
|
|
4309
4411
|
buildDisconnectError(info) {
|
|
4310
4412
|
const details = [
|
|
4311
4413
|
info.code !== void 0 ? `code=${info.code}` : void 0,
|
|
@@ -4315,8 +4417,6 @@ var WSClient = class {
|
|
|
4315
4417
|
return new Error(`WebSocket disconnected${suffix}`);
|
|
4316
4418
|
}
|
|
4317
4419
|
handleDisconnect(close = {}) {
|
|
4318
|
-
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4319
|
-
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;
|
|
4320
4420
|
const unexpected = !this.isExplicitlyDisconnected;
|
|
4321
4421
|
const info = {
|
|
4322
4422
|
code: close.code,
|
|
@@ -4336,32 +4436,9 @@ var WSClient = class {
|
|
|
4336
4436
|
const disconnectError = this.buildDisconnectError(info);
|
|
4337
4437
|
this.rejectPending(disconnectError);
|
|
4338
4438
|
this.emit("disconnect", info);
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
sessionId: this.sessionId,
|
|
4343
|
-
timestamp: Date.now()
|
|
4344
|
-
};
|
|
4345
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4346
|
-
if (this.options.onReconnectError) {
|
|
4347
|
-
try {
|
|
4348
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4349
|
-
} catch (callbackError) {
|
|
4350
|
-
console.error(
|
|
4351
|
-
"[Granular] onReconnectError callback failed:",
|
|
4352
|
-
callbackError
|
|
4353
|
-
);
|
|
4354
|
-
}
|
|
4355
|
-
}
|
|
4356
|
-
return;
|
|
4357
|
-
}
|
|
4358
|
-
this.reconnectAttempts += 1;
|
|
4359
|
-
const reconnectDelayMs = Math.min(
|
|
4360
|
-
3e4,
|
|
4361
|
-
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4362
|
-
);
|
|
4363
|
-
info.reconnectScheduled = true;
|
|
4364
|
-
info.reconnectDelayMs = reconnectDelayMs;
|
|
4439
|
+
const reconnectDelayMs = this.scheduleReconnectAttempt();
|
|
4440
|
+
info.reconnectScheduled = reconnectDelayMs !== null;
|
|
4441
|
+
if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
|
|
4365
4442
|
if (this.options.onUnexpectedClose) {
|
|
4366
4443
|
try {
|
|
4367
4444
|
this.options.onUnexpectedClose(info);
|
|
@@ -4372,28 +4449,6 @@ var WSClient = class {
|
|
|
4372
4449
|
);
|
|
4373
4450
|
}
|
|
4374
4451
|
}
|
|
4375
|
-
this.reconnectTimer = setTimeout(() => {
|
|
4376
|
-
console.log("[Granular] Attempting reconnect...");
|
|
4377
|
-
this.connect().catch((error) => {
|
|
4378
|
-
console.error("[Granular] Reconnect failed:", error);
|
|
4379
|
-
const reconnectInfo = {
|
|
4380
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4381
|
-
sessionId: this.sessionId,
|
|
4382
|
-
timestamp: Date.now()
|
|
4383
|
-
};
|
|
4384
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4385
|
-
if (this.options.onReconnectError) {
|
|
4386
|
-
try {
|
|
4387
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4388
|
-
} catch (callbackError) {
|
|
4389
|
-
console.error(
|
|
4390
|
-
"[Granular] onReconnectError callback failed:",
|
|
4391
|
-
callbackError
|
|
4392
|
-
);
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4395
|
-
});
|
|
4396
|
-
}, reconnectDelayMs);
|
|
4397
4452
|
}
|
|
4398
4453
|
}
|
|
4399
4454
|
handleMessage(message) {
|
|
@@ -4504,6 +4559,7 @@ var WSClient = class {
|
|
|
4504
4559
|
const response = message;
|
|
4505
4560
|
const pending = this.messageQueue.find((q) => q.id === response.id);
|
|
4506
4561
|
if (pending) {
|
|
4562
|
+
clearTimeout(pending.timeout);
|
|
4507
4563
|
if (response.type === "rpc_error") {
|
|
4508
4564
|
pending.reject(
|
|
4509
4565
|
new Error(
|
|
@@ -4549,16 +4605,22 @@ var WSClient = class {
|
|
|
4549
4605
|
id
|
|
4550
4606
|
};
|
|
4551
4607
|
return new Promise((resolve, reject) => {
|
|
4552
|
-
this.messageQueue.push({ resolve, reject, id });
|
|
4553
|
-
this.ws.send(JSON.stringify(request));
|
|
4554
4608
|
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
4555
|
-
setTimeout(() => {
|
|
4609
|
+
const timeout = setTimeout(() => {
|
|
4556
4610
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
4557
4611
|
if (pending) {
|
|
4558
4612
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4559
4613
|
reject(new Error(`RPC timeout: ${method}`));
|
|
4560
4614
|
}
|
|
4561
4615
|
}, timeoutMs);
|
|
4616
|
+
this.messageQueue.push({ resolve, reject, id, timeout });
|
|
4617
|
+
try {
|
|
4618
|
+
this.ws.send(JSON.stringify(request));
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
clearTimeout(timeout);
|
|
4621
|
+
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4622
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4623
|
+
}
|
|
4562
4624
|
});
|
|
4563
4625
|
}
|
|
4564
4626
|
async handleIncomingRpc(request) {
|
|
@@ -4644,15 +4706,18 @@ var WSClient = class {
|
|
|
4644
4706
|
/**
|
|
4645
4707
|
* Disconnect the WebSocket and clear state
|
|
4646
4708
|
*/
|
|
4647
|
-
disconnect() {
|
|
4709
|
+
disconnect(options = {}) {
|
|
4648
4710
|
this.isExplicitlyDisconnected = true;
|
|
4711
|
+
this.cancelConnectAttempt?.();
|
|
4712
|
+
this.cancelConnectAttempt = null;
|
|
4713
|
+
this.connectionEpoch += 1;
|
|
4649
4714
|
if (this.reconnectTimer) {
|
|
4650
4715
|
clearTimeout(this.reconnectTimer);
|
|
4651
4716
|
this.reconnectTimer = null;
|
|
4652
4717
|
}
|
|
4653
4718
|
this.clearTokenRefreshTimer();
|
|
4654
4719
|
if (this.ws) {
|
|
4655
|
-
this.ws.close(1e3, "Client disconnect");
|
|
4720
|
+
this.ws.close(1e3, options.reason || "Client disconnect");
|
|
4656
4721
|
this.ws = null;
|
|
4657
4722
|
}
|
|
4658
4723
|
this.rejectPending(new Error("Client explicitly disconnected"));
|
|
@@ -4748,8 +4813,12 @@ function normalizePrompt(rawValue) {
|
|
|
4748
4813
|
const source = promptRecord || raw;
|
|
4749
4814
|
const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
|
|
4750
4815
|
if (!id) return null;
|
|
4816
|
+
const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
|
|
4817
|
+
const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
|
|
4751
4818
|
return {
|
|
4752
4819
|
id,
|
|
4820
|
+
...jobId ? { jobId } : {},
|
|
4821
|
+
...turnId ? { turnId } : {},
|
|
4753
4822
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
4754
4823
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
4755
4824
|
message: typeof source.message === "string" ? source.message : "",
|
|
@@ -4790,6 +4859,9 @@ function resolvePromptAnswer(prompt, answer) {
|
|
|
4790
4859
|
|
|
4791
4860
|
// src/session.ts
|
|
4792
4861
|
var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
|
|
4862
|
+
function toPascalCase(value) {
|
|
4863
|
+
return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
4864
|
+
}
|
|
4793
4865
|
function withPromptTranscriptTimeout(promise) {
|
|
4794
4866
|
let timeout = null;
|
|
4795
4867
|
return Promise.race([
|
|
@@ -4833,6 +4905,9 @@ var Session = class {
|
|
|
4833
4905
|
this.initialQuota = options.initialQuota || null;
|
|
4834
4906
|
this.setupEventHandlers();
|
|
4835
4907
|
this.setupToolInvokeHandler();
|
|
4908
|
+
this.currentDomainRevision = this.extractDomainRevisionFromDoc(
|
|
4909
|
+
this.client.doc
|
|
4910
|
+
);
|
|
4836
4911
|
}
|
|
4837
4912
|
extractDomainRevisionFromDoc(doc) {
|
|
4838
4913
|
const domain = doc?.domain;
|
|
@@ -5352,9 +5427,7 @@ var Session = class {
|
|
|
5352
5427
|
if (classes && Object.keys(classes).length > 0) {
|
|
5353
5428
|
let docs2 = "# Domain Documentation\n\n";
|
|
5354
5429
|
docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
|
|
5355
|
-
const classNames = Object.keys(classes).map(
|
|
5356
|
-
(c) => c.charAt(0).toUpperCase() + c.slice(1)
|
|
5357
|
-
);
|
|
5430
|
+
const classNames = Object.keys(classes).map(toPascalCase);
|
|
5358
5431
|
const globalNames = (globalTools || []).map((t) => t.name);
|
|
5359
5432
|
const importLines = [
|
|
5360
5433
|
...classNames.map(
|
|
@@ -5368,7 +5441,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
|
|
|
5368
5441
|
|
|
5369
5442
|
`;
|
|
5370
5443
|
for (const [className, cls] of Object.entries(classes)) {
|
|
5371
|
-
const TsName =
|
|
5444
|
+
const TsName = toPascalCase(className);
|
|
5372
5445
|
docs2 += `## ${TsName}
|
|
5373
5446
|
|
|
5374
5447
|
`;
|
|
@@ -5740,6 +5813,7 @@ function normalizeJobAgentMessageEnvelope(data) {
|
|
|
5740
5813
|
}
|
|
5741
5814
|
return {
|
|
5742
5815
|
jobId: d.jobId,
|
|
5816
|
+
...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
|
|
5743
5817
|
message: {
|
|
5744
5818
|
messageId: d.messageId,
|
|
5745
5819
|
kind: d.kind === "artifacts" ? "artifacts" : "text",
|
|
@@ -6358,6 +6432,28 @@ function asString(value) {
|
|
|
6358
6432
|
function trimString(value) {
|
|
6359
6433
|
return typeof value === "string" ? value.trim() : "";
|
|
6360
6434
|
}
|
|
6435
|
+
function compactJson(value, maxLength = 320) {
|
|
6436
|
+
if (value === void 0 || value === null) return void 0;
|
|
6437
|
+
try {
|
|
6438
|
+
const json = JSON.stringify(value);
|
|
6439
|
+
if (!json || json === "undefined") return void 0;
|
|
6440
|
+
return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
|
|
6441
|
+
} catch {
|
|
6442
|
+
return String(value);
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
function artifactRecordsById(liveDoc) {
|
|
6446
|
+
const artifacts = asRecord3(liveDoc?.artifacts);
|
|
6447
|
+
const byId = asRecord3(artifacts?.byId) || {};
|
|
6448
|
+
return Object.fromEntries(
|
|
6449
|
+
Object.entries(byId).map(([artifactId, value]) => {
|
|
6450
|
+
const record = asRecord3(value);
|
|
6451
|
+
return record ? [artifactId, record] : null;
|
|
6452
|
+
}).filter(
|
|
6453
|
+
(entry) => Boolean(entry)
|
|
6454
|
+
)
|
|
6455
|
+
);
|
|
6456
|
+
}
|
|
6361
6457
|
function normalizeShowRefs(value) {
|
|
6362
6458
|
const record = asRecord3(value);
|
|
6363
6459
|
if (!record) return void 0;
|
|
@@ -6374,9 +6470,106 @@ function normalizeShowRefs(value) {
|
|
|
6374
6470
|
entryPaths: normalizeRefs(record.entryPaths),
|
|
6375
6471
|
listNames: normalizeRefs(record.listNames),
|
|
6376
6472
|
variableNames: normalizeRefs(record.variableNames),
|
|
6377
|
-
fileIds: normalizeRefs(record.fileIds)
|
|
6473
|
+
fileIds: normalizeRefs(record.fileIds),
|
|
6474
|
+
sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
|
|
6475
|
+
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
|
|
6476
|
+
tables: Array.isArray(record.tables) ? record.tables.filter(
|
|
6477
|
+
(table) => Boolean(
|
|
6478
|
+
table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
|
|
6479
|
+
)
|
|
6480
|
+
) : void 0
|
|
6378
6481
|
};
|
|
6379
|
-
return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
|
|
6482
|
+
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
|
|
6483
|
+
}
|
|
6484
|
+
function normalizeActionSuggestions(value) {
|
|
6485
|
+
if (!Array.isArray(value)) return void 0;
|
|
6486
|
+
const suggestions = [];
|
|
6487
|
+
for (const item of value) {
|
|
6488
|
+
const record = asRecord3(item);
|
|
6489
|
+
if (!record) continue;
|
|
6490
|
+
const label = trimString(record.label);
|
|
6491
|
+
if (!label) continue;
|
|
6492
|
+
const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
|
|
6493
|
+
suggestions.push({
|
|
6494
|
+
suggestionId,
|
|
6495
|
+
label,
|
|
6496
|
+
...typeof record.description === "string" ? { description: record.description } : {},
|
|
6497
|
+
...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
|
|
6498
|
+
...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
|
|
6499
|
+
...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
|
|
6500
|
+
});
|
|
6501
|
+
}
|
|
6502
|
+
return suggestions.length ? suggestions : void 0;
|
|
6503
|
+
}
|
|
6504
|
+
var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
|
|
6505
|
+
var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
|
|
6506
|
+
var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
|
|
6507
|
+
var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
|
|
6508
|
+
function normalizeConversationMessageActions(value) {
|
|
6509
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
6510
|
+
const actions = [];
|
|
6511
|
+
for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
|
|
6512
|
+
const record = asRecord3(item);
|
|
6513
|
+
const kind = record?.kind;
|
|
6514
|
+
const label = trimString(record?.label ?? record?.title);
|
|
6515
|
+
const status = record?.status;
|
|
6516
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6517
|
+
continue;
|
|
6518
|
+
}
|
|
6519
|
+
actions.push({
|
|
6520
|
+
kind,
|
|
6521
|
+
label,
|
|
6522
|
+
...status === "done" || status === "queued" || status === "failed" ? { status } : {}
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6525
|
+
return actions.length ? actions : void 0;
|
|
6526
|
+
}
|
|
6527
|
+
function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
|
|
6528
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
|
|
6529
|
+
return void 0;
|
|
6530
|
+
}
|
|
6531
|
+
const parts = [];
|
|
6532
|
+
const canonicalActionsById = new Map(
|
|
6533
|
+
(canonicalActions || []).map((action) => [
|
|
6534
|
+
`${action.kind}:${action.label}`,
|
|
6535
|
+
action
|
|
6536
|
+
])
|
|
6537
|
+
);
|
|
6538
|
+
const seenActionIds = /* @__PURE__ */ new Set();
|
|
6539
|
+
let textLength = 0;
|
|
6540
|
+
for (const item of value) {
|
|
6541
|
+
const record = asRecord3(item);
|
|
6542
|
+
if (!record) return void 0;
|
|
6543
|
+
if (record.type === "text") {
|
|
6544
|
+
if (typeof record.text !== "string" || record.text.length === 0) {
|
|
6545
|
+
return void 0;
|
|
6546
|
+
}
|
|
6547
|
+
textLength += record.text.length;
|
|
6548
|
+
if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
|
|
6549
|
+
parts.push({ type: "text", text: record.text });
|
|
6550
|
+
continue;
|
|
6551
|
+
}
|
|
6552
|
+
if (record.type !== "action") return void 0;
|
|
6553
|
+
const action = asRecord3(record.action);
|
|
6554
|
+
const kind = action?.kind;
|
|
6555
|
+
const label = trimString(action?.label);
|
|
6556
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6557
|
+
return void 0;
|
|
6558
|
+
}
|
|
6559
|
+
const actionId = `${kind}:${label}`;
|
|
6560
|
+
const canonicalAction = canonicalActionsById.get(actionId);
|
|
6561
|
+
if (!canonicalAction) return void 0;
|
|
6562
|
+
if (seenActionIds.has(actionId)) continue;
|
|
6563
|
+
seenActionIds.add(actionId);
|
|
6564
|
+
parts.push({
|
|
6565
|
+
type: "action",
|
|
6566
|
+
action: canonicalAction
|
|
6567
|
+
});
|
|
6568
|
+
}
|
|
6569
|
+
const orderedText = parts.filter(
|
|
6570
|
+
(part) => part.type === "text"
|
|
6571
|
+
).map((part) => part.text).join("");
|
|
6572
|
+
return orderedText === canonicalContent ? parts : void 0;
|
|
6380
6573
|
}
|
|
6381
6574
|
function stringifyTranscriptValue(value, fallback = "") {
|
|
6382
6575
|
if (typeof value === "string") {
|
|
@@ -6396,12 +6589,139 @@ function stringifyTranscriptValue(value, fallback = "") {
|
|
|
6396
6589
|
return String(value);
|
|
6397
6590
|
}
|
|
6398
6591
|
}
|
|
6399
|
-
function
|
|
6592
|
+
function latestInputEditSummary(metadata) {
|
|
6593
|
+
const lastInputEdit = asRecord3(metadata.lastInputEdit);
|
|
6594
|
+
if (!lastInputEdit) return null;
|
|
6595
|
+
const source = asString(lastInputEdit.source) || "unknown";
|
|
6596
|
+
const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
|
|
6597
|
+
const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
|
|
6598
|
+
(key) => typeof key === "string" && key.trim().length > 0
|
|
6599
|
+
).slice(0, 6) : [];
|
|
6600
|
+
const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
|
|
6601
|
+
(key) => typeof key === "string" && key.trim().length > 0
|
|
6602
|
+
).slice(0, 6) : [];
|
|
6603
|
+
const changed = [
|
|
6604
|
+
inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
|
|
6605
|
+
relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
|
|
6606
|
+
].filter(Boolean);
|
|
6607
|
+
return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
|
|
6608
|
+
}
|
|
6609
|
+
function artifactIssueSummary(record) {
|
|
6610
|
+
const validation = asRecord3(record.validation);
|
|
6611
|
+
if (!validation) return null;
|
|
6612
|
+
const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
|
|
6613
|
+
if (issues.length > 0) {
|
|
6614
|
+
return `issues=${issues.map((issue) => {
|
|
6615
|
+
const code = asString(issue.code) || asString(issue.kind) || "issue";
|
|
6616
|
+
const path2 = asString(issue.path);
|
|
6617
|
+
const message = trimString(issue.message);
|
|
6618
|
+
return `${code}${path2 ? ` at ${path2}` : ""}${message ? ` (${message})` : ""}`;
|
|
6619
|
+
}).join("; ")}`;
|
|
6620
|
+
}
|
|
6621
|
+
const error = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
|
|
6622
|
+
return error ? `validation=${error}` : null;
|
|
6623
|
+
}
|
|
6624
|
+
function artifactExecutionSummary(metadata) {
|
|
6625
|
+
const execution = asRecord3(metadata.execution);
|
|
6626
|
+
if (!execution) return null;
|
|
6627
|
+
const result = asRecord3(execution.result);
|
|
6628
|
+
const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
|
|
6629
|
+
const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
|
|
6630
|
+
const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
|
|
6631
|
+
const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
|
|
6632
|
+
const error = trimString(execution.error);
|
|
6633
|
+
const pieces = [
|
|
6634
|
+
awaiting ? `awaiting=${awaiting}` : null,
|
|
6635
|
+
pendingTransition ? `pendingTransition=${pendingTransition}` : null,
|
|
6636
|
+
approvalTarget ? `approvalTarget=${approvalTarget}` : null,
|
|
6637
|
+
error ? `executionError=${error}` : null
|
|
6638
|
+
].filter(Boolean);
|
|
6639
|
+
return pieces.length ? pieces.join("; ") : null;
|
|
6640
|
+
}
|
|
6641
|
+
function artifactStatePathSummary(metadata) {
|
|
6642
|
+
const statePlan = asRecord3(metadata.statePlan);
|
|
6643
|
+
if (!statePlan) return null;
|
|
6644
|
+
const machineName = asString(statePlan.machineName);
|
|
6645
|
+
const targetState = asString(statePlan.targetState);
|
|
6646
|
+
const objectPath = asString(statePlan.objectPath);
|
|
6647
|
+
const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
|
|
6648
|
+
const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
|
|
6649
|
+
const pieces = [
|
|
6650
|
+
machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
|
|
6651
|
+
objectPath ? `objectPath=${objectPath}` : null,
|
|
6652
|
+
approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
|
|
6653
|
+
approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
|
|
6654
|
+
].filter(Boolean);
|
|
6655
|
+
return pieces.length ? pieces.join("; ") : null;
|
|
6656
|
+
}
|
|
6657
|
+
function artifactSummaryLine(artifactId, record) {
|
|
6658
|
+
if (!record) return `- ${artifactId}: unavailable in session artifact store`;
|
|
6659
|
+
const label = trimString(record.label) || artifactId;
|
|
6660
|
+
const kind = asString(record.kind) || "artifact";
|
|
6661
|
+
const status = asString(record.status) || "unknown";
|
|
6662
|
+
const createdByJobId = asString(record.createdByJobId);
|
|
6663
|
+
const target = asRecord3(record.target);
|
|
6664
|
+
const metadata = asRecord3(record.metadata) || {};
|
|
6665
|
+
const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
|
|
6666
|
+
(id) => typeof id === "string" && id.trim().length > 0
|
|
6667
|
+
).slice(0, 8) : [];
|
|
6668
|
+
const relationships = compactJson(record.relationships, 220);
|
|
6669
|
+
const pieces = [
|
|
6670
|
+
`kind=${kind}`,
|
|
6671
|
+
`status=${status}`,
|
|
6672
|
+
createdByJobId ? `createdByJob=${createdByJobId}` : null,
|
|
6673
|
+
target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
|
|
6674
|
+
artifactStatePathSummary(metadata),
|
|
6675
|
+
artifactExecutionSummary(metadata),
|
|
6676
|
+
artifactIssueSummary(record),
|
|
6677
|
+
latestInputEditSummary(metadata),
|
|
6678
|
+
subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
|
|
6679
|
+
relationships ? `relationships=${relationships}` : null
|
|
6680
|
+
].filter(Boolean);
|
|
6681
|
+
return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
|
|
6682
|
+
}
|
|
6683
|
+
function buildArtifactHistory(show, artifactsById) {
|
|
6400
6684
|
if (!show) return void 0;
|
|
6401
|
-
|
|
6685
|
+
const artifactIds = show.sessionArtifactIds || [];
|
|
6686
|
+
const actionSuggestions = show.actionSuggestions || [];
|
|
6687
|
+
if (artifactIds.length === 0 && actionSuggestions.length === 0) {
|
|
6688
|
+
return `[Agent message]
|
|
6402
6689
|
${stringifyTranscriptValue({ show }, "")}`;
|
|
6690
|
+
}
|
|
6691
|
+
const lines = artifactIds.slice(0, 8).map(
|
|
6692
|
+
(artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
|
|
6693
|
+
);
|
|
6694
|
+
if (artifactIds.length > 8) {
|
|
6695
|
+
lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
|
|
6696
|
+
}
|
|
6697
|
+
if (actionSuggestions.length > 0) {
|
|
6698
|
+
if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
|
|
6699
|
+
for (const suggestion of actionSuggestions.slice(0, 8)) {
|
|
6700
|
+
lines.push(
|
|
6701
|
+
`- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
|
|
6702
|
+
);
|
|
6703
|
+
}
|
|
6704
|
+
if (actionSuggestions.length > 8) {
|
|
6705
|
+
lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6708
|
+
const otherRefs = {
|
|
6709
|
+
entryPaths: show.entryPaths,
|
|
6710
|
+
listNames: show.listNames,
|
|
6711
|
+
variableNames: show.variableNames,
|
|
6712
|
+
fileIds: show.fileIds
|
|
6713
|
+
};
|
|
6714
|
+
const hasOtherRefs = Object.values(otherRefs).some(
|
|
6715
|
+
(value) => Array.isArray(value) && value.length > 0
|
|
6716
|
+
);
|
|
6717
|
+
const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
|
|
6718
|
+
return [
|
|
6719
|
+
title,
|
|
6720
|
+
...lines,
|
|
6721
|
+
hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
|
|
6722
|
+
].filter(Boolean).join("\n");
|
|
6403
6723
|
}
|
|
6404
|
-
function normalizeConversationMessage(raw) {
|
|
6724
|
+
function normalizeConversationMessage(raw, artifactsById) {
|
|
6405
6725
|
const record = asRecord3(raw);
|
|
6406
6726
|
if (!record) return null;
|
|
6407
6727
|
const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
|
|
@@ -6409,10 +6729,18 @@ function normalizeConversationMessage(raw) {
|
|
|
6409
6729
|
const content = trimString(
|
|
6410
6730
|
record.content ?? record.reply ?? record.message ?? record.text
|
|
6411
6731
|
);
|
|
6732
|
+
const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
|
|
6733
|
+
const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
|
|
6412
6734
|
const show = normalizeShowRefs(record.show);
|
|
6413
6735
|
const id = asString(record.id) || crypto.randomUUID();
|
|
6414
6736
|
const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
|
|
6415
|
-
if (!content && !show) return null;
|
|
6737
|
+
if (!content && !show && !actions?.length) return null;
|
|
6738
|
+
const artifactHistory = buildArtifactHistory(show, artifactsById);
|
|
6739
|
+
const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
|
|
6740
|
+
${content}
|
|
6741
|
+
|
|
6742
|
+
${artifactHistory}` : content ? `[Assistant reply]
|
|
6743
|
+
${content}` : artifactHistory : void 0;
|
|
6416
6744
|
return {
|
|
6417
6745
|
id,
|
|
6418
6746
|
role,
|
|
@@ -6421,8 +6749,9 @@ function normalizeConversationMessage(raw) {
|
|
|
6421
6749
|
jobId: asString(record.jobId),
|
|
6422
6750
|
promptId: asString(record.promptId),
|
|
6423
6751
|
show,
|
|
6424
|
-
|
|
6425
|
-
|
|
6752
|
+
actions,
|
|
6753
|
+
parts,
|
|
6754
|
+
historyContent,
|
|
6426
6755
|
source: "conversation"
|
|
6427
6756
|
};
|
|
6428
6757
|
}
|
|
@@ -6465,7 +6794,7 @@ ${assistantContent}`,
|
|
|
6465
6794
|
return entries;
|
|
6466
6795
|
});
|
|
6467
6796
|
}
|
|
6468
|
-
function normalizeAgentMessageEntries(jobId, rawMessages) {
|
|
6797
|
+
function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
|
|
6469
6798
|
return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
|
|
6470
6799
|
(left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
|
|
6471
6800
|
).flatMap((message) => {
|
|
@@ -6496,14 +6825,14 @@ ${reply}`,
|
|
|
6496
6825
|
timestamp,
|
|
6497
6826
|
jobId,
|
|
6498
6827
|
show,
|
|
6499
|
-
historyContent: buildArtifactHistory(show),
|
|
6828
|
+
historyContent: buildArtifactHistory(show, artifactsById),
|
|
6500
6829
|
source: "job_agent_message"
|
|
6501
6830
|
});
|
|
6502
6831
|
}
|
|
6503
6832
|
return entries;
|
|
6504
6833
|
});
|
|
6505
6834
|
}
|
|
6506
|
-
function buildJobFallbackEntries(jobId, job, sessionHeap) {
|
|
6835
|
+
function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
|
|
6507
6836
|
const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
|
|
6508
6837
|
const resultPreview = stringifyTranscriptValue(
|
|
6509
6838
|
job.result,
|
|
@@ -6541,7 +6870,7 @@ ${responseText}`,
|
|
|
6541
6870
|
timestamp,
|
|
6542
6871
|
jobId,
|
|
6543
6872
|
show,
|
|
6544
|
-
historyContent: buildArtifactHistory(show),
|
|
6873
|
+
historyContent: buildArtifactHistory(show, artifactsById),
|
|
6545
6874
|
source: "job_result"
|
|
6546
6875
|
});
|
|
6547
6876
|
}
|
|
@@ -6595,10 +6924,11 @@ function buildJobCodeEntry(jobId, job) {
|
|
|
6595
6924
|
function buildSessionTranscript(input) {
|
|
6596
6925
|
const liveDoc = input.liveDoc || null;
|
|
6597
6926
|
const sessionHeap = input.sessionHeap || EMPTY_HEAP;
|
|
6927
|
+
const artifactsById = artifactRecordsById(liveDoc);
|
|
6598
6928
|
const transcript = [];
|
|
6599
6929
|
const conversationMessages = asArray(
|
|
6600
6930
|
asRecord3(liveDoc?.conversation)?.messages
|
|
6601
|
-
).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
|
|
6931
|
+
).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
|
|
6602
6932
|
const conversationPromptIds = new Set(
|
|
6603
6933
|
conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
|
|
6604
6934
|
);
|
|
@@ -6625,7 +6955,8 @@ function buildSessionTranscript(input) {
|
|
|
6625
6955
|
if (!assistantConversationJobIds.has(jobId)) {
|
|
6626
6956
|
const agentEntries = normalizeAgentMessageEntries(
|
|
6627
6957
|
jobId,
|
|
6628
|
-
job.agentMessages
|
|
6958
|
+
job.agentMessages,
|
|
6959
|
+
artifactsById
|
|
6629
6960
|
);
|
|
6630
6961
|
if (agentEntries.length > 0) {
|
|
6631
6962
|
transcript.push(...agentEntries);
|
|
@@ -6634,7 +6965,8 @@ function buildSessionTranscript(input) {
|
|
|
6634
6965
|
...buildJobFallbackEntries(
|
|
6635
6966
|
jobId,
|
|
6636
6967
|
job,
|
|
6637
|
-
sessionHeap
|
|
6968
|
+
sessionHeap,
|
|
6969
|
+
artifactsById
|
|
6638
6970
|
)
|
|
6639
6971
|
);
|
|
6640
6972
|
}
|
|
@@ -10800,16 +11132,107 @@ var StateMachineStateSchema = external_exports.union([
|
|
|
10800
11132
|
external_exports.string(),
|
|
10801
11133
|
external_exports.object({
|
|
10802
11134
|
name: external_exports.string().min(1),
|
|
11135
|
+
label: external_exports.string().optional(),
|
|
11136
|
+
description: external_exports.string().optional(),
|
|
10803
11137
|
isFinal: external_exports.boolean().optional()
|
|
10804
11138
|
}).strict()
|
|
10805
11139
|
]);
|
|
11140
|
+
var StateTransitionInputBindingSchema = external_exports.lazy(
|
|
11141
|
+
() => external_exports.union([
|
|
11142
|
+
external_exports.null(),
|
|
11143
|
+
external_exports.string(),
|
|
11144
|
+
external_exports.number(),
|
|
11145
|
+
external_exports.boolean(),
|
|
11146
|
+
external_exports.array(StateTransitionInputBindingSchema),
|
|
11147
|
+
external_exports.object({
|
|
11148
|
+
const: external_exports.unknown()
|
|
11149
|
+
}).strict(),
|
|
11150
|
+
external_exports.object({
|
|
11151
|
+
from: external_exports.literal("object"),
|
|
11152
|
+
path: external_exports.string().min(1),
|
|
11153
|
+
editable: external_exports.boolean().optional()
|
|
11154
|
+
}).strict(),
|
|
11155
|
+
external_exports.object({
|
|
11156
|
+
from: external_exports.literal("field"),
|
|
11157
|
+
name: external_exports.string().min(1),
|
|
11158
|
+
editable: external_exports.boolean().optional()
|
|
11159
|
+
}).strict(),
|
|
11160
|
+
external_exports.object({
|
|
11161
|
+
from: external_exports.literal("relationship"),
|
|
11162
|
+
name: external_exports.string().min(1),
|
|
11163
|
+
path: external_exports.string().min(1).optional(),
|
|
11164
|
+
many: external_exports.boolean().optional(),
|
|
11165
|
+
editable: external_exports.boolean().optional()
|
|
11166
|
+
}).strict(),
|
|
11167
|
+
external_exports.object({
|
|
11168
|
+
from: external_exports.literal("session"),
|
|
11169
|
+
path: external_exports.string().min(1),
|
|
11170
|
+
editable: external_exports.boolean().optional()
|
|
11171
|
+
}).strict(),
|
|
11172
|
+
external_exports.object({
|
|
11173
|
+
from: external_exports.literal("actor"),
|
|
11174
|
+
path: external_exports.string().min(1),
|
|
11175
|
+
editable: external_exports.boolean().optional()
|
|
11176
|
+
}).strict(),
|
|
11177
|
+
external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
|
|
11178
|
+
])
|
|
11179
|
+
);
|
|
11180
|
+
var StateTransitionActionSchema = external_exports.object({
|
|
11181
|
+
effect: external_exports.string().min(1),
|
|
11182
|
+
input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
|
|
11183
|
+
}).strict();
|
|
11184
|
+
var StateTransitionAssigneeSchema = external_exports.object({
|
|
11185
|
+
kind: external_exports.string().min(1),
|
|
11186
|
+
from: StateTransitionInputBindingSchema.optional(),
|
|
11187
|
+
role: external_exports.string().optional(),
|
|
11188
|
+
label: external_exports.string().optional()
|
|
11189
|
+
}).strict();
|
|
11190
|
+
var StateTransitionRelatedStateRequirementSchema = external_exports.object({
|
|
11191
|
+
relationship: external_exports.string().min(1),
|
|
11192
|
+
machine: external_exports.string().min(1),
|
|
11193
|
+
state: external_exports.string().min(1),
|
|
11194
|
+
className: external_exports.string().min(1).optional(),
|
|
11195
|
+
label: external_exports.string().optional(),
|
|
11196
|
+
mode: external_exports.enum(["every", "some", "any"]).optional()
|
|
11197
|
+
}).strict();
|
|
11198
|
+
var StateTransitionRequirementsSchema = external_exports.object({
|
|
11199
|
+
fields: external_exports.array(external_exports.string().min(1)).optional(),
|
|
11200
|
+
relationships: external_exports.array(external_exports.string().min(1)).optional(),
|
|
11201
|
+
relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
|
|
11202
|
+
}).strict();
|
|
11203
|
+
var StateTransitionPermissionSchema = external_exports.union([
|
|
11204
|
+
external_exports.string().min(1),
|
|
11205
|
+
external_exports.object({
|
|
11206
|
+
profile: external_exports.string().min(1).optional(),
|
|
11207
|
+
profileId: external_exports.string().min(1).optional(),
|
|
11208
|
+
label: external_exports.string().optional(),
|
|
11209
|
+
reason: external_exports.string().optional()
|
|
11210
|
+
}).strict()
|
|
11211
|
+
]);
|
|
11212
|
+
var StateTransitionExpectedOutcomeSchema = external_exports.union([
|
|
11213
|
+
external_exports.string().min(1),
|
|
11214
|
+
external_exports.object({
|
|
11215
|
+
machine: external_exports.string().min(1).optional(),
|
|
11216
|
+
state: external_exports.string().min(1),
|
|
11217
|
+
summary: external_exports.string().optional()
|
|
11218
|
+
}).strict()
|
|
11219
|
+
]);
|
|
10806
11220
|
var StateMachineTransitionSchema = external_exports.object({
|
|
10807
11221
|
name: external_exports.string().min(1),
|
|
10808
11222
|
from: external_exports.string().min(1),
|
|
10809
|
-
to: external_exports.string().min(1)
|
|
11223
|
+
to: external_exports.string().min(1),
|
|
11224
|
+
label: external_exports.string().optional(),
|
|
11225
|
+
description: external_exports.string().optional(),
|
|
11226
|
+
action: StateTransitionActionSchema.optional(),
|
|
11227
|
+
assignee: StateTransitionAssigneeSchema.optional(),
|
|
11228
|
+
requirements: StateTransitionRequirementsSchema.optional(),
|
|
11229
|
+
permission: StateTransitionPermissionSchema.optional(),
|
|
11230
|
+
risk: external_exports.enum(["low", "medium", "high"]).optional(),
|
|
11231
|
+
expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
|
|
10810
11232
|
}).strict();
|
|
10811
11233
|
external_exports.object({
|
|
10812
11234
|
name: external_exports.string().min(1),
|
|
11235
|
+
stateField: external_exports.string().min(1).optional(),
|
|
10813
11236
|
entryState: external_exports.string().min(1),
|
|
10814
11237
|
states: external_exports.array(StateMachineStateSchema).min(1),
|
|
10815
11238
|
transitions: external_exports.array(StateMachineTransitionSchema),
|
|
@@ -10876,6 +11299,16 @@ var PoliciesSchema = external_exports.object({
|
|
|
10876
11299
|
confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
|
|
10877
11300
|
denyWhen: external_exports.array(PolicyRuleSchema).optional()
|
|
10878
11301
|
}).strict();
|
|
11302
|
+
var CreatesSchema = external_exports.union([
|
|
11303
|
+
external_exports.string().min(1),
|
|
11304
|
+
external_exports.object({
|
|
11305
|
+
className: external_exports.string().min(1),
|
|
11306
|
+
idPath: external_exports.string().min(1).optional(),
|
|
11307
|
+
pathPath: external_exports.string().min(1).optional(),
|
|
11308
|
+
statePath: external_exports.string().min(1).optional(),
|
|
11309
|
+
classStateHandle: external_exports.boolean().optional()
|
|
11310
|
+
}).strict()
|
|
11311
|
+
]);
|
|
10879
11312
|
external_exports.object({
|
|
10880
11313
|
postCondition: external_exports.union([
|
|
10881
11314
|
external_exports.string(),
|
|
@@ -10906,6 +11339,7 @@ external_exports.object({
|
|
|
10906
11339
|
mode: external_exports.string().optional()
|
|
10907
11340
|
}).strict()
|
|
10908
11341
|
]).optional(),
|
|
11342
|
+
creates: CreatesSchema.optional(),
|
|
10909
11343
|
access: external_exports.enum(["read", "write", "ui"]).optional(),
|
|
10910
11344
|
effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
|
|
10911
11345
|
sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
|
|
@@ -11133,9 +11567,10 @@ function mergeMethodSummaryPatch(target, patch) {
|
|
|
11133
11567
|
if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
|
|
11134
11568
|
if (patch.effectBehaviors !== void 0)
|
|
11135
11569
|
target.effectBehaviors = patch.effectBehaviors;
|
|
11570
|
+
if (patch.creates !== void 0) target.creates = patch.creates;
|
|
11136
11571
|
if (patch.static !== void 0) target.static = patch.static;
|
|
11137
11572
|
}
|
|
11138
|
-
function
|
|
11573
|
+
function toPascalCase2(value) {
|
|
11139
11574
|
return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
11140
11575
|
}
|
|
11141
11576
|
function normalizeNotesInput(input) {
|
|
@@ -11193,29 +11628,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
|
|
|
11193
11628
|
}
|
|
11194
11629
|
return Object.keys(result).length > 0 ? result : null;
|
|
11195
11630
|
}
|
|
11196
|
-
function
|
|
11197
|
-
if (!
|
|
11198
|
-
|
|
11631
|
+
function normalizeCreationSummary(metamodels) {
|
|
11632
|
+
if (!isObject(metamodels)) return null;
|
|
11633
|
+
let raw = metamodels.creates;
|
|
11634
|
+
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
11635
|
+
const trimmed = raw.trim();
|
|
11636
|
+
if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
|
|
11637
|
+
try {
|
|
11638
|
+
raw = JSON.parse(trimmed);
|
|
11639
|
+
} catch {
|
|
11640
|
+
return { className: trimmed };
|
|
11641
|
+
}
|
|
11642
|
+
} else {
|
|
11643
|
+
return { className: trimmed };
|
|
11644
|
+
}
|
|
11645
|
+
}
|
|
11646
|
+
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
11647
|
+
return { className: raw.trim() };
|
|
11199
11648
|
}
|
|
11649
|
+
if (!isObject(raw)) return null;
|
|
11650
|
+
const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
|
|
11651
|
+
if (!className) return null;
|
|
11652
|
+
return {
|
|
11653
|
+
className,
|
|
11654
|
+
...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
|
|
11655
|
+
...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
|
|
11656
|
+
...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
|
|
11657
|
+
...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
|
|
11658
|
+
};
|
|
11659
|
+
}
|
|
11660
|
+
function buildEffectBehaviorDocs(effectBehaviors, creates) {
|
|
11200
11661
|
const docs = [];
|
|
11201
|
-
if (
|
|
11662
|
+
if (creates) {
|
|
11663
|
+
docs.push(
|
|
11664
|
+
`Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
|
|
11665
|
+
);
|
|
11666
|
+
}
|
|
11667
|
+
if (effectBehaviors?.approvalRequired?.required) {
|
|
11202
11668
|
docs.push(
|
|
11203
11669
|
effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
|
|
11204
11670
|
);
|
|
11205
11671
|
}
|
|
11206
|
-
if (effectBehaviors
|
|
11672
|
+
if (effectBehaviors?.postCondition) {
|
|
11207
11673
|
docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
|
|
11208
11674
|
if (effectBehaviors.postCondition.description) {
|
|
11209
11675
|
docs.push(effectBehaviors.postCondition.description);
|
|
11210
11676
|
}
|
|
11211
11677
|
}
|
|
11212
|
-
if (effectBehaviors
|
|
11678
|
+
if (effectBehaviors?.dryRun?.enabled) {
|
|
11213
11679
|
docs.push("Supports dry run.");
|
|
11214
11680
|
if (effectBehaviors.dryRun.description) {
|
|
11215
11681
|
docs.push(effectBehaviors.dryRun.description);
|
|
11216
11682
|
}
|
|
11217
11683
|
}
|
|
11218
|
-
if (effectBehaviors
|
|
11684
|
+
if (effectBehaviors?.reverse) {
|
|
11219
11685
|
if (effectBehaviors.reverse.handler) {
|
|
11220
11686
|
docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
|
|
11221
11687
|
} else {
|
|
@@ -11274,13 +11740,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
|
|
|
11274
11740
|
query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
|
|
11275
11741
|
});
|
|
11276
11742
|
}
|
|
11743
|
+
if (spec.creates !== void 0) {
|
|
11744
|
+
mutations.push({
|
|
11745
|
+
label: `set creates on ${toolPath}`,
|
|
11746
|
+
query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
|
|
11747
|
+
JSON.stringify(spec.creates)
|
|
11748
|
+
)}) { done } } } }`
|
|
11749
|
+
});
|
|
11750
|
+
}
|
|
11277
11751
|
return mutations;
|
|
11278
11752
|
}
|
|
11279
11753
|
function readMethodEffectBehaviors(rawMethod) {
|
|
11754
|
+
const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
|
|
11280
11755
|
return {
|
|
11281
|
-
effectBehaviors: normalizeEffectBehaviorSummary(
|
|
11282
|
-
|
|
11283
|
-
)
|
|
11756
|
+
effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
|
|
11757
|
+
creates: normalizeCreationSummary(metamodels)
|
|
11284
11758
|
};
|
|
11285
11759
|
}
|
|
11286
11760
|
var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
|
|
@@ -11302,6 +11776,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
|
|
|
11302
11776
|
{
|
|
11303
11777
|
key: "approvalRequired",
|
|
11304
11778
|
description: "Boolean or `{ required, reason, mode }`."
|
|
11779
|
+
},
|
|
11780
|
+
{
|
|
11781
|
+
key: "creates",
|
|
11782
|
+
description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
|
|
11305
11783
|
}
|
|
11306
11784
|
]
|
|
11307
11785
|
},
|
|
@@ -11421,7 +11899,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
|
|
|
11421
11899
|
...methodIR,
|
|
11422
11900
|
docs: [
|
|
11423
11901
|
...methodIR.docs,
|
|
11424
|
-
...buildEffectBehaviorDocs(
|
|
11902
|
+
...buildEffectBehaviorDocs(
|
|
11903
|
+
methodSummary.effectBehaviors,
|
|
11904
|
+
methodSummary.creates
|
|
11905
|
+
)
|
|
11425
11906
|
]
|
|
11426
11907
|
};
|
|
11427
11908
|
}
|
|
@@ -11662,15 +12143,50 @@ function toRecordSearchResult(className, node) {
|
|
|
11662
12143
|
return [];
|
|
11663
12144
|
}
|
|
11664
12145
|
) : [];
|
|
12146
|
+
const graphPathId = extractRecordIdFromGraphPath(path2, className);
|
|
12147
|
+
const realIdField = fields.find(
|
|
12148
|
+
(field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
|
|
12149
|
+
);
|
|
12150
|
+
const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
|
|
12151
|
+
const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
|
|
12152
|
+
if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path2)) {
|
|
12153
|
+
return null;
|
|
12154
|
+
}
|
|
12155
|
+
const fallbackLabel = displayLabelFromFields(fields);
|
|
12156
|
+
const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path2) ? rawLabel : fallbackLabel || rawLabel || id;
|
|
11665
12157
|
return {
|
|
11666
12158
|
path: path2,
|
|
11667
12159
|
className,
|
|
11668
|
-
id
|
|
11669
|
-
label
|
|
12160
|
+
id,
|
|
12161
|
+
label,
|
|
11670
12162
|
description: typeof node.description === "string" && node.description.trim() ? node.description : null,
|
|
11671
12163
|
fields
|
|
11672
12164
|
};
|
|
11673
12165
|
}
|
|
12166
|
+
function isPlaceholderRecordLabel(label, id, path2) {
|
|
12167
|
+
const normalizedLabel = normalizeGraphPathSegment(label);
|
|
12168
|
+
return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path2);
|
|
12169
|
+
}
|
|
12170
|
+
function displayLabelFromFields(fields) {
|
|
12171
|
+
const preferredFieldNames = [
|
|
12172
|
+
"name",
|
|
12173
|
+
"title",
|
|
12174
|
+
"label",
|
|
12175
|
+
"display_name",
|
|
12176
|
+
"file_name",
|
|
12177
|
+
"number",
|
|
12178
|
+
"code"
|
|
12179
|
+
];
|
|
12180
|
+
for (const preferred of preferredFieldNames) {
|
|
12181
|
+
const match = fields.find(
|
|
12182
|
+
(field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
|
|
12183
|
+
);
|
|
12184
|
+
if (typeof match?.value === "string") {
|
|
12185
|
+
return match.value.trim();
|
|
12186
|
+
}
|
|
12187
|
+
}
|
|
12188
|
+
return null;
|
|
12189
|
+
}
|
|
11674
12190
|
function normalizeRecordSearchText(value) {
|
|
11675
12191
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
11676
12192
|
}
|
|
@@ -11812,7 +12328,12 @@ async function recordOpenAIUsageSpend(options) {
|
|
|
11812
12328
|
const metadata = {
|
|
11813
12329
|
...options.metadata || {},
|
|
11814
12330
|
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
11815
|
-
usageContext: context
|
|
12331
|
+
usageContext: context,
|
|
12332
|
+
pricingContextTier: options.usage.pricingContextTier,
|
|
12333
|
+
cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
|
|
12334
|
+
cacheWriteTokens: options.usage.cacheWriteTokens,
|
|
12335
|
+
cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
|
|
12336
|
+
longContextThresholdTokens: options.usage.longContextThresholdTokens
|
|
11816
12337
|
};
|
|
11817
12338
|
const response = await fetch(
|
|
11818
12339
|
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
@@ -12570,15 +13091,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
12570
13091
|
|
|
12571
13092
|
// ../metamodel-state-machine/src/index.ts
|
|
12572
13093
|
function normalizeStateMachines(values) {
|
|
13094
|
+
const parseJsonRecord = (value) => {
|
|
13095
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
13096
|
+
return value;
|
|
13097
|
+
}
|
|
13098
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
13099
|
+
try {
|
|
13100
|
+
const parsed = JSON.parse(value);
|
|
13101
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
13102
|
+
} catch {
|
|
13103
|
+
return null;
|
|
13104
|
+
}
|
|
13105
|
+
};
|
|
13106
|
+
const parseJsonValue = (value) => {
|
|
13107
|
+
if (value === null || typeof value === "undefined") return null;
|
|
13108
|
+
if (typeof value !== "string") return value;
|
|
13109
|
+
if (!value.trim()) return null;
|
|
13110
|
+
try {
|
|
13111
|
+
return JSON.parse(value);
|
|
13112
|
+
} catch {
|
|
13113
|
+
return value;
|
|
13114
|
+
}
|
|
13115
|
+
};
|
|
12573
13116
|
return (values || []).map((machine) => {
|
|
12574
13117
|
const states = (machine?.states || []).map((state) => ({
|
|
12575
13118
|
name: String(state?.name || ""),
|
|
12576
|
-
|
|
13119
|
+
label: typeof state?.label === "string" ? state.label : null,
|
|
13120
|
+
description: typeof state?.description === "string" ? state.description : null,
|
|
13121
|
+
isFinal: Boolean(state?.is_final ?? state?.isFinal)
|
|
12577
13122
|
})).filter((state) => state.name.length > 0);
|
|
12578
13123
|
const transitions = (machine?.transitions || []).map((transition) => ({
|
|
12579
13124
|
name: String(transition?.name || ""),
|
|
12580
13125
|
from: String(transition?.from?.name || ""),
|
|
12581
|
-
to: String(transition?.to?.name || "")
|
|
13126
|
+
to: String(transition?.to?.name || ""),
|
|
13127
|
+
label: typeof transition?.label === "string" ? transition.label : null,
|
|
13128
|
+
description: typeof transition?.description === "string" ? transition.description : null,
|
|
13129
|
+
action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
|
|
13130
|
+
assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
|
|
13131
|
+
requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
|
|
13132
|
+
permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
|
|
13133
|
+
risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
|
|
13134
|
+
expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
|
|
12582
13135
|
})).filter(
|
|
12583
13136
|
(transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
|
|
12584
13137
|
);
|
|
@@ -12592,7 +13145,7 @@ function normalizeStateMachines(values) {
|
|
|
12592
13145
|
}).filter((machine) => machine.name.length > 0);
|
|
12593
13146
|
}
|
|
12594
13147
|
function stateTypeName(className, machineName) {
|
|
12595
|
-
return `${
|
|
13148
|
+
return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
|
|
12596
13149
|
}
|
|
12597
13150
|
function transitionTypeName(className, machineName) {
|
|
12598
13151
|
return `${stateTypeName(className, machineName)}Transition`;
|
|
@@ -12600,6 +13153,15 @@ function transitionTypeName(className, machineName) {
|
|
|
12600
13153
|
function pathTypeName(className, machineName) {
|
|
12601
13154
|
return `${stateTypeName(className, machineName)}Path`;
|
|
12602
13155
|
}
|
|
13156
|
+
function methodToken(value) {
|
|
13157
|
+
const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
13158
|
+
return token || "state";
|
|
13159
|
+
}
|
|
13160
|
+
function transitionActionsForMachine(machine) {
|
|
13161
|
+
return Object.fromEntries(
|
|
13162
|
+
(machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
|
|
13163
|
+
);
|
|
13164
|
+
}
|
|
12603
13165
|
function normalizeStateDefinitions(machine) {
|
|
12604
13166
|
const finalStates = new Set(machine.finalStates || []);
|
|
12605
13167
|
const states = /* @__PURE__ */ new Map();
|
|
@@ -12613,6 +13175,8 @@ function normalizeStateDefinitions(machine) {
|
|
|
12613
13175
|
}
|
|
12614
13176
|
states.set(rawState.name, {
|
|
12615
13177
|
name: rawState.name,
|
|
13178
|
+
label: rawState.label,
|
|
13179
|
+
description: rawState.description,
|
|
12616
13180
|
isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
|
|
12617
13181
|
});
|
|
12618
13182
|
}
|
|
@@ -12624,6 +13188,44 @@ function normalizeStateDefinitions(machine) {
|
|
|
12624
13188
|
}
|
|
12625
13189
|
return [...states.values()];
|
|
12626
13190
|
}
|
|
13191
|
+
function transitionMetadataGraphqlArgs(transition) {
|
|
13192
|
+
const args = [];
|
|
13193
|
+
if (typeof transition.label === "string") {
|
|
13194
|
+
args.push(`label: ${JSON.stringify(transition.label)}`);
|
|
13195
|
+
}
|
|
13196
|
+
if (typeof transition.description === "string") {
|
|
13197
|
+
args.push(`description: ${JSON.stringify(transition.description)}`);
|
|
13198
|
+
}
|
|
13199
|
+
if (transition.action) {
|
|
13200
|
+
args.push(
|
|
13201
|
+
`action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
|
|
13202
|
+
);
|
|
13203
|
+
}
|
|
13204
|
+
if (transition.assignee) {
|
|
13205
|
+
args.push(
|
|
13206
|
+
`assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
|
|
13207
|
+
);
|
|
13208
|
+
}
|
|
13209
|
+
if (transition.requirements) {
|
|
13210
|
+
args.push(
|
|
13211
|
+
`requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
|
|
13212
|
+
);
|
|
13213
|
+
}
|
|
13214
|
+
if (transition.permission) {
|
|
13215
|
+
args.push(
|
|
13216
|
+
`permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
|
|
13217
|
+
);
|
|
13218
|
+
}
|
|
13219
|
+
if (transition.risk) {
|
|
13220
|
+
args.push(`risk: ${JSON.stringify(transition.risk)}`);
|
|
13221
|
+
}
|
|
13222
|
+
if (transition.expectedOutcome) {
|
|
13223
|
+
args.push(
|
|
13224
|
+
`expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
|
|
13225
|
+
);
|
|
13226
|
+
}
|
|
13227
|
+
return args.length > 0 ? `, ${args.join(", ")}` : "";
|
|
13228
|
+
}
|
|
12627
13229
|
function buildStateMachineModelMutations(modelPath, machines) {
|
|
12628
13230
|
const mutations = [];
|
|
12629
13231
|
for (const machine of machines || []) {
|
|
@@ -12634,12 +13236,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
12634
13236
|
)}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
|
|
12635
13237
|
});
|
|
12636
13238
|
for (const state of normalizeStateDefinitions(machine)) {
|
|
12637
|
-
if (state.name === machine.entryState && !state.isFinal)
|
|
13239
|
+
if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
|
|
13240
|
+
continue;
|
|
12638
13241
|
mutations.push({
|
|
12639
13242
|
label: `add state ${state.name} on ${modelPath}.${machine.name}`,
|
|
12640
13243
|
query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
|
|
12641
13244
|
machine.name
|
|
12642
|
-
)}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
|
|
13245
|
+
)}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
|
|
12643
13246
|
});
|
|
12644
13247
|
}
|
|
12645
13248
|
for (const transition of machine.transitions || []) {
|
|
@@ -12651,18 +13254,27 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
12651
13254
|
transition.name
|
|
12652
13255
|
)}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
|
|
12653
13256
|
transition.to
|
|
12654
|
-
)}) { name } } } }`
|
|
13257
|
+
)}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
|
|
12655
13258
|
});
|
|
12656
13259
|
}
|
|
12657
13260
|
}
|
|
12658
13261
|
return mutations;
|
|
12659
13262
|
}
|
|
12660
13263
|
function buildMachineTypes(classSummary, machine) {
|
|
13264
|
+
const stateGlossary = machine.states.map((state) => {
|
|
13265
|
+
const label = state.label && state.label !== state.name ? state.label : null;
|
|
13266
|
+
const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
|
|
13267
|
+
const finalMarker = state.isFinal ? " Final state." : "";
|
|
13268
|
+
return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
|
|
13269
|
+
});
|
|
12661
13270
|
return [
|
|
12662
13271
|
{
|
|
12663
13272
|
kind: "union",
|
|
12664
13273
|
name: stateTypeName(classSummary.name, machine.name),
|
|
12665
|
-
docs: [
|
|
13274
|
+
docs: [
|
|
13275
|
+
`Allowed states for ${classSummary.name}.${machine.name}.`,
|
|
13276
|
+
...stateGlossary
|
|
13277
|
+
],
|
|
12666
13278
|
members: machine.states.map((state) => state.name)
|
|
12667
13279
|
},
|
|
12668
13280
|
{
|
|
@@ -12678,7 +13290,7 @@ function buildMachineMethods(classSummary, machine) {
|
|
|
12678
13290
|
const transitionName = transitionTypeName(classSummary.name, machine.name);
|
|
12679
13291
|
pathTypeName(classSummary.name, machine.name);
|
|
12680
13292
|
const docsPrefix = `${classSummary.name}.${machine.name}`;
|
|
12681
|
-
|
|
13293
|
+
const methods = [
|
|
12682
13294
|
{
|
|
12683
13295
|
name: `get_${machine.name}`,
|
|
12684
13296
|
docs: [`Get the current ${docsPrefix} state.`],
|
|
@@ -12701,7 +13313,7 @@ function buildMachineMethods(classSummary, machine) {
|
|
|
12701
13313
|
],
|
|
12702
13314
|
static: false,
|
|
12703
13315
|
params: [{ name: "target", type: stateName }],
|
|
12704
|
-
returnType: `Promise<${
|
|
13316
|
+
returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
|
|
12705
13317
|
runtime: {
|
|
12706
13318
|
kind: "state_machine",
|
|
12707
13319
|
machineName: machine.name,
|
|
@@ -12774,6 +13386,99 @@ function buildMachineMethods(classSummary, machine) {
|
|
|
12774
13386
|
}
|
|
12775
13387
|
}
|
|
12776
13388
|
];
|
|
13389
|
+
const creationMethods = (classSummary.methods || []).filter(
|
|
13390
|
+
(method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
|
|
13391
|
+
);
|
|
13392
|
+
for (const state of machine.states) {
|
|
13393
|
+
const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
|
|
13394
|
+
if (!stateNameValue) continue;
|
|
13395
|
+
const token = methodToken(stateNameValue);
|
|
13396
|
+
methods.push(
|
|
13397
|
+
{
|
|
13398
|
+
name: `reach_${machine.name}_to_${token}`,
|
|
13399
|
+
docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
|
|
13400
|
+
static: false,
|
|
13401
|
+
params: [],
|
|
13402
|
+
returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
|
|
13403
|
+
runtime: {
|
|
13404
|
+
kind: "state_machine",
|
|
13405
|
+
machineName: machine.name,
|
|
13406
|
+
className: classSummary.name,
|
|
13407
|
+
stateTypeName: stateName,
|
|
13408
|
+
transitionTypeName: transitionName,
|
|
13409
|
+
operation: "reach",
|
|
13410
|
+
targetState: stateNameValue,
|
|
13411
|
+
transitionActions: transitionActionsForMachine(machine)
|
|
13412
|
+
}
|
|
13413
|
+
},
|
|
13414
|
+
{
|
|
13415
|
+
name: `prepare_${machine.name}_to_${token}`,
|
|
13416
|
+
docs: [
|
|
13417
|
+
`Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
|
|
13418
|
+
],
|
|
13419
|
+
static: false,
|
|
13420
|
+
params: [],
|
|
13421
|
+
returnType: "Promise<SessionArtifactRecord>",
|
|
13422
|
+
runtime: {
|
|
13423
|
+
kind: "state_machine",
|
|
13424
|
+
machineName: machine.name,
|
|
13425
|
+
className: classSummary.name,
|
|
13426
|
+
stateTypeName: stateName,
|
|
13427
|
+
transitionTypeName: transitionName,
|
|
13428
|
+
operation: "prepare_reach",
|
|
13429
|
+
targetState: stateNameValue,
|
|
13430
|
+
transitionActions: transitionActionsForMachine(machine)
|
|
13431
|
+
}
|
|
13432
|
+
}
|
|
13433
|
+
);
|
|
13434
|
+
for (const creationMethod of creationMethods) {
|
|
13435
|
+
const creationRuntime = {
|
|
13436
|
+
kind: "state_machine",
|
|
13437
|
+
machineName: machine.name,
|
|
13438
|
+
className: classSummary.name,
|
|
13439
|
+
stateTypeName: stateName,
|
|
13440
|
+
transitionTypeName: transitionName,
|
|
13441
|
+
operation: "prepare_create_reach",
|
|
13442
|
+
targetState: stateNameValue,
|
|
13443
|
+
transitionActions: transitionActionsForMachine(machine),
|
|
13444
|
+
creation: {
|
|
13445
|
+
methodName: creationMethod.name,
|
|
13446
|
+
effectKey: creationMethod.effectKey || creationMethod.name,
|
|
13447
|
+
inputSchema: creationMethod.inputSchema,
|
|
13448
|
+
outputSchema: creationMethod.outputSchema,
|
|
13449
|
+
creates: creationMethod.creates
|
|
13450
|
+
}
|
|
13451
|
+
};
|
|
13452
|
+
const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
|
|
13453
|
+
methods.push({
|
|
13454
|
+
name: viaName,
|
|
13455
|
+
docs: [
|
|
13456
|
+
`Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
|
|
13457
|
+
],
|
|
13458
|
+
static: true,
|
|
13459
|
+
params: [
|
|
13460
|
+
{ name: "input", type: "Record<string, any>", optional: true }
|
|
13461
|
+
],
|
|
13462
|
+
returnType: "Promise<SessionArtifactRecord>",
|
|
13463
|
+
runtime: creationRuntime
|
|
13464
|
+
});
|
|
13465
|
+
if (creationMethods.length === 1) {
|
|
13466
|
+
methods.push({
|
|
13467
|
+
name: `prepare_${machine.name}_to_${token}`,
|
|
13468
|
+
docs: [
|
|
13469
|
+
`Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
|
|
13470
|
+
],
|
|
13471
|
+
static: true,
|
|
13472
|
+
params: [
|
|
13473
|
+
{ name: "input", type: "Record<string, any>", optional: true }
|
|
13474
|
+
],
|
|
13475
|
+
returnType: "Promise<SessionArtifactRecord>",
|
|
13476
|
+
runtime: creationRuntime
|
|
13477
|
+
});
|
|
13478
|
+
}
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13481
|
+
return methods;
|
|
12777
13482
|
}
|
|
12778
13483
|
function readStateMachineSummaries(rawClass) {
|
|
12779
13484
|
return {
|
|
@@ -12796,8 +13501,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
12796
13501
|
type StateMachineMutation {
|
|
12797
13502
|
name: String!
|
|
12798
13503
|
state_machine: StateMachine!
|
|
12799
|
-
add_state(name: String!, is_final: Boolean): StateMachineMutation!
|
|
12800
|
-
add_transition(name: String!, from: String!, to: String
|
|
13504
|
+
add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
|
|
13505
|
+
add_transition(name: String!, from: String!, to: String!, label: String, description: String, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String): StateMachineMutation!
|
|
12801
13506
|
activate_transition(name: String!): StateMachineMutation!
|
|
12802
13507
|
}
|
|
12803
13508
|
|
|
@@ -12814,6 +13519,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
12814
13519
|
type StateMachineSnapshotMutation {
|
|
12815
13520
|
snapshot: StateMachineSnapshot!
|
|
12816
13521
|
activate_transition(name: String!): StateMachineSnapshotMutation!
|
|
13522
|
+
observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
|
|
12817
13523
|
}
|
|
12818
13524
|
|
|
12819
13525
|
type StateMachine {
|
|
@@ -12833,6 +13539,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
12833
13539
|
|
|
12834
13540
|
type StateMachineState {
|
|
12835
13541
|
name: String!
|
|
13542
|
+
label: String
|
|
13543
|
+
description: String
|
|
12836
13544
|
is_final: Boolean!
|
|
12837
13545
|
}
|
|
12838
13546
|
|
|
@@ -12840,6 +13548,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
12840
13548
|
name: String!
|
|
12841
13549
|
from: StateMachineState!
|
|
12842
13550
|
to: StateMachineState!
|
|
13551
|
+
label: String
|
|
13552
|
+
description: String
|
|
13553
|
+
action_json: String
|
|
13554
|
+
assignee_json: String
|
|
13555
|
+
requirements_json: String
|
|
13556
|
+
permission_json: String
|
|
13557
|
+
risk: String
|
|
13558
|
+
expected_outcome_json: String
|
|
12843
13559
|
}
|
|
12844
13560
|
|
|
12845
13561
|
type StateMachinePath {
|
|
@@ -12892,23 +13608,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
12892
13608
|
StateMachineMutation: {
|
|
12893
13609
|
name: (value) => value.name,
|
|
12894
13610
|
state_machine: async (value) => await run(value.target.state_machine(value.name)),
|
|
12895
|
-
add_state: async (value, { name, is_final }) => {
|
|
13611
|
+
add_state: async (value, { name, is_final, label, description }) => {
|
|
12896
13612
|
await run(
|
|
12897
13613
|
value.target.add_state_machine_state(
|
|
12898
13614
|
value.name,
|
|
12899
13615
|
name,
|
|
12900
|
-
is_final ?? false
|
|
13616
|
+
is_final ?? false,
|
|
13617
|
+
label,
|
|
13618
|
+
description
|
|
12901
13619
|
)
|
|
12902
13620
|
);
|
|
12903
13621
|
return value;
|
|
12904
13622
|
},
|
|
12905
|
-
add_transition: async (value, {
|
|
13623
|
+
add_transition: async (value, {
|
|
13624
|
+
name,
|
|
13625
|
+
from: from2,
|
|
13626
|
+
to,
|
|
13627
|
+
label,
|
|
13628
|
+
description,
|
|
13629
|
+
action_json,
|
|
13630
|
+
assignee_json,
|
|
13631
|
+
requirements_json,
|
|
13632
|
+
permission_json,
|
|
13633
|
+
risk,
|
|
13634
|
+
expected_outcome_json
|
|
13635
|
+
}) => {
|
|
12906
13636
|
await run(
|
|
12907
13637
|
value.target.add_state_machine_transition(
|
|
12908
13638
|
value.name,
|
|
12909
13639
|
name,
|
|
12910
|
-
|
|
12911
|
-
to
|
|
13640
|
+
from2,
|
|
13641
|
+
to,
|
|
13642
|
+
{
|
|
13643
|
+
label,
|
|
13644
|
+
description,
|
|
13645
|
+
actionJson: action_json,
|
|
13646
|
+
assigneeJson: assignee_json,
|
|
13647
|
+
requirementsJson: requirements_json,
|
|
13648
|
+
permissionJson: permission_json,
|
|
13649
|
+
risk,
|
|
13650
|
+
expectedOutcomeJson: expected_outcome_json
|
|
13651
|
+
}
|
|
12912
13652
|
)
|
|
12913
13653
|
);
|
|
12914
13654
|
return value;
|
|
@@ -12927,16 +13667,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
12927
13667
|
value.target.activate_state_machine_transition(value.name, name)
|
|
12928
13668
|
);
|
|
12929
13669
|
return value;
|
|
13670
|
+
},
|
|
13671
|
+
observe_state: async (value, { state, force, source }) => {
|
|
13672
|
+
await run(
|
|
13673
|
+
value.target.observe_state_machine_state(
|
|
13674
|
+
value.name,
|
|
13675
|
+
state,
|
|
13676
|
+
force === true,
|
|
13677
|
+
source
|
|
13678
|
+
)
|
|
13679
|
+
);
|
|
13680
|
+
return value;
|
|
12930
13681
|
}
|
|
12931
13682
|
},
|
|
12932
13683
|
StateMachineState: {
|
|
12933
13684
|
name: (value) => value.name,
|
|
13685
|
+
label: (value) => value.label || null,
|
|
13686
|
+
description: (value) => value.description || null,
|
|
12934
13687
|
is_final: (value) => value.is_final
|
|
12935
13688
|
},
|
|
12936
13689
|
StateMachineTransition: {
|
|
12937
13690
|
name: (value) => value.name,
|
|
12938
13691
|
from: (value) => value.from_state || { name: value.from, is_final: false },
|
|
12939
|
-
to: (value) => value.to_state || { name: value.to, is_final: false }
|
|
13692
|
+
to: (value) => value.to_state || { name: value.to, is_final: false },
|
|
13693
|
+
label: (value) => value.label || null,
|
|
13694
|
+
description: (value) => value.description || null,
|
|
13695
|
+
action_json: (value) => value.action_json || null,
|
|
13696
|
+
assignee_json: (value) => value.assignee_json || null,
|
|
13697
|
+
requirements_json: (value) => value.requirements_json || null,
|
|
13698
|
+
permission_json: (value) => value.permission_json || null,
|
|
13699
|
+
risk: (value) => value.risk || null,
|
|
13700
|
+
expected_outcome_json: (value) => value.expected_outcome_json || null
|
|
12940
13701
|
},
|
|
12941
13702
|
StateMachinePath: {
|
|
12942
13703
|
states: (value) => value.states,
|
|
@@ -13003,6 +13764,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13003
13764
|
name
|
|
13004
13765
|
from { name }
|
|
13005
13766
|
to { name }
|
|
13767
|
+
label
|
|
13768
|
+
description
|
|
13769
|
+
action_json
|
|
13770
|
+
assignee_json
|
|
13771
|
+
requirements_json
|
|
13772
|
+
permission_json
|
|
13773
|
+
risk
|
|
13774
|
+
expected_outcome_json
|
|
13006
13775
|
}
|
|
13007
13776
|
}`
|
|
13008
13777
|
]
|
|
@@ -13220,6 +13989,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
13220
13989
|
}
|
|
13221
13990
|
|
|
13222
13991
|
// src/client.ts
|
|
13992
|
+
var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
|
|
13993
|
+
var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
|
|
13994
|
+
var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
|
|
13995
|
+
function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
|
|
13996
|
+
if (value === void 0) return fallback;
|
|
13997
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
13998
|
+
throw new RangeError(
|
|
13999
|
+
`Session list ${name} must be an integer between ${minimum} and ${maximum}.`
|
|
14000
|
+
);
|
|
14001
|
+
}
|
|
14002
|
+
return value;
|
|
14003
|
+
}
|
|
13223
14004
|
var STANDARD_MODULES_OPERATIONS = [
|
|
13224
14005
|
{
|
|
13225
14006
|
create: "entity",
|
|
@@ -13256,6 +14037,26 @@ var STANDARD_MODULES_OPERATIONS = [
|
|
|
13256
14037
|
var BUILTIN_MODULES = {
|
|
13257
14038
|
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
13258
14039
|
};
|
|
14040
|
+
function stateNameFromMethodName(methodName) {
|
|
14041
|
+
const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
|
|
14042
|
+
return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
14043
|
+
}
|
|
14044
|
+
function appendQueryOptions(searchParams, query) {
|
|
14045
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
14046
|
+
if (value === null || typeof value === "undefined" || value === "") {
|
|
14047
|
+
continue;
|
|
14048
|
+
}
|
|
14049
|
+
if (value instanceof Date) {
|
|
14050
|
+
searchParams.set(key, value.toISOString());
|
|
14051
|
+
continue;
|
|
14052
|
+
}
|
|
14053
|
+
if (Array.isArray(value)) {
|
|
14054
|
+
if (value.length > 0) searchParams.set(key, value.join(","));
|
|
14055
|
+
continue;
|
|
14056
|
+
}
|
|
14057
|
+
searchParams.set(key, String(value));
|
|
14058
|
+
}
|
|
14059
|
+
}
|
|
13259
14060
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
13260
14061
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
13261
14062
|
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
|
|
@@ -13286,8 +14087,20 @@ function bodyInitFromSessionFileUpload(body) {
|
|
|
13286
14087
|
return body;
|
|
13287
14088
|
}
|
|
13288
14089
|
var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
|
|
14090
|
+
var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
|
|
13289
14091
|
var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
|
|
13290
14092
|
var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
|
|
14093
|
+
function chunkItems(items, batchSize) {
|
|
14094
|
+
const chunks = [];
|
|
14095
|
+
for (let offset = 0; offset < items.length; offset += batchSize) {
|
|
14096
|
+
chunks.push(items.slice(offset, offset + batchSize));
|
|
14097
|
+
}
|
|
14098
|
+
return chunks;
|
|
14099
|
+
}
|
|
14100
|
+
function isUnsupportedEffectCatalogMutation(error) {
|
|
14101
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14102
|
+
return message.includes("Unknown RPC method: effects.resetCatalog") || message.includes("Unknown RPC method: effects.addCatalog") || message.includes("Method not found: effects.resetCatalog") || message.includes("Method not found: effects.addCatalog");
|
|
14103
|
+
}
|
|
13291
14104
|
function planRecordObjectsChunks(records, batchSize) {
|
|
13292
14105
|
const total = records.length;
|
|
13293
14106
|
const size = Math.max(1, Math.min(batchSize, total));
|
|
@@ -13299,6 +14112,23 @@ function planRecordObjectsChunks(records, batchSize) {
|
|
|
13299
14112
|
}
|
|
13300
14113
|
return plans;
|
|
13301
14114
|
}
|
|
14115
|
+
function preserveRecordObjectRealId(record) {
|
|
14116
|
+
const realId = record.id.trim();
|
|
14117
|
+
if (!realId) {
|
|
14118
|
+
return record;
|
|
14119
|
+
}
|
|
14120
|
+
const fields = record.fields || {};
|
|
14121
|
+
if (typeof fields.real_id === "string" && fields.real_id.trim()) {
|
|
14122
|
+
return record;
|
|
14123
|
+
}
|
|
14124
|
+
return {
|
|
14125
|
+
...record,
|
|
14126
|
+
fields: {
|
|
14127
|
+
...fields,
|
|
14128
|
+
real_id: realId
|
|
14129
|
+
}
|
|
14130
|
+
};
|
|
14131
|
+
}
|
|
13302
14132
|
function computeEffectKey2(effect) {
|
|
13303
14133
|
const attachedClass = effect.className?.trim();
|
|
13304
14134
|
if (!attachedClass) {
|
|
@@ -13509,7 +14339,7 @@ var Environment = class _Environment {
|
|
|
13509
14339
|
}
|
|
13510
14340
|
get sessions() {
|
|
13511
14341
|
return {
|
|
13512
|
-
list: async (options) => this.listSessions(options
|
|
14342
|
+
list: async (options = {}) => this.listSessions(options),
|
|
13513
14343
|
create: async (options) => this.createSession(options),
|
|
13514
14344
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
13515
14345
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -13536,11 +14366,105 @@ var Environment = class _Environment {
|
|
|
13536
14366
|
getAwaitingCount: async () => this.getAwaitingRecordCount()
|
|
13537
14367
|
};
|
|
13538
14368
|
}
|
|
14369
|
+
/**
|
|
14370
|
+
* Mirror product-owned workflow state into Granular without making Granular
|
|
14371
|
+
* own the customer application's state machine.
|
|
14372
|
+
*/
|
|
14373
|
+
async recordState(input) {
|
|
14374
|
+
const { machine, state, ...target } = input;
|
|
14375
|
+
if (!machine.trim()) {
|
|
14376
|
+
throw new Error("State update requires a machine name");
|
|
14377
|
+
}
|
|
14378
|
+
if (!state.trim()) {
|
|
14379
|
+
throw new Error("State update requires a state");
|
|
14380
|
+
}
|
|
14381
|
+
return this.recordObject({
|
|
14382
|
+
className: target.className,
|
|
14383
|
+
id: target.id,
|
|
14384
|
+
...target.label ? { label: target.label } : {},
|
|
14385
|
+
...target.fields ? { fields: target.fields } : {},
|
|
14386
|
+
...target.relationships ? { relationships: target.relationships } : {},
|
|
14387
|
+
states: {
|
|
14388
|
+
[machine.trim()]: {
|
|
14389
|
+
state: state.trim(),
|
|
14390
|
+
...target.source ? { source: target.source } : {},
|
|
14391
|
+
...target.cause ? { cause: target.cause } : {},
|
|
14392
|
+
...target.actorId ? { actorId: target.actorId } : {},
|
|
14393
|
+
...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
|
|
14394
|
+
...target.force !== void 0 ? { force: target.force } : {},
|
|
14395
|
+
...target.metadata ? { metadata: target.metadata } : {}
|
|
14396
|
+
}
|
|
14397
|
+
}
|
|
14398
|
+
});
|
|
14399
|
+
}
|
|
14400
|
+
/**
|
|
14401
|
+
* Mirror product-owned workflow state into Granular without making Granular
|
|
14402
|
+
* own the customer application's state machine.
|
|
14403
|
+
*
|
|
14404
|
+
* Example:
|
|
14405
|
+
* `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
|
|
14406
|
+
*/
|
|
14407
|
+
state(target) {
|
|
14408
|
+
const observe = async (machineName, stateName, input = {}) => {
|
|
14409
|
+
const observedState = input.observedState || input.state || stateName;
|
|
14410
|
+
if (!observedState) {
|
|
14411
|
+
throw new Error("State observation requires a target state");
|
|
14412
|
+
}
|
|
14413
|
+
return this.recordState({
|
|
14414
|
+
...target,
|
|
14415
|
+
machine: machineName,
|
|
14416
|
+
state: observedState,
|
|
14417
|
+
...input.source ? { source: input.source } : {},
|
|
14418
|
+
...input.cause ? { cause: input.cause } : {},
|
|
14419
|
+
...input.actorId ? { actorId: input.actorId } : {},
|
|
14420
|
+
...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
|
|
14421
|
+
...input.force !== void 0 ? { force: input.force } : {},
|
|
14422
|
+
...input.metadata ? { metadata: input.metadata } : {}
|
|
14423
|
+
});
|
|
14424
|
+
};
|
|
14425
|
+
return new Proxy(
|
|
14426
|
+
{},
|
|
14427
|
+
{
|
|
14428
|
+
get: (_target, machineProperty) => {
|
|
14429
|
+
if (typeof machineProperty !== "string") return void 0;
|
|
14430
|
+
return new Proxy(
|
|
14431
|
+
{},
|
|
14432
|
+
{
|
|
14433
|
+
get: (_machineTarget, stateProperty) => {
|
|
14434
|
+
if (stateProperty === "to") {
|
|
14435
|
+
return (stateName, input) => observe(machineProperty, stateName, input || {});
|
|
14436
|
+
}
|
|
14437
|
+
if (typeof stateProperty !== "string") return void 0;
|
|
14438
|
+
return (input) => observe(
|
|
14439
|
+
machineProperty,
|
|
14440
|
+
stateNameFromMethodName(stateProperty),
|
|
14441
|
+
input || {}
|
|
14442
|
+
);
|
|
14443
|
+
}
|
|
14444
|
+
}
|
|
14445
|
+
);
|
|
14446
|
+
}
|
|
14447
|
+
}
|
|
14448
|
+
);
|
|
14449
|
+
}
|
|
13539
14450
|
get feedback() {
|
|
13540
14451
|
return {
|
|
13541
14452
|
list: async () => this.listFeedback()
|
|
13542
14453
|
};
|
|
13543
14454
|
}
|
|
14455
|
+
get manualActions() {
|
|
14456
|
+
return {
|
|
14457
|
+
record: (input) => this.recordManualAction(input),
|
|
14458
|
+
list: (options = {}) => this.listManualActions(options),
|
|
14459
|
+
suggest: (options = {}) => this.suggestManualActions(options)
|
|
14460
|
+
};
|
|
14461
|
+
}
|
|
14462
|
+
get artifactApprovals() {
|
|
14463
|
+
return {
|
|
14464
|
+
list: (options = {}) => this.listArtifactApprovals(options),
|
|
14465
|
+
decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
|
|
14466
|
+
};
|
|
14467
|
+
}
|
|
13544
14468
|
/**
|
|
13545
14469
|
* Sessionless environments do not own a live transport, so disconnecting the
|
|
13546
14470
|
* environment handle itself is a no-op. This keeps the public surface
|
|
@@ -13550,17 +14474,12 @@ var Environment = class _Environment {
|
|
|
13550
14474
|
*/
|
|
13551
14475
|
async disconnect() {
|
|
13552
14476
|
}
|
|
13553
|
-
async listSessions(
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
return [...active, ...closed].sort(
|
|
13560
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
13561
|
-
);
|
|
13562
|
-
}
|
|
13563
|
-
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
14477
|
+
async listSessions(optionsOrStatus = {}) {
|
|
14478
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
14479
|
+
return this.granular.listSessions({
|
|
14480
|
+
...options,
|
|
14481
|
+
environmentId: this.environmentId
|
|
14482
|
+
});
|
|
13564
14483
|
}
|
|
13565
14484
|
async getUserEnvironmentState(options = {}) {
|
|
13566
14485
|
return this.granular.getUserEnvironmentState({
|
|
@@ -13578,6 +14497,7 @@ var Environment = class _Environment {
|
|
|
13578
14497
|
return this.granular.createSession({
|
|
13579
14498
|
environmentId: this.environmentId,
|
|
13580
14499
|
clientId: options?.clientId,
|
|
14500
|
+
sessionScope: options?.sessionScope,
|
|
13581
14501
|
initialHeap: options?.initialHeap
|
|
13582
14502
|
});
|
|
13583
14503
|
}
|
|
@@ -13619,6 +14539,50 @@ var Environment = class _Environment {
|
|
|
13619
14539
|
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
|
|
13620
14540
|
return Array.isArray(response.items) ? response.items : [];
|
|
13621
14541
|
}
|
|
14542
|
+
async recordManualAction(input) {
|
|
14543
|
+
const body = {
|
|
14544
|
+
...input,
|
|
14545
|
+
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
|
|
14546
|
+
};
|
|
14547
|
+
return this.controlPlaneRequest(
|
|
14548
|
+
`/control/environments/${this.environmentId}/manual-actions`,
|
|
14549
|
+
{
|
|
14550
|
+
method: "POST",
|
|
14551
|
+
body: JSON.stringify(body)
|
|
14552
|
+
}
|
|
14553
|
+
);
|
|
14554
|
+
}
|
|
14555
|
+
async listManualActions(options = {}) {
|
|
14556
|
+
const query = new URLSearchParams();
|
|
14557
|
+
appendQueryOptions(query, options);
|
|
14558
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
14559
|
+
return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
|
|
14560
|
+
}
|
|
14561
|
+
async suggestManualActions(options = {}) {
|
|
14562
|
+
const query = new URLSearchParams();
|
|
14563
|
+
appendQueryOptions(query, options);
|
|
14564
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
14565
|
+
return this.controlPlaneRequest(
|
|
14566
|
+
`/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
|
|
14567
|
+
);
|
|
14568
|
+
}
|
|
14569
|
+
async listArtifactApprovals(options = {}) {
|
|
14570
|
+
const query = new URLSearchParams();
|
|
14571
|
+
appendQueryOptions(query, options);
|
|
14572
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
14573
|
+
return this.controlPlaneRequest(
|
|
14574
|
+
`/control/environments/${this.environmentId}/artifact-approvals${suffix}`
|
|
14575
|
+
);
|
|
14576
|
+
}
|
|
14577
|
+
async decideArtifactApproval(approvalTaskId, input) {
|
|
14578
|
+
return this.controlPlaneRequest(
|
|
14579
|
+
`/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
|
|
14580
|
+
{
|
|
14581
|
+
method: "POST",
|
|
14582
|
+
body: JSON.stringify(input)
|
|
14583
|
+
}
|
|
14584
|
+
);
|
|
14585
|
+
}
|
|
13622
14586
|
getRuntimeBaseUrl() {
|
|
13623
14587
|
return deriveRuntimeBaseUrl(this._apiEndpoint);
|
|
13624
14588
|
}
|
|
@@ -14443,10 +15407,11 @@ var Environment = class _Environment {
|
|
|
14443
15407
|
if (!Array.isArray(records) || records.length === 0) {
|
|
14444
15408
|
return [];
|
|
14445
15409
|
}
|
|
15410
|
+
const recordsToWrite = records.map(preserveRecordObjectRealId);
|
|
14446
15411
|
const batchSize = Math.max(
|
|
14447
15412
|
1,
|
|
14448
15413
|
Math.min(
|
|
14449
|
-
|
|
15414
|
+
recordsToWrite.length,
|
|
14450
15415
|
options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
|
|
14451
15416
|
)
|
|
14452
15417
|
);
|
|
@@ -14454,8 +15419,8 @@ var Environment = class _Environment {
|
|
|
14454
15419
|
MAX_RECORD_OBJECTS_CONCURRENCY,
|
|
14455
15420
|
Math.max(1, options?.concurrency ?? 1)
|
|
14456
15421
|
);
|
|
14457
|
-
const plans = planRecordObjectsChunks(
|
|
14458
|
-
const total =
|
|
15422
|
+
const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
|
|
15423
|
+
const total = recordsToWrite.length;
|
|
14459
15424
|
const results = new Array(total);
|
|
14460
15425
|
const onChunk = options?.onChunkComplete;
|
|
14461
15426
|
for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
|
|
@@ -14526,12 +15491,13 @@ var Environment = class _Environment {
|
|
|
14526
15491
|
* synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
|
|
14527
15492
|
*/
|
|
14528
15493
|
async enqueueRecordImport(records, options = {}) {
|
|
15494
|
+
const recordsToImport = records.map(preserveRecordObjectRealId);
|
|
14529
15495
|
return this.controlPlaneRequest(
|
|
14530
15496
|
`/control/environments/${this.environmentId}/record-imports`,
|
|
14531
15497
|
{
|
|
14532
15498
|
method: "POST",
|
|
14533
15499
|
body: JSON.stringify({
|
|
14534
|
-
records,
|
|
15500
|
+
records: recordsToImport,
|
|
14535
15501
|
batchSize: options.batchSize,
|
|
14536
15502
|
setupRunId: options.setupRunId,
|
|
14537
15503
|
writeMode: options.writeMode
|
|
@@ -14639,11 +15605,7 @@ var EnvironmentSession = class extends Session {
|
|
|
14639
15605
|
}
|
|
14640
15606
|
buildSessionDataUrl(path2, query) {
|
|
14641
15607
|
const searchParams = new URLSearchParams();
|
|
14642
|
-
|
|
14643
|
-
if (value !== null && typeof value !== "undefined" && value !== "") {
|
|
14644
|
-
searchParams.set(key, String(value));
|
|
14645
|
-
}
|
|
14646
|
-
}
|
|
15608
|
+
appendQueryOptions(searchParams, query);
|
|
14647
15609
|
const queryString = searchParams.toString();
|
|
14648
15610
|
return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path2}${queryString ? `?${queryString}` : ""}`;
|
|
14649
15611
|
}
|
|
@@ -14726,9 +15688,108 @@ var EnvironmentSession = class extends Session {
|
|
|
14726
15688
|
),
|
|
14727
15689
|
get: (jobId) => this.sessionDataRequest(
|
|
14728
15690
|
`/jobs/${encodeURIComponent(jobId)}`
|
|
15691
|
+
),
|
|
15692
|
+
latest: async (options = {}) => {
|
|
15693
|
+
const page = await this.sessionDataRequest("/jobs", {
|
|
15694
|
+
status: options.status || "all",
|
|
15695
|
+
latest: true,
|
|
15696
|
+
limit: 1
|
|
15697
|
+
});
|
|
15698
|
+
return page.items[0] || null;
|
|
15699
|
+
}
|
|
15700
|
+
};
|
|
15701
|
+
}
|
|
15702
|
+
get artifacts() {
|
|
15703
|
+
return {
|
|
15704
|
+
list: (options = {}) => {
|
|
15705
|
+
const queryOptions = { ...options };
|
|
15706
|
+
if (options.target) {
|
|
15707
|
+
queryOptions.targetClassName = options.target.className;
|
|
15708
|
+
queryOptions.targetId = options.target.id;
|
|
15709
|
+
delete queryOptions.target;
|
|
15710
|
+
}
|
|
15711
|
+
return this.sessionDataRequest("/artifacts", queryOptions);
|
|
15712
|
+
},
|
|
15713
|
+
listForLatestJob: (options = {}) => this.artifacts.list({
|
|
15714
|
+
...options,
|
|
15715
|
+
latestJob: true
|
|
15716
|
+
}),
|
|
15717
|
+
get: (artifactId) => this.sessionDataRequest(
|
|
15718
|
+
`/artifacts/${encodeURIComponent(artifactId)}`
|
|
15719
|
+
),
|
|
15720
|
+
create: (artifact) => this.sessionDataRequest(
|
|
15721
|
+
"/artifacts",
|
|
15722
|
+
void 0,
|
|
15723
|
+
{
|
|
15724
|
+
method: "POST",
|
|
15725
|
+
body: artifact
|
|
15726
|
+
}
|
|
15727
|
+
),
|
|
15728
|
+
updateInputs: (artifactId, patch) => this.sessionDataRequest(
|
|
15729
|
+
`/artifacts/${encodeURIComponent(artifactId)}`,
|
|
15730
|
+
void 0,
|
|
15731
|
+
{
|
|
15732
|
+
method: "PATCH",
|
|
15733
|
+
body: patch
|
|
15734
|
+
}
|
|
15735
|
+
),
|
|
15736
|
+
validate: (artifactId) => this.sessionDataRequest(
|
|
15737
|
+
`/artifacts/${encodeURIComponent(artifactId)}/validate`,
|
|
15738
|
+
void 0,
|
|
15739
|
+
{ method: "POST" }
|
|
15740
|
+
),
|
|
15741
|
+
execute: (artifactId, options) => this.sessionDataRequest(
|
|
15742
|
+
`/artifacts/${encodeURIComponent(artifactId)}/execute`,
|
|
15743
|
+
void 0,
|
|
15744
|
+
{ method: "POST", body: options }
|
|
15745
|
+
),
|
|
15746
|
+
approve: (artifactId, options) => this.sessionDataRequest(
|
|
15747
|
+
`/artifacts/${encodeURIComponent(artifactId)}/approve`,
|
|
15748
|
+
void 0,
|
|
15749
|
+
{ method: "POST", body: options }
|
|
15750
|
+
),
|
|
15751
|
+
cancel: (artifactId) => this.sessionDataRequest(
|
|
15752
|
+
`/artifacts/${encodeURIComponent(artifactId)}/cancel`,
|
|
15753
|
+
void 0,
|
|
15754
|
+
{ method: "POST" }
|
|
14729
15755
|
)
|
|
14730
15756
|
};
|
|
14731
15757
|
}
|
|
15758
|
+
get manualActions() {
|
|
15759
|
+
const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
|
|
15760
|
+
return {
|
|
15761
|
+
record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
|
|
15762
|
+
"/manual-actions",
|
|
15763
|
+
void 0,
|
|
15764
|
+
{
|
|
15765
|
+
method: "POST",
|
|
15766
|
+
body: { ...input, sessionId: this.sessionId }
|
|
15767
|
+
}
|
|
15768
|
+
) : this.environment.manualActions.record({
|
|
15769
|
+
...input,
|
|
15770
|
+
sessionId: this.sessionId
|
|
15771
|
+
}),
|
|
15772
|
+
list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
|
|
15773
|
+
...options,
|
|
15774
|
+
sessionId: this.sessionId
|
|
15775
|
+
}),
|
|
15776
|
+
suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
|
|
15777
|
+
"/manual-actions/suggestions",
|
|
15778
|
+
options
|
|
15779
|
+
) : this.environment.manualActions.suggest(options)
|
|
15780
|
+
};
|
|
15781
|
+
}
|
|
15782
|
+
get artifactApprovals() {
|
|
15783
|
+
const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
|
|
15784
|
+
return {
|
|
15785
|
+
list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
|
|
15786
|
+
decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
|
|
15787
|
+
`/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
|
|
15788
|
+
void 0,
|
|
15789
|
+
{ method: "POST", body: input }
|
|
15790
|
+
) : this.environment.artifactApprovals.decide(approvalTaskId, input)
|
|
15791
|
+
};
|
|
15792
|
+
}
|
|
14732
15793
|
get files() {
|
|
14733
15794
|
return {
|
|
14734
15795
|
list: (options = {}) => this.sessionDataRequest(
|
|
@@ -14809,13 +15870,16 @@ var EnvironmentSession = class extends Session {
|
|
|
14809
15870
|
get transcript() {
|
|
14810
15871
|
return {
|
|
14811
15872
|
list: async (options = {}) => {
|
|
14812
|
-
const [messages, jobs, entries, lists] = await Promise.all([
|
|
15873
|
+
const [messages, jobs, entries, lists, artifacts] = await Promise.all([
|
|
14813
15874
|
this.collectAllSessionItems(this.messages.list),
|
|
14814
15875
|
this.collectAllSessionItems(
|
|
14815
15876
|
(pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
|
|
14816
15877
|
),
|
|
14817
15878
|
this.collectAllSessionItems(this.heap.entries.list),
|
|
14818
|
-
this.collectAllSessionItems(this.heap.lists.list)
|
|
15879
|
+
this.collectAllSessionItems(this.heap.lists.list),
|
|
15880
|
+
this.collectAllSessionItems(
|
|
15881
|
+
(pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
|
|
15882
|
+
)
|
|
14819
15883
|
]);
|
|
14820
15884
|
const liveDoc = {
|
|
14821
15885
|
conversation: { messages },
|
|
@@ -14829,6 +15893,21 @@ var EnvironmentSession = class extends Session {
|
|
|
14829
15893
|
(entry) => Boolean(entry)
|
|
14830
15894
|
)
|
|
14831
15895
|
)
|
|
15896
|
+
},
|
|
15897
|
+
artifacts: {
|
|
15898
|
+
byId: Object.fromEntries(
|
|
15899
|
+
artifacts.map((artifact) => {
|
|
15900
|
+
return artifact?.artifactId ? [
|
|
15901
|
+
artifact.artifactId,
|
|
15902
|
+
artifact
|
|
15903
|
+
] : null;
|
|
15904
|
+
}).filter(
|
|
15905
|
+
(entry) => Boolean(entry)
|
|
15906
|
+
)
|
|
15907
|
+
),
|
|
15908
|
+
order: artifacts.map((artifact) => artifact?.artifactId).filter(
|
|
15909
|
+
(artifactId) => Boolean(artifactId)
|
|
15910
|
+
)
|
|
14832
15911
|
}
|
|
14833
15912
|
};
|
|
14834
15913
|
const heap = normalizeHeapSnapshot({
|
|
@@ -14905,6 +15984,12 @@ var EnvironmentSession = class extends Session {
|
|
|
14905
15984
|
async recordObject(options) {
|
|
14906
15985
|
return this.environment.recordObject(options);
|
|
14907
15986
|
}
|
|
15987
|
+
async recordState(input) {
|
|
15988
|
+
return this.environment.recordState(input);
|
|
15989
|
+
}
|
|
15990
|
+
state(target) {
|
|
15991
|
+
return this.environment.state(target);
|
|
15992
|
+
}
|
|
14908
15993
|
async recordObjects(records, options) {
|
|
14909
15994
|
return this.environment.recordObjects(records, options);
|
|
14910
15995
|
}
|
|
@@ -14975,7 +16060,7 @@ var EnvironmentSession = class extends Session {
|
|
|
14975
16060
|
* Close only the socket transport without sending `client.goodbye`.
|
|
14976
16061
|
*/
|
|
14977
16062
|
disconnectTransport() {
|
|
14978
|
-
this.client.disconnect();
|
|
16063
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
14979
16064
|
}
|
|
14980
16065
|
/**
|
|
14981
16066
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -15370,16 +16455,71 @@ var Granular = class _Granular {
|
|
|
15370
16455
|
};
|
|
15371
16456
|
}
|
|
15372
16457
|
/**
|
|
15373
|
-
* List
|
|
16458
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
16459
|
+
*/
|
|
16460
|
+
async listSessions(options) {
|
|
16461
|
+
const environmentId = options.environmentId?.trim();
|
|
16462
|
+
const sandboxId = options.sandboxId?.trim();
|
|
16463
|
+
const subjectId = options.subjectId?.trim();
|
|
16464
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
16465
|
+
throw new Error(
|
|
16466
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
16467
|
+
);
|
|
16468
|
+
}
|
|
16469
|
+
const status = options.status || "active";
|
|
16470
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
16471
|
+
"active",
|
|
16472
|
+
"closed",
|
|
16473
|
+
"expired",
|
|
16474
|
+
"failed",
|
|
16475
|
+
"timeout",
|
|
16476
|
+
"all"
|
|
16477
|
+
]);
|
|
16478
|
+
if (!allowedStatuses.has(status)) {
|
|
16479
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
16480
|
+
}
|
|
16481
|
+
const limit = boundedSessionListInteger(
|
|
16482
|
+
options.limit,
|
|
16483
|
+
"limit",
|
|
16484
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
16485
|
+
1,
|
|
16486
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
16487
|
+
);
|
|
16488
|
+
const offset = boundedSessionListInteger(
|
|
16489
|
+
options.offset,
|
|
16490
|
+
"offset",
|
|
16491
|
+
0,
|
|
16492
|
+
0,
|
|
16493
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
16494
|
+
);
|
|
16495
|
+
const query = new URLSearchParams({
|
|
16496
|
+
limit: String(limit),
|
|
16497
|
+
offset: String(offset)
|
|
16498
|
+
});
|
|
16499
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
16500
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
16501
|
+
if (subjectId) query.set("userId", subjectId);
|
|
16502
|
+
if (options.sessionScope?.trim()) {
|
|
16503
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
16504
|
+
}
|
|
16505
|
+
if (status !== "all") query.set("status", status);
|
|
16506
|
+
const res = await this.request(
|
|
16507
|
+
`/control/sessions?${query.toString()}`
|
|
16508
|
+
);
|
|
16509
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
16510
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
16511
|
+
}
|
|
16512
|
+
/**
|
|
16513
|
+
* List active (open) sessions for an environment.
|
|
15374
16514
|
*/
|
|
15375
16515
|
async listOpenSessions(filters) {
|
|
15376
|
-
return this.
|
|
16516
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
15377
16517
|
}
|
|
15378
16518
|
/**
|
|
15379
16519
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
15380
16520
|
*/
|
|
15381
16521
|
async listClosedSessions(filters) {
|
|
15382
|
-
return this.
|
|
16522
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
15383
16523
|
}
|
|
15384
16524
|
async getUserEnvironmentState(options) {
|
|
15385
16525
|
const query = new URLSearchParams({
|
|
@@ -15414,14 +16554,6 @@ var Granular = class _Granular {
|
|
|
15414
16554
|
});
|
|
15415
16555
|
return result.readAtBySessionId || {};
|
|
15416
16556
|
}
|
|
15417
|
-
async listSessionsForEnvironment(environmentId, status) {
|
|
15418
|
-
const query = new URLSearchParams({ environmentId, status });
|
|
15419
|
-
const res = await this.request(
|
|
15420
|
-
`/control/sessions?${query.toString()}`
|
|
15421
|
-
);
|
|
15422
|
-
const items = Array.isArray(res.items) ? res.items : [];
|
|
15423
|
-
return items.map((row) => this.normalizeConversationSession(row));
|
|
15424
|
-
}
|
|
15425
16557
|
normalizeConversationSession(row) {
|
|
15426
16558
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
15427
16559
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -15480,6 +16612,7 @@ var Granular = class _Granular {
|
|
|
15480
16612
|
*/
|
|
15481
16613
|
async createSession(options) {
|
|
15482
16614
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
16615
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
15483
16616
|
await this.activateEnvironment(options.environmentId);
|
|
15484
16617
|
const envData = await this.environments.get(options.environmentId);
|
|
15485
16618
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -15488,6 +16621,8 @@ var Granular = class _Granular {
|
|
|
15488
16621
|
body: JSON.stringify({
|
|
15489
16622
|
environmentId: options.environmentId,
|
|
15490
16623
|
clientId,
|
|
16624
|
+
sessionScope,
|
|
16625
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
15491
16626
|
initialHeap: options.initialHeap
|
|
15492
16627
|
})
|
|
15493
16628
|
});
|
|
@@ -15732,15 +16867,43 @@ var Granular = class _Granular {
|
|
|
15732
16867
|
const effects = Array.from(
|
|
15733
16868
|
this.getSandboxEffectMap(host.sandboxId).values()
|
|
15734
16869
|
).map((effect) => this.serializeEffect(effect));
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
16870
|
+
let acceptedCount = 0;
|
|
16871
|
+
const rejected = [];
|
|
16872
|
+
try {
|
|
16873
|
+
await withTimeout(
|
|
16874
|
+
host.wsClient.call("effects.resetCatalog", {}),
|
|
16875
|
+
EFFECT_CATALOG_SYNC_TIMEOUT_MS,
|
|
16876
|
+
`effects.resetCatalog for sandbox ${host.sandboxId}`
|
|
16877
|
+
);
|
|
16878
|
+
for (const batch of chunkItems(effects, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
|
|
16879
|
+
const result = await withTimeout(
|
|
16880
|
+
host.wsClient.call("effects.addCatalog", {
|
|
16881
|
+
effects: batch
|
|
16882
|
+
}),
|
|
16883
|
+
EFFECT_CATALOG_SYNC_TIMEOUT_MS,
|
|
16884
|
+
`effects.addCatalog for sandbox ${host.sandboxId}`
|
|
16885
|
+
);
|
|
16886
|
+
acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
16887
|
+
if (Array.isArray(result?.rejected)) {
|
|
16888
|
+
rejected.push(...result.rejected);
|
|
16889
|
+
}
|
|
16890
|
+
}
|
|
16891
|
+
} catch (error) {
|
|
16892
|
+
if (!isUnsupportedEffectCatalogMutation(error)) {
|
|
16893
|
+
throw error;
|
|
16894
|
+
}
|
|
16895
|
+
const result = await withTimeout(
|
|
16896
|
+
host.wsClient.call("effects.publishCatalog", {
|
|
16897
|
+
effects
|
|
16898
|
+
}),
|
|
16899
|
+
EFFECT_CATALOG_SYNC_TIMEOUT_MS,
|
|
16900
|
+
`effects.publishCatalog for sandbox ${host.sandboxId}`
|
|
16901
|
+
);
|
|
16902
|
+
acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
16903
|
+
if (Array.isArray(result?.rejected)) {
|
|
16904
|
+
rejected.push(...result.rejected);
|
|
16905
|
+
}
|
|
16906
|
+
}
|
|
15744
16907
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
15745
16908
|
const detail = rejected.map(
|
|
15746
16909
|
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
@@ -16966,6 +18129,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
|
|
|
16966
18129
|
var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
|
|
16967
18130
|
var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
|
|
16968
18131
|
var LEGACY_SANDBOX_TOOLS_MODULE_PATTERN = "\\.\\/sandbox-tools(?:\\.js)?";
|
|
18132
|
+
var HARNESS_V3_RUNTIME_MODULE_PATTERN = "@granular/(?:agent|session|domain(?:/[A-Za-z_$][\\w$]*)?|actions/(?:backend|frontend)|utils/(?:csv|xlsx))";
|
|
16969
18133
|
function hasNamedModuleImport(source, moduleName, name) {
|
|
16970
18134
|
const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16971
18135
|
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -17024,6 +18188,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
|
|
|
17024
18188
|
message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
|
|
17025
18189
|
});
|
|
17026
18190
|
}
|
|
18191
|
+
if (new RegExp(
|
|
18192
|
+
`import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*['"]${HARNESS_V3_RUNTIME_MODULE_PATTERN}['"]`
|
|
18193
|
+
).test(normalized)) {
|
|
18194
|
+
issues.push({
|
|
18195
|
+
code: "runtime_namespace_import",
|
|
18196
|
+
severity: "error",
|
|
18197
|
+
message: 'Generated code must use named imports from Harness runtime modules. Do not use namespace imports such as `import * as agent from "@granular/agent"`; use `import { replyToUser, artifacts } from "@granular/agent"`.'
|
|
18198
|
+
});
|
|
18199
|
+
}
|
|
17027
18200
|
if (/\bprocess\.exit\s*\(/.test(normalized)) {
|
|
17028
18201
|
issues.push({
|
|
17029
18202
|
code: "process_exit",
|
|
@@ -18043,6 +19216,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
|
|
|
18043
19216
|
entries: {}
|
|
18044
19217
|
});
|
|
18045
19218
|
}
|
|
19219
|
+
function buildGranularAgentManualActionMemorySummary(input) {
|
|
19220
|
+
const maxItems = Math.max(1, Math.min(12, input.maxItems ?? 8));
|
|
19221
|
+
const suggestions = (input.suggestions || []).filter((suggestion) => suggestion?.actionKey).slice(0, maxItems).map((suggestion) => ({
|
|
19222
|
+
actionKey: suggestion.actionKey,
|
|
19223
|
+
label: suggestion.label || null,
|
|
19224
|
+
targetClassName: suggestion.targetClassName || null,
|
|
19225
|
+
count: typeof suggestion.count === "number" && Number.isFinite(suggestion.count) ? suggestion.count : null,
|
|
19226
|
+
subjectCount: typeof suggestion.subjectCount === "number" && Number.isFinite(suggestion.subjectCount) ? suggestion.subjectCount : null,
|
|
19227
|
+
successCount: typeof suggestion.successCount === "number" && Number.isFinite(suggestion.successCount) ? suggestion.successCount : null,
|
|
19228
|
+
failureCount: typeof suggestion.failureCount === "number" && Number.isFinite(suggestion.failureCount) ? suggestion.failureCount : null,
|
|
19229
|
+
lastOccurredAt: typeof suggestion.lastOccurredAt === "number" && Number.isFinite(suggestion.lastOccurredAt) ? suggestion.lastOccurredAt : null,
|
|
19230
|
+
sampleTargetIds: Array.isArray(suggestion.sampleTargetIds) ? suggestion.sampleTargetIds.filter(
|
|
19231
|
+
(id) => typeof id === "string" && id.trim().length > 0
|
|
19232
|
+
).slice(0, 6) : []
|
|
19233
|
+
}));
|
|
19234
|
+
return [
|
|
19235
|
+
renderConstBlock("manualActionMemory", {
|
|
19236
|
+
suggestions
|
|
19237
|
+
}),
|
|
19238
|
+
"Use manualActionMemory only as behavioral context for likely next actions. Ground the current target and validate permissions before creating or running prepared actions."
|
|
19239
|
+
].join("\n");
|
|
19240
|
+
}
|
|
19241
|
+
function buildGranularAgentManualActionBlock(manualActionSummary) {
|
|
19242
|
+
return manualActionSummary?.trim() || buildGranularAgentManualActionMemorySummary({ suggestions: [] });
|
|
19243
|
+
}
|
|
18046
19244
|
function projectSessionFileSummary(liveDoc) {
|
|
18047
19245
|
const files = asRecord4(liveDoc?.files);
|
|
18048
19246
|
const byId = asRecord4(files?.byId) || {};
|
|
@@ -18074,8 +19272,12 @@ function buildGranularAgentFileBlock(fileSummary) {
|
|
|
18074
19272
|
function extractRuntimeContractExports(domainBlock) {
|
|
18075
19273
|
const classes = /* @__PURE__ */ new Set();
|
|
18076
19274
|
const actions = /* @__PURE__ */ new Set();
|
|
18077
|
-
const
|
|
18078
|
-
for (const match of domainBlock.matchAll(
|
|
19275
|
+
const classConstPattern = /export\s+declare\s+const\s+([A-Za-z_$][\w$]*)\s*:\s*EntityClass\b/g;
|
|
19276
|
+
for (const match of domainBlock.matchAll(classConstPattern)) {
|
|
19277
|
+
classes.add(match[1]);
|
|
19278
|
+
}
|
|
19279
|
+
const classDeclPattern = /export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\b/g;
|
|
19280
|
+
for (const match of domainBlock.matchAll(classDeclPattern)) {
|
|
18079
19281
|
classes.add(match[1]);
|
|
18080
19282
|
}
|
|
18081
19283
|
const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
|
|
@@ -18580,6 +19782,9 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
18580
19782
|
});
|
|
18581
19783
|
const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
|
|
18582
19784
|
const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
|
|
19785
|
+
const manualActionBlock = buildGranularAgentManualActionBlock(
|
|
19786
|
+
input.manualActionSummary
|
|
19787
|
+
);
|
|
18583
19788
|
const knownFactsBlock = renderConstBlock(
|
|
18584
19789
|
"knownFacts",
|
|
18585
19790
|
buildKnownFactsFromCheckpoint(input.checkpoint)
|
|
@@ -18593,16 +19798,16 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
18593
19798
|
- \`replyToUser(...)\` displays text directly to the user in the host UI. Treat it as the user-facing progress and reply channel, not as a debug log.
|
|
18594
19799
|
- For long-running or multi-step jobs, send several short \`replyToUser(...)\` updates as useful milestones are reached so the user can see what is happening instead of waiting in silence.
|
|
18595
19800
|
- Write \`replyToUser(...)\` content in a friendly, readable product-assistant style: concrete, concise, and natural. Avoid robotic status dumps, raw implementation names, and unexplained IDs unless the ID helps the user.
|
|
18596
|
-
- When \`replyToUser(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag
|
|
18597
|
-
- Treat \`showObjects(...)\` as the UI display call for user-visible records,
|
|
18598
|
-
-
|
|
18599
|
-
-
|
|
18600
|
-
-
|
|
18601
|
-
-
|
|
19801
|
+
- When \`replyToUser(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag using the actual class name and id/path.
|
|
19802
|
+
- Treat \`showObjects(...)\` as the UI display call for user-visible records. When records should remain reusable for follow-ups, first save the runtime record or ordered record array with \`await groundedObjects.save("stable_selection_name", value)\`, then display it once with \`showObjects({ variableNames: ["stable_selection_name"] })\`.
|
|
19803
|
+
- \`groundedObjects.save(...)\` only accepts scalar values, session files, runtime records/sandbox instances, or arrays of runtime records/sandbox instances from one class. Do not save plain action/effect result objects; fetch affected records first or answer from summaries with \`replyToUser(...)\`.
|
|
19804
|
+
- Do not use \`showObjects({ entries: [...] })\` or \`showObjects({ saveAs, entries })\`. Save ordered pages, queues, search results, or ranked lists with \`groundedObjects.save(...)\`, then call \`showObjects({ variableNames: [...] })\` once.
|
|
19805
|
+
- Use \`showAgentResponse({ reply, show: [record, action] })\` when one assistant message should combine text, grounded records, files, prepared actions, or action suggestions. The \`action\` can be a state handle such as \`record.lifecycle.approved\` or an action handle such as \`record.lifecycle.approved.reach()\`.
|
|
19806
|
+
- Pass grounded records directly in \`show\` when the default record presentation answers the request. When the user asks for particular columns, comparisons, or computed values, import \`table\` (and \`relativeTime\` when useful) from \`@granular/agent\` and call \`showAgentResponse({ reply, show: table(records, [{ label: "Object", value: record => record.label }, { label: "When", value: record => relativeTime(record.timestamp) }]) })\`. Column callbacks must be synchronous and return a scalar, \`Date\`, or \`relativeTime(...)\`; they run inside the job and only resolved cells are persisted.
|
|
19807
|
+
- Do not use deprecated side-channel helpers such as \`agent_text_message(...)\`, \`agent_heap_objects(...)\`, or \`agent_message(...)\` unless the generated types expose no Harness v3 helper alternative.
|
|
18602
19808
|
- When the user asks to show, list, display, open, or "show them" for records you found, call \`showObjects(...)\`; do not answer only with a count or text summary.
|
|
18603
19809
|
- For count-only questions such as "how many", "how many X do I have", or "what is the total number of X", call the entity \`.count(...)\` or use page \`totalCount\` only when a page is already needed for other reasons. Answer with \`replyToUser(...)\` only. Do not call \`showObjects(...)\`, \`saveAs\`, or \`groundedObjects.save(...)\` unless the user also asked to see records or a later requested action needs a reusable record selection.
|
|
18604
|
-
-
|
|
18605
|
-
- For ordered record slices, pages, queues, search results, or ranked lists, save the slice with \`groundedObjects.save(...)\` and then call \`showObjects({ variableNames: [...] })\` once. Use a stable name that preserves the slice identity and ordering so later references such as "the second item" or "back on the first slice" resolve to the correct earlier slice, not merely the most recent record.
|
|
19810
|
+
- Use stable saved list names that preserve identity and ordering so later references such as "the second item" or "back on the first slice" resolve to the correct earlier slice, not merely the most recent record.
|
|
18606
19811
|
- 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.` : `- Every job that answers the user must emit \`replyToUser(...)\` from \`@granular/agent\`.
|
|
18607
19812
|
- \`replyToUser(...)\` displays text directly to the user in the host UI. Treat it as the user-facing progress and reply channel, not as a debug log.
|
|
18608
19813
|
- For long-running or multi-step jobs, send several short \`replyToUser(...)\` updates as useful milestones are reached so the user can see what is happening instead of waiting in silence.
|
|
@@ -18613,7 +19818,7 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
18613
19818
|
- When using code, assistant text must be empty or one brief summary.
|
|
18614
19819
|
- Code must be plain runnable JavaScript with top-level await.
|
|
18615
19820
|
- Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
|
|
18616
|
-
- Use static top-level imports such as \`import { Foo } from "@granular/domain/Foo"; import { replyToUser } from "@granular/agent";\`. Do not use dynamic imports for runtime modules.
|
|
19821
|
+
- Use static top-level named imports such as \`import { Foo } from "@granular/domain/Foo"; import { replyToUser, artifacts } from "@granular/agent";\`. Do not use dynamic imports or namespace imports like \`import * as agent from "@granular/agent"\` for runtime modules.
|
|
18617
19822
|
- Read and write session files through the virtual filesystem modules listed in [Runtime Imports]. Input files are mounted under \`/session/input\`; files written under \`/session/output\` are persisted as agent-created session files.
|
|
18618
19823
|
- Do not ask the user to provide virtual filesystem paths. Users attach or mention files by name in the UI; resolve the right file from \`sessionFileManifest.files\` or the current attachment context, then use its provided path internally.
|
|
18619
19824
|
- The \`sessionFileManifest\` block is prompt context, not an imported module or runtime variable. For dynamic file lookup, import \`files\` from \`@granular/session\` and match \`filename\` to a returned file's \`path\`.
|
|
@@ -18676,6 +19881,7 @@ ${workflowRules}
|
|
|
18676
19881
|
High-priority execution rules:
|
|
18677
19882
|
- Treat a human reference as something to ground, not as missing data. When the user names or describes a record, group, queue, parent, relationship, or prior result and asks to inspect, decide, update, schedule, approve, send, or otherwise act on session data, run a code job to ground it before asking the user for more details.
|
|
18678
19883
|
- For a human-described primary anchor, a no-match answer is only justified after more than one distinct grounding attempt, such as owner/container grounding, relationship traversal, exact id/path lookup, or shorter target-local search. Before the primary no-match return, retry that same anchor with fewer text constraints or a distinct grounding strategy; do not stop after one zero-result list/find/page call.
|
|
19884
|
+
- If the latest user wording names an entity type that has an importable class, ground that class first. A previously installed parent class or related action method is not a substitute; after zero results in one class, pivot to the latest named importable class before reporting no match.
|
|
18679
19885
|
- A confirmation requirement is not a reason to stay text-only. Do all safe read-only grounding and availability/status checks first, then call \`userInteraction.askConfirmation(...)\` or \`userInteraction.askChoice(...)\` before the mutation.
|
|
18680
19886
|
- In any code branch where a requested action or mutation has multiple possible targets, import \`userInteraction\` from \`@granular/session\` and use \`await userInteraction.askChoice(...)\` in that branch. This includes ambiguity discovered after a query returns several records. A branch that only shows candidates, asks in text, and returns leaves the requested action unfinished.
|
|
18681
19887
|
- Before any mutation, know whether the target is one record or several. A singular phrase like "the item" is not proof of uniqueness after a query finds multiple matching records. If the user did not give an exact identifier or explicit selection criterion, call \`userInteraction.askChoice({ options, ... })\` with grounded choices; do not choose by age, amount, priority, order, or convenience on your own. Resolve the target before any yes/no confirmation.
|
|
@@ -18719,7 +19925,7 @@ Intent resolution:
|
|
|
18719
19925
|
- For follow-up words like "other", "another", or "remaining" after the user selected one candidate from a previous choice, resolve within the active contrast from that choice and the user's answer. Exclude the selected item, preserve descriptors such as larger, smaller, next, older, different, or same status, and do not take the first leftover from a wider saved list when the contrast narrows the intended set.
|
|
18720
19926
|
- Before any mutation, prove the target resolves to exactly one grounded record. If the request describes a set, category, relationship, prior result group, or other non-unique scope, gather the candidate records first; when more than one candidate remains, ask the user to choose before calling the action.
|
|
18721
19927
|
- For ambiguous choice prompts before a mutation, every option that describes a different candidate must carry a distinct grounded record value/path. After the answer, do not fall back to the first candidate if matching fails; ask again or stop without mutating.
|
|
18722
|
-
- The [State] constants and [Runtime Imports] map are prompt context, not runtime variables. Never reference \`runtimeImports\`, \`savedData\`, \`sessionFileManifest\`, \`recentReferences\`, \`workflowContext\`, \`workflowState\`, or \`capabilities\` as variables in generated code. When using a recent reference or file path, copy
|
|
19928
|
+
- The [State] constants and [Runtime Imports] map are prompt context, not runtime variables. Never reference \`runtimeImports\`, \`savedData\`, \`manualActionMemory\`, \`sessionFileManifest\`, \`recentReferences\`, \`workflowContext\`, \`workflowState\`, or \`capabilities\` as variables in generated code. When using a recent reference, manual-action pattern, or file path, copy concrete ids/values into code and fetch/read it with the relevant runtime API.
|
|
18723
19929
|
- Never write placeholder grounding code such as \`const path = null\`, \`const groundedPath = ""\`, or \`const recordPath = ""\`. If no saved reference is available, delete that branch entirely and execute the fallback lookup directly.
|
|
18724
19930
|
- Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
|
|
18725
19931
|
- For ordinal references to earlier pages, slices, lists, or ranked results, use the saved list/recent references first. If no saved list is available, rerun the exact same ordered query and select the ordinal index from its returned \`items\`; never invent a record path from a label or ordinal.
|
|
@@ -18846,20 +20052,10 @@ Ask the user when:
|
|
|
18846
20052
|
- the target is unique but the requested action is unclear
|
|
18847
20053
|
|
|
18848
20054
|
Relationship filters:
|
|
18849
|
-
-
|
|
18850
|
-
-
|
|
18851
|
-
-
|
|
18852
|
-
-
|
|
18853
|
-
- Do not invent nested operators under relationship fields. A one-record relationship filter accepts only its documented operators such as \`id\`, \`path\`, \`is\`, \`null\`, and \`not_null\`; deeper conditions must go under \`is\` or be handled by fetching records and following getters.
|
|
18854
|
-
- Never use \`some\` on one-record fields. If the generated TypeScript type says \`OneRelationFilter\`, valid operators are \`id\`, \`path\`, \`is\`, \`null\`, and \`not_null\`; \`some\` is invalid.
|
|
18855
|
-
- Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
|
|
18856
|
-
- For a singular relationship that points to an intermediate record, nested filters still use \`is\` at the singular hop. Do not use \`some\` because the nested condition names another related record.
|
|
18857
|
-
- Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
|
|
18858
|
-
- When you already fetched the related record, use \`{ relationship: { path: record._graphPath } }\` or \`{ relationship: { id: record.id } }\`; do not wrap a known id/path under \`is\`.
|
|
18859
|
-
- The path used in a relationship filter must be the path of the relationship target. For same-queue follow-ups from an item/batch/ticket, fetch that item's related unit/site/depot first and use the related unit/site/depot path; do not use the item path as a unit/site/depot path.
|
|
18860
|
-
- Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
|
|
18861
|
-
- Do not write \`{ relationship: { some: ... } }\` unless the generated filter type for that exact relationship says it is a many/collection relationship. For one-record, parent, owner, or many-to-one relationships, use \`path\`, \`id\`, \`is\`, or getter traversal.
|
|
18862
|
-
- Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
|
|
20055
|
+
- Use the generated filter type as the authority: \`OneRelationFilter\` supports \`id\`, \`path\`, \`is\`, \`null\`, \`not_null\`; \`ManyRelationFilter\` supports those plus \`some\`.
|
|
20056
|
+
- Use \`id\` or \`path\` for a known related record; use \`is\` or \`some\` only for nested target-field filters.
|
|
20057
|
+
- Relationship paths must belong to the relationship target type. If you have a parent/container/item of another type, fetch the declared related record first and filter with that related record's id/path.
|
|
20058
|
+
- Never pass a full record instance into a filter. Use its id/path or a declared relationship getter.
|
|
18863
20059
|
${domainSections.docs ? `
|
|
18864
20060
|
Domain notes:
|
|
18865
20061
|
${domainSections.docs}
|
|
@@ -18870,6 +20066,24 @@ ${actionIndex}
|
|
|
18870
20066
|
- Global backend actions are executable functions exported by \`@granular/actions/backend\`; import each backend action you call, e.g. \`import { some_action } from "@granular/actions/backend"; await some_action(...)\`. Frontend actions are exported by \`@granular/actions/frontend\` when the action index marks them as frontend actions.
|
|
18871
20067
|
- Actions listed under "Record-level" are instance methods. First fetch or find the specific record, then call the action on that instance, e.g. \`const item = await Item.get({ path }); await item.action_name(...)\`.
|
|
18872
20068
|
- Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
|
|
20069
|
+
- State-machine handles are the preferred workflow surface. For an existing record, use \`record.lifecycle.targetState\`; for a not-yet-created record, use class-level handles such as \`SpendRequest.lifecycle.policy_review\` only to prepare a provisional new-record action flow.
|
|
20070
|
+
- For pure field-collection requests, target the class-level entry state handle; for submit/review requests, target the nearest requested later state.
|
|
20071
|
+
- Choose the nearest target state that matches the user's words. Do not aim at a later state just because it is reachable.
|
|
20072
|
+
- For grounded stateful records, choose the target state handle from the user's requested outcome and handle docs. Use \`plan()\`, \`blockers()\`, or \`permissions()\` to explain reachability, missing requirements, stale state, and who can run the action; do not derive workflow meaning or adjacent actions from raw \`record.status\` or \`current().state\`.
|
|
20073
|
+
- Before opening a stateful action, call \`const plan = await handle.plan()\`. If the plan or opened artifact shows blockers, stale state, missing relationships, permissions, or no reachable path, explain those structured results and display the record/artifact. Do not skip to a later state or build your own status ladder.
|
|
20074
|
+
- Use typed plan fields and helpers directly. For visible blocker text, use the declared blocker-formatting helper when available instead of hand-written object casts.
|
|
20075
|
+
- A suggestion is a recommendation button: \`await stateHandle.suggest(message)\`. A prepared action is the editable form the user can review/run: \`const action = stateHandle.reach(); const prepared = await action.open()\`.
|
|
20076
|
+
- Use suggestions when the user asks "what can I do next?", asks for options, or gives an unclear intent. Prefer 2 to 5 concrete suggestions and keep text short.
|
|
20077
|
+
- Opening a prepared action does not execute the underlying mutation. Use \`.open()\` only when the user asks to prepare, review, show, or edit an action before running it, when a new record must be prepared, or when a reviewable form must collect missing editable inputs. Prefill only grounded values and leave unknown fields empty.
|
|
20078
|
+
- When the user explicitly commands an existing-record mutation such as approve, reject, block, route, send, or update, and one visible domain action uniquely matches, call that domain action directly. Do not substitute a state-handle \`.open()\` artifact for execution. If runtime policy requires confirmation, invoke the action once and let the runtime pause and resume that same invocation.
|
|
20079
|
+
- When a grounded related/context record can satisfy the prepared action through declared relationships, use those relationships to fill required relationship inputs before opening or updating the action. If a required relationship remains empty, continue through declared relationship chains from the grounded object when the next hop can fill that slot. Do not only save the context record in memory while leaving derivable relationship slots blank.
|
|
20080
|
+
- A derived intermediate relationship is not enough when another required relationship is still reachable from it. For example, if a team gives a cost center and the action also requires a budget, traverse the cost center's declared budget relationship before opening the action.
|
|
20081
|
+
- If the user says they have a document/work item/event but no matching record is found, check for a declared class-level new-record state/action handle or importable backend create/preparation action for that named class before giving up. Use grounded required fields to open the prepared action or call the create/preparation action; if required values are still missing and no prepared action can collect them, ask only for those values. Do not claim the record already exists.
|
|
20082
|
+
- Do not ask for confirmation before opening a prepared action requested for review; the artifact is itself reviewable. For a direct mutation command, do not open an artifact merely to obtain confirmation. Use \`userInteraction.askConfirmation\` only when the user explicitly asks for a separate yes/no step, material ambiguity remains after grounding, or policy requires confirmation outside the invoked action runtime.
|
|
20083
|
+
- If the action cannot continue because of missing input, stale state, missing relationships, related-state requirements, or permissions, keep/show the prepared action at that blocker and explain the next needed person, record, or value. Do not skip workflow steps or target a later state.
|
|
20084
|
+
- Reuse an already-open prepared action for the same target/action when available: update it, show it again, or explain what is still needed instead of creating a duplicate.
|
|
20085
|
+
- If an open prepared action needs edits, prefer the returned record helper: \`const prepared = await action.open(); await prepared.updateInputs({ inputValues, relationships });\`. Use \`artifacts.updateInputs(id, patch)\` only when you only have an id.
|
|
20086
|
+
- Use \`await prepared.show()\` or \`await actions.show(prepared)\` only to display an already-created prepared action again.
|
|
18873
20087
|
- The action index is the visibility contract. If an action is listed for a class, call it directly on fetched/listed instances of that class; do not use \`typeof record.action_name === "function"\` as a discovery gate. If an action is not listed, do not call it.
|
|
18874
20088
|
- Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
|
|
18875
20089
|
- For action inputs, use the exact property names from the generated TypeScript method signature or the input schema shown in the action list. Do not invent synonym fields for required inputs.
|
|
@@ -18900,6 +20114,8 @@ ${loopBlock}
|
|
|
18900
20114
|
|
|
18901
20115
|
${knownFactsBlock}
|
|
18902
20116
|
|
|
20117
|
+
${manualActionBlock}
|
|
20118
|
+
|
|
18903
20119
|
[Request]
|
|
18904
20120
|
${input.request?.trim() || "Use the latest user message in the conversation."}`;
|
|
18905
20121
|
}
|
|
@@ -19055,8 +20271,9 @@ function resolveHarnessTemplate(templateId = "stable", options) {
|
|
|
19055
20271
|
}
|
|
19056
20272
|
|
|
19057
20273
|
// src/openai-usage.ts
|
|
19058
|
-
var
|
|
19059
|
-
var
|
|
20274
|
+
var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
20275
|
+
var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
|
|
20276
|
+
var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
|
|
19060
20277
|
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
19061
20278
|
"gpt-5.4": {
|
|
19062
20279
|
provider: "openai",
|
|
@@ -19065,8 +20282,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
|
19065
20282
|
inputUsdPerMillion: 2.5,
|
|
19066
20283
|
cachedInputUsdPerMillion: 0.25,
|
|
19067
20284
|
outputUsdPerMillion: 15,
|
|
19068
|
-
sourceUrl:
|
|
19069
|
-
effectiveDate:
|
|
20285
|
+
sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
|
|
20286
|
+
effectiveDate: "2026-05-19"
|
|
20287
|
+
},
|
|
20288
|
+
"gpt-5.6-luna": {
|
|
20289
|
+
provider: "openai",
|
|
20290
|
+
model: "gpt-5.6-luna",
|
|
20291
|
+
currency: "USD",
|
|
20292
|
+
inputUsdPerMillion: 1,
|
|
20293
|
+
cachedInputUsdPerMillion: 0.1,
|
|
20294
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
20295
|
+
outputUsdPerMillion: 6,
|
|
20296
|
+
sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
|
|
20297
|
+
effectiveDate: "2026-07-11",
|
|
20298
|
+
longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
|
|
20299
|
+
longContextPricing: {
|
|
20300
|
+
inputUsdPerMillion: 2,
|
|
20301
|
+
cachedInputUsdPerMillion: 0.2,
|
|
20302
|
+
cacheWriteUsdPerMillion: 2.5,
|
|
20303
|
+
outputUsdPerMillion: 9
|
|
20304
|
+
}
|
|
19070
20305
|
}
|
|
19071
20306
|
};
|
|
19072
20307
|
function asRecord5(value) {
|
|
@@ -19079,8 +20314,18 @@ function numberField(record, key) {
|
|
|
19079
20314
|
function microsPerMillion(usdPerMillion) {
|
|
19080
20315
|
return Math.round(usdPerMillion * 1e6);
|
|
19081
20316
|
}
|
|
19082
|
-
function getOpenAIModelPricing(model) {
|
|
19083
|
-
|
|
20317
|
+
function getOpenAIModelPricing(model, inputTokens = 0) {
|
|
20318
|
+
const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
|
|
20319
|
+
if (!pricing) return null;
|
|
20320
|
+
const threshold = pricing.longContextThresholdTokens ?? null;
|
|
20321
|
+
if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
|
|
20322
|
+
return {
|
|
20323
|
+
...pricing,
|
|
20324
|
+
...pricing.longContextPricing,
|
|
20325
|
+
contextTier: "long"
|
|
20326
|
+
};
|
|
20327
|
+
}
|
|
20328
|
+
return { ...pricing, contextTier: "short" };
|
|
19084
20329
|
}
|
|
19085
20330
|
function normalizeOpenAIUsage(rawUsage) {
|
|
19086
20331
|
const usage = asRecord5(rawUsage);
|
|
@@ -19088,6 +20333,7 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
19088
20333
|
return {
|
|
19089
20334
|
inputTokens: 0,
|
|
19090
20335
|
cachedInputTokens: 0,
|
|
20336
|
+
cacheWriteTokens: 0,
|
|
19091
20337
|
uncachedInputTokens: 0,
|
|
19092
20338
|
outputTokens: 0,
|
|
19093
20339
|
reasoningTokens: 0,
|
|
@@ -19103,20 +20349,28 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
19103
20349
|
inputTokens,
|
|
19104
20350
|
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
19105
20351
|
);
|
|
20352
|
+
const cacheWriteTokens = Math.min(
|
|
20353
|
+
Math.max(inputTokens - cachedInputTokens, 0),
|
|
20354
|
+
numberField(inputDetails, "cache_write_tokens")
|
|
20355
|
+
);
|
|
19106
20356
|
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
19107
20357
|
return {
|
|
19108
20358
|
inputTokens,
|
|
19109
20359
|
cachedInputTokens,
|
|
19110
|
-
|
|
20360
|
+
cacheWriteTokens,
|
|
20361
|
+
uncachedInputTokens: Math.max(
|
|
20362
|
+
inputTokens - cachedInputTokens - cacheWriteTokens,
|
|
20363
|
+
0
|
|
20364
|
+
),
|
|
19111
20365
|
outputTokens,
|
|
19112
20366
|
reasoningTokens,
|
|
19113
20367
|
totalTokens
|
|
19114
20368
|
};
|
|
19115
20369
|
}
|
|
19116
20370
|
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
19117
|
-
const pricing = getOpenAIModelPricing(model);
|
|
19118
|
-
if (!pricing) return null;
|
|
19119
20371
|
const usage = normalizeOpenAIUsage(rawUsage);
|
|
20372
|
+
const pricing = getOpenAIModelPricing(model, usage.inputTokens);
|
|
20373
|
+
if (!pricing) return null;
|
|
19120
20374
|
const inputPricePerMillionMicros = microsPerMillion(
|
|
19121
20375
|
pricing.inputUsdPerMillion
|
|
19122
20376
|
);
|
|
@@ -19126,14 +20380,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
19126
20380
|
const outputPricePerMillionMicros = microsPerMillion(
|
|
19127
20381
|
pricing.outputUsdPerMillion
|
|
19128
20382
|
);
|
|
20383
|
+
const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
|
|
20384
|
+
const cacheWriteCostMicros = Math.round(
|
|
20385
|
+
usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
|
|
20386
|
+
);
|
|
19129
20387
|
const amountMicros = Math.round(
|
|
19130
|
-
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20388
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
19131
20389
|
);
|
|
19132
20390
|
return {
|
|
19133
20391
|
provider: "openai",
|
|
19134
20392
|
model,
|
|
19135
20393
|
inputTokens: usage.inputTokens,
|
|
19136
20394
|
cachedInputTokens: usage.cachedInputTokens,
|
|
20395
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
19137
20396
|
uncachedInputTokens: usage.uncachedInputTokens,
|
|
19138
20397
|
outputTokens: usage.outputTokens,
|
|
19139
20398
|
reasoningTokens: usage.reasoningTokens,
|
|
@@ -19142,7 +20401,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
19142
20401
|
currency: "USD",
|
|
19143
20402
|
inputPricePerMillionMicros,
|
|
19144
20403
|
cachedInputPricePerMillionMicros,
|
|
20404
|
+
cacheWritePricePerMillionMicros,
|
|
20405
|
+
cacheWriteCostMicros,
|
|
19145
20406
|
outputPricePerMillionMicros,
|
|
20407
|
+
pricingContextTier: pricing.contextTier || "short",
|
|
20408
|
+
longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
|
|
19146
20409
|
pricingSource: pricing.sourceUrl,
|
|
19147
20410
|
pricingEffectiveAt: pricing.effectiveDate,
|
|
19148
20411
|
usage
|
|
@@ -19650,6 +20913,8 @@ function modelOutputInstruction() {
|
|
|
19650
20913
|
'Do not use "action":"reply" to say a record is not grounded yet; if the request names or describes a domain record, use "action":"job" and ground it from session state, relationships, searches, or visible read-only actions first.',
|
|
19651
20914
|
'Before claiming you lack access, inspect the visible action list. If a visible read-only search, lookup, list, guidance, note, policy, or knowledge action can satisfy a "check", "find", "look up", or "whether we have guidance" request, choose "action":"job" and call it.',
|
|
19652
20915
|
"Generated code must not report no matches for the primary human-described anchor after a single zero-result list/find/page call. Before that primary no-match return, retry the primary anchor with fewer text constraints or a distinct fallback such as owner/container grounding, relationship traversal, exact-id/path lookup, or shorter target-local search.",
|
|
20916
|
+
"If the latest request names an importable entity type, generated code must ground that class first; a previously installed parent class or related action method is not a substitute. After zero results in one class, pivot to the latest named importable class before reporting no match.",
|
|
20917
|
+
"When the user says they have a document/work item/event but no matching record exists yet, generated code must check the visible class-level new-record/create/preparation surface before returning no-match; if required create inputs are still missing and no prepared action can collect them, ask only for those inputs.",
|
|
19653
20918
|
'Use "action":"job" when the next step should run code or mutate workflow state.',
|
|
19654
20919
|
'When action is "job", include runnable code in "code" and emit the "code" field before any non-empty "reply" field so generated code comments can stream as progress.',
|
|
19655
20920
|
"Generated job code must use plain ASCII punctuation in string literals and comments. Do not use curly quotes, smart apostrophes, en dashes, em dashes, or other typographic punctuation in code.",
|
|
@@ -19719,7 +20984,12 @@ function createOpenAIChatTurnGenerator(options) {
|
|
|
19719
20984
|
|
|
19720
20985
|
${modelOutputInstruction()}`
|
|
19721
20986
|
},
|
|
19722
|
-
...input.history
|
|
20987
|
+
...input.history.map(
|
|
20988
|
+
(message) => ({
|
|
20989
|
+
role: message.role,
|
|
20990
|
+
content: message.content
|
|
20991
|
+
})
|
|
20992
|
+
),
|
|
19723
20993
|
{ role: "user", content: input.request }
|
|
19724
20994
|
];
|
|
19725
20995
|
const payload = {
|
|
@@ -19738,11 +21008,12 @@ ${modelOutputInstruction()}`
|
|
|
19738
21008
|
let usage = null;
|
|
19739
21009
|
let requestId = null;
|
|
19740
21010
|
if (input.onTextDelta || input.onReplyDelta || input.onCodeDelta) {
|
|
19741
|
-
const
|
|
21011
|
+
const streamPayload = {
|
|
19742
21012
|
...payload,
|
|
19743
21013
|
stream: true,
|
|
19744
21014
|
stream_options: { include_usage: true }
|
|
19745
|
-
}
|
|
21015
|
+
};
|
|
21016
|
+
const stream = await client.chat.completions.create(streamPayload);
|
|
19746
21017
|
let streamedReply = "";
|
|
19747
21018
|
let streamedCode = "";
|
|
19748
21019
|
const emitReplyDelta = async () => {
|
|
@@ -19768,7 +21039,8 @@ ${modelOutputInstruction()}`
|
|
|
19768
21039
|
await input.onCodeDelta(delta);
|
|
19769
21040
|
};
|
|
19770
21041
|
for await (const event of stream) {
|
|
19771
|
-
|
|
21042
|
+
const eventRecord = event;
|
|
21043
|
+
requestId = requestId || event.id || (typeof eventRecord._request_id === "string" ? eventRecord._request_id : null);
|
|
19772
21044
|
usage = event.usage || usage;
|
|
19773
21045
|
const delta = event.choices?.[0]?.delta?.content;
|
|
19774
21046
|
const deltaText = typeof delta === "string" ? delta : Array.isArray(delta) ? delta.map((part) => asRecord6(part)?.text || "").join("") : "";
|
|
@@ -19782,14 +21054,13 @@ ${modelOutputInstruction()}`
|
|
|
19782
21054
|
await emitCodeDelta();
|
|
19783
21055
|
raw = { streamed: true, model, usage, request_id: requestId };
|
|
19784
21056
|
} else {
|
|
19785
|
-
const completion = await client.chat.completions.create(
|
|
19786
|
-
|
|
19787
|
-
);
|
|
21057
|
+
const completion = await client.chat.completions.create(payload);
|
|
21058
|
+
const completionRecord = completion;
|
|
19788
21059
|
raw = completion;
|
|
19789
21060
|
usage = completion.usage;
|
|
19790
|
-
requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof
|
|
21061
|
+
requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof completionRecord._request_id === "string" ? completionRecord._request_id : null);
|
|
19791
21062
|
const content = asRecord6(
|
|
19792
|
-
asRecord6(completion.choices
|
|
21063
|
+
asRecord6(completion.choices[0])?.message
|
|
19793
21064
|
)?.content;
|
|
19794
21065
|
text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord6(part)?.text || "").join("") : "";
|
|
19795
21066
|
}
|
|
@@ -20126,6 +21397,9 @@ async function generateTurnWithRepair(generator, input) {
|
|
|
20126
21397
|
"",
|
|
20127
21398
|
"The previous generated job code failed preflight review against [Runtime Imports] and the runtime contract.",
|
|
20128
21399
|
"Return a corrected JSON object. Keep the user's requested behavior, but fix every issue below before execution.",
|
|
21400
|
+
"If an issue says workflow path, blockers, permissions, or readiness were derived from raw status fields, remove the status/current-state ladder. Pick the declared state handle that matches the user's requested outcome, call plan()/blockers()/permissions() first, then open/show that handle or explain its structured blockers.",
|
|
21401
|
+
"If an issue says an unsafe type assertion was used, remove the cast and use declared fields or typed helpers directly. For state plans, use plan.blockers, plan.permissions, plan.steps, plan.summary, or blocker-formatting helpers without hand-written object casts.",
|
|
21402
|
+
"If the previous code stopped after no matching record for a user-supplied document/work item, preserve the lookup but check declared new-record/create/preparation surfaces next. Import the backend action module if needed, or ask only for missing required create inputs when no prepared action can collect them.",
|
|
20129
21403
|
"",
|
|
20130
21404
|
"Preflight issues:",
|
|
20131
21405
|
...issues.map((issue) => `- ${issue.code}: ${issue.message}`),
|