@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/dist/index.mjs CHANGED
@@ -22,11 +22,11 @@ var __export = (target, all) => {
22
22
  for (var name in all)
23
23
  __defProp(target, name, { get: all[name], enumerable: true });
24
24
  };
25
- var __copyProps = (to, from, except, desc) => {
26
- if (from && typeof from === "object" || typeof from === "function") {
27
- for (let key of __getOwnPropNames(from))
25
+ var __copyProps = (to, from2, except, desc) => {
26
+ if (from2 && typeof from2 === "object" || typeof from2 === "function") {
27
+ for (let key of __getOwnPropNames(from2))
28
28
  if (!__hasOwnProp.call(to, key) && key !== except)
29
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
+ __defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
30
30
  }
31
31
  return to;
32
32
  };
@@ -4016,6 +4016,9 @@ function rpcTimeoutMsForMethod(method) {
4016
4016
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
4017
4017
  case "client.heartbeat":
4018
4018
  case "effects.publishCatalog":
4019
+ case "effects.resetCatalog":
4020
+ case "effects.addCatalog":
4021
+ case "effects.removeCatalog":
4019
4022
  case "effects.refresh":
4020
4023
  return EFFECT_CONTROL_RPC_TIMEOUT_MS;
4021
4024
  case "harness.run":
@@ -4040,16 +4043,37 @@ var WSClient = class {
4040
4043
  tokenRefreshTimer = null;
4041
4044
  isExplicitlyDisconnected = false;
4042
4045
  reconnectAttempts = 0;
4046
+ connectPromise = null;
4047
+ connectionEpoch = 0;
4048
+ cancelConnectAttempt = null;
4043
4049
  options;
4044
4050
  constructor(options) {
4045
4051
  this.options = options;
4046
4052
  this.url = options.url;
4047
4053
  this.sessionId = options.sessionId;
4048
4054
  this.token = options.token;
4055
+ if (options.initialDocumentSnapshot) {
4056
+ this.seedDocumentSnapshot(options.initialDocumentSnapshot);
4057
+ }
4049
4058
  }
4050
4059
  get currentSessionId() {
4051
4060
  return this.sessionId;
4052
4061
  }
4062
+ seedDocumentSnapshot(document) {
4063
+ if (!document || typeof document !== "object" || Array.isArray(document)) {
4064
+ return;
4065
+ }
4066
+ try {
4067
+ this.doc = document instanceof Uint8Array ? Automerge.load(document) : Automerge.from(document);
4068
+ this.syncState = Automerge.initSyncState();
4069
+ this.emit("sync", this.doc);
4070
+ } catch (error) {
4071
+ console.warn("[Granular] Failed to seed cached session document", error);
4072
+ }
4073
+ }
4074
+ saveDocumentSnapshot() {
4075
+ return Automerge.save(this.doc);
4076
+ }
4053
4077
  clearTokenRefreshTimer() {
4054
4078
  if (this.tokenRefreshTimer) {
4055
4079
  clearTimeout(this.tokenRefreshTimer);
@@ -4159,8 +4183,23 @@ var WSClient = class {
4159
4183
  * Connect to the WebSocket server
4160
4184
  * @returns {Promise<void>} Resolves when connection is open
4161
4185
  */
4162
- async connect() {
4186
+ async connect(options = {}) {
4187
+ if (this.ws?.readyState === READY_STATE_OPEN) return;
4188
+ if (this.connectPromise) return this.connectPromise;
4189
+ const connectPromise = this.connectAttempt(options.signal);
4190
+ this.connectPromise = connectPromise;
4191
+ try {
4192
+ await connectPromise;
4193
+ } finally {
4194
+ if (this.connectPromise === connectPromise) {
4195
+ this.connectPromise = null;
4196
+ }
4197
+ }
4198
+ }
4199
+ async connectAttempt(signal) {
4200
+ if (signal?.aborted) throw new Error("WebSocket connect aborted");
4163
4201
  const token = await this.resolveTokenForConnect();
4202
+ if (signal?.aborted) throw new Error("WebSocket connect aborted");
4164
4203
  this.isExplicitlyDisconnected = false;
4165
4204
  this.scheduleTokenRefresh();
4166
4205
  if (this.reconnectTimer) {
@@ -4172,7 +4211,7 @@ var WSClient = class {
4172
4211
  try {
4173
4212
  const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
4174
4213
  WebSocketClass = wsModule.default || wsModule;
4175
- } catch (e) {
4214
+ } catch {
4176
4215
  }
4177
4216
  }
4178
4217
  if (!WebSocketClass) {
@@ -4180,83 +4219,97 @@ var WSClient = class {
4180
4219
  'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
4181
4220
  );
4182
4221
  }
4222
+ const epoch = ++this.connectionEpoch;
4223
+ const wsUrl = new URL(this.url);
4224
+ wsUrl.searchParams.set("sessionId", this.sessionId);
4225
+ wsUrl.searchParams.set("token", token);
4226
+ const socket = new WebSocketClass(wsUrl.toString());
4227
+ this.ws = socket;
4183
4228
  return new Promise((resolve, reject) => {
4184
- try {
4185
- const wsUrl = new URL(this.url);
4186
- wsUrl.searchParams.set("sessionId", this.sessionId);
4187
- wsUrl.searchParams.set("token", token);
4188
- this.ws = new WebSocketClass(wsUrl.toString());
4189
- if (!this.ws) throw new Error("Failed to create WebSocket");
4190
- const socket = this.ws;
4191
- if (typeof socket.on === "function") {
4192
- socket.on("open", () => {
4193
- if (this.reconnectTimer) {
4194
- clearTimeout(this.reconnectTimer);
4195
- this.reconnectTimer = null;
4196
- }
4197
- this.reconnectAttempts = 0;
4198
- this.emit("open", {});
4199
- resolve();
4200
- });
4201
- socket.on("message", (data) => {
4202
- try {
4203
- const message = JSON.parse(data.toString());
4204
- this.handleMessage(message);
4205
- } catch (error) {
4206
- console.error("[Granular] Failed to parse message:", error);
4207
- }
4208
- });
4209
- socket.on("error", (error) => {
4210
- this.emit("error", error);
4211
- if (socket.readyState !== READY_STATE_OPEN) {
4212
- reject(error);
4213
- }
4214
- });
4215
- socket.on("close", (code, reason) => {
4216
- this.handleDisconnect({
4217
- code,
4218
- reason: this.normalizeReason(reason),
4219
- // ws does not provide wasClean on Node-style close callback
4220
- wasClean: code === 1e3
4221
- });
4222
- });
4229
+ let settled = false;
4230
+ const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
4231
+ const finish = (error) => {
4232
+ if (settled) return;
4233
+ settled = true;
4234
+ if (this.cancelConnectAttempt === handleAbort) {
4235
+ this.cancelConnectAttempt = null;
4236
+ }
4237
+ signal?.removeEventListener("abort", handleAbort);
4238
+ if (error) {
4239
+ reject(error instanceof Error ? error : new Error(String(error)));
4223
4240
  } else {
4224
- this.ws.onopen = () => {
4225
- if (this.reconnectTimer) {
4226
- clearTimeout(this.reconnectTimer);
4227
- this.reconnectTimer = null;
4228
- }
4229
- this.reconnectAttempts = 0;
4230
- this.emit("open", {});
4231
- resolve();
4232
- };
4233
- this.ws.onmessage = (event) => {
4234
- try {
4235
- const data = event.data;
4236
- const message = JSON.parse(data.toString());
4237
- this.handleMessage(message);
4238
- } catch (error) {
4239
- console.error("[Granular] Failed to parse message:", error);
4240
- }
4241
- };
4242
- this.ws.onerror = (event) => {
4243
- const error = new Error("WebSocket error");
4244
- error.event = event;
4245
- this.emit("error", error);
4246
- if (this.ws?.readyState !== READY_STATE_OPEN) {
4247
- reject(error);
4248
- }
4249
- };
4250
- this.ws.onclose = (event) => {
4251
- this.handleDisconnect({
4252
- code: event.code,
4253
- reason: event.reason,
4254
- wasClean: event.wasClean
4255
- });
4256
- };
4241
+ resolve();
4257
4242
  }
4258
- } catch (error) {
4259
- reject(error);
4243
+ };
4244
+ const closeStaleSocket = () => {
4245
+ try {
4246
+ socket.close(1e3, "Stale connection attempt");
4247
+ } catch {
4248
+ }
4249
+ };
4250
+ const handleAbort = () => {
4251
+ if (isCurrent()) {
4252
+ this.connectionEpoch += 1;
4253
+ this.ws = null;
4254
+ }
4255
+ closeStaleSocket();
4256
+ finish(new Error("WebSocket connect aborted"));
4257
+ };
4258
+ this.cancelConnectAttempt = handleAbort;
4259
+ const handleOpen = () => {
4260
+ if (!isCurrent()) {
4261
+ closeStaleSocket();
4262
+ return;
4263
+ }
4264
+ this.reconnectAttempts = 0;
4265
+ this.emit("open", {});
4266
+ finish();
4267
+ };
4268
+ const handleMessage = (data) => {
4269
+ if (!isCurrent()) return;
4270
+ try {
4271
+ const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
4272
+ this.handleMessage(JSON.parse(text));
4273
+ } catch (error) {
4274
+ console.error("[Granular] Failed to parse message:", error);
4275
+ }
4276
+ };
4277
+ const handleError = (error) => {
4278
+ if (!isCurrent()) return;
4279
+ const typedError = error instanceof Error ? error : new Error("WebSocket error");
4280
+ this.emit("error", typedError);
4281
+ if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
4282
+ };
4283
+ const handleClose = (close) => {
4284
+ if (!isCurrent()) return;
4285
+ if (!settled) {
4286
+ finish(
4287
+ new Error(
4288
+ `WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
4289
+ )
4290
+ );
4291
+ }
4292
+ this.handleDisconnect({
4293
+ code: close.code,
4294
+ reason: this.normalizeReason(close.reason),
4295
+ wasClean: close.wasClean
4296
+ });
4297
+ };
4298
+ signal?.addEventListener("abort", handleAbort, { once: true });
4299
+ const nodeSocket = socket;
4300
+ if (typeof nodeSocket.on === "function") {
4301
+ nodeSocket.on("open", handleOpen);
4302
+ nodeSocket.on("message", handleMessage);
4303
+ nodeSocket.on("error", handleError);
4304
+ nodeSocket.on(
4305
+ "close",
4306
+ (code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
4307
+ );
4308
+ } else {
4309
+ socket.onopen = handleOpen;
4310
+ socket.onmessage = (event) => handleMessage(event.data);
4311
+ socket.onerror = handleError;
4312
+ socket.onclose = (event) => handleClose(event);
4260
4313
  }
4261
4314
  });
4262
4315
  }
@@ -4274,9 +4327,58 @@ var WSClient = class {
4274
4327
  return void 0;
4275
4328
  }
4276
4329
  rejectPending(error) {
4277
- this.messageQueue.forEach((pending) => pending.reject(error));
4330
+ this.messageQueue.forEach((pending) => {
4331
+ clearTimeout(pending.timeout);
4332
+ pending.reject(error);
4333
+ });
4278
4334
  this.messageQueue = [];
4279
4335
  }
4336
+ emitReconnectErrorMessage(error) {
4337
+ const reconnectInfo = {
4338
+ error,
4339
+ sessionId: this.sessionId,
4340
+ timestamp: Date.now()
4341
+ };
4342
+ this.emit("reconnect_error", reconnectInfo);
4343
+ if (this.options.onReconnectError) {
4344
+ try {
4345
+ this.options.onReconnectError(reconnectInfo);
4346
+ } catch (callbackError) {
4347
+ console.error(
4348
+ "[Granular] onReconnectError callback failed:",
4349
+ callbackError
4350
+ );
4351
+ }
4352
+ }
4353
+ }
4354
+ scheduleReconnectAttempt() {
4355
+ if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
4356
+ const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
4357
+ 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;
4358
+ if (this.reconnectAttempts >= maxReconnectAttempts) {
4359
+ this.emitReconnectErrorMessage(
4360
+ `WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
4361
+ );
4362
+ return null;
4363
+ }
4364
+ this.reconnectAttempts += 1;
4365
+ const reconnectDelayMs = Math.min(
4366
+ 3e4,
4367
+ baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
4368
+ );
4369
+ this.reconnectTimer = setTimeout(() => {
4370
+ this.reconnectTimer = null;
4371
+ console.log("[Granular] Attempting reconnect...");
4372
+ this.connect().catch((error) => {
4373
+ console.error("[Granular] Reconnect failed:", error);
4374
+ this.emitReconnectErrorMessage(
4375
+ error instanceof Error ? error.message : String(error)
4376
+ );
4377
+ this.scheduleReconnectAttempt();
4378
+ });
4379
+ }, reconnectDelayMs);
4380
+ return reconnectDelayMs;
4381
+ }
4280
4382
  buildDisconnectError(info) {
4281
4383
  const details = [
4282
4384
  info.code !== void 0 ? `code=${info.code}` : void 0,
@@ -4286,8 +4388,6 @@ var WSClient = class {
4286
4388
  return new Error(`WebSocket disconnected${suffix}`);
4287
4389
  }
4288
4390
  handleDisconnect(close = {}) {
4289
- const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
4290
- 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;
4291
4391
  const unexpected = !this.isExplicitlyDisconnected;
4292
4392
  const info = {
4293
4393
  code: close.code,
@@ -4307,32 +4407,9 @@ var WSClient = class {
4307
4407
  const disconnectError = this.buildDisconnectError(info);
4308
4408
  this.rejectPending(disconnectError);
4309
4409
  this.emit("disconnect", info);
4310
- if (this.reconnectAttempts >= maxReconnectAttempts) {
4311
- const reconnectInfo = {
4312
- error: `WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`,
4313
- sessionId: this.sessionId,
4314
- timestamp: Date.now()
4315
- };
4316
- this.emit("reconnect_error", reconnectInfo);
4317
- if (this.options.onReconnectError) {
4318
- try {
4319
- this.options.onReconnectError(reconnectInfo);
4320
- } catch (callbackError) {
4321
- console.error(
4322
- "[Granular] onReconnectError callback failed:",
4323
- callbackError
4324
- );
4325
- }
4326
- }
4327
- return;
4328
- }
4329
- this.reconnectAttempts += 1;
4330
- const reconnectDelayMs = Math.min(
4331
- 3e4,
4332
- baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
4333
- );
4334
- info.reconnectScheduled = true;
4335
- info.reconnectDelayMs = reconnectDelayMs;
4410
+ const reconnectDelayMs = this.scheduleReconnectAttempt();
4411
+ info.reconnectScheduled = reconnectDelayMs !== null;
4412
+ if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
4336
4413
  if (this.options.onUnexpectedClose) {
4337
4414
  try {
4338
4415
  this.options.onUnexpectedClose(info);
@@ -4343,28 +4420,6 @@ var WSClient = class {
4343
4420
  );
4344
4421
  }
4345
4422
  }
4346
- this.reconnectTimer = setTimeout(() => {
4347
- console.log("[Granular] Attempting reconnect...");
4348
- this.connect().catch((error) => {
4349
- console.error("[Granular] Reconnect failed:", error);
4350
- const reconnectInfo = {
4351
- error: error instanceof Error ? error.message : String(error),
4352
- sessionId: this.sessionId,
4353
- timestamp: Date.now()
4354
- };
4355
- this.emit("reconnect_error", reconnectInfo);
4356
- if (this.options.onReconnectError) {
4357
- try {
4358
- this.options.onReconnectError(reconnectInfo);
4359
- } catch (callbackError) {
4360
- console.error(
4361
- "[Granular] onReconnectError callback failed:",
4362
- callbackError
4363
- );
4364
- }
4365
- }
4366
- });
4367
- }, reconnectDelayMs);
4368
4423
  }
4369
4424
  }
4370
4425
  handleMessage(message) {
@@ -4475,6 +4530,7 @@ var WSClient = class {
4475
4530
  const response = message;
4476
4531
  const pending = this.messageQueue.find((q) => q.id === response.id);
4477
4532
  if (pending) {
4533
+ clearTimeout(pending.timeout);
4478
4534
  if (response.type === "rpc_error") {
4479
4535
  pending.reject(
4480
4536
  new Error(
@@ -4520,16 +4576,22 @@ var WSClient = class {
4520
4576
  id
4521
4577
  };
4522
4578
  return new Promise((resolve, reject) => {
4523
- this.messageQueue.push({ resolve, reject, id });
4524
- this.ws.send(JSON.stringify(request));
4525
4579
  const timeoutMs = rpcTimeoutMsForMethod(method);
4526
- setTimeout(() => {
4580
+ const timeout = setTimeout(() => {
4527
4581
  const pending = this.messageQueue.find((q) => q.id === id);
4528
4582
  if (pending) {
4529
4583
  this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
4530
4584
  reject(new Error(`RPC timeout: ${method}`));
4531
4585
  }
4532
4586
  }, timeoutMs);
4587
+ this.messageQueue.push({ resolve, reject, id, timeout });
4588
+ try {
4589
+ this.ws.send(JSON.stringify(request));
4590
+ } catch (error) {
4591
+ clearTimeout(timeout);
4592
+ this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
4593
+ reject(error instanceof Error ? error : new Error(String(error)));
4594
+ }
4533
4595
  });
4534
4596
  }
4535
4597
  async handleIncomingRpc(request) {
@@ -4615,15 +4677,18 @@ var WSClient = class {
4615
4677
  /**
4616
4678
  * Disconnect the WebSocket and clear state
4617
4679
  */
4618
- disconnect() {
4680
+ disconnect(options = {}) {
4619
4681
  this.isExplicitlyDisconnected = true;
4682
+ this.cancelConnectAttempt?.();
4683
+ this.cancelConnectAttempt = null;
4684
+ this.connectionEpoch += 1;
4620
4685
  if (this.reconnectTimer) {
4621
4686
  clearTimeout(this.reconnectTimer);
4622
4687
  this.reconnectTimer = null;
4623
4688
  }
4624
4689
  this.clearTokenRefreshTimer();
4625
4690
  if (this.ws) {
4626
- this.ws.close(1e3, "Client disconnect");
4691
+ this.ws.close(1e3, options.reason || "Client disconnect");
4627
4692
  this.ws = null;
4628
4693
  }
4629
4694
  this.rejectPending(new Error("Client explicitly disconnected"));
@@ -4719,8 +4784,12 @@ function normalizePrompt(rawValue) {
4719
4784
  const source = promptRecord || raw;
4720
4785
  const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
4721
4786
  if (!id) return null;
4787
+ const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
4788
+ const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
4722
4789
  return {
4723
4790
  id,
4791
+ ...jobId ? { jobId } : {},
4792
+ ...turnId ? { turnId } : {},
4724
4793
  type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
4725
4794
  title: typeof source.title === "string" ? source.title : "Input required",
4726
4795
  message: typeof source.message === "string" ? source.message : "",
@@ -4761,6 +4830,9 @@ function resolvePromptAnswer(prompt, answer) {
4761
4830
 
4762
4831
  // src/session.ts
4763
4832
  var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
4833
+ function toPascalCase(value) {
4834
+ return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4835
+ }
4764
4836
  function withPromptTranscriptTimeout(promise) {
4765
4837
  let timeout = null;
4766
4838
  return Promise.race([
@@ -4804,6 +4876,9 @@ var Session = class {
4804
4876
  this.initialQuota = options.initialQuota || null;
4805
4877
  this.setupEventHandlers();
4806
4878
  this.setupToolInvokeHandler();
4879
+ this.currentDomainRevision = this.extractDomainRevisionFromDoc(
4880
+ this.client.doc
4881
+ );
4807
4882
  }
4808
4883
  extractDomainRevisionFromDoc(doc) {
4809
4884
  const domain = doc?.domain;
@@ -5323,9 +5398,7 @@ var Session = class {
5323
5398
  if (classes && Object.keys(classes).length > 0) {
5324
5399
  let docs2 = "# Domain Documentation\n\n";
5325
5400
  docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
5326
- const classNames = Object.keys(classes).map(
5327
- (c) => c.charAt(0).toUpperCase() + c.slice(1)
5328
- );
5401
+ const classNames = Object.keys(classes).map(toPascalCase);
5329
5402
  const globalNames = (globalTools || []).map((t) => t.name);
5330
5403
  const importLines = [
5331
5404
  ...classNames.map(
@@ -5339,7 +5412,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
5339
5412
 
5340
5413
  `;
5341
5414
  for (const [className, cls] of Object.entries(classes)) {
5342
- const TsName = className.charAt(0).toUpperCase() + className.slice(1);
5415
+ const TsName = toPascalCase(className);
5343
5416
  docs2 += `## ${TsName}
5344
5417
 
5345
5418
  `;
@@ -5711,6 +5784,7 @@ function normalizeJobAgentMessageEnvelope(data) {
5711
5784
  }
5712
5785
  return {
5713
5786
  jobId: d.jobId,
5787
+ ...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
5714
5788
  message: {
5715
5789
  messageId: d.messageId,
5716
5790
  kind: d.kind === "artifacts" ? "artifacts" : "text",
@@ -6329,6 +6403,28 @@ function asString(value) {
6329
6403
  function trimString(value) {
6330
6404
  return typeof value === "string" ? value.trim() : "";
6331
6405
  }
6406
+ function compactJson(value, maxLength = 320) {
6407
+ if (value === void 0 || value === null) return void 0;
6408
+ try {
6409
+ const json = JSON.stringify(value);
6410
+ if (!json || json === "undefined") return void 0;
6411
+ return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
6412
+ } catch {
6413
+ return String(value);
6414
+ }
6415
+ }
6416
+ function artifactRecordsById(liveDoc) {
6417
+ const artifacts = asRecord3(liveDoc?.artifacts);
6418
+ const byId = asRecord3(artifacts?.byId) || {};
6419
+ return Object.fromEntries(
6420
+ Object.entries(byId).map(([artifactId, value]) => {
6421
+ const record = asRecord3(value);
6422
+ return record ? [artifactId, record] : null;
6423
+ }).filter(
6424
+ (entry) => Boolean(entry)
6425
+ )
6426
+ );
6427
+ }
6332
6428
  function normalizeShowRefs(value) {
6333
6429
  const record = asRecord3(value);
6334
6430
  if (!record) return void 0;
@@ -6345,9 +6441,106 @@ function normalizeShowRefs(value) {
6345
6441
  entryPaths: normalizeRefs(record.entryPaths),
6346
6442
  listNames: normalizeRefs(record.listNames),
6347
6443
  variableNames: normalizeRefs(record.variableNames),
6348
- fileIds: normalizeRefs(record.fileIds)
6444
+ fileIds: normalizeRefs(record.fileIds),
6445
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
6446
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
6447
+ tables: Array.isArray(record.tables) ? record.tables.filter(
6448
+ (table) => Boolean(
6449
+ table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
6450
+ )
6451
+ ) : void 0
6349
6452
  };
6350
- return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
6453
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
6454
+ }
6455
+ function normalizeActionSuggestions(value) {
6456
+ if (!Array.isArray(value)) return void 0;
6457
+ const suggestions = [];
6458
+ for (const item of value) {
6459
+ const record = asRecord3(item);
6460
+ if (!record) continue;
6461
+ const label = trimString(record.label);
6462
+ if (!label) continue;
6463
+ const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
6464
+ suggestions.push({
6465
+ suggestionId,
6466
+ label,
6467
+ ...typeof record.description === "string" ? { description: record.description } : {},
6468
+ ...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
6469
+ ...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
6470
+ ...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
6471
+ });
6472
+ }
6473
+ return suggestions.length ? suggestions : void 0;
6474
+ }
6475
+ var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
6476
+ var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
6477
+ var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
6478
+ var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
6479
+ function normalizeConversationMessageActions(value) {
6480
+ if (!Array.isArray(value) || value.length === 0) return void 0;
6481
+ const actions = [];
6482
+ for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
6483
+ const record = asRecord3(item);
6484
+ const kind = record?.kind;
6485
+ const label = trimString(record?.label ?? record?.title);
6486
+ const status = record?.status;
6487
+ if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
6488
+ continue;
6489
+ }
6490
+ actions.push({
6491
+ kind,
6492
+ label,
6493
+ ...status === "done" || status === "queued" || status === "failed" ? { status } : {}
6494
+ });
6495
+ }
6496
+ return actions.length ? actions : void 0;
6497
+ }
6498
+ function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
6499
+ if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
6500
+ return void 0;
6501
+ }
6502
+ const parts = [];
6503
+ const canonicalActionsById = new Map(
6504
+ (canonicalActions || []).map((action) => [
6505
+ `${action.kind}:${action.label}`,
6506
+ action
6507
+ ])
6508
+ );
6509
+ const seenActionIds = /* @__PURE__ */ new Set();
6510
+ let textLength = 0;
6511
+ for (const item of value) {
6512
+ const record = asRecord3(item);
6513
+ if (!record) return void 0;
6514
+ if (record.type === "text") {
6515
+ if (typeof record.text !== "string" || record.text.length === 0) {
6516
+ return void 0;
6517
+ }
6518
+ textLength += record.text.length;
6519
+ if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
6520
+ parts.push({ type: "text", text: record.text });
6521
+ continue;
6522
+ }
6523
+ if (record.type !== "action") return void 0;
6524
+ const action = asRecord3(record.action);
6525
+ const kind = action?.kind;
6526
+ const label = trimString(action?.label);
6527
+ if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
6528
+ return void 0;
6529
+ }
6530
+ const actionId = `${kind}:${label}`;
6531
+ const canonicalAction = canonicalActionsById.get(actionId);
6532
+ if (!canonicalAction) return void 0;
6533
+ if (seenActionIds.has(actionId)) continue;
6534
+ seenActionIds.add(actionId);
6535
+ parts.push({
6536
+ type: "action",
6537
+ action: canonicalAction
6538
+ });
6539
+ }
6540
+ const orderedText = parts.filter(
6541
+ (part) => part.type === "text"
6542
+ ).map((part) => part.text).join("");
6543
+ return orderedText === canonicalContent ? parts : void 0;
6351
6544
  }
6352
6545
  function stringifyTranscriptValue(value, fallback = "") {
6353
6546
  if (typeof value === "string") {
@@ -6367,12 +6560,139 @@ function stringifyTranscriptValue(value, fallback = "") {
6367
6560
  return String(value);
6368
6561
  }
6369
6562
  }
6370
- function buildArtifactHistory(show) {
6563
+ function latestInputEditSummary(metadata) {
6564
+ const lastInputEdit = asRecord3(metadata.lastInputEdit);
6565
+ if (!lastInputEdit) return null;
6566
+ const source = asString(lastInputEdit.source) || "unknown";
6567
+ const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
6568
+ const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
6569
+ (key) => typeof key === "string" && key.trim().length > 0
6570
+ ).slice(0, 6) : [];
6571
+ const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
6572
+ (key) => typeof key === "string" && key.trim().length > 0
6573
+ ).slice(0, 6) : [];
6574
+ const changed = [
6575
+ inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
6576
+ relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
6577
+ ].filter(Boolean);
6578
+ return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
6579
+ }
6580
+ function artifactIssueSummary(record) {
6581
+ const validation = asRecord3(record.validation);
6582
+ if (!validation) return null;
6583
+ const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
6584
+ if (issues.length > 0) {
6585
+ return `issues=${issues.map((issue) => {
6586
+ const code = asString(issue.code) || asString(issue.kind) || "issue";
6587
+ const path = asString(issue.path);
6588
+ const message = trimString(issue.message);
6589
+ return `${code}${path ? ` at ${path}` : ""}${message ? ` (${message})` : ""}`;
6590
+ }).join("; ")}`;
6591
+ }
6592
+ const error = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
6593
+ return error ? `validation=${error}` : null;
6594
+ }
6595
+ function artifactExecutionSummary(metadata) {
6596
+ const execution = asRecord3(metadata.execution);
6597
+ if (!execution) return null;
6598
+ const result = asRecord3(execution.result);
6599
+ const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
6600
+ const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
6601
+ const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
6602
+ const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
6603
+ const error = trimString(execution.error);
6604
+ const pieces = [
6605
+ awaiting ? `awaiting=${awaiting}` : null,
6606
+ pendingTransition ? `pendingTransition=${pendingTransition}` : null,
6607
+ approvalTarget ? `approvalTarget=${approvalTarget}` : null,
6608
+ error ? `executionError=${error}` : null
6609
+ ].filter(Boolean);
6610
+ return pieces.length ? pieces.join("; ") : null;
6611
+ }
6612
+ function artifactStatePathSummary(metadata) {
6613
+ const statePlan = asRecord3(metadata.statePlan);
6614
+ if (!statePlan) return null;
6615
+ const machineName = asString(statePlan.machineName);
6616
+ const targetState = asString(statePlan.targetState);
6617
+ const objectPath = asString(statePlan.objectPath);
6618
+ const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
6619
+ const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
6620
+ const pieces = [
6621
+ machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
6622
+ objectPath ? `objectPath=${objectPath}` : null,
6623
+ approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
6624
+ approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
6625
+ ].filter(Boolean);
6626
+ return pieces.length ? pieces.join("; ") : null;
6627
+ }
6628
+ function artifactSummaryLine(artifactId, record) {
6629
+ if (!record) return `- ${artifactId}: unavailable in session artifact store`;
6630
+ const label = trimString(record.label) || artifactId;
6631
+ const kind = asString(record.kind) || "artifact";
6632
+ const status = asString(record.status) || "unknown";
6633
+ const createdByJobId = asString(record.createdByJobId);
6634
+ const target = asRecord3(record.target);
6635
+ const metadata = asRecord3(record.metadata) || {};
6636
+ const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
6637
+ (id) => typeof id === "string" && id.trim().length > 0
6638
+ ).slice(0, 8) : [];
6639
+ const relationships = compactJson(record.relationships, 220);
6640
+ const pieces = [
6641
+ `kind=${kind}`,
6642
+ `status=${status}`,
6643
+ createdByJobId ? `createdByJob=${createdByJobId}` : null,
6644
+ target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
6645
+ artifactStatePathSummary(metadata),
6646
+ artifactExecutionSummary(metadata),
6647
+ artifactIssueSummary(record),
6648
+ latestInputEditSummary(metadata),
6649
+ subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
6650
+ relationships ? `relationships=${relationships}` : null
6651
+ ].filter(Boolean);
6652
+ return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
6653
+ }
6654
+ function buildArtifactHistory(show, artifactsById) {
6371
6655
  if (!show) return void 0;
6372
- return `[Agent message]
6656
+ const artifactIds = show.sessionArtifactIds || [];
6657
+ const actionSuggestions = show.actionSuggestions || [];
6658
+ if (artifactIds.length === 0 && actionSuggestions.length === 0) {
6659
+ return `[Agent message]
6373
6660
  ${stringifyTranscriptValue({ show }, "")}`;
6661
+ }
6662
+ const lines = artifactIds.slice(0, 8).map(
6663
+ (artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
6664
+ );
6665
+ if (artifactIds.length > 8) {
6666
+ lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
6667
+ }
6668
+ if (actionSuggestions.length > 0) {
6669
+ if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
6670
+ for (const suggestion of actionSuggestions.slice(0, 8)) {
6671
+ lines.push(
6672
+ `- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
6673
+ );
6674
+ }
6675
+ if (actionSuggestions.length > 8) {
6676
+ lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
6677
+ }
6678
+ }
6679
+ const otherRefs = {
6680
+ entryPaths: show.entryPaths,
6681
+ listNames: show.listNames,
6682
+ variableNames: show.variableNames,
6683
+ fileIds: show.fileIds
6684
+ };
6685
+ const hasOtherRefs = Object.values(otherRefs).some(
6686
+ (value) => Array.isArray(value) && value.length > 0
6687
+ );
6688
+ const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
6689
+ return [
6690
+ title,
6691
+ ...lines,
6692
+ hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
6693
+ ].filter(Boolean).join("\n");
6374
6694
  }
6375
- function normalizeConversationMessage(raw) {
6695
+ function normalizeConversationMessage(raw, artifactsById) {
6376
6696
  const record = asRecord3(raw);
6377
6697
  if (!record) return null;
6378
6698
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -6380,10 +6700,18 @@ function normalizeConversationMessage(raw) {
6380
6700
  const content = trimString(
6381
6701
  record.content ?? record.reply ?? record.message ?? record.text
6382
6702
  );
6703
+ const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
6704
+ const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
6383
6705
  const show = normalizeShowRefs(record.show);
6384
6706
  const id = asString(record.id) || crypto.randomUUID();
6385
6707
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
6386
- if (!content && !show) return null;
6708
+ if (!content && !show && !actions?.length) return null;
6709
+ const artifactHistory = buildArtifactHistory(show, artifactsById);
6710
+ const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
6711
+ ${content}
6712
+
6713
+ ${artifactHistory}` : content ? `[Assistant reply]
6714
+ ${content}` : artifactHistory : void 0;
6387
6715
  return {
6388
6716
  id,
6389
6717
  role,
@@ -6392,8 +6720,9 @@ function normalizeConversationMessage(raw) {
6392
6720
  jobId: asString(record.jobId),
6393
6721
  promptId: asString(record.promptId),
6394
6722
  show,
6395
- historyContent: role === "assistant" ? content ? `[Assistant reply]
6396
- ${content}` : buildArtifactHistory(show) : void 0,
6723
+ actions,
6724
+ parts,
6725
+ historyContent,
6397
6726
  source: "conversation"
6398
6727
  };
6399
6728
  }
@@ -6436,7 +6765,7 @@ ${assistantContent}`,
6436
6765
  return entries;
6437
6766
  });
6438
6767
  }
6439
- function normalizeAgentMessageEntries(jobId, rawMessages) {
6768
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
6440
6769
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6441
6770
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6442
6771
  ).flatMap((message) => {
@@ -6467,14 +6796,14 @@ ${reply}`,
6467
6796
  timestamp,
6468
6797
  jobId,
6469
6798
  show,
6470
- historyContent: buildArtifactHistory(show),
6799
+ historyContent: buildArtifactHistory(show, artifactsById),
6471
6800
  source: "job_agent_message"
6472
6801
  });
6473
6802
  }
6474
6803
  return entries;
6475
6804
  });
6476
6805
  }
6477
- function buildJobFallbackEntries(jobId, job, sessionHeap) {
6806
+ function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
6478
6807
  const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6479
6808
  const resultPreview = stringifyTranscriptValue(
6480
6809
  job.result,
@@ -6512,7 +6841,7 @@ ${responseText}`,
6512
6841
  timestamp,
6513
6842
  jobId,
6514
6843
  show,
6515
- historyContent: buildArtifactHistory(show),
6844
+ historyContent: buildArtifactHistory(show, artifactsById),
6516
6845
  source: "job_result"
6517
6846
  });
6518
6847
  }
@@ -6566,10 +6895,11 @@ function buildJobCodeEntry(jobId, job) {
6566
6895
  function buildSessionTranscript(input) {
6567
6896
  const liveDoc = input.liveDoc || null;
6568
6897
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6898
+ const artifactsById = artifactRecordsById(liveDoc);
6569
6899
  const transcript = [];
6570
6900
  const conversationMessages = asArray(
6571
6901
  asRecord3(liveDoc?.conversation)?.messages
6572
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6902
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
6573
6903
  const conversationPromptIds = new Set(
6574
6904
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6575
6905
  );
@@ -6596,7 +6926,8 @@ function buildSessionTranscript(input) {
6596
6926
  if (!assistantConversationJobIds.has(jobId)) {
6597
6927
  const agentEntries = normalizeAgentMessageEntries(
6598
6928
  jobId,
6599
- job.agentMessages
6929
+ job.agentMessages,
6930
+ artifactsById
6600
6931
  );
6601
6932
  if (agentEntries.length > 0) {
6602
6933
  transcript.push(...agentEntries);
@@ -6605,7 +6936,8 @@ function buildSessionTranscript(input) {
6605
6936
  ...buildJobFallbackEntries(
6606
6937
  jobId,
6607
6938
  job,
6608
- sessionHeap
6939
+ sessionHeap,
6940
+ artifactsById
6609
6941
  )
6610
6942
  );
6611
6943
  }
@@ -10771,16 +11103,107 @@ var StateMachineStateSchema = external_exports.union([
10771
11103
  external_exports.string(),
10772
11104
  external_exports.object({
10773
11105
  name: external_exports.string().min(1),
11106
+ label: external_exports.string().optional(),
11107
+ description: external_exports.string().optional(),
10774
11108
  isFinal: external_exports.boolean().optional()
10775
11109
  }).strict()
10776
11110
  ]);
11111
+ var StateTransitionInputBindingSchema = external_exports.lazy(
11112
+ () => external_exports.union([
11113
+ external_exports.null(),
11114
+ external_exports.string(),
11115
+ external_exports.number(),
11116
+ external_exports.boolean(),
11117
+ external_exports.array(StateTransitionInputBindingSchema),
11118
+ external_exports.object({
11119
+ const: external_exports.unknown()
11120
+ }).strict(),
11121
+ external_exports.object({
11122
+ from: external_exports.literal("object"),
11123
+ path: external_exports.string().min(1),
11124
+ editable: external_exports.boolean().optional()
11125
+ }).strict(),
11126
+ external_exports.object({
11127
+ from: external_exports.literal("field"),
11128
+ name: external_exports.string().min(1),
11129
+ editable: external_exports.boolean().optional()
11130
+ }).strict(),
11131
+ external_exports.object({
11132
+ from: external_exports.literal("relationship"),
11133
+ name: external_exports.string().min(1),
11134
+ path: external_exports.string().min(1).optional(),
11135
+ many: external_exports.boolean().optional(),
11136
+ editable: external_exports.boolean().optional()
11137
+ }).strict(),
11138
+ external_exports.object({
11139
+ from: external_exports.literal("session"),
11140
+ path: external_exports.string().min(1),
11141
+ editable: external_exports.boolean().optional()
11142
+ }).strict(),
11143
+ external_exports.object({
11144
+ from: external_exports.literal("actor"),
11145
+ path: external_exports.string().min(1),
11146
+ editable: external_exports.boolean().optional()
11147
+ }).strict(),
11148
+ external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
11149
+ ])
11150
+ );
11151
+ var StateTransitionActionSchema = external_exports.object({
11152
+ effect: external_exports.string().min(1),
11153
+ input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
11154
+ }).strict();
11155
+ var StateTransitionAssigneeSchema = external_exports.object({
11156
+ kind: external_exports.string().min(1),
11157
+ from: StateTransitionInputBindingSchema.optional(),
11158
+ role: external_exports.string().optional(),
11159
+ label: external_exports.string().optional()
11160
+ }).strict();
11161
+ var StateTransitionRelatedStateRequirementSchema = external_exports.object({
11162
+ relationship: external_exports.string().min(1),
11163
+ machine: external_exports.string().min(1),
11164
+ state: external_exports.string().min(1),
11165
+ className: external_exports.string().min(1).optional(),
11166
+ label: external_exports.string().optional(),
11167
+ mode: external_exports.enum(["every", "some", "any"]).optional()
11168
+ }).strict();
11169
+ var StateTransitionRequirementsSchema = external_exports.object({
11170
+ fields: external_exports.array(external_exports.string().min(1)).optional(),
11171
+ relationships: external_exports.array(external_exports.string().min(1)).optional(),
11172
+ relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
11173
+ }).strict();
11174
+ var StateTransitionPermissionSchema = external_exports.union([
11175
+ external_exports.string().min(1),
11176
+ external_exports.object({
11177
+ profile: external_exports.string().min(1).optional(),
11178
+ profileId: external_exports.string().min(1).optional(),
11179
+ label: external_exports.string().optional(),
11180
+ reason: external_exports.string().optional()
11181
+ }).strict()
11182
+ ]);
11183
+ var StateTransitionExpectedOutcomeSchema = external_exports.union([
11184
+ external_exports.string().min(1),
11185
+ external_exports.object({
11186
+ machine: external_exports.string().min(1).optional(),
11187
+ state: external_exports.string().min(1),
11188
+ summary: external_exports.string().optional()
11189
+ }).strict()
11190
+ ]);
10777
11191
  var StateMachineTransitionSchema = external_exports.object({
10778
11192
  name: external_exports.string().min(1),
10779
11193
  from: external_exports.string().min(1),
10780
- to: external_exports.string().min(1)
11194
+ to: external_exports.string().min(1),
11195
+ label: external_exports.string().optional(),
11196
+ description: external_exports.string().optional(),
11197
+ action: StateTransitionActionSchema.optional(),
11198
+ assignee: StateTransitionAssigneeSchema.optional(),
11199
+ requirements: StateTransitionRequirementsSchema.optional(),
11200
+ permission: StateTransitionPermissionSchema.optional(),
11201
+ risk: external_exports.enum(["low", "medium", "high"]).optional(),
11202
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
10781
11203
  }).strict();
10782
11204
  external_exports.object({
10783
11205
  name: external_exports.string().min(1),
11206
+ stateField: external_exports.string().min(1).optional(),
10784
11207
  entryState: external_exports.string().min(1),
10785
11208
  states: external_exports.array(StateMachineStateSchema).min(1),
10786
11209
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -10847,6 +11270,16 @@ var PoliciesSchema = external_exports.object({
10847
11270
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
10848
11271
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
10849
11272
  }).strict();
11273
+ var CreatesSchema = external_exports.union([
11274
+ external_exports.string().min(1),
11275
+ external_exports.object({
11276
+ className: external_exports.string().min(1),
11277
+ idPath: external_exports.string().min(1).optional(),
11278
+ pathPath: external_exports.string().min(1).optional(),
11279
+ statePath: external_exports.string().min(1).optional(),
11280
+ classStateHandle: external_exports.boolean().optional()
11281
+ }).strict()
11282
+ ]);
10850
11283
  external_exports.object({
10851
11284
  postCondition: external_exports.union([
10852
11285
  external_exports.string(),
@@ -10877,6 +11310,7 @@ external_exports.object({
10877
11310
  mode: external_exports.string().optional()
10878
11311
  }).strict()
10879
11312
  ]).optional(),
11313
+ creates: CreatesSchema.optional(),
10880
11314
  access: external_exports.enum(["read", "write", "ui"]).optional(),
10881
11315
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
10882
11316
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -11104,9 +11538,10 @@ function mergeMethodSummaryPatch(target, patch) {
11104
11538
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
11105
11539
  if (patch.effectBehaviors !== void 0)
11106
11540
  target.effectBehaviors = patch.effectBehaviors;
11541
+ if (patch.creates !== void 0) target.creates = patch.creates;
11107
11542
  if (patch.static !== void 0) target.static = patch.static;
11108
11543
  }
11109
- function toPascalCase(value) {
11544
+ function toPascalCase2(value) {
11110
11545
  return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11111
11546
  }
11112
11547
  function normalizeNotesInput(input) {
@@ -11164,29 +11599,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
11164
11599
  }
11165
11600
  return Object.keys(result).length > 0 ? result : null;
11166
11601
  }
11167
- function buildEffectBehaviorDocs(effectBehaviors) {
11168
- if (!effectBehaviors) {
11169
- return [];
11602
+ function normalizeCreationSummary(metamodels) {
11603
+ if (!isObject(metamodels)) return null;
11604
+ let raw = metamodels.creates;
11605
+ if (typeof raw === "string" && raw.trim().length > 0) {
11606
+ const trimmed = raw.trim();
11607
+ if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
11608
+ try {
11609
+ raw = JSON.parse(trimmed);
11610
+ } catch {
11611
+ return { className: trimmed };
11612
+ }
11613
+ } else {
11614
+ return { className: trimmed };
11615
+ }
11616
+ }
11617
+ if (typeof raw === "string" && raw.trim().length > 0) {
11618
+ return { className: raw.trim() };
11170
11619
  }
11620
+ if (!isObject(raw)) return null;
11621
+ const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
11622
+ if (!className) return null;
11623
+ return {
11624
+ className,
11625
+ ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
11626
+ ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
11627
+ ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
11628
+ ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
11629
+ };
11630
+ }
11631
+ function buildEffectBehaviorDocs(effectBehaviors, creates) {
11171
11632
  const docs = [];
11172
- if (effectBehaviors.approvalRequired?.required) {
11633
+ if (creates) {
11634
+ docs.push(
11635
+ `Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
11636
+ );
11637
+ }
11638
+ if (effectBehaviors?.approvalRequired?.required) {
11173
11639
  docs.push(
11174
11640
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
11175
11641
  );
11176
11642
  }
11177
- if (effectBehaviors.postCondition) {
11643
+ if (effectBehaviors?.postCondition) {
11178
11644
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
11179
11645
  if (effectBehaviors.postCondition.description) {
11180
11646
  docs.push(effectBehaviors.postCondition.description);
11181
11647
  }
11182
11648
  }
11183
- if (effectBehaviors.dryRun?.enabled) {
11649
+ if (effectBehaviors?.dryRun?.enabled) {
11184
11650
  docs.push("Supports dry run.");
11185
11651
  if (effectBehaviors.dryRun.description) {
11186
11652
  docs.push(effectBehaviors.dryRun.description);
11187
11653
  }
11188
11654
  }
11189
- if (effectBehaviors.reverse) {
11655
+ if (effectBehaviors?.reverse) {
11190
11656
  if (effectBehaviors.reverse.handler) {
11191
11657
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
11192
11658
  } else {
@@ -11245,13 +11711,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
11245
11711
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
11246
11712
  });
11247
11713
  }
11714
+ if (spec.creates !== void 0) {
11715
+ mutations.push({
11716
+ label: `set creates on ${toolPath}`,
11717
+ query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
11718
+ JSON.stringify(spec.creates)
11719
+ )}) { done } } } }`
11720
+ });
11721
+ }
11248
11722
  return mutations;
11249
11723
  }
11250
11724
  function readMethodEffectBehaviors(rawMethod) {
11725
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
11251
11726
  return {
11252
- effectBehaviors: normalizeEffectBehaviorSummary(
11253
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
11254
- )
11727
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
11728
+ creates: normalizeCreationSummary(metamodels)
11255
11729
  };
11256
11730
  }
11257
11731
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -11273,6 +11747,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11273
11747
  {
11274
11748
  key: "approvalRequired",
11275
11749
  description: "Boolean or `{ required, reason, mode }`."
11750
+ },
11751
+ {
11752
+ key: "creates",
11753
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
11276
11754
  }
11277
11755
  ]
11278
11756
  },
@@ -11392,7 +11870,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11392
11870
  ...methodIR,
11393
11871
  docs: [
11394
11872
  ...methodIR.docs,
11395
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
11873
+ ...buildEffectBehaviorDocs(
11874
+ methodSummary.effectBehaviors,
11875
+ methodSummary.creates
11876
+ )
11396
11877
  ]
11397
11878
  };
11398
11879
  }
@@ -11633,15 +12114,50 @@ function toRecordSearchResult(className, node) {
11633
12114
  return [];
11634
12115
  }
11635
12116
  ) : [];
12117
+ const graphPathId = extractRecordIdFromGraphPath(path, className);
12118
+ const realIdField = fields.find(
12119
+ (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
12120
+ );
12121
+ const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
12122
+ const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
12123
+ if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path)) {
12124
+ return null;
12125
+ }
12126
+ const fallbackLabel = displayLabelFromFields(fields);
12127
+ const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path) ? rawLabel : fallbackLabel || rawLabel || id;
11636
12128
  return {
11637
12129
  path,
11638
12130
  className,
11639
- id: extractRecordIdFromGraphPath(path, className),
11640
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path, className),
12131
+ id,
12132
+ label,
11641
12133
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
11642
12134
  fields
11643
12135
  };
11644
12136
  }
12137
+ function isPlaceholderRecordLabel(label, id, path) {
12138
+ const normalizedLabel = normalizeGraphPathSegment(label);
12139
+ return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path);
12140
+ }
12141
+ function displayLabelFromFields(fields) {
12142
+ const preferredFieldNames = [
12143
+ "name",
12144
+ "title",
12145
+ "label",
12146
+ "display_name",
12147
+ "file_name",
12148
+ "number",
12149
+ "code"
12150
+ ];
12151
+ for (const preferred of preferredFieldNames) {
12152
+ const match = fields.find(
12153
+ (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
12154
+ );
12155
+ if (typeof match?.value === "string") {
12156
+ return match.value.trim();
12157
+ }
12158
+ }
12159
+ return null;
12160
+ }
11645
12161
  function normalizeRecordSearchText(value) {
11646
12162
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
11647
12163
  }
@@ -11783,7 +12299,12 @@ async function recordOpenAIUsageSpend(options) {
11783
12299
  const metadata = {
11784
12300
  ...options.metadata || {},
11785
12301
  ...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
11786
- usageContext: context
12302
+ usageContext: context,
12303
+ pricingContextTier: options.usage.pricingContextTier,
12304
+ cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
12305
+ cacheWriteTokens: options.usage.cacheWriteTokens,
12306
+ cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
12307
+ longContextThresholdTokens: options.usage.longContextThresholdTokens
11787
12308
  };
11788
12309
  const response = await fetch(
11789
12310
  `${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
@@ -12541,15 +13062,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
12541
13062
 
12542
13063
  // ../metamodel-state-machine/src/index.ts
12543
13064
  function normalizeStateMachines(values) {
13065
+ const parseJsonRecord = (value) => {
13066
+ if (value && typeof value === "object" && !Array.isArray(value)) {
13067
+ return value;
13068
+ }
13069
+ if (typeof value !== "string" || !value.trim()) return null;
13070
+ try {
13071
+ const parsed = JSON.parse(value);
13072
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
13073
+ } catch {
13074
+ return null;
13075
+ }
13076
+ };
13077
+ const parseJsonValue = (value) => {
13078
+ if (value === null || typeof value === "undefined") return null;
13079
+ if (typeof value !== "string") return value;
13080
+ if (!value.trim()) return null;
13081
+ try {
13082
+ return JSON.parse(value);
13083
+ } catch {
13084
+ return value;
13085
+ }
13086
+ };
12544
13087
  return (values || []).map((machine) => {
12545
13088
  const states = (machine?.states || []).map((state) => ({
12546
13089
  name: String(state?.name || ""),
12547
- isFinal: Boolean(state?.is_final)
13090
+ label: typeof state?.label === "string" ? state.label : null,
13091
+ description: typeof state?.description === "string" ? state.description : null,
13092
+ isFinal: Boolean(state?.is_final ?? state?.isFinal)
12548
13093
  })).filter((state) => state.name.length > 0);
12549
13094
  const transitions = (machine?.transitions || []).map((transition) => ({
12550
13095
  name: String(transition?.name || ""),
12551
13096
  from: String(transition?.from?.name || ""),
12552
- to: String(transition?.to?.name || "")
13097
+ to: String(transition?.to?.name || ""),
13098
+ label: typeof transition?.label === "string" ? transition.label : null,
13099
+ description: typeof transition?.description === "string" ? transition.description : null,
13100
+ action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
13101
+ assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
13102
+ requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
13103
+ permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
13104
+ risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
13105
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
12553
13106
  })).filter(
12554
13107
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
12555
13108
  );
@@ -12563,7 +13116,7 @@ function normalizeStateMachines(values) {
12563
13116
  }).filter((machine) => machine.name.length > 0);
12564
13117
  }
12565
13118
  function stateTypeName(className, machineName) {
12566
- return `${toPascalCase(className)}${toPascalCase(machineName)}`;
13119
+ return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
12567
13120
  }
12568
13121
  function transitionTypeName(className, machineName) {
12569
13122
  return `${stateTypeName(className, machineName)}Transition`;
@@ -12571,6 +13124,15 @@ function transitionTypeName(className, machineName) {
12571
13124
  function pathTypeName(className, machineName) {
12572
13125
  return `${stateTypeName(className, machineName)}Path`;
12573
13126
  }
13127
+ function methodToken(value) {
13128
+ const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
13129
+ return token || "state";
13130
+ }
13131
+ function transitionActionsForMachine(machine) {
13132
+ return Object.fromEntries(
13133
+ (machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
13134
+ );
13135
+ }
12574
13136
  function normalizeStateDefinitions(machine) {
12575
13137
  const finalStates = new Set(machine.finalStates || []);
12576
13138
  const states = /* @__PURE__ */ new Map();
@@ -12584,6 +13146,8 @@ function normalizeStateDefinitions(machine) {
12584
13146
  }
12585
13147
  states.set(rawState.name, {
12586
13148
  name: rawState.name,
13149
+ label: rawState.label,
13150
+ description: rawState.description,
12587
13151
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
12588
13152
  });
12589
13153
  }
@@ -12595,6 +13159,44 @@ function normalizeStateDefinitions(machine) {
12595
13159
  }
12596
13160
  return [...states.values()];
12597
13161
  }
13162
+ function transitionMetadataGraphqlArgs(transition) {
13163
+ const args = [];
13164
+ if (typeof transition.label === "string") {
13165
+ args.push(`label: ${JSON.stringify(transition.label)}`);
13166
+ }
13167
+ if (typeof transition.description === "string") {
13168
+ args.push(`description: ${JSON.stringify(transition.description)}`);
13169
+ }
13170
+ if (transition.action) {
13171
+ args.push(
13172
+ `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
13173
+ );
13174
+ }
13175
+ if (transition.assignee) {
13176
+ args.push(
13177
+ `assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
13178
+ );
13179
+ }
13180
+ if (transition.requirements) {
13181
+ args.push(
13182
+ `requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
13183
+ );
13184
+ }
13185
+ if (transition.permission) {
13186
+ args.push(
13187
+ `permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
13188
+ );
13189
+ }
13190
+ if (transition.risk) {
13191
+ args.push(`risk: ${JSON.stringify(transition.risk)}`);
13192
+ }
13193
+ if (transition.expectedOutcome) {
13194
+ args.push(
13195
+ `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
13196
+ );
13197
+ }
13198
+ return args.length > 0 ? `, ${args.join(", ")}` : "";
13199
+ }
12598
13200
  function buildStateMachineModelMutations(modelPath, machines) {
12599
13201
  const mutations = [];
12600
13202
  for (const machine of machines || []) {
@@ -12605,12 +13207,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
12605
13207
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
12606
13208
  });
12607
13209
  for (const state of normalizeStateDefinitions(machine)) {
12608
- if (state.name === machine.entryState && !state.isFinal) continue;
13210
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
13211
+ continue;
12609
13212
  mutations.push({
12610
13213
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
12611
13214
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
12612
13215
  machine.name
12613
- )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
13216
+ )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
12614
13217
  });
12615
13218
  }
12616
13219
  for (const transition of machine.transitions || []) {
@@ -12622,18 +13225,27 @@ function buildStateMachineModelMutations(modelPath, machines) {
12622
13225
  transition.name
12623
13226
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
12624
13227
  transition.to
12625
- )}) { name } } } }`
13228
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
12626
13229
  });
12627
13230
  }
12628
13231
  }
12629
13232
  return mutations;
12630
13233
  }
12631
13234
  function buildMachineTypes(classSummary, machine) {
13235
+ const stateGlossary = machine.states.map((state) => {
13236
+ const label = state.label && state.label !== state.name ? state.label : null;
13237
+ const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
13238
+ const finalMarker = state.isFinal ? " Final state." : "";
13239
+ return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
13240
+ });
12632
13241
  return [
12633
13242
  {
12634
13243
  kind: "union",
12635
13244
  name: stateTypeName(classSummary.name, machine.name),
12636
- docs: [`Allowed states for ${classSummary.name}.${machine.name}.`],
13245
+ docs: [
13246
+ `Allowed states for ${classSummary.name}.${machine.name}.`,
13247
+ ...stateGlossary
13248
+ ],
12637
13249
  members: machine.states.map((state) => state.name)
12638
13250
  },
12639
13251
  {
@@ -12649,7 +13261,7 @@ function buildMachineMethods(classSummary, machine) {
12649
13261
  const transitionName = transitionTypeName(classSummary.name, machine.name);
12650
13262
  pathTypeName(classSummary.name, machine.name);
12651
13263
  const docsPrefix = `${classSummary.name}.${machine.name}`;
12652
- return [
13264
+ const methods = [
12653
13265
  {
12654
13266
  name: `get_${machine.name}`,
12655
13267
  docs: [`Get the current ${docsPrefix} state.`],
@@ -12672,7 +13284,7 @@ function buildMachineMethods(classSummary, machine) {
12672
13284
  ],
12673
13285
  static: false,
12674
13286
  params: [{ name: "target", type: stateName }],
12675
- returnType: `Promise<${toPascalCase(classSummary.name)}>`,
13287
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
12676
13288
  runtime: {
12677
13289
  kind: "state_machine",
12678
13290
  machineName: machine.name,
@@ -12745,6 +13357,99 @@ function buildMachineMethods(classSummary, machine) {
12745
13357
  }
12746
13358
  }
12747
13359
  ];
13360
+ const creationMethods = (classSummary.methods || []).filter(
13361
+ (method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
13362
+ );
13363
+ for (const state of machine.states) {
13364
+ const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
13365
+ if (!stateNameValue) continue;
13366
+ const token = methodToken(stateNameValue);
13367
+ methods.push(
13368
+ {
13369
+ name: `reach_${machine.name}_to_${token}`,
13370
+ docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
13371
+ static: false,
13372
+ params: [],
13373
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
13374
+ runtime: {
13375
+ kind: "state_machine",
13376
+ machineName: machine.name,
13377
+ className: classSummary.name,
13378
+ stateTypeName: stateName,
13379
+ transitionTypeName: transitionName,
13380
+ operation: "reach",
13381
+ targetState: stateNameValue,
13382
+ transitionActions: transitionActionsForMachine(machine)
13383
+ }
13384
+ },
13385
+ {
13386
+ name: `prepare_${machine.name}_to_${token}`,
13387
+ docs: [
13388
+ `Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
13389
+ ],
13390
+ static: false,
13391
+ params: [],
13392
+ returnType: "Promise<SessionArtifactRecord>",
13393
+ runtime: {
13394
+ kind: "state_machine",
13395
+ machineName: machine.name,
13396
+ className: classSummary.name,
13397
+ stateTypeName: stateName,
13398
+ transitionTypeName: transitionName,
13399
+ operation: "prepare_reach",
13400
+ targetState: stateNameValue,
13401
+ transitionActions: transitionActionsForMachine(machine)
13402
+ }
13403
+ }
13404
+ );
13405
+ for (const creationMethod of creationMethods) {
13406
+ const creationRuntime = {
13407
+ kind: "state_machine",
13408
+ machineName: machine.name,
13409
+ className: classSummary.name,
13410
+ stateTypeName: stateName,
13411
+ transitionTypeName: transitionName,
13412
+ operation: "prepare_create_reach",
13413
+ targetState: stateNameValue,
13414
+ transitionActions: transitionActionsForMachine(machine),
13415
+ creation: {
13416
+ methodName: creationMethod.name,
13417
+ effectKey: creationMethod.effectKey || creationMethod.name,
13418
+ inputSchema: creationMethod.inputSchema,
13419
+ outputSchema: creationMethod.outputSchema,
13420
+ creates: creationMethod.creates
13421
+ }
13422
+ };
13423
+ const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
13424
+ methods.push({
13425
+ name: viaName,
13426
+ docs: [
13427
+ `Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
13428
+ ],
13429
+ static: true,
13430
+ params: [
13431
+ { name: "input", type: "Record<string, any>", optional: true }
13432
+ ],
13433
+ returnType: "Promise<SessionArtifactRecord>",
13434
+ runtime: creationRuntime
13435
+ });
13436
+ if (creationMethods.length === 1) {
13437
+ methods.push({
13438
+ name: `prepare_${machine.name}_to_${token}`,
13439
+ docs: [
13440
+ `Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
13441
+ ],
13442
+ static: true,
13443
+ params: [
13444
+ { name: "input", type: "Record<string, any>", optional: true }
13445
+ ],
13446
+ returnType: "Promise<SessionArtifactRecord>",
13447
+ runtime: creationRuntime
13448
+ });
13449
+ }
13450
+ }
13451
+ }
13452
+ return methods;
12748
13453
  }
12749
13454
  function readStateMachineSummaries(rawClass) {
12750
13455
  return {
@@ -12767,8 +13472,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12767
13472
  type StateMachineMutation {
12768
13473
  name: String!
12769
13474
  state_machine: StateMachine!
12770
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
12771
- add_transition(name: String!, from: String!, to: String!): StateMachineMutation!
13475
+ add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
13476
+ 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!
12772
13477
  activate_transition(name: String!): StateMachineMutation!
12773
13478
  }
12774
13479
 
@@ -12785,6 +13490,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12785
13490
  type StateMachineSnapshotMutation {
12786
13491
  snapshot: StateMachineSnapshot!
12787
13492
  activate_transition(name: String!): StateMachineSnapshotMutation!
13493
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
12788
13494
  }
12789
13495
 
12790
13496
  type StateMachine {
@@ -12804,6 +13510,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12804
13510
 
12805
13511
  type StateMachineState {
12806
13512
  name: String!
13513
+ label: String
13514
+ description: String
12807
13515
  is_final: Boolean!
12808
13516
  }
12809
13517
 
@@ -12811,6 +13519,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12811
13519
  name: String!
12812
13520
  from: StateMachineState!
12813
13521
  to: StateMachineState!
13522
+ label: String
13523
+ description: String
13524
+ action_json: String
13525
+ assignee_json: String
13526
+ requirements_json: String
13527
+ permission_json: String
13528
+ risk: String
13529
+ expected_outcome_json: String
12814
13530
  }
12815
13531
 
12816
13532
  type StateMachinePath {
@@ -12863,23 +13579,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12863
13579
  StateMachineMutation: {
12864
13580
  name: (value) => value.name,
12865
13581
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
12866
- add_state: async (value, { name, is_final }) => {
13582
+ add_state: async (value, { name, is_final, label, description }) => {
12867
13583
  await run(
12868
13584
  value.target.add_state_machine_state(
12869
13585
  value.name,
12870
13586
  name,
12871
- is_final ?? false
13587
+ is_final ?? false,
13588
+ label,
13589
+ description
12872
13590
  )
12873
13591
  );
12874
13592
  return value;
12875
13593
  },
12876
- add_transition: async (value, { name, from, to }) => {
13594
+ add_transition: async (value, {
13595
+ name,
13596
+ from: from2,
13597
+ to,
13598
+ label,
13599
+ description,
13600
+ action_json,
13601
+ assignee_json,
13602
+ requirements_json,
13603
+ permission_json,
13604
+ risk,
13605
+ expected_outcome_json
13606
+ }) => {
12877
13607
  await run(
12878
13608
  value.target.add_state_machine_transition(
12879
13609
  value.name,
12880
13610
  name,
12881
- from,
12882
- to
13611
+ from2,
13612
+ to,
13613
+ {
13614
+ label,
13615
+ description,
13616
+ actionJson: action_json,
13617
+ assigneeJson: assignee_json,
13618
+ requirementsJson: requirements_json,
13619
+ permissionJson: permission_json,
13620
+ risk,
13621
+ expectedOutcomeJson: expected_outcome_json
13622
+ }
12883
13623
  )
12884
13624
  );
12885
13625
  return value;
@@ -12898,16 +13638,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12898
13638
  value.target.activate_state_machine_transition(value.name, name)
12899
13639
  );
12900
13640
  return value;
13641
+ },
13642
+ observe_state: async (value, { state, force, source }) => {
13643
+ await run(
13644
+ value.target.observe_state_machine_state(
13645
+ value.name,
13646
+ state,
13647
+ force === true,
13648
+ source
13649
+ )
13650
+ );
13651
+ return value;
12901
13652
  }
12902
13653
  },
12903
13654
  StateMachineState: {
12904
13655
  name: (value) => value.name,
13656
+ label: (value) => value.label || null,
13657
+ description: (value) => value.description || null,
12905
13658
  is_final: (value) => value.is_final
12906
13659
  },
12907
13660
  StateMachineTransition: {
12908
13661
  name: (value) => value.name,
12909
13662
  from: (value) => value.from_state || { name: value.from, is_final: false },
12910
- to: (value) => value.to_state || { name: value.to, is_final: false }
13663
+ to: (value) => value.to_state || { name: value.to, is_final: false },
13664
+ label: (value) => value.label || null,
13665
+ description: (value) => value.description || null,
13666
+ action_json: (value) => value.action_json || null,
13667
+ assignee_json: (value) => value.assignee_json || null,
13668
+ requirements_json: (value) => value.requirements_json || null,
13669
+ permission_json: (value) => value.permission_json || null,
13670
+ risk: (value) => value.risk || null,
13671
+ expected_outcome_json: (value) => value.expected_outcome_json || null
12911
13672
  },
12912
13673
  StateMachinePath: {
12913
13674
  states: (value) => value.states,
@@ -12974,6 +13735,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12974
13735
  name
12975
13736
  from { name }
12976
13737
  to { name }
13738
+ label
13739
+ description
13740
+ action_json
13741
+ assignee_json
13742
+ requirements_json
13743
+ permission_json
13744
+ risk
13745
+ expected_outcome_json
12977
13746
  }
12978
13747
  }`
12979
13748
  ]
@@ -13005,6 +13774,104 @@ function describeRule(rule) {
13005
13774
  return `${rule.operator} ${String(rule.booleanValue)}`;
13006
13775
  return rule.operator;
13007
13776
  }
13777
+ function valueAsString(value) {
13778
+ if (typeof value === "string") return value;
13779
+ if (typeof value === "number" || typeof value === "boolean") {
13780
+ return String(value);
13781
+ }
13782
+ return "";
13783
+ }
13784
+ function valueAsNumber(value) {
13785
+ if (typeof value === "number") return value;
13786
+ if (typeof value === "string" && value.trim().length > 0) {
13787
+ const parsed = Number(value);
13788
+ return Number.isFinite(parsed) ? parsed : NaN;
13789
+ }
13790
+ return NaN;
13791
+ }
13792
+ function ruleStringValue(rule) {
13793
+ return rule.stringValue ?? rule.string_value ?? "";
13794
+ }
13795
+ function ruleNumberValue(rule) {
13796
+ return rule.numberValue ?? rule.number_value;
13797
+ }
13798
+ function ruleBooleanValue(rule) {
13799
+ return rule.booleanValue ?? rule.boolean_value;
13800
+ }
13801
+ function evaluateValidationRule(value, rule) {
13802
+ const operator = typeof rule.operator === "string" ? rule.operator : "";
13803
+ const stringValue2 = ruleStringValue(rule);
13804
+ const numberValue = ruleNumberValue(rule);
13805
+ const booleanValue = ruleBooleanValue(rule);
13806
+ let passed = true;
13807
+ switch (operator) {
13808
+ case "eq":
13809
+ if (numberValue !== void 0) {
13810
+ passed = valueAsNumber(value) === numberValue;
13811
+ } else if (booleanValue !== void 0) {
13812
+ passed = value === booleanValue;
13813
+ } else {
13814
+ passed = valueAsString(value) === stringValue2;
13815
+ }
13816
+ break;
13817
+ case "neq":
13818
+ if (numberValue !== void 0) {
13819
+ passed = valueAsNumber(value) !== numberValue;
13820
+ } else if (booleanValue !== void 0) {
13821
+ passed = value !== booleanValue;
13822
+ } else {
13823
+ passed = valueAsString(value) !== stringValue2;
13824
+ }
13825
+ break;
13826
+ case "gt":
13827
+ passed = valueAsNumber(value) > (numberValue ?? NaN);
13828
+ break;
13829
+ case "gte":
13830
+ passed = valueAsNumber(value) >= (numberValue ?? NaN);
13831
+ break;
13832
+ case "lt":
13833
+ passed = valueAsNumber(value) < (numberValue ?? NaN);
13834
+ break;
13835
+ case "lte":
13836
+ passed = valueAsNumber(value) <= (numberValue ?? NaN);
13837
+ break;
13838
+ case "true":
13839
+ passed = value === true;
13840
+ break;
13841
+ case "false":
13842
+ passed = value === false;
13843
+ break;
13844
+ case "regex":
13845
+ try {
13846
+ passed = new RegExp(stringValue2).test(valueAsString(value));
13847
+ } catch {
13848
+ passed = false;
13849
+ }
13850
+ break;
13851
+ case "contains":
13852
+ passed = valueAsString(value).includes(stringValue2);
13853
+ break;
13854
+ case "not_contains":
13855
+ passed = !valueAsString(value).includes(stringValue2);
13856
+ break;
13857
+ case "starts_with":
13858
+ passed = valueAsString(value).startsWith(stringValue2);
13859
+ break;
13860
+ case "ends_with":
13861
+ passed = valueAsString(value).endsWith(stringValue2);
13862
+ break;
13863
+ default:
13864
+ passed = true;
13865
+ }
13866
+ return {
13867
+ passed,
13868
+ operator,
13869
+ message: rule.message ?? null
13870
+ };
13871
+ }
13872
+ function validationRuleFailureMessage(path, rule) {
13873
+ return rule.message || `${path} failed ${rule.operator} validation`;
13874
+ }
13008
13875
  function normalizeRule(rule) {
13009
13876
  const operator = typeof rule?.operator === "string" ? rule.operator : "";
13010
13877
  if (operator.length === 0) return null;
@@ -13191,6 +14058,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
13191
14058
  }
13192
14059
 
13193
14060
  // src/client.ts
14061
+ var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
14062
+ var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
14063
+ var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
14064
+ function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
14065
+ if (value === void 0) return fallback;
14066
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
14067
+ throw new RangeError(
14068
+ `Session list ${name} must be an integer between ${minimum} and ${maximum}.`
14069
+ );
14070
+ }
14071
+ return value;
14072
+ }
13194
14073
  var STANDARD_MODULES_OPERATIONS = [
13195
14074
  {
13196
14075
  create: "entity",
@@ -13227,6 +14106,26 @@ var STANDARD_MODULES_OPERATIONS = [
13227
14106
  var BUILTIN_MODULES = {
13228
14107
  standard_modules: STANDARD_MODULES_OPERATIONS
13229
14108
  };
14109
+ function stateNameFromMethodName(methodName) {
14110
+ const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
14111
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
14112
+ }
14113
+ function appendQueryOptions(searchParams, query) {
14114
+ for (const [key, value] of Object.entries(query || {})) {
14115
+ if (value === null || typeof value === "undefined" || value === "") {
14116
+ continue;
14117
+ }
14118
+ if (value instanceof Date) {
14119
+ searchParams.set(key, value.toISOString());
14120
+ continue;
14121
+ }
14122
+ if (Array.isArray(value)) {
14123
+ if (value.length > 0) searchParams.set(key, value.join(","));
14124
+ continue;
14125
+ }
14126
+ searchParams.set(key, String(value));
14127
+ }
14128
+ }
13230
14129
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
13231
14130
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
13232
14131
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -13257,8 +14156,20 @@ function bodyInitFromSessionFileUpload(body) {
13257
14156
  return body;
13258
14157
  }
13259
14158
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
14159
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
13260
14160
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
13261
14161
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
14162
+ function chunkItems(items, batchSize) {
14163
+ const chunks = [];
14164
+ for (let offset = 0; offset < items.length; offset += batchSize) {
14165
+ chunks.push(items.slice(offset, offset + batchSize));
14166
+ }
14167
+ return chunks;
14168
+ }
14169
+ function isUnsupportedEffectCatalogMutation(error) {
14170
+ const message = error instanceof Error ? error.message : String(error);
14171
+ 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");
14172
+ }
13262
14173
  function planRecordObjectsChunks(records, batchSize) {
13263
14174
  const total = records.length;
13264
14175
  const size = Math.max(1, Math.min(batchSize, total));
@@ -13270,6 +14181,23 @@ function planRecordObjectsChunks(records, batchSize) {
13270
14181
  }
13271
14182
  return plans;
13272
14183
  }
14184
+ function preserveRecordObjectRealId(record) {
14185
+ const realId = record.id.trim();
14186
+ if (!realId) {
14187
+ return record;
14188
+ }
14189
+ const fields = record.fields || {};
14190
+ if (typeof fields.real_id === "string" && fields.real_id.trim()) {
14191
+ return record;
14192
+ }
14193
+ return {
14194
+ ...record,
14195
+ fields: {
14196
+ ...fields,
14197
+ real_id: realId
14198
+ }
14199
+ };
14200
+ }
13273
14201
  function computeEffectKey2(effect) {
13274
14202
  const attachedClass = effect.className?.trim();
13275
14203
  if (!attachedClass) {
@@ -13480,7 +14408,7 @@ var Environment = class _Environment {
13480
14408
  }
13481
14409
  get sessions() {
13482
14410
  return {
13483
- list: async (options) => this.listSessions(options?.status || "active"),
14411
+ list: async (options = {}) => this.listSessions(options),
13484
14412
  create: async (options) => this.createSession(options),
13485
14413
  connect: async (sessionId, options) => this.connectSession(sessionId, options),
13486
14414
  reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
@@ -13507,11 +14435,105 @@ var Environment = class _Environment {
13507
14435
  getAwaitingCount: async () => this.getAwaitingRecordCount()
13508
14436
  };
13509
14437
  }
14438
+ /**
14439
+ * Mirror product-owned workflow state into Granular without making Granular
14440
+ * own the customer application's state machine.
14441
+ */
14442
+ async recordState(input) {
14443
+ const { machine, state, ...target } = input;
14444
+ if (!machine.trim()) {
14445
+ throw new Error("State update requires a machine name");
14446
+ }
14447
+ if (!state.trim()) {
14448
+ throw new Error("State update requires a state");
14449
+ }
14450
+ return this.recordObject({
14451
+ className: target.className,
14452
+ id: target.id,
14453
+ ...target.label ? { label: target.label } : {},
14454
+ ...target.fields ? { fields: target.fields } : {},
14455
+ ...target.relationships ? { relationships: target.relationships } : {},
14456
+ states: {
14457
+ [machine.trim()]: {
14458
+ state: state.trim(),
14459
+ ...target.source ? { source: target.source } : {},
14460
+ ...target.cause ? { cause: target.cause } : {},
14461
+ ...target.actorId ? { actorId: target.actorId } : {},
14462
+ ...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
14463
+ ...target.force !== void 0 ? { force: target.force } : {},
14464
+ ...target.metadata ? { metadata: target.metadata } : {}
14465
+ }
14466
+ }
14467
+ });
14468
+ }
14469
+ /**
14470
+ * Mirror product-owned workflow state into Granular without making Granular
14471
+ * own the customer application's state machine.
14472
+ *
14473
+ * Example:
14474
+ * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
14475
+ */
14476
+ state(target) {
14477
+ const observe = async (machineName, stateName, input = {}) => {
14478
+ const observedState = input.observedState || input.state || stateName;
14479
+ if (!observedState) {
14480
+ throw new Error("State observation requires a target state");
14481
+ }
14482
+ return this.recordState({
14483
+ ...target,
14484
+ machine: machineName,
14485
+ state: observedState,
14486
+ ...input.source ? { source: input.source } : {},
14487
+ ...input.cause ? { cause: input.cause } : {},
14488
+ ...input.actorId ? { actorId: input.actorId } : {},
14489
+ ...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
14490
+ ...input.force !== void 0 ? { force: input.force } : {},
14491
+ ...input.metadata ? { metadata: input.metadata } : {}
14492
+ });
14493
+ };
14494
+ return new Proxy(
14495
+ {},
14496
+ {
14497
+ get: (_target, machineProperty) => {
14498
+ if (typeof machineProperty !== "string") return void 0;
14499
+ return new Proxy(
14500
+ {},
14501
+ {
14502
+ get: (_machineTarget, stateProperty) => {
14503
+ if (stateProperty === "to") {
14504
+ return (stateName, input) => observe(machineProperty, stateName, input || {});
14505
+ }
14506
+ if (typeof stateProperty !== "string") return void 0;
14507
+ return (input) => observe(
14508
+ machineProperty,
14509
+ stateNameFromMethodName(stateProperty),
14510
+ input || {}
14511
+ );
14512
+ }
14513
+ }
14514
+ );
14515
+ }
14516
+ }
14517
+ );
14518
+ }
13510
14519
  get feedback() {
13511
14520
  return {
13512
14521
  list: async () => this.listFeedback()
13513
14522
  };
13514
14523
  }
14524
+ get manualActions() {
14525
+ return {
14526
+ record: (input) => this.recordManualAction(input),
14527
+ list: (options = {}) => this.listManualActions(options),
14528
+ suggest: (options = {}) => this.suggestManualActions(options)
14529
+ };
14530
+ }
14531
+ get artifactApprovals() {
14532
+ return {
14533
+ list: (options = {}) => this.listArtifactApprovals(options),
14534
+ decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
14535
+ };
14536
+ }
13515
14537
  /**
13516
14538
  * Sessionless environments do not own a live transport, so disconnecting the
13517
14539
  * environment handle itself is a no-op. This keeps the public surface
@@ -13521,17 +14543,12 @@ var Environment = class _Environment {
13521
14543
  */
13522
14544
  async disconnect() {
13523
14545
  }
13524
- async listSessions(status = "active") {
13525
- if (status === "all") {
13526
- const [active, closed] = await Promise.all([
13527
- this.granular.listOpenSessions({ environmentId: this.environmentId }),
13528
- this.granular.listClosedSessions({ environmentId: this.environmentId })
13529
- ]);
13530
- return [...active, ...closed].sort(
13531
- (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
13532
- );
13533
- }
13534
- return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
14546
+ async listSessions(optionsOrStatus = {}) {
14547
+ const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
14548
+ return this.granular.listSessions({
14549
+ ...options,
14550
+ environmentId: this.environmentId
14551
+ });
13535
14552
  }
13536
14553
  async getUserEnvironmentState(options = {}) {
13537
14554
  return this.granular.getUserEnvironmentState({
@@ -13549,6 +14566,7 @@ var Environment = class _Environment {
13549
14566
  return this.granular.createSession({
13550
14567
  environmentId: this.environmentId,
13551
14568
  clientId: options?.clientId,
14569
+ sessionScope: options?.sessionScope,
13552
14570
  initialHeap: options?.initialHeap
13553
14571
  });
13554
14572
  }
@@ -13590,6 +14608,50 @@ var Environment = class _Environment {
13590
14608
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
13591
14609
  return Array.isArray(response.items) ? response.items : [];
13592
14610
  }
14611
+ async recordManualAction(input) {
14612
+ const body = {
14613
+ ...input,
14614
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
14615
+ };
14616
+ return this.controlPlaneRequest(
14617
+ `/control/environments/${this.environmentId}/manual-actions`,
14618
+ {
14619
+ method: "POST",
14620
+ body: JSON.stringify(body)
14621
+ }
14622
+ );
14623
+ }
14624
+ async listManualActions(options = {}) {
14625
+ const query = new URLSearchParams();
14626
+ appendQueryOptions(query, options);
14627
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14628
+ return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
14629
+ }
14630
+ async suggestManualActions(options = {}) {
14631
+ const query = new URLSearchParams();
14632
+ appendQueryOptions(query, options);
14633
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14634
+ return this.controlPlaneRequest(
14635
+ `/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
14636
+ );
14637
+ }
14638
+ async listArtifactApprovals(options = {}) {
14639
+ const query = new URLSearchParams();
14640
+ appendQueryOptions(query, options);
14641
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14642
+ return this.controlPlaneRequest(
14643
+ `/control/environments/${this.environmentId}/artifact-approvals${suffix}`
14644
+ );
14645
+ }
14646
+ async decideArtifactApproval(approvalTaskId, input) {
14647
+ return this.controlPlaneRequest(
14648
+ `/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
14649
+ {
14650
+ method: "POST",
14651
+ body: JSON.stringify(input)
14652
+ }
14653
+ );
14654
+ }
13593
14655
  getRuntimeBaseUrl() {
13594
14656
  return deriveRuntimeBaseUrl(this._apiEndpoint);
13595
14657
  }
@@ -14414,10 +15476,11 @@ var Environment = class _Environment {
14414
15476
  if (!Array.isArray(records) || records.length === 0) {
14415
15477
  return [];
14416
15478
  }
15479
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
14417
15480
  const batchSize = Math.max(
14418
15481
  1,
14419
15482
  Math.min(
14420
- records.length,
15483
+ recordsToWrite.length,
14421
15484
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
14422
15485
  )
14423
15486
  );
@@ -14425,8 +15488,8 @@ var Environment = class _Environment {
14425
15488
  MAX_RECORD_OBJECTS_CONCURRENCY,
14426
15489
  Math.max(1, options?.concurrency ?? 1)
14427
15490
  );
14428
- const plans = planRecordObjectsChunks(records, batchSize);
14429
- const total = records.length;
15491
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
15492
+ const total = recordsToWrite.length;
14430
15493
  const results = new Array(total);
14431
15494
  const onChunk = options?.onChunkComplete;
14432
15495
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -14497,12 +15560,13 @@ var Environment = class _Environment {
14497
15560
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
14498
15561
  */
14499
15562
  async enqueueRecordImport(records, options = {}) {
15563
+ const recordsToImport = records.map(preserveRecordObjectRealId);
14500
15564
  return this.controlPlaneRequest(
14501
15565
  `/control/environments/${this.environmentId}/record-imports`,
14502
15566
  {
14503
15567
  method: "POST",
14504
15568
  body: JSON.stringify({
14505
- records,
15569
+ records: recordsToImport,
14506
15570
  batchSize: options.batchSize,
14507
15571
  setupRunId: options.setupRunId,
14508
15572
  writeMode: options.writeMode
@@ -14610,11 +15674,7 @@ var EnvironmentSession = class extends Session {
14610
15674
  }
14611
15675
  buildSessionDataUrl(path, query) {
14612
15676
  const searchParams = new URLSearchParams();
14613
- for (const [key, value] of Object.entries(query || {})) {
14614
- if (value !== null && typeof value !== "undefined" && value !== "") {
14615
- searchParams.set(key, String(value));
14616
- }
14617
- }
15677
+ appendQueryOptions(searchParams, query);
14618
15678
  const queryString = searchParams.toString();
14619
15679
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path}${queryString ? `?${queryString}` : ""}`;
14620
15680
  }
@@ -14697,9 +15757,108 @@ var EnvironmentSession = class extends Session {
14697
15757
  ),
14698
15758
  get: (jobId) => this.sessionDataRequest(
14699
15759
  `/jobs/${encodeURIComponent(jobId)}`
15760
+ ),
15761
+ latest: async (options = {}) => {
15762
+ const page = await this.sessionDataRequest("/jobs", {
15763
+ status: options.status || "all",
15764
+ latest: true,
15765
+ limit: 1
15766
+ });
15767
+ return page.items[0] || null;
15768
+ }
15769
+ };
15770
+ }
15771
+ get artifacts() {
15772
+ return {
15773
+ list: (options = {}) => {
15774
+ const queryOptions = { ...options };
15775
+ if (options.target) {
15776
+ queryOptions.targetClassName = options.target.className;
15777
+ queryOptions.targetId = options.target.id;
15778
+ delete queryOptions.target;
15779
+ }
15780
+ return this.sessionDataRequest("/artifacts", queryOptions);
15781
+ },
15782
+ listForLatestJob: (options = {}) => this.artifacts.list({
15783
+ ...options,
15784
+ latestJob: true
15785
+ }),
15786
+ get: (artifactId) => this.sessionDataRequest(
15787
+ `/artifacts/${encodeURIComponent(artifactId)}`
15788
+ ),
15789
+ create: (artifact) => this.sessionDataRequest(
15790
+ "/artifacts",
15791
+ void 0,
15792
+ {
15793
+ method: "POST",
15794
+ body: artifact
15795
+ }
15796
+ ),
15797
+ updateInputs: (artifactId, patch) => this.sessionDataRequest(
15798
+ `/artifacts/${encodeURIComponent(artifactId)}`,
15799
+ void 0,
15800
+ {
15801
+ method: "PATCH",
15802
+ body: patch
15803
+ }
15804
+ ),
15805
+ validate: (artifactId) => this.sessionDataRequest(
15806
+ `/artifacts/${encodeURIComponent(artifactId)}/validate`,
15807
+ void 0,
15808
+ { method: "POST" }
15809
+ ),
15810
+ execute: (artifactId, options) => this.sessionDataRequest(
15811
+ `/artifacts/${encodeURIComponent(artifactId)}/execute`,
15812
+ void 0,
15813
+ { method: "POST", body: options }
15814
+ ),
15815
+ approve: (artifactId, options) => this.sessionDataRequest(
15816
+ `/artifacts/${encodeURIComponent(artifactId)}/approve`,
15817
+ void 0,
15818
+ { method: "POST", body: options }
15819
+ ),
15820
+ cancel: (artifactId) => this.sessionDataRequest(
15821
+ `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
15822
+ void 0,
15823
+ { method: "POST" }
14700
15824
  )
14701
15825
  };
14702
15826
  }
15827
+ get manualActions() {
15828
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15829
+ return {
15830
+ record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15831
+ "/manual-actions",
15832
+ void 0,
15833
+ {
15834
+ method: "POST",
15835
+ body: { ...input, sessionId: this.sessionId }
15836
+ }
15837
+ ) : this.environment.manualActions.record({
15838
+ ...input,
15839
+ sessionId: this.sessionId
15840
+ }),
15841
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
15842
+ ...options,
15843
+ sessionId: this.sessionId
15844
+ }),
15845
+ suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15846
+ "/manual-actions/suggestions",
15847
+ options
15848
+ ) : this.environment.manualActions.suggest(options)
15849
+ };
15850
+ }
15851
+ get artifactApprovals() {
15852
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15853
+ return {
15854
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
15855
+ decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15856
+ `/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
15857
+ void 0,
15858
+ { method: "POST", body: input }
15859
+ ) : this.environment.artifactApprovals.decide(approvalTaskId, input)
15860
+ };
15861
+ }
14703
15862
  get files() {
14704
15863
  return {
14705
15864
  list: (options = {}) => this.sessionDataRequest(
@@ -14780,13 +15939,16 @@ var EnvironmentSession = class extends Session {
14780
15939
  get transcript() {
14781
15940
  return {
14782
15941
  list: async (options = {}) => {
14783
- const [messages, jobs, entries, lists] = await Promise.all([
15942
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
14784
15943
  this.collectAllSessionItems(this.messages.list),
14785
15944
  this.collectAllSessionItems(
14786
15945
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
14787
15946
  ),
14788
15947
  this.collectAllSessionItems(this.heap.entries.list),
14789
- this.collectAllSessionItems(this.heap.lists.list)
15948
+ this.collectAllSessionItems(this.heap.lists.list),
15949
+ this.collectAllSessionItems(
15950
+ (pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
15951
+ )
14790
15952
  ]);
14791
15953
  const liveDoc = {
14792
15954
  conversation: { messages },
@@ -14800,6 +15962,21 @@ var EnvironmentSession = class extends Session {
14800
15962
  (entry) => Boolean(entry)
14801
15963
  )
14802
15964
  )
15965
+ },
15966
+ artifacts: {
15967
+ byId: Object.fromEntries(
15968
+ artifacts.map((artifact) => {
15969
+ return artifact?.artifactId ? [
15970
+ artifact.artifactId,
15971
+ artifact
15972
+ ] : null;
15973
+ }).filter(
15974
+ (entry) => Boolean(entry)
15975
+ )
15976
+ ),
15977
+ order: artifacts.map((artifact) => artifact?.artifactId).filter(
15978
+ (artifactId) => Boolean(artifactId)
15979
+ )
14803
15980
  }
14804
15981
  };
14805
15982
  const heap = normalizeHeapSnapshot({
@@ -14876,6 +16053,12 @@ var EnvironmentSession = class extends Session {
14876
16053
  async recordObject(options) {
14877
16054
  return this.environment.recordObject(options);
14878
16055
  }
16056
+ async recordState(input) {
16057
+ return this.environment.recordState(input);
16058
+ }
16059
+ state(target) {
16060
+ return this.environment.state(target);
16061
+ }
14879
16062
  async recordObjects(records, options) {
14880
16063
  return this.environment.recordObjects(records, options);
14881
16064
  }
@@ -14946,7 +16129,7 @@ var EnvironmentSession = class extends Session {
14946
16129
  * Close only the socket transport without sending `client.goodbye`.
14947
16130
  */
14948
16131
  disconnectTransport() {
14949
- this.client.disconnect();
16132
+ this.client.disconnect({ reason: "Transport detach" });
14950
16133
  }
14951
16134
  /**
14952
16135
  * Backwards-compatible alias for `disconnect()`.
@@ -15341,16 +16524,71 @@ var Granular = class _Granular {
15341
16524
  };
15342
16525
  }
15343
16526
  /**
15344
- * List active (open) sessions for an environment each session is one agent conversation thread.
16527
+ * List indexed sessions using ownership filters and bounded pagination.
16528
+ */
16529
+ async listSessions(options) {
16530
+ const environmentId = options.environmentId?.trim();
16531
+ const sandboxId = options.sandboxId?.trim();
16532
+ const subjectId = options.subjectId?.trim();
16533
+ if (!environmentId && !sandboxId && !subjectId) {
16534
+ throw new Error(
16535
+ "listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
16536
+ );
16537
+ }
16538
+ const status = options.status || "active";
16539
+ const allowedStatuses = /* @__PURE__ */ new Set([
16540
+ "active",
16541
+ "closed",
16542
+ "expired",
16543
+ "failed",
16544
+ "timeout",
16545
+ "all"
16546
+ ]);
16547
+ if (!allowedStatuses.has(status)) {
16548
+ throw new Error(`Unsupported session status: ${String(status)}`);
16549
+ }
16550
+ const limit = boundedSessionListInteger(
16551
+ options.limit,
16552
+ "limit",
16553
+ DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
16554
+ 1,
16555
+ MAX_CONVERSATION_SESSION_LIST_LIMIT
16556
+ );
16557
+ const offset = boundedSessionListInteger(
16558
+ options.offset,
16559
+ "offset",
16560
+ 0,
16561
+ 0,
16562
+ MAX_CONVERSATION_SESSION_LIST_OFFSET
16563
+ );
16564
+ const query = new URLSearchParams({
16565
+ limit: String(limit),
16566
+ offset: String(offset)
16567
+ });
16568
+ if (environmentId) query.set("environmentId", environmentId);
16569
+ if (sandboxId) query.set("sandboxId", sandboxId);
16570
+ if (subjectId) query.set("userId", subjectId);
16571
+ if (options.sessionScope?.trim()) {
16572
+ query.set("sessionScope", options.sessionScope.trim());
16573
+ }
16574
+ if (status !== "all") query.set("status", status);
16575
+ const res = await this.request(
16576
+ `/control/sessions?${query.toString()}`
16577
+ );
16578
+ const items = Array.isArray(res.items) ? res.items : [];
16579
+ return items.map((row) => this.normalizeConversationSession(row));
16580
+ }
16581
+ /**
16582
+ * List active (open) sessions for an environment.
15345
16583
  */
15346
16584
  async listOpenSessions(filters) {
15347
- return this.listSessionsForEnvironment(filters.environmentId, "active");
16585
+ return this.listSessions({ ...filters, status: "active" });
15348
16586
  }
15349
16587
  /**
15350
16588
  * List closed sessions for an environment (conversations that have disconnected).
15351
16589
  */
15352
16590
  async listClosedSessions(filters) {
15353
- return this.listSessionsForEnvironment(filters.environmentId, "closed");
16591
+ return this.listSessions({ ...filters, status: "closed" });
15354
16592
  }
15355
16593
  async getUserEnvironmentState(options) {
15356
16594
  const query = new URLSearchParams({
@@ -15385,14 +16623,6 @@ var Granular = class _Granular {
15385
16623
  });
15386
16624
  return result.readAtBySessionId || {};
15387
16625
  }
15388
- async listSessionsForEnvironment(environmentId, status) {
15389
- const query = new URLSearchParams({ environmentId, status });
15390
- const res = await this.request(
15391
- `/control/sessions?${query.toString()}`
15392
- );
15393
- const items = Array.isArray(res.items) ? res.items : [];
15394
- return items.map((row) => this.normalizeConversationSession(row));
15395
- }
15396
16626
  normalizeConversationSession(row) {
15397
16627
  const sessionId = String(row.sessionId ?? row.session_id ?? "");
15398
16628
  const environmentId = String(row.environmentId ?? row.environment_id ?? "");
@@ -15451,6 +16681,7 @@ var Granular = class _Granular {
15451
16681
  */
15452
16682
  async createSession(options) {
15453
16683
  const clientId = options.clientId || `client_${Date.now()}`;
16684
+ const sessionScope = options.sessionScope?.trim() || void 0;
15454
16685
  await this.activateEnvironment(options.environmentId);
15455
16686
  const envData = await this.environments.get(options.environmentId);
15456
16687
  const environment = this.bindEnvironmentHandle(envData);
@@ -15459,6 +16690,8 @@ var Granular = class _Granular {
15459
16690
  body: JSON.stringify({
15460
16691
  environmentId: options.environmentId,
15461
16692
  clientId,
16693
+ sessionScope,
16694
+ capabilities: sessionScope ? { sessionScope } : void 0,
15462
16695
  initialHeap: options.initialHeap
15463
16696
  })
15464
16697
  });
@@ -15703,15 +16936,43 @@ var Granular = class _Granular {
15703
16936
  const effects = Array.from(
15704
16937
  this.getSandboxEffectMap(host.sandboxId).values()
15705
16938
  ).map((effect) => this.serializeEffect(effect));
15706
- const result = await withTimeout(
15707
- host.wsClient.call("effects.publishCatalog", {
15708
- effects
15709
- }),
15710
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
15711
- `effects.publishCatalog for sandbox ${host.sandboxId}`
15712
- );
15713
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
15714
- const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
16939
+ let acceptedCount = 0;
16940
+ const rejected = [];
16941
+ try {
16942
+ await withTimeout(
16943
+ host.wsClient.call("effects.resetCatalog", {}),
16944
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16945
+ `effects.resetCatalog for sandbox ${host.sandboxId}`
16946
+ );
16947
+ for (const batch of chunkItems(effects, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
16948
+ const result = await withTimeout(
16949
+ host.wsClient.call("effects.addCatalog", {
16950
+ effects: batch
16951
+ }),
16952
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16953
+ `effects.addCatalog for sandbox ${host.sandboxId}`
16954
+ );
16955
+ acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16956
+ if (Array.isArray(result?.rejected)) {
16957
+ rejected.push(...result.rejected);
16958
+ }
16959
+ }
16960
+ } catch (error) {
16961
+ if (!isUnsupportedEffectCatalogMutation(error)) {
16962
+ throw error;
16963
+ }
16964
+ const result = await withTimeout(
16965
+ host.wsClient.call("effects.publishCatalog", {
16966
+ effects
16967
+ }),
16968
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16969
+ `effects.publishCatalog for sandbox ${host.sandboxId}`
16970
+ );
16971
+ acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16972
+ if (Array.isArray(result?.rejected)) {
16973
+ rejected.push(...result.rejected);
16974
+ }
16975
+ }
15715
16976
  if (acceptedCount === 0 && rejected.length > 0) {
15716
16977
  const detail = rejected.map(
15717
16978
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -16943,6 +18204,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
16943
18204
  var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
16944
18205
  var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
16945
18206
  var LEGACY_SANDBOX_TOOLS_MODULE_PATTERN = "\\.\\/sandbox-tools(?:\\.js)?";
18207
+ var HARNESS_V3_RUNTIME_MODULE_PATTERN = "@granular/(?:agent|session|domain(?:/[A-Za-z_$][\\w$]*)?|actions/(?:backend|frontend)|utils/(?:csv|xlsx))";
16946
18208
  function hasNamedModuleImport(source, moduleName, name) {
16947
18209
  const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16948
18210
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -17001,6 +18263,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
17001
18263
  message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
17002
18264
  });
17003
18265
  }
18266
+ if (new RegExp(
18267
+ `import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*['"]${HARNESS_V3_RUNTIME_MODULE_PATTERN}['"]`
18268
+ ).test(normalized)) {
18269
+ issues.push({
18270
+ code: "runtime_namespace_import",
18271
+ severity: "error",
18272
+ 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"`.'
18273
+ });
18274
+ }
17004
18275
  if (/\bprocess\.exit\s*\(/.test(normalized)) {
17005
18276
  issues.push({
17006
18277
  code: "process_exit",
@@ -18025,6 +19296,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
18025
19296
  entries: {}
18026
19297
  });
18027
19298
  }
19299
+ function buildGranularAgentManualActionMemorySummary(input) {
19300
+ const maxItems = Math.max(1, Math.min(12, input.maxItems ?? 8));
19301
+ const suggestions = (input.suggestions || []).filter((suggestion) => suggestion?.actionKey).slice(0, maxItems).map((suggestion) => ({
19302
+ actionKey: suggestion.actionKey,
19303
+ label: suggestion.label || null,
19304
+ targetClassName: suggestion.targetClassName || null,
19305
+ count: typeof suggestion.count === "number" && Number.isFinite(suggestion.count) ? suggestion.count : null,
19306
+ subjectCount: typeof suggestion.subjectCount === "number" && Number.isFinite(suggestion.subjectCount) ? suggestion.subjectCount : null,
19307
+ successCount: typeof suggestion.successCount === "number" && Number.isFinite(suggestion.successCount) ? suggestion.successCount : null,
19308
+ failureCount: typeof suggestion.failureCount === "number" && Number.isFinite(suggestion.failureCount) ? suggestion.failureCount : null,
19309
+ lastOccurredAt: typeof suggestion.lastOccurredAt === "number" && Number.isFinite(suggestion.lastOccurredAt) ? suggestion.lastOccurredAt : null,
19310
+ sampleTargetIds: Array.isArray(suggestion.sampleTargetIds) ? suggestion.sampleTargetIds.filter(
19311
+ (id) => typeof id === "string" && id.trim().length > 0
19312
+ ).slice(0, 6) : []
19313
+ }));
19314
+ return [
19315
+ renderConstBlock("manualActionMemory", {
19316
+ suggestions
19317
+ }),
19318
+ "Use manualActionMemory only as behavioral context for likely next actions. Ground the current target and validate permissions before creating or running prepared actions."
19319
+ ].join("\n");
19320
+ }
19321
+ function buildGranularAgentManualActionBlock(manualActionSummary) {
19322
+ return manualActionSummary?.trim() || buildGranularAgentManualActionMemorySummary({ suggestions: [] });
19323
+ }
18028
19324
  function projectSessionFileSummary(liveDoc) {
18029
19325
  const files = asRecord4(liveDoc?.files);
18030
19326
  const byId = asRecord4(files?.byId) || {};
@@ -18056,8 +19352,12 @@ function buildGranularAgentFileBlock(fileSummary) {
18056
19352
  function extractRuntimeContractExports(domainBlock) {
18057
19353
  const classes = /* @__PURE__ */ new Set();
18058
19354
  const actions = /* @__PURE__ */ new Set();
18059
- const classPattern = /export\s+declare\s+(?:const|class)\s+([A-Za-z_$][\w$]*)/g;
18060
- for (const match of domainBlock.matchAll(classPattern)) {
19355
+ const classConstPattern = /export\s+declare\s+const\s+([A-Za-z_$][\w$]*)\s*:\s*EntityClass\b/g;
19356
+ for (const match of domainBlock.matchAll(classConstPattern)) {
19357
+ classes.add(match[1]);
19358
+ }
19359
+ const classDeclPattern = /export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\b/g;
19360
+ for (const match of domainBlock.matchAll(classDeclPattern)) {
18061
19361
  classes.add(match[1]);
18062
19362
  }
18063
19363
  const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
@@ -18562,6 +19862,9 @@ function buildGranularAgentSystemPrompt(input) {
18562
19862
  });
18563
19863
  const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
18564
19864
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
19865
+ const manualActionBlock = buildGranularAgentManualActionBlock(
19866
+ input.manualActionSummary
19867
+ );
18565
19868
  const knownFactsBlock = renderConstBlock(
18566
19869
  "knownFacts",
18567
19870
  buildKnownFactsFromCheckpoint(input.checkpoint)
@@ -18575,16 +19878,16 @@ function buildGranularAgentSystemPrompt(input) {
18575
19878
  - \`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.
18576
19879
  - 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.
18577
19880
  - 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.
18578
- - When \`replyToUser(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag: \`<granular-object class="class_name" id="stable_id_or_path" label="Visible label" />\`. Use the actual class name and stable id/path from the runtime record or effect result; do not invent ids, field names, or snake/camel-case aliases that are not present in the type declarations or returned object.
18579
- - Treat \`showObjects(...)\` as the UI display call for user-visible records, not as a general storage helper. Do not wrap records under an \`items\` key.
18580
- - 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 that saved selection exactly once with \`showObjects({ variableNames: ["stable_selection_name"] })\`.
18581
- - \`groundedObjects.save(...)\` only accepts scalar values, runtime records/sandbox instances, or arrays of runtime records/sandbox instances from one class. Do not save plain action/effect result objects or arrays of JSON summaries returned by actions. If an action returns ids/paths for records that should remain referable or displayed as records, fetch the matching runtime records first with the generated class \`.get(...)\`/query API, then save/display those fetched records. If the action returned only structured summaries, answer from those summaries with \`replyToUser(...)\`.
18582
- - Do not use \`showObjects({ entries: [...] })\` or \`showObjects({ saveAs, entries })\` as a shortcut for ordered pages, queues, search results, or ranked lists; those forms can create duplicate or poorly labelled displays. Save the selection with \`groundedObjects.save(...)\` and display it via \`variableNames\` instead.
18583
- - Use \`entryPaths\` only for a few already-known individual records and \`listNames\` only for a host-created list that you intentionally want to show. Do not display both an entry/list selection and a heap variable for the same records.
19881
+ - 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.
19882
+ - 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"] })\`.
19883
+ - \`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(...)\`.
19884
+ - 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.
19885
+ - 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()\`.
19886
+ - 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.
19887
+ - 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.
18584
19888
  - 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.
18585
19889
  - 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.
18586
- - Any job that identifies a specific record in the visible answer must also display that grounded record with \`showObjects(...)\` when the user should see/open it, or save it with \`groundedObjects.save(...)\` when it is only needed for follow-up resolution.
18587
- - 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.
19890
+ - 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.
18588
19891
  - 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\`.
18589
19892
  - \`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.
18590
19893
  - 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,7 +19898,7 @@ function buildGranularAgentSystemPrompt(input) {
18595
19898
  - When using code, assistant text must be empty or one brief summary.
18596
19899
  - Code must be plain runnable JavaScript with top-level await.
18597
19900
  - Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
18598
- - 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.
19901
+ - 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.
18599
19902
  - 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.
18600
19903
  - 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.
18601
19904
  - 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\`.
@@ -18658,6 +19961,7 @@ ${workflowRules}
18658
19961
  High-priority execution rules:
18659
19962
  - 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.
18660
19963
  - 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.
19964
+ - 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.
18661
19965
  - 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.
18662
19966
  - 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.
18663
19967
  - 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.
@@ -18701,7 +20005,7 @@ Intent resolution:
18701
20005
  - 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.
18702
20006
  - 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.
18703
20007
  - 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.
18704
- - 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 its path string into code and fetch/read it with the relevant runtime API.
20008
+ - 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.
18705
20009
  - 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.
18706
20010
  - Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
18707
20011
  - 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.
@@ -18828,20 +20132,10 @@ Ask the user when:
18828
20132
  - the target is unique but the requested action is unclear
18829
20133
 
18830
20134
  Relationship filters:
18831
- - One-record relationships use \`is\`.
18832
- - Multi-record relationships use \`some\`.
18833
- - Never guess relationship cardinality from wording. Check the generated TypeScript filter type for the field before writing a relationship filter; if you are not sure, use declared relationship getters from already grounded records instead of a relationship filter.
18834
- - If a relationship filter type or field is one-record/singular, never use \`some\` on that field. Match by \`id\`, \`path\`, or \`is\`, or fetch the related record and continue through declared getters when you need to traverse farther.
18835
- - 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.
18836
- - 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.
18837
- - Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
18838
- - 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.
18839
- - Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
18840
- - 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\`.
18841
- - 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.
18842
- - Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
18843
- - 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.
18844
- - Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
20135
+ - Use the generated filter type as the authority: \`OneRelationFilter\` supports \`id\`, \`path\`, \`is\`, \`null\`, \`not_null\`; \`ManyRelationFilter\` supports those plus \`some\`.
20136
+ - Use \`id\` or \`path\` for a known related record; use \`is\` or \`some\` only for nested target-field filters.
20137
+ - 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.
20138
+ - Never pass a full record instance into a filter. Use its id/path or a declared relationship getter.
18845
20139
  ${domainSections.docs ? `
18846
20140
  Domain notes:
18847
20141
  ${domainSections.docs}
@@ -18852,6 +20146,24 @@ ${actionIndex}
18852
20146
  - 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.
18853
20147
  - 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(...)\`.
18854
20148
  - Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
20149
+ - 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.
20150
+ - For pure field-collection requests, target the class-level entry state handle; for submit/review requests, target the nearest requested later state.
20151
+ - Choose the nearest target state that matches the user's words. Do not aim at a later state just because it is reachable.
20152
+ - 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\`.
20153
+ - 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.
20154
+ - 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.
20155
+ - 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()\`.
20156
+ - 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.
20157
+ - 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.
20158
+ - 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.
20159
+ - 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.
20160
+ - 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.
20161
+ - 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.
20162
+ - 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.
20163
+ - 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.
20164
+ - 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.
20165
+ - 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.
20166
+ - Use \`await prepared.show()\` or \`await actions.show(prepared)\` only to display an already-created prepared action again.
18855
20167
  - 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.
18856
20168
  - Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
18857
20169
  - 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.
@@ -18882,6 +20194,8 @@ ${loopBlock}
18882
20194
 
18883
20195
  ${knownFactsBlock}
18884
20196
 
20197
+ ${manualActionBlock}
20198
+
18885
20199
  [Request]
18886
20200
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18887
20201
  }
@@ -19075,8 +20389,9 @@ function buildContinuationInstructionFromTemplate(resultPreview, options) {
19075
20389
  }
19076
20390
 
19077
20391
  // src/openai-usage.ts
19078
- var OPENAI_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
19079
- var OPENAI_PRICING_EFFECTIVE_DATE = "2026-05-19";
20392
+ var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
20393
+ var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
20394
+ var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
19080
20395
  var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
19081
20396
  "gpt-5.4": {
19082
20397
  provider: "openai",
@@ -19085,8 +20400,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
19085
20400
  inputUsdPerMillion: 2.5,
19086
20401
  cachedInputUsdPerMillion: 0.25,
19087
20402
  outputUsdPerMillion: 15,
19088
- sourceUrl: OPENAI_PRICING_SOURCE_URL,
19089
- effectiveDate: OPENAI_PRICING_EFFECTIVE_DATE
20403
+ sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
20404
+ effectiveDate: "2026-05-19"
20405
+ },
20406
+ "gpt-5.6-luna": {
20407
+ provider: "openai",
20408
+ model: "gpt-5.6-luna",
20409
+ currency: "USD",
20410
+ inputUsdPerMillion: 1,
20411
+ cachedInputUsdPerMillion: 0.1,
20412
+ cacheWriteUsdPerMillion: 1.25,
20413
+ outputUsdPerMillion: 6,
20414
+ sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
20415
+ effectiveDate: "2026-07-11",
20416
+ longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
20417
+ longContextPricing: {
20418
+ inputUsdPerMillion: 2,
20419
+ cachedInputUsdPerMillion: 0.2,
20420
+ cacheWriteUsdPerMillion: 2.5,
20421
+ outputUsdPerMillion: 9
20422
+ }
19090
20423
  }
19091
20424
  };
19092
20425
  function asRecord5(value) {
@@ -19099,8 +20432,18 @@ function numberField(record, key) {
19099
20432
  function microsPerMillion(usdPerMillion) {
19100
20433
  return Math.round(usdPerMillion * 1e6);
19101
20434
  }
19102
- function getOpenAIModelPricing(model) {
19103
- return OPENAI_MODEL_PRICING_USD_PER_MILLION[model] || null;
20435
+ function getOpenAIModelPricing(model, inputTokens = 0) {
20436
+ const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
20437
+ if (!pricing) return null;
20438
+ const threshold = pricing.longContextThresholdTokens ?? null;
20439
+ if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
20440
+ return {
20441
+ ...pricing,
20442
+ ...pricing.longContextPricing,
20443
+ contextTier: "long"
20444
+ };
20445
+ }
20446
+ return { ...pricing, contextTier: "short" };
19104
20447
  }
19105
20448
  function normalizeOpenAIUsage(rawUsage) {
19106
20449
  const usage = asRecord5(rawUsage);
@@ -19108,6 +20451,7 @@ function normalizeOpenAIUsage(rawUsage) {
19108
20451
  return {
19109
20452
  inputTokens: 0,
19110
20453
  cachedInputTokens: 0,
20454
+ cacheWriteTokens: 0,
19111
20455
  uncachedInputTokens: 0,
19112
20456
  outputTokens: 0,
19113
20457
  reasoningTokens: 0,
@@ -19123,20 +20467,28 @@ function normalizeOpenAIUsage(rawUsage) {
19123
20467
  inputTokens,
19124
20468
  numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
19125
20469
  );
20470
+ const cacheWriteTokens = Math.min(
20471
+ Math.max(inputTokens - cachedInputTokens, 0),
20472
+ numberField(inputDetails, "cache_write_tokens")
20473
+ );
19126
20474
  const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
19127
20475
  return {
19128
20476
  inputTokens,
19129
20477
  cachedInputTokens,
19130
- uncachedInputTokens: Math.max(inputTokens - cachedInputTokens, 0),
20478
+ cacheWriteTokens,
20479
+ uncachedInputTokens: Math.max(
20480
+ inputTokens - cachedInputTokens - cacheWriteTokens,
20481
+ 0
20482
+ ),
19131
20483
  outputTokens,
19132
20484
  reasoningTokens,
19133
20485
  totalTokens
19134
20486
  };
19135
20487
  }
19136
20488
  function calculateOpenAITokenSpend(model, rawUsage) {
19137
- const pricing = getOpenAIModelPricing(model);
19138
- if (!pricing) return null;
19139
20489
  const usage = normalizeOpenAIUsage(rawUsage);
20490
+ const pricing = getOpenAIModelPricing(model, usage.inputTokens);
20491
+ if (!pricing) return null;
19140
20492
  const inputPricePerMillionMicros = microsPerMillion(
19141
20493
  pricing.inputUsdPerMillion
19142
20494
  );
@@ -19146,14 +20498,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
19146
20498
  const outputPricePerMillionMicros = microsPerMillion(
19147
20499
  pricing.outputUsdPerMillion
19148
20500
  );
20501
+ const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
20502
+ const cacheWriteCostMicros = Math.round(
20503
+ usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
20504
+ );
19149
20505
  const amountMicros = Math.round(
19150
- (usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
20506
+ (usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
19151
20507
  );
19152
20508
  return {
19153
20509
  provider: "openai",
19154
20510
  model,
19155
20511
  inputTokens: usage.inputTokens,
19156
20512
  cachedInputTokens: usage.cachedInputTokens,
20513
+ cacheWriteTokens: usage.cacheWriteTokens,
19157
20514
  uncachedInputTokens: usage.uncachedInputTokens,
19158
20515
  outputTokens: usage.outputTokens,
19159
20516
  reasoningTokens: usage.reasoningTokens,
@@ -19162,13 +20519,17 @@ function calculateOpenAITokenSpend(model, rawUsage) {
19162
20519
  currency: "USD",
19163
20520
  inputPricePerMillionMicros,
19164
20521
  cachedInputPricePerMillionMicros,
20522
+ cacheWritePricePerMillionMicros,
20523
+ cacheWriteCostMicros,
19165
20524
  outputPricePerMillionMicros,
20525
+ pricingContextTier: pricing.contextTier || "short",
20526
+ longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
19166
20527
  pricingSource: pricing.sourceUrl,
19167
20528
  pricingEffectiveAt: pricing.effectiveDate,
19168
20529
  usage
19169
20530
  };
19170
20531
  }
19171
20532
 
19172
- export { Environment, EnvironmentSession, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, calculateOpenAITokenSpend, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createHarnessVerifierSnapshot, evaluateContinuation, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isLocalApiUrl, listHarnessTemplates, normalizeEffectBehaviors, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest };
20533
+ export { Environment, EnvironmentSession, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentManualActionBlock, buildGranularAgentManualActionMemorySummary, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, calculateOpenAITokenSpend, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createHarnessVerifierSnapshot, evaluateContinuation, evaluateValidationRule, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isLocalApiUrl, listHarnessTemplates, normalizeEffectBehaviors, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest, validationRuleFailureMessage };
19173
20534
  //# sourceMappingURL=index.mjs.map
19174
20535
  //# sourceMappingURL=index.mjs.map