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