@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.
@@ -25,11 +25,11 @@ var __export = (target, all) => {
25
25
  for (var name in all)
26
26
  __defProp(target, name, { get: all[name], enumerable: true });
27
27
  };
28
- var __copyProps = (to, from, except, desc) => {
29
- if (from && typeof from === "object" || typeof from === "function") {
30
- for (let key of __getOwnPropNames(from))
28
+ var __copyProps = (to, from2, except, desc) => {
29
+ if (from2 && typeof from2 === "object" || typeof from2 === "function") {
30
+ for (let key of __getOwnPropNames(from2))
31
31
  if (!__hasOwnProp.call(to, key) && key !== except)
32
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ __defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
33
33
  }
34
34
  return to;
35
35
  };
@@ -4019,6 +4019,9 @@ function rpcTimeoutMsForMethod(method) {
4019
4019
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
4020
4020
  case "client.heartbeat":
4021
4021
  case "effects.publishCatalog":
4022
+ case "effects.resetCatalog":
4023
+ case "effects.addCatalog":
4024
+ case "effects.removeCatalog":
4022
4025
  case "effects.refresh":
4023
4026
  return EFFECT_CONTROL_RPC_TIMEOUT_MS;
4024
4027
  case "harness.run":
@@ -4043,16 +4046,37 @@ var WSClient = class {
4043
4046
  tokenRefreshTimer = null;
4044
4047
  isExplicitlyDisconnected = false;
4045
4048
  reconnectAttempts = 0;
4049
+ connectPromise = null;
4050
+ connectionEpoch = 0;
4051
+ cancelConnectAttempt = null;
4046
4052
  options;
4047
4053
  constructor(options) {
4048
4054
  this.options = options;
4049
4055
  this.url = options.url;
4050
4056
  this.sessionId = options.sessionId;
4051
4057
  this.token = options.token;
4058
+ if (options.initialDocumentSnapshot) {
4059
+ this.seedDocumentSnapshot(options.initialDocumentSnapshot);
4060
+ }
4052
4061
  }
4053
4062
  get currentSessionId() {
4054
4063
  return this.sessionId;
4055
4064
  }
4065
+ seedDocumentSnapshot(document) {
4066
+ if (!document || typeof document !== "object" || Array.isArray(document)) {
4067
+ return;
4068
+ }
4069
+ try {
4070
+ this.doc = document instanceof Uint8Array ? Automerge.load(document) : Automerge.from(document);
4071
+ this.syncState = Automerge.initSyncState();
4072
+ this.emit("sync", this.doc);
4073
+ } catch (error) {
4074
+ console.warn("[Granular] Failed to seed cached session document", error);
4075
+ }
4076
+ }
4077
+ saveDocumentSnapshot() {
4078
+ return Automerge.save(this.doc);
4079
+ }
4056
4080
  clearTokenRefreshTimer() {
4057
4081
  if (this.tokenRefreshTimer) {
4058
4082
  clearTimeout(this.tokenRefreshTimer);
@@ -4162,8 +4186,23 @@ var WSClient = class {
4162
4186
  * Connect to the WebSocket server
4163
4187
  * @returns {Promise<void>} Resolves when connection is open
4164
4188
  */
4165
- async connect() {
4189
+ async connect(options = {}) {
4190
+ if (this.ws?.readyState === READY_STATE_OPEN) return;
4191
+ if (this.connectPromise) return this.connectPromise;
4192
+ const connectPromise = this.connectAttempt(options.signal);
4193
+ this.connectPromise = connectPromise;
4194
+ try {
4195
+ await connectPromise;
4196
+ } finally {
4197
+ if (this.connectPromise === connectPromise) {
4198
+ this.connectPromise = null;
4199
+ }
4200
+ }
4201
+ }
4202
+ async connectAttempt(signal) {
4203
+ if (signal?.aborted) throw new Error("WebSocket connect aborted");
4166
4204
  const token = await this.resolveTokenForConnect();
4205
+ if (signal?.aborted) throw new Error("WebSocket connect aborted");
4167
4206
  this.isExplicitlyDisconnected = false;
4168
4207
  this.scheduleTokenRefresh();
4169
4208
  if (this.reconnectTimer) {
@@ -4175,7 +4214,7 @@ var WSClient = class {
4175
4214
  try {
4176
4215
  const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
4177
4216
  WebSocketClass = wsModule.default || wsModule;
4178
- } catch (e) {
4217
+ } catch {
4179
4218
  }
4180
4219
  }
4181
4220
  if (!WebSocketClass) {
@@ -4183,83 +4222,97 @@ var WSClient = class {
4183
4222
  'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
4184
4223
  );
4185
4224
  }
4225
+ const epoch = ++this.connectionEpoch;
4226
+ const wsUrl = new URL(this.url);
4227
+ wsUrl.searchParams.set("sessionId", this.sessionId);
4228
+ wsUrl.searchParams.set("token", token);
4229
+ const socket = new WebSocketClass(wsUrl.toString());
4230
+ this.ws = socket;
4186
4231
  return new Promise((resolve, reject) => {
4187
- try {
4188
- const wsUrl = new URL(this.url);
4189
- wsUrl.searchParams.set("sessionId", this.sessionId);
4190
- wsUrl.searchParams.set("token", token);
4191
- this.ws = new WebSocketClass(wsUrl.toString());
4192
- if (!this.ws) throw new Error("Failed to create WebSocket");
4193
- const socket = this.ws;
4194
- if (typeof socket.on === "function") {
4195
- socket.on("open", () => {
4196
- if (this.reconnectTimer) {
4197
- clearTimeout(this.reconnectTimer);
4198
- this.reconnectTimer = null;
4199
- }
4200
- this.reconnectAttempts = 0;
4201
- this.emit("open", {});
4202
- resolve();
4203
- });
4204
- socket.on("message", (data) => {
4205
- try {
4206
- const message = JSON.parse(data.toString());
4207
- this.handleMessage(message);
4208
- } catch (error) {
4209
- console.error("[Granular] Failed to parse message:", error);
4210
- }
4211
- });
4212
- socket.on("error", (error) => {
4213
- this.emit("error", error);
4214
- if (socket.readyState !== READY_STATE_OPEN) {
4215
- reject(error);
4216
- }
4217
- });
4218
- socket.on("close", (code, reason) => {
4219
- this.handleDisconnect({
4220
- code,
4221
- reason: this.normalizeReason(reason),
4222
- // ws does not provide wasClean on Node-style close callback
4223
- wasClean: code === 1e3
4224
- });
4225
- });
4232
+ let settled = false;
4233
+ const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
4234
+ const finish = (error) => {
4235
+ if (settled) return;
4236
+ settled = true;
4237
+ if (this.cancelConnectAttempt === handleAbort) {
4238
+ this.cancelConnectAttempt = null;
4239
+ }
4240
+ signal?.removeEventListener("abort", handleAbort);
4241
+ if (error) {
4242
+ reject(error instanceof Error ? error : new Error(String(error)));
4226
4243
  } else {
4227
- this.ws.onopen = () => {
4228
- if (this.reconnectTimer) {
4229
- clearTimeout(this.reconnectTimer);
4230
- this.reconnectTimer = null;
4231
- }
4232
- this.reconnectAttempts = 0;
4233
- this.emit("open", {});
4234
- resolve();
4235
- };
4236
- this.ws.onmessage = (event) => {
4237
- try {
4238
- const data = event.data;
4239
- const message = JSON.parse(data.toString());
4240
- this.handleMessage(message);
4241
- } catch (error) {
4242
- console.error("[Granular] Failed to parse message:", error);
4243
- }
4244
- };
4245
- this.ws.onerror = (event) => {
4246
- const error = new Error("WebSocket error");
4247
- error.event = event;
4248
- this.emit("error", error);
4249
- if (this.ws?.readyState !== READY_STATE_OPEN) {
4250
- reject(error);
4251
- }
4252
- };
4253
- this.ws.onclose = (event) => {
4254
- this.handleDisconnect({
4255
- code: event.code,
4256
- reason: event.reason,
4257
- wasClean: event.wasClean
4258
- });
4259
- };
4244
+ resolve();
4260
4245
  }
4261
- } catch (error) {
4262
- reject(error);
4246
+ };
4247
+ const closeStaleSocket = () => {
4248
+ try {
4249
+ socket.close(1e3, "Stale connection attempt");
4250
+ } catch {
4251
+ }
4252
+ };
4253
+ const handleAbort = () => {
4254
+ if (isCurrent()) {
4255
+ this.connectionEpoch += 1;
4256
+ this.ws = null;
4257
+ }
4258
+ closeStaleSocket();
4259
+ finish(new Error("WebSocket connect aborted"));
4260
+ };
4261
+ this.cancelConnectAttempt = handleAbort;
4262
+ const handleOpen = () => {
4263
+ if (!isCurrent()) {
4264
+ closeStaleSocket();
4265
+ return;
4266
+ }
4267
+ this.reconnectAttempts = 0;
4268
+ this.emit("open", {});
4269
+ finish();
4270
+ };
4271
+ const handleMessage = (data) => {
4272
+ if (!isCurrent()) return;
4273
+ try {
4274
+ const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
4275
+ this.handleMessage(JSON.parse(text));
4276
+ } catch (error) {
4277
+ console.error("[Granular] Failed to parse message:", error);
4278
+ }
4279
+ };
4280
+ const handleError = (error) => {
4281
+ if (!isCurrent()) return;
4282
+ const typedError = error instanceof Error ? error : new Error("WebSocket error");
4283
+ this.emit("error", typedError);
4284
+ if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
4285
+ };
4286
+ const handleClose = (close) => {
4287
+ if (!isCurrent()) return;
4288
+ if (!settled) {
4289
+ finish(
4290
+ new Error(
4291
+ `WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
4292
+ )
4293
+ );
4294
+ }
4295
+ this.handleDisconnect({
4296
+ code: close.code,
4297
+ reason: this.normalizeReason(close.reason),
4298
+ wasClean: close.wasClean
4299
+ });
4300
+ };
4301
+ signal?.addEventListener("abort", handleAbort, { once: true });
4302
+ const nodeSocket = socket;
4303
+ if (typeof nodeSocket.on === "function") {
4304
+ nodeSocket.on("open", handleOpen);
4305
+ nodeSocket.on("message", handleMessage);
4306
+ nodeSocket.on("error", handleError);
4307
+ nodeSocket.on(
4308
+ "close",
4309
+ (code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
4310
+ );
4311
+ } else {
4312
+ socket.onopen = handleOpen;
4313
+ socket.onmessage = (event) => handleMessage(event.data);
4314
+ socket.onerror = handleError;
4315
+ socket.onclose = (event) => handleClose(event);
4263
4316
  }
4264
4317
  });
4265
4318
  }
@@ -4277,9 +4330,58 @@ var WSClient = class {
4277
4330
  return void 0;
4278
4331
  }
4279
4332
  rejectPending(error) {
4280
- this.messageQueue.forEach((pending) => pending.reject(error));
4333
+ this.messageQueue.forEach((pending) => {
4334
+ clearTimeout(pending.timeout);
4335
+ pending.reject(error);
4336
+ });
4281
4337
  this.messageQueue = [];
4282
4338
  }
4339
+ emitReconnectErrorMessage(error) {
4340
+ const reconnectInfo = {
4341
+ error,
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
+ }
4357
+ scheduleReconnectAttempt() {
4358
+ if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
4359
+ const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
4360
+ 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;
4361
+ if (this.reconnectAttempts >= maxReconnectAttempts) {
4362
+ this.emitReconnectErrorMessage(
4363
+ `WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
4364
+ );
4365
+ return null;
4366
+ }
4367
+ this.reconnectAttempts += 1;
4368
+ const reconnectDelayMs = Math.min(
4369
+ 3e4,
4370
+ baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
4371
+ );
4372
+ this.reconnectTimer = setTimeout(() => {
4373
+ this.reconnectTimer = null;
4374
+ console.log("[Granular] Attempting reconnect...");
4375
+ this.connect().catch((error) => {
4376
+ console.error("[Granular] Reconnect failed:", error);
4377
+ this.emitReconnectErrorMessage(
4378
+ error instanceof Error ? error.message : String(error)
4379
+ );
4380
+ this.scheduleReconnectAttempt();
4381
+ });
4382
+ }, reconnectDelayMs);
4383
+ return reconnectDelayMs;
4384
+ }
4283
4385
  buildDisconnectError(info) {
4284
4386
  const details = [
4285
4387
  info.code !== void 0 ? `code=${info.code}` : void 0,
@@ -4289,8 +4391,6 @@ var WSClient = class {
4289
4391
  return new Error(`WebSocket disconnected${suffix}`);
4290
4392
  }
4291
4393
  handleDisconnect(close = {}) {
4292
- const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
4293
- 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;
4294
4394
  const unexpected = !this.isExplicitlyDisconnected;
4295
4395
  const info = {
4296
4396
  code: close.code,
@@ -4310,32 +4410,9 @@ var WSClient = class {
4310
4410
  const disconnectError = this.buildDisconnectError(info);
4311
4411
  this.rejectPending(disconnectError);
4312
4412
  this.emit("disconnect", info);
4313
- if (this.reconnectAttempts >= maxReconnectAttempts) {
4314
- const reconnectInfo = {
4315
- error: `WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`,
4316
- sessionId: this.sessionId,
4317
- timestamp: Date.now()
4318
- };
4319
- this.emit("reconnect_error", reconnectInfo);
4320
- if (this.options.onReconnectError) {
4321
- try {
4322
- this.options.onReconnectError(reconnectInfo);
4323
- } catch (callbackError) {
4324
- console.error(
4325
- "[Granular] onReconnectError callback failed:",
4326
- callbackError
4327
- );
4328
- }
4329
- }
4330
- return;
4331
- }
4332
- this.reconnectAttempts += 1;
4333
- const reconnectDelayMs = Math.min(
4334
- 3e4,
4335
- baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
4336
- );
4337
- info.reconnectScheduled = true;
4338
- info.reconnectDelayMs = reconnectDelayMs;
4413
+ const reconnectDelayMs = this.scheduleReconnectAttempt();
4414
+ info.reconnectScheduled = reconnectDelayMs !== null;
4415
+ if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
4339
4416
  if (this.options.onUnexpectedClose) {
4340
4417
  try {
4341
4418
  this.options.onUnexpectedClose(info);
@@ -4346,28 +4423,6 @@ var WSClient = class {
4346
4423
  );
4347
4424
  }
4348
4425
  }
4349
- this.reconnectTimer = setTimeout(() => {
4350
- console.log("[Granular] Attempting reconnect...");
4351
- this.connect().catch((error) => {
4352
- console.error("[Granular] Reconnect failed:", error);
4353
- const reconnectInfo = {
4354
- error: error instanceof Error ? error.message : String(error),
4355
- sessionId: this.sessionId,
4356
- timestamp: Date.now()
4357
- };
4358
- this.emit("reconnect_error", reconnectInfo);
4359
- if (this.options.onReconnectError) {
4360
- try {
4361
- this.options.onReconnectError(reconnectInfo);
4362
- } catch (callbackError) {
4363
- console.error(
4364
- "[Granular] onReconnectError callback failed:",
4365
- callbackError
4366
- );
4367
- }
4368
- }
4369
- });
4370
- }, reconnectDelayMs);
4371
4426
  }
4372
4427
  }
4373
4428
  handleMessage(message) {
@@ -4478,6 +4533,7 @@ var WSClient = class {
4478
4533
  const response = message;
4479
4534
  const pending = this.messageQueue.find((q) => q.id === response.id);
4480
4535
  if (pending) {
4536
+ clearTimeout(pending.timeout);
4481
4537
  if (response.type === "rpc_error") {
4482
4538
  pending.reject(
4483
4539
  new Error(
@@ -4523,16 +4579,22 @@ var WSClient = class {
4523
4579
  id
4524
4580
  };
4525
4581
  return new Promise((resolve, reject) => {
4526
- this.messageQueue.push({ resolve, reject, id });
4527
- this.ws.send(JSON.stringify(request));
4528
4582
  const timeoutMs = rpcTimeoutMsForMethod(method);
4529
- setTimeout(() => {
4583
+ const timeout = setTimeout(() => {
4530
4584
  const pending = this.messageQueue.find((q) => q.id === id);
4531
4585
  if (pending) {
4532
4586
  this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
4533
4587
  reject(new Error(`RPC timeout: ${method}`));
4534
4588
  }
4535
4589
  }, timeoutMs);
4590
+ this.messageQueue.push({ resolve, reject, id, timeout });
4591
+ try {
4592
+ this.ws.send(JSON.stringify(request));
4593
+ } catch (error) {
4594
+ clearTimeout(timeout);
4595
+ this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
4596
+ reject(error instanceof Error ? error : new Error(String(error)));
4597
+ }
4536
4598
  });
4537
4599
  }
4538
4600
  async handleIncomingRpc(request) {
@@ -4618,15 +4680,18 @@ var WSClient = class {
4618
4680
  /**
4619
4681
  * Disconnect the WebSocket and clear state
4620
4682
  */
4621
- disconnect() {
4683
+ disconnect(options = {}) {
4622
4684
  this.isExplicitlyDisconnected = true;
4685
+ this.cancelConnectAttempt?.();
4686
+ this.cancelConnectAttempt = null;
4687
+ this.connectionEpoch += 1;
4623
4688
  if (this.reconnectTimer) {
4624
4689
  clearTimeout(this.reconnectTimer);
4625
4690
  this.reconnectTimer = null;
4626
4691
  }
4627
4692
  this.clearTokenRefreshTimer();
4628
4693
  if (this.ws) {
4629
- this.ws.close(1e3, "Client disconnect");
4694
+ this.ws.close(1e3, options.reason || "Client disconnect");
4630
4695
  this.ws = null;
4631
4696
  }
4632
4697
  this.rejectPending(new Error("Client explicitly disconnected"));
@@ -4722,8 +4787,12 @@ function normalizePrompt(rawValue) {
4722
4787
  const source = promptRecord || raw;
4723
4788
  const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
4724
4789
  if (!id) return null;
4790
+ const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
4791
+ const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
4725
4792
  return {
4726
4793
  id,
4794
+ ...jobId ? { jobId } : {},
4795
+ ...turnId ? { turnId } : {},
4727
4796
  type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
4728
4797
  title: typeof source.title === "string" ? source.title : "Input required",
4729
4798
  message: typeof source.message === "string" ? source.message : "",
@@ -4764,6 +4833,9 @@ function resolvePromptAnswer(prompt, answer) {
4764
4833
 
4765
4834
  // src/session.ts
4766
4835
  var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
4836
+ function toPascalCase(value) {
4837
+ return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4838
+ }
4767
4839
  function withPromptTranscriptTimeout(promise) {
4768
4840
  let timeout = null;
4769
4841
  return Promise.race([
@@ -4807,6 +4879,9 @@ var Session = class {
4807
4879
  this.initialQuota = options.initialQuota || null;
4808
4880
  this.setupEventHandlers();
4809
4881
  this.setupToolInvokeHandler();
4882
+ this.currentDomainRevision = this.extractDomainRevisionFromDoc(
4883
+ this.client.doc
4884
+ );
4810
4885
  }
4811
4886
  extractDomainRevisionFromDoc(doc) {
4812
4887
  const domain = doc?.domain;
@@ -5326,9 +5401,7 @@ var Session = class {
5326
5401
  if (classes && Object.keys(classes).length > 0) {
5327
5402
  let docs2 = "# Domain Documentation\n\n";
5328
5403
  docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
5329
- const classNames = Object.keys(classes).map(
5330
- (c) => c.charAt(0).toUpperCase() + c.slice(1)
5331
- );
5404
+ const classNames = Object.keys(classes).map(toPascalCase);
5332
5405
  const globalNames = (globalTools || []).map((t) => t.name);
5333
5406
  const importLines = [
5334
5407
  ...classNames.map(
@@ -5342,7 +5415,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
5342
5415
 
5343
5416
  `;
5344
5417
  for (const [className, cls] of Object.entries(classes)) {
5345
- const TsName = className.charAt(0).toUpperCase() + className.slice(1);
5418
+ const TsName = toPascalCase(className);
5346
5419
  docs2 += `## ${TsName}
5347
5420
 
5348
5421
  `;
@@ -5714,6 +5787,7 @@ function normalizeJobAgentMessageEnvelope(data) {
5714
5787
  }
5715
5788
  return {
5716
5789
  jobId: d.jobId,
5790
+ ...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
5717
5791
  message: {
5718
5792
  messageId: d.messageId,
5719
5793
  kind: d.kind === "artifacts" ? "artifacts" : "text",
@@ -6332,6 +6406,28 @@ function asString(value) {
6332
6406
  function trimString(value) {
6333
6407
  return typeof value === "string" ? value.trim() : "";
6334
6408
  }
6409
+ function compactJson(value, maxLength = 320) {
6410
+ if (value === void 0 || value === null) return void 0;
6411
+ try {
6412
+ const json = JSON.stringify(value);
6413
+ if (!json || json === "undefined") return void 0;
6414
+ return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
6415
+ } catch {
6416
+ return String(value);
6417
+ }
6418
+ }
6419
+ function artifactRecordsById(liveDoc) {
6420
+ const artifacts = asRecord3(liveDoc?.artifacts);
6421
+ const byId = asRecord3(artifacts?.byId) || {};
6422
+ return Object.fromEntries(
6423
+ Object.entries(byId).map(([artifactId, value]) => {
6424
+ const record = asRecord3(value);
6425
+ return record ? [artifactId, record] : null;
6426
+ }).filter(
6427
+ (entry) => Boolean(entry)
6428
+ )
6429
+ );
6430
+ }
6335
6431
  function normalizeShowRefs(value) {
6336
6432
  const record = asRecord3(value);
6337
6433
  if (!record) return void 0;
@@ -6348,9 +6444,106 @@ function normalizeShowRefs(value) {
6348
6444
  entryPaths: normalizeRefs(record.entryPaths),
6349
6445
  listNames: normalizeRefs(record.listNames),
6350
6446
  variableNames: normalizeRefs(record.variableNames),
6351
- fileIds: normalizeRefs(record.fileIds)
6447
+ fileIds: normalizeRefs(record.fileIds),
6448
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
6449
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
6450
+ tables: Array.isArray(record.tables) ? record.tables.filter(
6451
+ (table) => Boolean(
6452
+ table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
6453
+ )
6454
+ ) : void 0
6352
6455
  };
6353
- return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
6456
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
6457
+ }
6458
+ function normalizeActionSuggestions(value) {
6459
+ if (!Array.isArray(value)) return void 0;
6460
+ const suggestions = [];
6461
+ for (const item of value) {
6462
+ const record = asRecord3(item);
6463
+ if (!record) continue;
6464
+ const label = trimString(record.label);
6465
+ if (!label) continue;
6466
+ const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
6467
+ suggestions.push({
6468
+ suggestionId,
6469
+ label,
6470
+ ...typeof record.description === "string" ? { description: record.description } : {},
6471
+ ...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
6472
+ ...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
6473
+ ...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
6474
+ });
6475
+ }
6476
+ return suggestions.length ? suggestions : void 0;
6477
+ }
6478
+ var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
6479
+ var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
6480
+ var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
6481
+ var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
6482
+ function normalizeConversationMessageActions(value) {
6483
+ if (!Array.isArray(value) || value.length === 0) return void 0;
6484
+ const actions = [];
6485
+ for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
6486
+ const record = asRecord3(item);
6487
+ const kind = record?.kind;
6488
+ const label = trimString(record?.label ?? record?.title);
6489
+ const status = record?.status;
6490
+ if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
6491
+ continue;
6492
+ }
6493
+ actions.push({
6494
+ kind,
6495
+ label,
6496
+ ...status === "done" || status === "queued" || status === "failed" ? { status } : {}
6497
+ });
6498
+ }
6499
+ return actions.length ? actions : void 0;
6500
+ }
6501
+ function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
6502
+ if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
6503
+ return void 0;
6504
+ }
6505
+ const parts = [];
6506
+ const canonicalActionsById = new Map(
6507
+ (canonicalActions || []).map((action) => [
6508
+ `${action.kind}:${action.label}`,
6509
+ action
6510
+ ])
6511
+ );
6512
+ const seenActionIds = /* @__PURE__ */ new Set();
6513
+ let textLength = 0;
6514
+ for (const item of value) {
6515
+ const record = asRecord3(item);
6516
+ if (!record) return void 0;
6517
+ if (record.type === "text") {
6518
+ if (typeof record.text !== "string" || record.text.length === 0) {
6519
+ return void 0;
6520
+ }
6521
+ textLength += record.text.length;
6522
+ if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
6523
+ parts.push({ type: "text", text: record.text });
6524
+ continue;
6525
+ }
6526
+ if (record.type !== "action") return void 0;
6527
+ const action = asRecord3(record.action);
6528
+ const kind = action?.kind;
6529
+ const label = trimString(action?.label);
6530
+ if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
6531
+ return void 0;
6532
+ }
6533
+ const actionId = `${kind}:${label}`;
6534
+ const canonicalAction = canonicalActionsById.get(actionId);
6535
+ if (!canonicalAction) return void 0;
6536
+ if (seenActionIds.has(actionId)) continue;
6537
+ seenActionIds.add(actionId);
6538
+ parts.push({
6539
+ type: "action",
6540
+ action: canonicalAction
6541
+ });
6542
+ }
6543
+ const orderedText = parts.filter(
6544
+ (part) => part.type === "text"
6545
+ ).map((part) => part.text).join("");
6546
+ return orderedText === canonicalContent ? parts : void 0;
6354
6547
  }
6355
6548
  function stringifyTranscriptValue(value, fallback = "") {
6356
6549
  if (typeof value === "string") {
@@ -6370,12 +6563,139 @@ function stringifyTranscriptValue(value, fallback = "") {
6370
6563
  return String(value);
6371
6564
  }
6372
6565
  }
6373
- function buildArtifactHistory(show) {
6566
+ function latestInputEditSummary(metadata) {
6567
+ const lastInputEdit = asRecord3(metadata.lastInputEdit);
6568
+ if (!lastInputEdit) return null;
6569
+ const source = asString(lastInputEdit.source) || "unknown";
6570
+ const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
6571
+ const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
6572
+ (key) => typeof key === "string" && key.trim().length > 0
6573
+ ).slice(0, 6) : [];
6574
+ const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
6575
+ (key) => typeof key === "string" && key.trim().length > 0
6576
+ ).slice(0, 6) : [];
6577
+ const changed = [
6578
+ inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
6579
+ relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
6580
+ ].filter(Boolean);
6581
+ return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
6582
+ }
6583
+ function artifactIssueSummary(record) {
6584
+ const validation = asRecord3(record.validation);
6585
+ if (!validation) return null;
6586
+ const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
6587
+ if (issues.length > 0) {
6588
+ return `issues=${issues.map((issue) => {
6589
+ const code = asString(issue.code) || asString(issue.kind) || "issue";
6590
+ const path2 = asString(issue.path);
6591
+ const message = trimString(issue.message);
6592
+ return `${code}${path2 ? ` at ${path2}` : ""}${message ? ` (${message})` : ""}`;
6593
+ }).join("; ")}`;
6594
+ }
6595
+ const error = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
6596
+ return error ? `validation=${error}` : null;
6597
+ }
6598
+ function artifactExecutionSummary(metadata) {
6599
+ const execution = asRecord3(metadata.execution);
6600
+ if (!execution) return null;
6601
+ const result = asRecord3(execution.result);
6602
+ const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
6603
+ const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
6604
+ const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
6605
+ const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
6606
+ const error = trimString(execution.error);
6607
+ const pieces = [
6608
+ awaiting ? `awaiting=${awaiting}` : null,
6609
+ pendingTransition ? `pendingTransition=${pendingTransition}` : null,
6610
+ approvalTarget ? `approvalTarget=${approvalTarget}` : null,
6611
+ error ? `executionError=${error}` : null
6612
+ ].filter(Boolean);
6613
+ return pieces.length ? pieces.join("; ") : null;
6614
+ }
6615
+ function artifactStatePathSummary(metadata) {
6616
+ const statePlan = asRecord3(metadata.statePlan);
6617
+ if (!statePlan) return null;
6618
+ const machineName = asString(statePlan.machineName);
6619
+ const targetState = asString(statePlan.targetState);
6620
+ const objectPath = asString(statePlan.objectPath);
6621
+ const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
6622
+ const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
6623
+ const pieces = [
6624
+ machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
6625
+ objectPath ? `objectPath=${objectPath}` : null,
6626
+ approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
6627
+ approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
6628
+ ].filter(Boolean);
6629
+ return pieces.length ? pieces.join("; ") : null;
6630
+ }
6631
+ function artifactSummaryLine(artifactId, record) {
6632
+ if (!record) return `- ${artifactId}: unavailable in session artifact store`;
6633
+ const label = trimString(record.label) || artifactId;
6634
+ const kind = asString(record.kind) || "artifact";
6635
+ const status = asString(record.status) || "unknown";
6636
+ const createdByJobId = asString(record.createdByJobId);
6637
+ const target = asRecord3(record.target);
6638
+ const metadata = asRecord3(record.metadata) || {};
6639
+ const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
6640
+ (id) => typeof id === "string" && id.trim().length > 0
6641
+ ).slice(0, 8) : [];
6642
+ const relationships = compactJson(record.relationships, 220);
6643
+ const pieces = [
6644
+ `kind=${kind}`,
6645
+ `status=${status}`,
6646
+ createdByJobId ? `createdByJob=${createdByJobId}` : null,
6647
+ target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
6648
+ artifactStatePathSummary(metadata),
6649
+ artifactExecutionSummary(metadata),
6650
+ artifactIssueSummary(record),
6651
+ latestInputEditSummary(metadata),
6652
+ subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
6653
+ relationships ? `relationships=${relationships}` : null
6654
+ ].filter(Boolean);
6655
+ return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
6656
+ }
6657
+ function buildArtifactHistory(show, artifactsById) {
6374
6658
  if (!show) return void 0;
6375
- return `[Agent message]
6659
+ const artifactIds = show.sessionArtifactIds || [];
6660
+ const actionSuggestions = show.actionSuggestions || [];
6661
+ if (artifactIds.length === 0 && actionSuggestions.length === 0) {
6662
+ return `[Agent message]
6376
6663
  ${stringifyTranscriptValue({ show }, "")}`;
6664
+ }
6665
+ const lines = artifactIds.slice(0, 8).map(
6666
+ (artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
6667
+ );
6668
+ if (artifactIds.length > 8) {
6669
+ lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
6670
+ }
6671
+ if (actionSuggestions.length > 0) {
6672
+ if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
6673
+ for (const suggestion of actionSuggestions.slice(0, 8)) {
6674
+ lines.push(
6675
+ `- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
6676
+ );
6677
+ }
6678
+ if (actionSuggestions.length > 8) {
6679
+ lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
6680
+ }
6681
+ }
6682
+ const otherRefs = {
6683
+ entryPaths: show.entryPaths,
6684
+ listNames: show.listNames,
6685
+ variableNames: show.variableNames,
6686
+ fileIds: show.fileIds
6687
+ };
6688
+ const hasOtherRefs = Object.values(otherRefs).some(
6689
+ (value) => Array.isArray(value) && value.length > 0
6690
+ );
6691
+ const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
6692
+ return [
6693
+ title,
6694
+ ...lines,
6695
+ hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
6696
+ ].filter(Boolean).join("\n");
6377
6697
  }
6378
- function normalizeConversationMessage(raw) {
6698
+ function normalizeConversationMessage(raw, artifactsById) {
6379
6699
  const record = asRecord3(raw);
6380
6700
  if (!record) return null;
6381
6701
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -6383,10 +6703,18 @@ function normalizeConversationMessage(raw) {
6383
6703
  const content = trimString(
6384
6704
  record.content ?? record.reply ?? record.message ?? record.text
6385
6705
  );
6706
+ const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
6707
+ const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
6386
6708
  const show = normalizeShowRefs(record.show);
6387
6709
  const id = asString(record.id) || crypto.randomUUID();
6388
6710
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
6389
- if (!content && !show) return null;
6711
+ if (!content && !show && !actions?.length) return null;
6712
+ const artifactHistory = buildArtifactHistory(show, artifactsById);
6713
+ const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
6714
+ ${content}
6715
+
6716
+ ${artifactHistory}` : content ? `[Assistant reply]
6717
+ ${content}` : artifactHistory : void 0;
6390
6718
  return {
6391
6719
  id,
6392
6720
  role,
@@ -6395,8 +6723,9 @@ function normalizeConversationMessage(raw) {
6395
6723
  jobId: asString(record.jobId),
6396
6724
  promptId: asString(record.promptId),
6397
6725
  show,
6398
- historyContent: role === "assistant" ? content ? `[Assistant reply]
6399
- ${content}` : buildArtifactHistory(show) : void 0,
6726
+ actions,
6727
+ parts,
6728
+ historyContent,
6400
6729
  source: "conversation"
6401
6730
  };
6402
6731
  }
@@ -6439,7 +6768,7 @@ ${assistantContent}`,
6439
6768
  return entries;
6440
6769
  });
6441
6770
  }
6442
- function normalizeAgentMessageEntries(jobId, rawMessages) {
6771
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
6443
6772
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6444
6773
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6445
6774
  ).flatMap((message) => {
@@ -6470,14 +6799,14 @@ ${reply}`,
6470
6799
  timestamp,
6471
6800
  jobId,
6472
6801
  show,
6473
- historyContent: buildArtifactHistory(show),
6802
+ historyContent: buildArtifactHistory(show, artifactsById),
6474
6803
  source: "job_agent_message"
6475
6804
  });
6476
6805
  }
6477
6806
  return entries;
6478
6807
  });
6479
6808
  }
6480
- function buildJobFallbackEntries(jobId, job, sessionHeap) {
6809
+ function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
6481
6810
  const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6482
6811
  const resultPreview = stringifyTranscriptValue(
6483
6812
  job.result,
@@ -6515,7 +6844,7 @@ ${responseText}`,
6515
6844
  timestamp,
6516
6845
  jobId,
6517
6846
  show,
6518
- historyContent: buildArtifactHistory(show),
6847
+ historyContent: buildArtifactHistory(show, artifactsById),
6519
6848
  source: "job_result"
6520
6849
  });
6521
6850
  }
@@ -6569,10 +6898,11 @@ function buildJobCodeEntry(jobId, job) {
6569
6898
  function buildSessionTranscript(input) {
6570
6899
  const liveDoc = input.liveDoc || null;
6571
6900
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6901
+ const artifactsById = artifactRecordsById(liveDoc);
6572
6902
  const transcript = [];
6573
6903
  const conversationMessages = asArray(
6574
6904
  asRecord3(liveDoc?.conversation)?.messages
6575
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6905
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
6576
6906
  const conversationPromptIds = new Set(
6577
6907
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6578
6908
  );
@@ -6599,7 +6929,8 @@ function buildSessionTranscript(input) {
6599
6929
  if (!assistantConversationJobIds.has(jobId)) {
6600
6930
  const agentEntries = normalizeAgentMessageEntries(
6601
6931
  jobId,
6602
- job.agentMessages
6932
+ job.agentMessages,
6933
+ artifactsById
6603
6934
  );
6604
6935
  if (agentEntries.length > 0) {
6605
6936
  transcript.push(...agentEntries);
@@ -6608,7 +6939,8 @@ function buildSessionTranscript(input) {
6608
6939
  ...buildJobFallbackEntries(
6609
6940
  jobId,
6610
6941
  job,
6611
- sessionHeap
6942
+ sessionHeap,
6943
+ artifactsById
6612
6944
  )
6613
6945
  );
6614
6946
  }
@@ -10774,16 +11106,107 @@ var StateMachineStateSchema = external_exports.union([
10774
11106
  external_exports.string(),
10775
11107
  external_exports.object({
10776
11108
  name: external_exports.string().min(1),
11109
+ label: external_exports.string().optional(),
11110
+ description: external_exports.string().optional(),
10777
11111
  isFinal: external_exports.boolean().optional()
10778
11112
  }).strict()
10779
11113
  ]);
11114
+ var StateTransitionInputBindingSchema = external_exports.lazy(
11115
+ () => external_exports.union([
11116
+ external_exports.null(),
11117
+ external_exports.string(),
11118
+ external_exports.number(),
11119
+ external_exports.boolean(),
11120
+ external_exports.array(StateTransitionInputBindingSchema),
11121
+ external_exports.object({
11122
+ const: external_exports.unknown()
11123
+ }).strict(),
11124
+ external_exports.object({
11125
+ from: external_exports.literal("object"),
11126
+ path: external_exports.string().min(1),
11127
+ editable: external_exports.boolean().optional()
11128
+ }).strict(),
11129
+ external_exports.object({
11130
+ from: external_exports.literal("field"),
11131
+ name: external_exports.string().min(1),
11132
+ editable: external_exports.boolean().optional()
11133
+ }).strict(),
11134
+ external_exports.object({
11135
+ from: external_exports.literal("relationship"),
11136
+ name: external_exports.string().min(1),
11137
+ path: external_exports.string().min(1).optional(),
11138
+ many: external_exports.boolean().optional(),
11139
+ editable: external_exports.boolean().optional()
11140
+ }).strict(),
11141
+ external_exports.object({
11142
+ from: external_exports.literal("session"),
11143
+ path: external_exports.string().min(1),
11144
+ editable: external_exports.boolean().optional()
11145
+ }).strict(),
11146
+ external_exports.object({
11147
+ from: external_exports.literal("actor"),
11148
+ path: external_exports.string().min(1),
11149
+ editable: external_exports.boolean().optional()
11150
+ }).strict(),
11151
+ external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
11152
+ ])
11153
+ );
11154
+ var StateTransitionActionSchema = external_exports.object({
11155
+ effect: external_exports.string().min(1),
11156
+ input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
11157
+ }).strict();
11158
+ var StateTransitionAssigneeSchema = external_exports.object({
11159
+ kind: external_exports.string().min(1),
11160
+ from: StateTransitionInputBindingSchema.optional(),
11161
+ role: external_exports.string().optional(),
11162
+ label: external_exports.string().optional()
11163
+ }).strict();
11164
+ var StateTransitionRelatedStateRequirementSchema = external_exports.object({
11165
+ relationship: external_exports.string().min(1),
11166
+ machine: external_exports.string().min(1),
11167
+ state: external_exports.string().min(1),
11168
+ className: external_exports.string().min(1).optional(),
11169
+ label: external_exports.string().optional(),
11170
+ mode: external_exports.enum(["every", "some", "any"]).optional()
11171
+ }).strict();
11172
+ var StateTransitionRequirementsSchema = external_exports.object({
11173
+ fields: external_exports.array(external_exports.string().min(1)).optional(),
11174
+ relationships: external_exports.array(external_exports.string().min(1)).optional(),
11175
+ relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
11176
+ }).strict();
11177
+ var StateTransitionPermissionSchema = external_exports.union([
11178
+ external_exports.string().min(1),
11179
+ external_exports.object({
11180
+ profile: external_exports.string().min(1).optional(),
11181
+ profileId: external_exports.string().min(1).optional(),
11182
+ label: external_exports.string().optional(),
11183
+ reason: external_exports.string().optional()
11184
+ }).strict()
11185
+ ]);
11186
+ var StateTransitionExpectedOutcomeSchema = external_exports.union([
11187
+ external_exports.string().min(1),
11188
+ external_exports.object({
11189
+ machine: external_exports.string().min(1).optional(),
11190
+ state: external_exports.string().min(1),
11191
+ summary: external_exports.string().optional()
11192
+ }).strict()
11193
+ ]);
10780
11194
  var StateMachineTransitionSchema = external_exports.object({
10781
11195
  name: external_exports.string().min(1),
10782
11196
  from: external_exports.string().min(1),
10783
- to: external_exports.string().min(1)
11197
+ to: external_exports.string().min(1),
11198
+ label: external_exports.string().optional(),
11199
+ description: external_exports.string().optional(),
11200
+ action: StateTransitionActionSchema.optional(),
11201
+ assignee: StateTransitionAssigneeSchema.optional(),
11202
+ requirements: StateTransitionRequirementsSchema.optional(),
11203
+ permission: StateTransitionPermissionSchema.optional(),
11204
+ risk: external_exports.enum(["low", "medium", "high"]).optional(),
11205
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
10784
11206
  }).strict();
10785
11207
  external_exports.object({
10786
11208
  name: external_exports.string().min(1),
11209
+ stateField: external_exports.string().min(1).optional(),
10787
11210
  entryState: external_exports.string().min(1),
10788
11211
  states: external_exports.array(StateMachineStateSchema).min(1),
10789
11212
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -10850,6 +11273,16 @@ var PoliciesSchema = external_exports.object({
10850
11273
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
10851
11274
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
10852
11275
  }).strict();
11276
+ var CreatesSchema = external_exports.union([
11277
+ external_exports.string().min(1),
11278
+ external_exports.object({
11279
+ className: external_exports.string().min(1),
11280
+ idPath: external_exports.string().min(1).optional(),
11281
+ pathPath: external_exports.string().min(1).optional(),
11282
+ statePath: external_exports.string().min(1).optional(),
11283
+ classStateHandle: external_exports.boolean().optional()
11284
+ }).strict()
11285
+ ]);
10853
11286
  external_exports.object({
10854
11287
  postCondition: external_exports.union([
10855
11288
  external_exports.string(),
@@ -10880,6 +11313,7 @@ external_exports.object({
10880
11313
  mode: external_exports.string().optional()
10881
11314
  }).strict()
10882
11315
  ]).optional(),
11316
+ creates: CreatesSchema.optional(),
10883
11317
  access: external_exports.enum(["read", "write", "ui"]).optional(),
10884
11318
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
10885
11319
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -11107,9 +11541,10 @@ function mergeMethodSummaryPatch(target, patch) {
11107
11541
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
11108
11542
  if (patch.effectBehaviors !== void 0)
11109
11543
  target.effectBehaviors = patch.effectBehaviors;
11544
+ if (patch.creates !== void 0) target.creates = patch.creates;
11110
11545
  if (patch.static !== void 0) target.static = patch.static;
11111
11546
  }
11112
- function toPascalCase(value) {
11547
+ function toPascalCase2(value) {
11113
11548
  return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11114
11549
  }
11115
11550
  function normalizeNotesInput(input) {
@@ -11167,29 +11602,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
11167
11602
  }
11168
11603
  return Object.keys(result).length > 0 ? result : null;
11169
11604
  }
11170
- function buildEffectBehaviorDocs(effectBehaviors) {
11171
- if (!effectBehaviors) {
11172
- return [];
11605
+ function normalizeCreationSummary(metamodels) {
11606
+ if (!isObject(metamodels)) return null;
11607
+ let raw = metamodels.creates;
11608
+ if (typeof raw === "string" && raw.trim().length > 0) {
11609
+ const trimmed = raw.trim();
11610
+ if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
11611
+ try {
11612
+ raw = JSON.parse(trimmed);
11613
+ } catch {
11614
+ return { className: trimmed };
11615
+ }
11616
+ } else {
11617
+ return { className: trimmed };
11618
+ }
11619
+ }
11620
+ if (typeof raw === "string" && raw.trim().length > 0) {
11621
+ return { className: raw.trim() };
11173
11622
  }
11623
+ if (!isObject(raw)) return null;
11624
+ const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
11625
+ if (!className) return null;
11626
+ return {
11627
+ className,
11628
+ ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
11629
+ ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
11630
+ ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
11631
+ ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
11632
+ };
11633
+ }
11634
+ function buildEffectBehaviorDocs(effectBehaviors, creates) {
11174
11635
  const docs = [];
11175
- if (effectBehaviors.approvalRequired?.required) {
11636
+ if (creates) {
11637
+ docs.push(
11638
+ `Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
11639
+ );
11640
+ }
11641
+ if (effectBehaviors?.approvalRequired?.required) {
11176
11642
  docs.push(
11177
11643
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
11178
11644
  );
11179
11645
  }
11180
- if (effectBehaviors.postCondition) {
11646
+ if (effectBehaviors?.postCondition) {
11181
11647
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
11182
11648
  if (effectBehaviors.postCondition.description) {
11183
11649
  docs.push(effectBehaviors.postCondition.description);
11184
11650
  }
11185
11651
  }
11186
- if (effectBehaviors.dryRun?.enabled) {
11652
+ if (effectBehaviors?.dryRun?.enabled) {
11187
11653
  docs.push("Supports dry run.");
11188
11654
  if (effectBehaviors.dryRun.description) {
11189
11655
  docs.push(effectBehaviors.dryRun.description);
11190
11656
  }
11191
11657
  }
11192
- if (effectBehaviors.reverse) {
11658
+ if (effectBehaviors?.reverse) {
11193
11659
  if (effectBehaviors.reverse.handler) {
11194
11660
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
11195
11661
  } else {
@@ -11248,13 +11714,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
11248
11714
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
11249
11715
  });
11250
11716
  }
11717
+ if (spec.creates !== void 0) {
11718
+ mutations.push({
11719
+ label: `set creates on ${toolPath}`,
11720
+ query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
11721
+ JSON.stringify(spec.creates)
11722
+ )}) { done } } } }`
11723
+ });
11724
+ }
11251
11725
  return mutations;
11252
11726
  }
11253
11727
  function readMethodEffectBehaviors(rawMethod) {
11728
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
11254
11729
  return {
11255
- effectBehaviors: normalizeEffectBehaviorSummary(
11256
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
11257
- )
11730
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
11731
+ creates: normalizeCreationSummary(metamodels)
11258
11732
  };
11259
11733
  }
11260
11734
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -11276,6 +11750,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11276
11750
  {
11277
11751
  key: "approvalRequired",
11278
11752
  description: "Boolean or `{ required, reason, mode }`."
11753
+ },
11754
+ {
11755
+ key: "creates",
11756
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
11279
11757
  }
11280
11758
  ]
11281
11759
  },
@@ -11395,7 +11873,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11395
11873
  ...methodIR,
11396
11874
  docs: [
11397
11875
  ...methodIR.docs,
11398
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
11876
+ ...buildEffectBehaviorDocs(
11877
+ methodSummary.effectBehaviors,
11878
+ methodSummary.creates
11879
+ )
11399
11880
  ]
11400
11881
  };
11401
11882
  }
@@ -11636,15 +12117,50 @@ function toRecordSearchResult(className, node) {
11636
12117
  return [];
11637
12118
  }
11638
12119
  ) : [];
12120
+ const graphPathId = extractRecordIdFromGraphPath(path2, className);
12121
+ const realIdField = fields.find(
12122
+ (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
12123
+ );
12124
+ const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
12125
+ const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
12126
+ if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path2)) {
12127
+ return null;
12128
+ }
12129
+ const fallbackLabel = displayLabelFromFields(fields);
12130
+ const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path2) ? rawLabel : fallbackLabel || rawLabel || id;
11639
12131
  return {
11640
12132
  path: path2,
11641
12133
  className,
11642
- id: extractRecordIdFromGraphPath(path2, className),
11643
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path2, className),
12134
+ id,
12135
+ label,
11644
12136
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
11645
12137
  fields
11646
12138
  };
11647
12139
  }
12140
+ function isPlaceholderRecordLabel(label, id, path2) {
12141
+ const normalizedLabel = normalizeGraphPathSegment(label);
12142
+ return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path2);
12143
+ }
12144
+ function displayLabelFromFields(fields) {
12145
+ const preferredFieldNames = [
12146
+ "name",
12147
+ "title",
12148
+ "label",
12149
+ "display_name",
12150
+ "file_name",
12151
+ "number",
12152
+ "code"
12153
+ ];
12154
+ for (const preferred of preferredFieldNames) {
12155
+ const match = fields.find(
12156
+ (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
12157
+ );
12158
+ if (typeof match?.value === "string") {
12159
+ return match.value.trim();
12160
+ }
12161
+ }
12162
+ return null;
12163
+ }
11648
12164
  function normalizeRecordSearchText(value) {
11649
12165
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
11650
12166
  }
@@ -11786,7 +12302,12 @@ async function recordOpenAIUsageSpend(options) {
11786
12302
  const metadata = {
11787
12303
  ...options.metadata || {},
11788
12304
  ...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
11789
- usageContext: context
12305
+ usageContext: context,
12306
+ pricingContextTier: options.usage.pricingContextTier,
12307
+ cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
12308
+ cacheWriteTokens: options.usage.cacheWriteTokens,
12309
+ cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
12310
+ longContextThresholdTokens: options.usage.longContextThresholdTokens
11790
12311
  };
11791
12312
  const response = await fetch(
11792
12313
  `${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
@@ -12544,15 +13065,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
12544
13065
 
12545
13066
  // ../metamodel-state-machine/src/index.ts
12546
13067
  function normalizeStateMachines(values) {
13068
+ const parseJsonRecord = (value) => {
13069
+ if (value && typeof value === "object" && !Array.isArray(value)) {
13070
+ return value;
13071
+ }
13072
+ if (typeof value !== "string" || !value.trim()) return null;
13073
+ try {
13074
+ const parsed = JSON.parse(value);
13075
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
13076
+ } catch {
13077
+ return null;
13078
+ }
13079
+ };
13080
+ const parseJsonValue = (value) => {
13081
+ if (value === null || typeof value === "undefined") return null;
13082
+ if (typeof value !== "string") return value;
13083
+ if (!value.trim()) return null;
13084
+ try {
13085
+ return JSON.parse(value);
13086
+ } catch {
13087
+ return value;
13088
+ }
13089
+ };
12547
13090
  return (values || []).map((machine) => {
12548
13091
  const states = (machine?.states || []).map((state) => ({
12549
13092
  name: String(state?.name || ""),
12550
- isFinal: Boolean(state?.is_final)
13093
+ label: typeof state?.label === "string" ? state.label : null,
13094
+ description: typeof state?.description === "string" ? state.description : null,
13095
+ isFinal: Boolean(state?.is_final ?? state?.isFinal)
12551
13096
  })).filter((state) => state.name.length > 0);
12552
13097
  const transitions = (machine?.transitions || []).map((transition) => ({
12553
13098
  name: String(transition?.name || ""),
12554
13099
  from: String(transition?.from?.name || ""),
12555
- to: String(transition?.to?.name || "")
13100
+ to: String(transition?.to?.name || ""),
13101
+ label: typeof transition?.label === "string" ? transition.label : null,
13102
+ description: typeof transition?.description === "string" ? transition.description : null,
13103
+ action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
13104
+ assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
13105
+ requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
13106
+ permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
13107
+ risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
13108
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
12556
13109
  })).filter(
12557
13110
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
12558
13111
  );
@@ -12566,7 +13119,7 @@ function normalizeStateMachines(values) {
12566
13119
  }).filter((machine) => machine.name.length > 0);
12567
13120
  }
12568
13121
  function stateTypeName(className, machineName) {
12569
- return `${toPascalCase(className)}${toPascalCase(machineName)}`;
13122
+ return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
12570
13123
  }
12571
13124
  function transitionTypeName(className, machineName) {
12572
13125
  return `${stateTypeName(className, machineName)}Transition`;
@@ -12574,6 +13127,15 @@ function transitionTypeName(className, machineName) {
12574
13127
  function pathTypeName(className, machineName) {
12575
13128
  return `${stateTypeName(className, machineName)}Path`;
12576
13129
  }
13130
+ function methodToken(value) {
13131
+ const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
13132
+ return token || "state";
13133
+ }
13134
+ function transitionActionsForMachine(machine) {
13135
+ return Object.fromEntries(
13136
+ (machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
13137
+ );
13138
+ }
12577
13139
  function normalizeStateDefinitions(machine) {
12578
13140
  const finalStates = new Set(machine.finalStates || []);
12579
13141
  const states = /* @__PURE__ */ new Map();
@@ -12587,6 +13149,8 @@ function normalizeStateDefinitions(machine) {
12587
13149
  }
12588
13150
  states.set(rawState.name, {
12589
13151
  name: rawState.name,
13152
+ label: rawState.label,
13153
+ description: rawState.description,
12590
13154
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
12591
13155
  });
12592
13156
  }
@@ -12598,6 +13162,44 @@ function normalizeStateDefinitions(machine) {
12598
13162
  }
12599
13163
  return [...states.values()];
12600
13164
  }
13165
+ function transitionMetadataGraphqlArgs(transition) {
13166
+ const args = [];
13167
+ if (typeof transition.label === "string") {
13168
+ args.push(`label: ${JSON.stringify(transition.label)}`);
13169
+ }
13170
+ if (typeof transition.description === "string") {
13171
+ args.push(`description: ${JSON.stringify(transition.description)}`);
13172
+ }
13173
+ if (transition.action) {
13174
+ args.push(
13175
+ `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
13176
+ );
13177
+ }
13178
+ if (transition.assignee) {
13179
+ args.push(
13180
+ `assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
13181
+ );
13182
+ }
13183
+ if (transition.requirements) {
13184
+ args.push(
13185
+ `requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
13186
+ );
13187
+ }
13188
+ if (transition.permission) {
13189
+ args.push(
13190
+ `permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
13191
+ );
13192
+ }
13193
+ if (transition.risk) {
13194
+ args.push(`risk: ${JSON.stringify(transition.risk)}`);
13195
+ }
13196
+ if (transition.expectedOutcome) {
13197
+ args.push(
13198
+ `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
13199
+ );
13200
+ }
13201
+ return args.length > 0 ? `, ${args.join(", ")}` : "";
13202
+ }
12601
13203
  function buildStateMachineModelMutations(modelPath, machines) {
12602
13204
  const mutations = [];
12603
13205
  for (const machine of machines || []) {
@@ -12608,12 +13210,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
12608
13210
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
12609
13211
  });
12610
13212
  for (const state of normalizeStateDefinitions(machine)) {
12611
- if (state.name === machine.entryState && !state.isFinal) continue;
13213
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
13214
+ continue;
12612
13215
  mutations.push({
12613
13216
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
12614
13217
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
12615
13218
  machine.name
12616
- )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
13219
+ )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
12617
13220
  });
12618
13221
  }
12619
13222
  for (const transition of machine.transitions || []) {
@@ -12625,18 +13228,27 @@ function buildStateMachineModelMutations(modelPath, machines) {
12625
13228
  transition.name
12626
13229
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
12627
13230
  transition.to
12628
- )}) { name } } } }`
13231
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
12629
13232
  });
12630
13233
  }
12631
13234
  }
12632
13235
  return mutations;
12633
13236
  }
12634
13237
  function buildMachineTypes(classSummary, machine) {
13238
+ const stateGlossary = machine.states.map((state) => {
13239
+ const label = state.label && state.label !== state.name ? state.label : null;
13240
+ const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
13241
+ const finalMarker = state.isFinal ? " Final state." : "";
13242
+ return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
13243
+ });
12635
13244
  return [
12636
13245
  {
12637
13246
  kind: "union",
12638
13247
  name: stateTypeName(classSummary.name, machine.name),
12639
- docs: [`Allowed states for ${classSummary.name}.${machine.name}.`],
13248
+ docs: [
13249
+ `Allowed states for ${classSummary.name}.${machine.name}.`,
13250
+ ...stateGlossary
13251
+ ],
12640
13252
  members: machine.states.map((state) => state.name)
12641
13253
  },
12642
13254
  {
@@ -12652,7 +13264,7 @@ function buildMachineMethods(classSummary, machine) {
12652
13264
  const transitionName = transitionTypeName(classSummary.name, machine.name);
12653
13265
  pathTypeName(classSummary.name, machine.name);
12654
13266
  const docsPrefix = `${classSummary.name}.${machine.name}`;
12655
- return [
13267
+ const methods = [
12656
13268
  {
12657
13269
  name: `get_${machine.name}`,
12658
13270
  docs: [`Get the current ${docsPrefix} state.`],
@@ -12675,7 +13287,7 @@ function buildMachineMethods(classSummary, machine) {
12675
13287
  ],
12676
13288
  static: false,
12677
13289
  params: [{ name: "target", type: stateName }],
12678
- returnType: `Promise<${toPascalCase(classSummary.name)}>`,
13290
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
12679
13291
  runtime: {
12680
13292
  kind: "state_machine",
12681
13293
  machineName: machine.name,
@@ -12748,6 +13360,99 @@ function buildMachineMethods(classSummary, machine) {
12748
13360
  }
12749
13361
  }
12750
13362
  ];
13363
+ const creationMethods = (classSummary.methods || []).filter(
13364
+ (method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
13365
+ );
13366
+ for (const state of machine.states) {
13367
+ const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
13368
+ if (!stateNameValue) continue;
13369
+ const token = methodToken(stateNameValue);
13370
+ methods.push(
13371
+ {
13372
+ name: `reach_${machine.name}_to_${token}`,
13373
+ docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
13374
+ static: false,
13375
+ params: [],
13376
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
13377
+ runtime: {
13378
+ kind: "state_machine",
13379
+ machineName: machine.name,
13380
+ className: classSummary.name,
13381
+ stateTypeName: stateName,
13382
+ transitionTypeName: transitionName,
13383
+ operation: "reach",
13384
+ targetState: stateNameValue,
13385
+ transitionActions: transitionActionsForMachine(machine)
13386
+ }
13387
+ },
13388
+ {
13389
+ name: `prepare_${machine.name}_to_${token}`,
13390
+ docs: [
13391
+ `Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
13392
+ ],
13393
+ static: false,
13394
+ params: [],
13395
+ returnType: "Promise<SessionArtifactRecord>",
13396
+ runtime: {
13397
+ kind: "state_machine",
13398
+ machineName: machine.name,
13399
+ className: classSummary.name,
13400
+ stateTypeName: stateName,
13401
+ transitionTypeName: transitionName,
13402
+ operation: "prepare_reach",
13403
+ targetState: stateNameValue,
13404
+ transitionActions: transitionActionsForMachine(machine)
13405
+ }
13406
+ }
13407
+ );
13408
+ for (const creationMethod of creationMethods) {
13409
+ const creationRuntime = {
13410
+ kind: "state_machine",
13411
+ machineName: machine.name,
13412
+ className: classSummary.name,
13413
+ stateTypeName: stateName,
13414
+ transitionTypeName: transitionName,
13415
+ operation: "prepare_create_reach",
13416
+ targetState: stateNameValue,
13417
+ transitionActions: transitionActionsForMachine(machine),
13418
+ creation: {
13419
+ methodName: creationMethod.name,
13420
+ effectKey: creationMethod.effectKey || creationMethod.name,
13421
+ inputSchema: creationMethod.inputSchema,
13422
+ outputSchema: creationMethod.outputSchema,
13423
+ creates: creationMethod.creates
13424
+ }
13425
+ };
13426
+ const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
13427
+ methods.push({
13428
+ name: viaName,
13429
+ docs: [
13430
+ `Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
13431
+ ],
13432
+ static: true,
13433
+ params: [
13434
+ { name: "input", type: "Record<string, any>", optional: true }
13435
+ ],
13436
+ returnType: "Promise<SessionArtifactRecord>",
13437
+ runtime: creationRuntime
13438
+ });
13439
+ if (creationMethods.length === 1) {
13440
+ methods.push({
13441
+ name: `prepare_${machine.name}_to_${token}`,
13442
+ docs: [
13443
+ `Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
13444
+ ],
13445
+ static: true,
13446
+ params: [
13447
+ { name: "input", type: "Record<string, any>", optional: true }
13448
+ ],
13449
+ returnType: "Promise<SessionArtifactRecord>",
13450
+ runtime: creationRuntime
13451
+ });
13452
+ }
13453
+ }
13454
+ }
13455
+ return methods;
12751
13456
  }
12752
13457
  function readStateMachineSummaries(rawClass) {
12753
13458
  return {
@@ -12770,8 +13475,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12770
13475
  type StateMachineMutation {
12771
13476
  name: String!
12772
13477
  state_machine: StateMachine!
12773
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
12774
- add_transition(name: String!, from: String!, to: String!): StateMachineMutation!
13478
+ add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
13479
+ 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!
12775
13480
  activate_transition(name: String!): StateMachineMutation!
12776
13481
  }
12777
13482
 
@@ -12788,6 +13493,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12788
13493
  type StateMachineSnapshotMutation {
12789
13494
  snapshot: StateMachineSnapshot!
12790
13495
  activate_transition(name: String!): StateMachineSnapshotMutation!
13496
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
12791
13497
  }
12792
13498
 
12793
13499
  type StateMachine {
@@ -12807,6 +13513,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12807
13513
 
12808
13514
  type StateMachineState {
12809
13515
  name: String!
13516
+ label: String
13517
+ description: String
12810
13518
  is_final: Boolean!
12811
13519
  }
12812
13520
 
@@ -12814,6 +13522,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12814
13522
  name: String!
12815
13523
  from: StateMachineState!
12816
13524
  to: StateMachineState!
13525
+ label: String
13526
+ description: String
13527
+ action_json: String
13528
+ assignee_json: String
13529
+ requirements_json: String
13530
+ permission_json: String
13531
+ risk: String
13532
+ expected_outcome_json: String
12817
13533
  }
12818
13534
 
12819
13535
  type StateMachinePath {
@@ -12866,23 +13582,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12866
13582
  StateMachineMutation: {
12867
13583
  name: (value) => value.name,
12868
13584
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
12869
- add_state: async (value, { name, is_final }) => {
13585
+ add_state: async (value, { name, is_final, label, description }) => {
12870
13586
  await run(
12871
13587
  value.target.add_state_machine_state(
12872
13588
  value.name,
12873
13589
  name,
12874
- is_final ?? false
13590
+ is_final ?? false,
13591
+ label,
13592
+ description
12875
13593
  )
12876
13594
  );
12877
13595
  return value;
12878
13596
  },
12879
- add_transition: async (value, { name, from, to }) => {
13597
+ add_transition: async (value, {
13598
+ name,
13599
+ from: from2,
13600
+ to,
13601
+ label,
13602
+ description,
13603
+ action_json,
13604
+ assignee_json,
13605
+ requirements_json,
13606
+ permission_json,
13607
+ risk,
13608
+ expected_outcome_json
13609
+ }) => {
12880
13610
  await run(
12881
13611
  value.target.add_state_machine_transition(
12882
13612
  value.name,
12883
13613
  name,
12884
- from,
12885
- to
13614
+ from2,
13615
+ to,
13616
+ {
13617
+ label,
13618
+ description,
13619
+ actionJson: action_json,
13620
+ assigneeJson: assignee_json,
13621
+ requirementsJson: requirements_json,
13622
+ permissionJson: permission_json,
13623
+ risk,
13624
+ expectedOutcomeJson: expected_outcome_json
13625
+ }
12886
13626
  )
12887
13627
  );
12888
13628
  return value;
@@ -12901,16 +13641,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12901
13641
  value.target.activate_state_machine_transition(value.name, name)
12902
13642
  );
12903
13643
  return value;
13644
+ },
13645
+ observe_state: async (value, { state, force, source }) => {
13646
+ await run(
13647
+ value.target.observe_state_machine_state(
13648
+ value.name,
13649
+ state,
13650
+ force === true,
13651
+ source
13652
+ )
13653
+ );
13654
+ return value;
12904
13655
  }
12905
13656
  },
12906
13657
  StateMachineState: {
12907
13658
  name: (value) => value.name,
13659
+ label: (value) => value.label || null,
13660
+ description: (value) => value.description || null,
12908
13661
  is_final: (value) => value.is_final
12909
13662
  },
12910
13663
  StateMachineTransition: {
12911
13664
  name: (value) => value.name,
12912
13665
  from: (value) => value.from_state || { name: value.from, is_final: false },
12913
- to: (value) => value.to_state || { name: value.to, is_final: false }
13666
+ to: (value) => value.to_state || { name: value.to, is_final: false },
13667
+ label: (value) => value.label || null,
13668
+ description: (value) => value.description || null,
13669
+ action_json: (value) => value.action_json || null,
13670
+ assignee_json: (value) => value.assignee_json || null,
13671
+ requirements_json: (value) => value.requirements_json || null,
13672
+ permission_json: (value) => value.permission_json || null,
13673
+ risk: (value) => value.risk || null,
13674
+ expected_outcome_json: (value) => value.expected_outcome_json || null
12914
13675
  },
12915
13676
  StateMachinePath: {
12916
13677
  states: (value) => value.states,
@@ -12977,6 +13738,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12977
13738
  name
12978
13739
  from { name }
12979
13740
  to { name }
13741
+ label
13742
+ description
13743
+ action_json
13744
+ assignee_json
13745
+ requirements_json
13746
+ permission_json
13747
+ risk
13748
+ expected_outcome_json
12980
13749
  }
12981
13750
  }`
12982
13751
  ]
@@ -13194,6 +13963,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
13194
13963
  }
13195
13964
 
13196
13965
  // src/client.ts
13966
+ var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
13967
+ var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
13968
+ var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
13969
+ function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
13970
+ if (value === void 0) return fallback;
13971
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
13972
+ throw new RangeError(
13973
+ `Session list ${name} must be an integer between ${minimum} and ${maximum}.`
13974
+ );
13975
+ }
13976
+ return value;
13977
+ }
13197
13978
  var STANDARD_MODULES_OPERATIONS = [
13198
13979
  {
13199
13980
  create: "entity",
@@ -13230,6 +14011,26 @@ var STANDARD_MODULES_OPERATIONS = [
13230
14011
  var BUILTIN_MODULES = {
13231
14012
  standard_modules: STANDARD_MODULES_OPERATIONS
13232
14013
  };
14014
+ function stateNameFromMethodName(methodName) {
14015
+ const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
14016
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
14017
+ }
14018
+ function appendQueryOptions(searchParams, query) {
14019
+ for (const [key, value] of Object.entries(query || {})) {
14020
+ if (value === null || typeof value === "undefined" || value === "") {
14021
+ continue;
14022
+ }
14023
+ if (value instanceof Date) {
14024
+ searchParams.set(key, value.toISOString());
14025
+ continue;
14026
+ }
14027
+ if (Array.isArray(value)) {
14028
+ if (value.length > 0) searchParams.set(key, value.join(","));
14029
+ continue;
14030
+ }
14031
+ searchParams.set(key, String(value));
14032
+ }
14033
+ }
13233
14034
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
13234
14035
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
13235
14036
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -13260,8 +14061,20 @@ function bodyInitFromSessionFileUpload(body) {
13260
14061
  return body;
13261
14062
  }
13262
14063
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
14064
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
13263
14065
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
13264
14066
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
14067
+ function chunkItems(items, batchSize) {
14068
+ const chunks = [];
14069
+ for (let offset = 0; offset < items.length; offset += batchSize) {
14070
+ chunks.push(items.slice(offset, offset + batchSize));
14071
+ }
14072
+ return chunks;
14073
+ }
14074
+ function isUnsupportedEffectCatalogMutation(error) {
14075
+ const message = error instanceof Error ? error.message : String(error);
14076
+ 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");
14077
+ }
13265
14078
  function planRecordObjectsChunks(records, batchSize) {
13266
14079
  const total = records.length;
13267
14080
  const size = Math.max(1, Math.min(batchSize, total));
@@ -13273,6 +14086,23 @@ function planRecordObjectsChunks(records, batchSize) {
13273
14086
  }
13274
14087
  return plans;
13275
14088
  }
14089
+ function preserveRecordObjectRealId(record) {
14090
+ const realId = record.id.trim();
14091
+ if (!realId) {
14092
+ return record;
14093
+ }
14094
+ const fields = record.fields || {};
14095
+ if (typeof fields.real_id === "string" && fields.real_id.trim()) {
14096
+ return record;
14097
+ }
14098
+ return {
14099
+ ...record,
14100
+ fields: {
14101
+ ...fields,
14102
+ real_id: realId
14103
+ }
14104
+ };
14105
+ }
13276
14106
  function computeEffectKey2(effect) {
13277
14107
  const attachedClass = effect.className?.trim();
13278
14108
  if (!attachedClass) {
@@ -13483,7 +14313,7 @@ var Environment = class _Environment {
13483
14313
  }
13484
14314
  get sessions() {
13485
14315
  return {
13486
- list: async (options) => this.listSessions(options?.status || "active"),
14316
+ list: async (options = {}) => this.listSessions(options),
13487
14317
  create: async (options) => this.createSession(options),
13488
14318
  connect: async (sessionId, options) => this.connectSession(sessionId, options),
13489
14319
  reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
@@ -13510,11 +14340,105 @@ var Environment = class _Environment {
13510
14340
  getAwaitingCount: async () => this.getAwaitingRecordCount()
13511
14341
  };
13512
14342
  }
14343
+ /**
14344
+ * Mirror product-owned workflow state into Granular without making Granular
14345
+ * own the customer application's state machine.
14346
+ */
14347
+ async recordState(input) {
14348
+ const { machine, state, ...target } = input;
14349
+ if (!machine.trim()) {
14350
+ throw new Error("State update requires a machine name");
14351
+ }
14352
+ if (!state.trim()) {
14353
+ throw new Error("State update requires a state");
14354
+ }
14355
+ return this.recordObject({
14356
+ className: target.className,
14357
+ id: target.id,
14358
+ ...target.label ? { label: target.label } : {},
14359
+ ...target.fields ? { fields: target.fields } : {},
14360
+ ...target.relationships ? { relationships: target.relationships } : {},
14361
+ states: {
14362
+ [machine.trim()]: {
14363
+ state: state.trim(),
14364
+ ...target.source ? { source: target.source } : {},
14365
+ ...target.cause ? { cause: target.cause } : {},
14366
+ ...target.actorId ? { actorId: target.actorId } : {},
14367
+ ...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
14368
+ ...target.force !== void 0 ? { force: target.force } : {},
14369
+ ...target.metadata ? { metadata: target.metadata } : {}
14370
+ }
14371
+ }
14372
+ });
14373
+ }
14374
+ /**
14375
+ * Mirror product-owned workflow state into Granular without making Granular
14376
+ * own the customer application's state machine.
14377
+ *
14378
+ * Example:
14379
+ * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
14380
+ */
14381
+ state(target) {
14382
+ const observe = async (machineName, stateName, input = {}) => {
14383
+ const observedState = input.observedState || input.state || stateName;
14384
+ if (!observedState) {
14385
+ throw new Error("State observation requires a target state");
14386
+ }
14387
+ return this.recordState({
14388
+ ...target,
14389
+ machine: machineName,
14390
+ state: observedState,
14391
+ ...input.source ? { source: input.source } : {},
14392
+ ...input.cause ? { cause: input.cause } : {},
14393
+ ...input.actorId ? { actorId: input.actorId } : {},
14394
+ ...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
14395
+ ...input.force !== void 0 ? { force: input.force } : {},
14396
+ ...input.metadata ? { metadata: input.metadata } : {}
14397
+ });
14398
+ };
14399
+ return new Proxy(
14400
+ {},
14401
+ {
14402
+ get: (_target, machineProperty) => {
14403
+ if (typeof machineProperty !== "string") return void 0;
14404
+ return new Proxy(
14405
+ {},
14406
+ {
14407
+ get: (_machineTarget, stateProperty) => {
14408
+ if (stateProperty === "to") {
14409
+ return (stateName, input) => observe(machineProperty, stateName, input || {});
14410
+ }
14411
+ if (typeof stateProperty !== "string") return void 0;
14412
+ return (input) => observe(
14413
+ machineProperty,
14414
+ stateNameFromMethodName(stateProperty),
14415
+ input || {}
14416
+ );
14417
+ }
14418
+ }
14419
+ );
14420
+ }
14421
+ }
14422
+ );
14423
+ }
13513
14424
  get feedback() {
13514
14425
  return {
13515
14426
  list: async () => this.listFeedback()
13516
14427
  };
13517
14428
  }
14429
+ get manualActions() {
14430
+ return {
14431
+ record: (input) => this.recordManualAction(input),
14432
+ list: (options = {}) => this.listManualActions(options),
14433
+ suggest: (options = {}) => this.suggestManualActions(options)
14434
+ };
14435
+ }
14436
+ get artifactApprovals() {
14437
+ return {
14438
+ list: (options = {}) => this.listArtifactApprovals(options),
14439
+ decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
14440
+ };
14441
+ }
13518
14442
  /**
13519
14443
  * Sessionless environments do not own a live transport, so disconnecting the
13520
14444
  * environment handle itself is a no-op. This keeps the public surface
@@ -13524,17 +14448,12 @@ var Environment = class _Environment {
13524
14448
  */
13525
14449
  async disconnect() {
13526
14450
  }
13527
- async listSessions(status = "active") {
13528
- if (status === "all") {
13529
- const [active, closed] = await Promise.all([
13530
- this.granular.listOpenSessions({ environmentId: this.environmentId }),
13531
- this.granular.listClosedSessions({ environmentId: this.environmentId })
13532
- ]);
13533
- return [...active, ...closed].sort(
13534
- (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
13535
- );
13536
- }
13537
- return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
14451
+ async listSessions(optionsOrStatus = {}) {
14452
+ const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
14453
+ return this.granular.listSessions({
14454
+ ...options,
14455
+ environmentId: this.environmentId
14456
+ });
13538
14457
  }
13539
14458
  async getUserEnvironmentState(options = {}) {
13540
14459
  return this.granular.getUserEnvironmentState({
@@ -13552,6 +14471,7 @@ var Environment = class _Environment {
13552
14471
  return this.granular.createSession({
13553
14472
  environmentId: this.environmentId,
13554
14473
  clientId: options?.clientId,
14474
+ sessionScope: options?.sessionScope,
13555
14475
  initialHeap: options?.initialHeap
13556
14476
  });
13557
14477
  }
@@ -13593,6 +14513,50 @@ var Environment = class _Environment {
13593
14513
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
13594
14514
  return Array.isArray(response.items) ? response.items : [];
13595
14515
  }
14516
+ async recordManualAction(input) {
14517
+ const body = {
14518
+ ...input,
14519
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
14520
+ };
14521
+ return this.controlPlaneRequest(
14522
+ `/control/environments/${this.environmentId}/manual-actions`,
14523
+ {
14524
+ method: "POST",
14525
+ body: JSON.stringify(body)
14526
+ }
14527
+ );
14528
+ }
14529
+ async listManualActions(options = {}) {
14530
+ const query = new URLSearchParams();
14531
+ appendQueryOptions(query, options);
14532
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14533
+ return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
14534
+ }
14535
+ async suggestManualActions(options = {}) {
14536
+ const query = new URLSearchParams();
14537
+ appendQueryOptions(query, options);
14538
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14539
+ return this.controlPlaneRequest(
14540
+ `/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
14541
+ );
14542
+ }
14543
+ async listArtifactApprovals(options = {}) {
14544
+ const query = new URLSearchParams();
14545
+ appendQueryOptions(query, options);
14546
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14547
+ return this.controlPlaneRequest(
14548
+ `/control/environments/${this.environmentId}/artifact-approvals${suffix}`
14549
+ );
14550
+ }
14551
+ async decideArtifactApproval(approvalTaskId, input) {
14552
+ return this.controlPlaneRequest(
14553
+ `/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
14554
+ {
14555
+ method: "POST",
14556
+ body: JSON.stringify(input)
14557
+ }
14558
+ );
14559
+ }
13596
14560
  getRuntimeBaseUrl() {
13597
14561
  return deriveRuntimeBaseUrl(this._apiEndpoint);
13598
14562
  }
@@ -14417,10 +15381,11 @@ var Environment = class _Environment {
14417
15381
  if (!Array.isArray(records) || records.length === 0) {
14418
15382
  return [];
14419
15383
  }
15384
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
14420
15385
  const batchSize = Math.max(
14421
15386
  1,
14422
15387
  Math.min(
14423
- records.length,
15388
+ recordsToWrite.length,
14424
15389
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
14425
15390
  )
14426
15391
  );
@@ -14428,8 +15393,8 @@ var Environment = class _Environment {
14428
15393
  MAX_RECORD_OBJECTS_CONCURRENCY,
14429
15394
  Math.max(1, options?.concurrency ?? 1)
14430
15395
  );
14431
- const plans = planRecordObjectsChunks(records, batchSize);
14432
- const total = records.length;
15396
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
15397
+ const total = recordsToWrite.length;
14433
15398
  const results = new Array(total);
14434
15399
  const onChunk = options?.onChunkComplete;
14435
15400
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -14500,12 +15465,13 @@ var Environment = class _Environment {
14500
15465
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
14501
15466
  */
14502
15467
  async enqueueRecordImport(records, options = {}) {
15468
+ const recordsToImport = records.map(preserveRecordObjectRealId);
14503
15469
  return this.controlPlaneRequest(
14504
15470
  `/control/environments/${this.environmentId}/record-imports`,
14505
15471
  {
14506
15472
  method: "POST",
14507
15473
  body: JSON.stringify({
14508
- records,
15474
+ records: recordsToImport,
14509
15475
  batchSize: options.batchSize,
14510
15476
  setupRunId: options.setupRunId,
14511
15477
  writeMode: options.writeMode
@@ -14613,11 +15579,7 @@ var EnvironmentSession = class extends Session {
14613
15579
  }
14614
15580
  buildSessionDataUrl(path2, query) {
14615
15581
  const searchParams = new URLSearchParams();
14616
- for (const [key, value] of Object.entries(query || {})) {
14617
- if (value !== null && typeof value !== "undefined" && value !== "") {
14618
- searchParams.set(key, String(value));
14619
- }
14620
- }
15582
+ appendQueryOptions(searchParams, query);
14621
15583
  const queryString = searchParams.toString();
14622
15584
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path2}${queryString ? `?${queryString}` : ""}`;
14623
15585
  }
@@ -14700,9 +15662,108 @@ var EnvironmentSession = class extends Session {
14700
15662
  ),
14701
15663
  get: (jobId) => this.sessionDataRequest(
14702
15664
  `/jobs/${encodeURIComponent(jobId)}`
15665
+ ),
15666
+ latest: async (options = {}) => {
15667
+ const page = await this.sessionDataRequest("/jobs", {
15668
+ status: options.status || "all",
15669
+ latest: true,
15670
+ limit: 1
15671
+ });
15672
+ return page.items[0] || null;
15673
+ }
15674
+ };
15675
+ }
15676
+ get artifacts() {
15677
+ return {
15678
+ list: (options = {}) => {
15679
+ const queryOptions = { ...options };
15680
+ if (options.target) {
15681
+ queryOptions.targetClassName = options.target.className;
15682
+ queryOptions.targetId = options.target.id;
15683
+ delete queryOptions.target;
15684
+ }
15685
+ return this.sessionDataRequest("/artifacts", queryOptions);
15686
+ },
15687
+ listForLatestJob: (options = {}) => this.artifacts.list({
15688
+ ...options,
15689
+ latestJob: true
15690
+ }),
15691
+ get: (artifactId) => this.sessionDataRequest(
15692
+ `/artifacts/${encodeURIComponent(artifactId)}`
15693
+ ),
15694
+ create: (artifact) => this.sessionDataRequest(
15695
+ "/artifacts",
15696
+ void 0,
15697
+ {
15698
+ method: "POST",
15699
+ body: artifact
15700
+ }
15701
+ ),
15702
+ updateInputs: (artifactId, patch) => this.sessionDataRequest(
15703
+ `/artifacts/${encodeURIComponent(artifactId)}`,
15704
+ void 0,
15705
+ {
15706
+ method: "PATCH",
15707
+ body: patch
15708
+ }
15709
+ ),
15710
+ validate: (artifactId) => this.sessionDataRequest(
15711
+ `/artifacts/${encodeURIComponent(artifactId)}/validate`,
15712
+ void 0,
15713
+ { method: "POST" }
15714
+ ),
15715
+ execute: (artifactId, options) => this.sessionDataRequest(
15716
+ `/artifacts/${encodeURIComponent(artifactId)}/execute`,
15717
+ void 0,
15718
+ { method: "POST", body: options }
15719
+ ),
15720
+ approve: (artifactId, options) => this.sessionDataRequest(
15721
+ `/artifacts/${encodeURIComponent(artifactId)}/approve`,
15722
+ void 0,
15723
+ { method: "POST", body: options }
15724
+ ),
15725
+ cancel: (artifactId) => this.sessionDataRequest(
15726
+ `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
15727
+ void 0,
15728
+ { method: "POST" }
14703
15729
  )
14704
15730
  };
14705
15731
  }
15732
+ get manualActions() {
15733
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15734
+ return {
15735
+ record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15736
+ "/manual-actions",
15737
+ void 0,
15738
+ {
15739
+ method: "POST",
15740
+ body: { ...input, sessionId: this.sessionId }
15741
+ }
15742
+ ) : this.environment.manualActions.record({
15743
+ ...input,
15744
+ sessionId: this.sessionId
15745
+ }),
15746
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
15747
+ ...options,
15748
+ sessionId: this.sessionId
15749
+ }),
15750
+ suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15751
+ "/manual-actions/suggestions",
15752
+ options
15753
+ ) : this.environment.manualActions.suggest(options)
15754
+ };
15755
+ }
15756
+ get artifactApprovals() {
15757
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15758
+ return {
15759
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
15760
+ decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15761
+ `/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
15762
+ void 0,
15763
+ { method: "POST", body: input }
15764
+ ) : this.environment.artifactApprovals.decide(approvalTaskId, input)
15765
+ };
15766
+ }
14706
15767
  get files() {
14707
15768
  return {
14708
15769
  list: (options = {}) => this.sessionDataRequest(
@@ -14783,13 +15844,16 @@ var EnvironmentSession = class extends Session {
14783
15844
  get transcript() {
14784
15845
  return {
14785
15846
  list: async (options = {}) => {
14786
- const [messages, jobs, entries, lists] = await Promise.all([
15847
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
14787
15848
  this.collectAllSessionItems(this.messages.list),
14788
15849
  this.collectAllSessionItems(
14789
15850
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
14790
15851
  ),
14791
15852
  this.collectAllSessionItems(this.heap.entries.list),
14792
- this.collectAllSessionItems(this.heap.lists.list)
15853
+ this.collectAllSessionItems(this.heap.lists.list),
15854
+ this.collectAllSessionItems(
15855
+ (pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
15856
+ )
14793
15857
  ]);
14794
15858
  const liveDoc = {
14795
15859
  conversation: { messages },
@@ -14803,6 +15867,21 @@ var EnvironmentSession = class extends Session {
14803
15867
  (entry) => Boolean(entry)
14804
15868
  )
14805
15869
  )
15870
+ },
15871
+ artifacts: {
15872
+ byId: Object.fromEntries(
15873
+ artifacts.map((artifact) => {
15874
+ return artifact?.artifactId ? [
15875
+ artifact.artifactId,
15876
+ artifact
15877
+ ] : null;
15878
+ }).filter(
15879
+ (entry) => Boolean(entry)
15880
+ )
15881
+ ),
15882
+ order: artifacts.map((artifact) => artifact?.artifactId).filter(
15883
+ (artifactId) => Boolean(artifactId)
15884
+ )
14806
15885
  }
14807
15886
  };
14808
15887
  const heap = normalizeHeapSnapshot({
@@ -14879,6 +15958,12 @@ var EnvironmentSession = class extends Session {
14879
15958
  async recordObject(options) {
14880
15959
  return this.environment.recordObject(options);
14881
15960
  }
15961
+ async recordState(input) {
15962
+ return this.environment.recordState(input);
15963
+ }
15964
+ state(target) {
15965
+ return this.environment.state(target);
15966
+ }
14882
15967
  async recordObjects(records, options) {
14883
15968
  return this.environment.recordObjects(records, options);
14884
15969
  }
@@ -14949,7 +16034,7 @@ var EnvironmentSession = class extends Session {
14949
16034
  * Close only the socket transport without sending `client.goodbye`.
14950
16035
  */
14951
16036
  disconnectTransport() {
14952
- this.client.disconnect();
16037
+ this.client.disconnect({ reason: "Transport detach" });
14953
16038
  }
14954
16039
  /**
14955
16040
  * Backwards-compatible alias for `disconnect()`.
@@ -15344,16 +16429,71 @@ var Granular = class _Granular {
15344
16429
  };
15345
16430
  }
15346
16431
  /**
15347
- * List active (open) sessions for an environment each session is one agent conversation thread.
16432
+ * List indexed sessions using ownership filters and bounded pagination.
16433
+ */
16434
+ async listSessions(options) {
16435
+ const environmentId = options.environmentId?.trim();
16436
+ const sandboxId = options.sandboxId?.trim();
16437
+ const subjectId = options.subjectId?.trim();
16438
+ if (!environmentId && !sandboxId && !subjectId) {
16439
+ throw new Error(
16440
+ "listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
16441
+ );
16442
+ }
16443
+ const status = options.status || "active";
16444
+ const allowedStatuses = /* @__PURE__ */ new Set([
16445
+ "active",
16446
+ "closed",
16447
+ "expired",
16448
+ "failed",
16449
+ "timeout",
16450
+ "all"
16451
+ ]);
16452
+ if (!allowedStatuses.has(status)) {
16453
+ throw new Error(`Unsupported session status: ${String(status)}`);
16454
+ }
16455
+ const limit = boundedSessionListInteger(
16456
+ options.limit,
16457
+ "limit",
16458
+ DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
16459
+ 1,
16460
+ MAX_CONVERSATION_SESSION_LIST_LIMIT
16461
+ );
16462
+ const offset = boundedSessionListInteger(
16463
+ options.offset,
16464
+ "offset",
16465
+ 0,
16466
+ 0,
16467
+ MAX_CONVERSATION_SESSION_LIST_OFFSET
16468
+ );
16469
+ const query = new URLSearchParams({
16470
+ limit: String(limit),
16471
+ offset: String(offset)
16472
+ });
16473
+ if (environmentId) query.set("environmentId", environmentId);
16474
+ if (sandboxId) query.set("sandboxId", sandboxId);
16475
+ if (subjectId) query.set("userId", subjectId);
16476
+ if (options.sessionScope?.trim()) {
16477
+ query.set("sessionScope", options.sessionScope.trim());
16478
+ }
16479
+ if (status !== "all") query.set("status", status);
16480
+ const res = await this.request(
16481
+ `/control/sessions?${query.toString()}`
16482
+ );
16483
+ const items = Array.isArray(res.items) ? res.items : [];
16484
+ return items.map((row) => this.normalizeConversationSession(row));
16485
+ }
16486
+ /**
16487
+ * List active (open) sessions for an environment.
15348
16488
  */
15349
16489
  async listOpenSessions(filters) {
15350
- return this.listSessionsForEnvironment(filters.environmentId, "active");
16490
+ return this.listSessions({ ...filters, status: "active" });
15351
16491
  }
15352
16492
  /**
15353
16493
  * List closed sessions for an environment (conversations that have disconnected).
15354
16494
  */
15355
16495
  async listClosedSessions(filters) {
15356
- return this.listSessionsForEnvironment(filters.environmentId, "closed");
16496
+ return this.listSessions({ ...filters, status: "closed" });
15357
16497
  }
15358
16498
  async getUserEnvironmentState(options) {
15359
16499
  const query = new URLSearchParams({
@@ -15388,14 +16528,6 @@ var Granular = class _Granular {
15388
16528
  });
15389
16529
  return result.readAtBySessionId || {};
15390
16530
  }
15391
- async listSessionsForEnvironment(environmentId, status) {
15392
- const query = new URLSearchParams({ environmentId, status });
15393
- const res = await this.request(
15394
- `/control/sessions?${query.toString()}`
15395
- );
15396
- const items = Array.isArray(res.items) ? res.items : [];
15397
- return items.map((row) => this.normalizeConversationSession(row));
15398
- }
15399
16531
  normalizeConversationSession(row) {
15400
16532
  const sessionId = String(row.sessionId ?? row.session_id ?? "");
15401
16533
  const environmentId = String(row.environmentId ?? row.environment_id ?? "");
@@ -15454,6 +16586,7 @@ var Granular = class _Granular {
15454
16586
  */
15455
16587
  async createSession(options) {
15456
16588
  const clientId = options.clientId || `client_${Date.now()}`;
16589
+ const sessionScope = options.sessionScope?.trim() || void 0;
15457
16590
  await this.activateEnvironment(options.environmentId);
15458
16591
  const envData = await this.environments.get(options.environmentId);
15459
16592
  const environment = this.bindEnvironmentHandle(envData);
@@ -15462,6 +16595,8 @@ var Granular = class _Granular {
15462
16595
  body: JSON.stringify({
15463
16596
  environmentId: options.environmentId,
15464
16597
  clientId,
16598
+ sessionScope,
16599
+ capabilities: sessionScope ? { sessionScope } : void 0,
15465
16600
  initialHeap: options.initialHeap
15466
16601
  })
15467
16602
  });
@@ -15706,15 +16841,43 @@ var Granular = class _Granular {
15706
16841
  const effects = Array.from(
15707
16842
  this.getSandboxEffectMap(host.sandboxId).values()
15708
16843
  ).map((effect) => this.serializeEffect(effect));
15709
- const result = await withTimeout(
15710
- host.wsClient.call("effects.publishCatalog", {
15711
- effects
15712
- }),
15713
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
15714
- `effects.publishCatalog for sandbox ${host.sandboxId}`
15715
- );
15716
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
15717
- const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
16844
+ let acceptedCount = 0;
16845
+ const rejected = [];
16846
+ try {
16847
+ await withTimeout(
16848
+ host.wsClient.call("effects.resetCatalog", {}),
16849
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16850
+ `effects.resetCatalog for sandbox ${host.sandboxId}`
16851
+ );
16852
+ for (const batch of chunkItems(effects, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
16853
+ const result = await withTimeout(
16854
+ host.wsClient.call("effects.addCatalog", {
16855
+ effects: batch
16856
+ }),
16857
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16858
+ `effects.addCatalog for sandbox ${host.sandboxId}`
16859
+ );
16860
+ acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16861
+ if (Array.isArray(result?.rejected)) {
16862
+ rejected.push(...result.rejected);
16863
+ }
16864
+ }
16865
+ } catch (error) {
16866
+ if (!isUnsupportedEffectCatalogMutation(error)) {
16867
+ throw error;
16868
+ }
16869
+ const result = await withTimeout(
16870
+ host.wsClient.call("effects.publishCatalog", {
16871
+ effects
16872
+ }),
16873
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16874
+ `effects.publishCatalog for sandbox ${host.sandboxId}`
16875
+ );
16876
+ acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16877
+ if (Array.isArray(result?.rejected)) {
16878
+ rejected.push(...result.rejected);
16879
+ }
16880
+ }
15718
16881
  if (acceptedCount === 0 && rejected.length > 0) {
15719
16882
  const detail = rejected.map(
15720
16883
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -16940,6 +18103,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
16940
18103
  var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
16941
18104
  var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
16942
18105
  var LEGACY_SANDBOX_TOOLS_MODULE_PATTERN = "\\.\\/sandbox-tools(?:\\.js)?";
18106
+ var HARNESS_V3_RUNTIME_MODULE_PATTERN = "@granular/(?:agent|session|domain(?:/[A-Za-z_$][\\w$]*)?|actions/(?:backend|frontend)|utils/(?:csv|xlsx))";
16943
18107
  function hasNamedModuleImport(source, moduleName, name) {
16944
18108
  const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16945
18109
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -16998,6 +18162,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
16998
18162
  message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
16999
18163
  });
17000
18164
  }
18165
+ if (new RegExp(
18166
+ `import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*['"]${HARNESS_V3_RUNTIME_MODULE_PATTERN}['"]`
18167
+ ).test(normalized)) {
18168
+ issues.push({
18169
+ code: "runtime_namespace_import",
18170
+ severity: "error",
18171
+ 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"`.'
18172
+ });
18173
+ }
17001
18174
  if (/\bprocess\.exit\s*\(/.test(normalized)) {
17002
18175
  issues.push({
17003
18176
  code: "process_exit",
@@ -18017,6 +19190,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
18017
19190
  entries: {}
18018
19191
  });
18019
19192
  }
19193
+ function buildGranularAgentManualActionMemorySummary(input) {
19194
+ const maxItems = Math.max(1, Math.min(12, input.maxItems ?? 8));
19195
+ const suggestions = (input.suggestions || []).filter((suggestion) => suggestion?.actionKey).slice(0, maxItems).map((suggestion) => ({
19196
+ actionKey: suggestion.actionKey,
19197
+ label: suggestion.label || null,
19198
+ targetClassName: suggestion.targetClassName || null,
19199
+ count: typeof suggestion.count === "number" && Number.isFinite(suggestion.count) ? suggestion.count : null,
19200
+ subjectCount: typeof suggestion.subjectCount === "number" && Number.isFinite(suggestion.subjectCount) ? suggestion.subjectCount : null,
19201
+ successCount: typeof suggestion.successCount === "number" && Number.isFinite(suggestion.successCount) ? suggestion.successCount : null,
19202
+ failureCount: typeof suggestion.failureCount === "number" && Number.isFinite(suggestion.failureCount) ? suggestion.failureCount : null,
19203
+ lastOccurredAt: typeof suggestion.lastOccurredAt === "number" && Number.isFinite(suggestion.lastOccurredAt) ? suggestion.lastOccurredAt : null,
19204
+ sampleTargetIds: Array.isArray(suggestion.sampleTargetIds) ? suggestion.sampleTargetIds.filter(
19205
+ (id) => typeof id === "string" && id.trim().length > 0
19206
+ ).slice(0, 6) : []
19207
+ }));
19208
+ return [
19209
+ renderConstBlock("manualActionMemory", {
19210
+ suggestions
19211
+ }),
19212
+ "Use manualActionMemory only as behavioral context for likely next actions. Ground the current target and validate permissions before creating or running prepared actions."
19213
+ ].join("\n");
19214
+ }
19215
+ function buildGranularAgentManualActionBlock(manualActionSummary) {
19216
+ return manualActionSummary?.trim() || buildGranularAgentManualActionMemorySummary({ suggestions: [] });
19217
+ }
18020
19218
  function projectSessionFileSummary(liveDoc) {
18021
19219
  const files = asRecord4(liveDoc?.files);
18022
19220
  const byId = asRecord4(files?.byId) || {};
@@ -18048,8 +19246,12 @@ function buildGranularAgentFileBlock(fileSummary) {
18048
19246
  function extractRuntimeContractExports(domainBlock) {
18049
19247
  const classes = /* @__PURE__ */ new Set();
18050
19248
  const actions = /* @__PURE__ */ new Set();
18051
- const classPattern = /export\s+declare\s+(?:const|class)\s+([A-Za-z_$][\w$]*)/g;
18052
- for (const match of domainBlock.matchAll(classPattern)) {
19249
+ const classConstPattern = /export\s+declare\s+const\s+([A-Za-z_$][\w$]*)\s*:\s*EntityClass\b/g;
19250
+ for (const match of domainBlock.matchAll(classConstPattern)) {
19251
+ classes.add(match[1]);
19252
+ }
19253
+ const classDeclPattern = /export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\b/g;
19254
+ for (const match of domainBlock.matchAll(classDeclPattern)) {
18053
19255
  classes.add(match[1]);
18054
19256
  }
18055
19257
  const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
@@ -18554,6 +19756,9 @@ function buildGranularAgentSystemPrompt(input) {
18554
19756
  });
18555
19757
  const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
18556
19758
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
19759
+ const manualActionBlock = buildGranularAgentManualActionBlock(
19760
+ input.manualActionSummary
19761
+ );
18557
19762
  const knownFactsBlock = renderConstBlock(
18558
19763
  "knownFacts",
18559
19764
  buildKnownFactsFromCheckpoint(input.checkpoint)
@@ -18567,16 +19772,16 @@ function buildGranularAgentSystemPrompt(input) {
18567
19772
  - \`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.
18568
19773
  - 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.
18569
19774
  - 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.
18570
- - 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.
18571
- - 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.
18572
- - 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"] })\`.
18573
- - \`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(...)\`.
18574
- - 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.
18575
- - 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.
19775
+ - 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.
19776
+ - 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"] })\`.
19777
+ - \`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(...)\`.
19778
+ - 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.
19779
+ - 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()\`.
19780
+ - 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.
19781
+ - 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.
18576
19782
  - 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.
18577
19783
  - 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.
18578
- - 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.
18579
- - 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.
19784
+ - 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.
18580
19785
  - 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\`.
18581
19786
  - \`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.
18582
19787
  - 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.
@@ -18587,7 +19792,7 @@ function buildGranularAgentSystemPrompt(input) {
18587
19792
  - When using code, assistant text must be empty or one brief summary.
18588
19793
  - Code must be plain runnable JavaScript with top-level await.
18589
19794
  - Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
18590
- - 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.
19795
+ - 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.
18591
19796
  - 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.
18592
19797
  - 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.
18593
19798
  - 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\`.
@@ -18650,6 +19855,7 @@ ${workflowRules}
18650
19855
  High-priority execution rules:
18651
19856
  - 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.
18652
19857
  - 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.
19858
+ - 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.
18653
19859
  - 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.
18654
19860
  - 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.
18655
19861
  - 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.
@@ -18693,7 +19899,7 @@ Intent resolution:
18693
19899
  - 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.
18694
19900
  - 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.
18695
19901
  - 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.
18696
- - 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.
19902
+ - 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.
18697
19903
  - 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.
18698
19904
  - Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
18699
19905
  - 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.
@@ -18820,20 +20026,10 @@ Ask the user when:
18820
20026
  - the target is unique but the requested action is unclear
18821
20027
 
18822
20028
  Relationship filters:
18823
- - One-record relationships use \`is\`.
18824
- - Multi-record relationships use \`some\`.
18825
- - 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.
18826
- - 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.
18827
- - 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.
18828
- - 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.
18829
- - Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
18830
- - 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.
18831
- - Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
18832
- - 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\`.
18833
- - 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.
18834
- - Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
18835
- - 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.
18836
- - Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
20029
+ - Use the generated filter type as the authority: \`OneRelationFilter\` supports \`id\`, \`path\`, \`is\`, \`null\`, \`not_null\`; \`ManyRelationFilter\` supports those plus \`some\`.
20030
+ - Use \`id\` or \`path\` for a known related record; use \`is\` or \`some\` only for nested target-field filters.
20031
+ - 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.
20032
+ - Never pass a full record instance into a filter. Use its id/path or a declared relationship getter.
18837
20033
  ${domainSections.docs ? `
18838
20034
  Domain notes:
18839
20035
  ${domainSections.docs}
@@ -18844,6 +20040,24 @@ ${actionIndex}
18844
20040
  - 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.
18845
20041
  - 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(...)\`.
18846
20042
  - Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
20043
+ - 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.
20044
+ - For pure field-collection requests, target the class-level entry state handle; for submit/review requests, target the nearest requested later state.
20045
+ - Choose the nearest target state that matches the user's words. Do not aim at a later state just because it is reachable.
20046
+ - 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\`.
20047
+ - 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.
20048
+ - 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.
20049
+ - 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()\`.
20050
+ - 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.
20051
+ - 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.
20052
+ - 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.
20053
+ - 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.
20054
+ - 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.
20055
+ - 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.
20056
+ - 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.
20057
+ - 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.
20058
+ - 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.
20059
+ - 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.
20060
+ - Use \`await prepared.show()\` or \`await actions.show(prepared)\` only to display an already-created prepared action again.
18847
20061
  - 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.
18848
20062
  - Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
18849
20063
  - 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.
@@ -18874,6 +20088,8 @@ ${loopBlock}
18874
20088
 
18875
20089
  ${knownFactsBlock}
18876
20090
 
20091
+ ${manualActionBlock}
20092
+
18877
20093
  [Request]
18878
20094
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18879
20095
  }
@@ -19029,8 +20245,9 @@ function resolveHarnessTemplate(templateId = "stable", options) {
19029
20245
  }
19030
20246
 
19031
20247
  // src/openai-usage.ts
19032
- var OPENAI_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
19033
- var OPENAI_PRICING_EFFECTIVE_DATE = "2026-05-19";
20248
+ var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
20249
+ var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
20250
+ var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
19034
20251
  var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
19035
20252
  "gpt-5.4": {
19036
20253
  provider: "openai",
@@ -19039,8 +20256,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
19039
20256
  inputUsdPerMillion: 2.5,
19040
20257
  cachedInputUsdPerMillion: 0.25,
19041
20258
  outputUsdPerMillion: 15,
19042
- sourceUrl: OPENAI_PRICING_SOURCE_URL,
19043
- effectiveDate: OPENAI_PRICING_EFFECTIVE_DATE
20259
+ sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
20260
+ effectiveDate: "2026-05-19"
20261
+ },
20262
+ "gpt-5.6-luna": {
20263
+ provider: "openai",
20264
+ model: "gpt-5.6-luna",
20265
+ currency: "USD",
20266
+ inputUsdPerMillion: 1,
20267
+ cachedInputUsdPerMillion: 0.1,
20268
+ cacheWriteUsdPerMillion: 1.25,
20269
+ outputUsdPerMillion: 6,
20270
+ sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
20271
+ effectiveDate: "2026-07-11",
20272
+ longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
20273
+ longContextPricing: {
20274
+ inputUsdPerMillion: 2,
20275
+ cachedInputUsdPerMillion: 0.2,
20276
+ cacheWriteUsdPerMillion: 2.5,
20277
+ outputUsdPerMillion: 9
20278
+ }
19044
20279
  }
19045
20280
  };
19046
20281
  function asRecord5(value) {
@@ -19053,8 +20288,18 @@ function numberField(record, key) {
19053
20288
  function microsPerMillion(usdPerMillion) {
19054
20289
  return Math.round(usdPerMillion * 1e6);
19055
20290
  }
19056
- function getOpenAIModelPricing(model) {
19057
- return OPENAI_MODEL_PRICING_USD_PER_MILLION[model] || null;
20291
+ function getOpenAIModelPricing(model, inputTokens = 0) {
20292
+ const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
20293
+ if (!pricing) return null;
20294
+ const threshold = pricing.longContextThresholdTokens ?? null;
20295
+ if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
20296
+ return {
20297
+ ...pricing,
20298
+ ...pricing.longContextPricing,
20299
+ contextTier: "long"
20300
+ };
20301
+ }
20302
+ return { ...pricing, contextTier: "short" };
19058
20303
  }
19059
20304
  function normalizeOpenAIUsage(rawUsage) {
19060
20305
  const usage = asRecord5(rawUsage);
@@ -19062,6 +20307,7 @@ function normalizeOpenAIUsage(rawUsage) {
19062
20307
  return {
19063
20308
  inputTokens: 0,
19064
20309
  cachedInputTokens: 0,
20310
+ cacheWriteTokens: 0,
19065
20311
  uncachedInputTokens: 0,
19066
20312
  outputTokens: 0,
19067
20313
  reasoningTokens: 0,
@@ -19077,20 +20323,28 @@ function normalizeOpenAIUsage(rawUsage) {
19077
20323
  inputTokens,
19078
20324
  numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
19079
20325
  );
20326
+ const cacheWriteTokens = Math.min(
20327
+ Math.max(inputTokens - cachedInputTokens, 0),
20328
+ numberField(inputDetails, "cache_write_tokens")
20329
+ );
19080
20330
  const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
19081
20331
  return {
19082
20332
  inputTokens,
19083
20333
  cachedInputTokens,
19084
- uncachedInputTokens: Math.max(inputTokens - cachedInputTokens, 0),
20334
+ cacheWriteTokens,
20335
+ uncachedInputTokens: Math.max(
20336
+ inputTokens - cachedInputTokens - cacheWriteTokens,
20337
+ 0
20338
+ ),
19085
20339
  outputTokens,
19086
20340
  reasoningTokens,
19087
20341
  totalTokens
19088
20342
  };
19089
20343
  }
19090
20344
  function calculateOpenAITokenSpend(model, rawUsage) {
19091
- const pricing = getOpenAIModelPricing(model);
19092
- if (!pricing) return null;
19093
20345
  const usage = normalizeOpenAIUsage(rawUsage);
20346
+ const pricing = getOpenAIModelPricing(model, usage.inputTokens);
20347
+ if (!pricing) return null;
19094
20348
  const inputPricePerMillionMicros = microsPerMillion(
19095
20349
  pricing.inputUsdPerMillion
19096
20350
  );
@@ -19100,14 +20354,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
19100
20354
  const outputPricePerMillionMicros = microsPerMillion(
19101
20355
  pricing.outputUsdPerMillion
19102
20356
  );
20357
+ const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
20358
+ const cacheWriteCostMicros = Math.round(
20359
+ usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
20360
+ );
19103
20361
  const amountMicros = Math.round(
19104
- (usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
20362
+ (usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
19105
20363
  );
19106
20364
  return {
19107
20365
  provider: "openai",
19108
20366
  model,
19109
20367
  inputTokens: usage.inputTokens,
19110
20368
  cachedInputTokens: usage.cachedInputTokens,
20369
+ cacheWriteTokens: usage.cacheWriteTokens,
19111
20370
  uncachedInputTokens: usage.uncachedInputTokens,
19112
20371
  outputTokens: usage.outputTokens,
19113
20372
  reasoningTokens: usage.reasoningTokens,
@@ -19116,7 +20375,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
19116
20375
  currency: "USD",
19117
20376
  inputPricePerMillionMicros,
19118
20377
  cachedInputPricePerMillionMicros,
20378
+ cacheWritePricePerMillionMicros,
20379
+ cacheWriteCostMicros,
19119
20380
  outputPricePerMillionMicros,
20381
+ pricingContextTier: pricing.contextTier || "short",
20382
+ longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
19120
20383
  pricingSource: pricing.sourceUrl,
19121
20384
  pricingEffectiveAt: pricing.effectiveDate,
19122
20385
  usage
@@ -19624,6 +20887,8 @@ function modelOutputInstruction() {
19624
20887
  '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.',
19625
20888
  '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.',
19626
20889
  "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.",
20890
+ "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.",
20891
+ "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.",
19627
20892
  'Use "action":"job" when the next step should run code or mutate workflow state.',
19628
20893
  '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.',
19629
20894
  "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.",
@@ -19693,7 +20958,12 @@ function createOpenAIChatTurnGenerator(options) {
19693
20958
 
19694
20959
  ${modelOutputInstruction()}`
19695
20960
  },
19696
- ...input.history,
20961
+ ...input.history.map(
20962
+ (message) => ({
20963
+ role: message.role,
20964
+ content: message.content
20965
+ })
20966
+ ),
19697
20967
  { role: "user", content: input.request }
19698
20968
  ];
19699
20969
  const payload = {
@@ -19712,11 +20982,12 @@ ${modelOutputInstruction()}`
19712
20982
  let usage = null;
19713
20983
  let requestId = null;
19714
20984
  if (input.onTextDelta || input.onReplyDelta || input.onCodeDelta) {
19715
- const stream = await client.chat.completions.create({
20985
+ const streamPayload = {
19716
20986
  ...payload,
19717
20987
  stream: true,
19718
20988
  stream_options: { include_usage: true }
19719
- });
20989
+ };
20990
+ const stream = await client.chat.completions.create(streamPayload);
19720
20991
  let streamedReply = "";
19721
20992
  let streamedCode = "";
19722
20993
  const emitReplyDelta = async () => {
@@ -19742,7 +21013,8 @@ ${modelOutputInstruction()}`
19742
21013
  await input.onCodeDelta(delta);
19743
21014
  };
19744
21015
  for await (const event of stream) {
19745
- requestId = requestId || event.id || event._request_id || null;
21016
+ const eventRecord = event;
21017
+ requestId = requestId || event.id || (typeof eventRecord._request_id === "string" ? eventRecord._request_id : null);
19746
21018
  usage = event.usage || usage;
19747
21019
  const delta = event.choices?.[0]?.delta?.content;
19748
21020
  const deltaText = typeof delta === "string" ? delta : Array.isArray(delta) ? delta.map((part) => asRecord6(part)?.text || "").join("") : "";
@@ -19756,14 +21028,13 @@ ${modelOutputInstruction()}`
19756
21028
  await emitCodeDelta();
19757
21029
  raw = { streamed: true, model, usage, request_id: requestId };
19758
21030
  } else {
19759
- const completion = await client.chat.completions.create(
19760
- payload
19761
- );
21031
+ const completion = await client.chat.completions.create(payload);
21032
+ const completionRecord = completion;
19762
21033
  raw = completion;
19763
21034
  usage = completion.usage;
19764
- requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof completion._request_id === "string" ? completion._request_id : null);
21035
+ requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof completionRecord._request_id === "string" ? completionRecord._request_id : null);
19765
21036
  const content = asRecord6(
19766
- asRecord6(completion.choices?.[0])?.message
21037
+ asRecord6(completion.choices[0])?.message
19767
21038
  )?.content;
19768
21039
  text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord6(part)?.text || "").join("") : "";
19769
21040
  }
@@ -20100,6 +21371,9 @@ async function generateTurnWithRepair(generator, input) {
20100
21371
  "",
20101
21372
  "The previous generated job code failed preflight review against [Runtime Imports] and the runtime contract.",
20102
21373
  "Return a corrected JSON object. Keep the user's requested behavior, but fix every issue below before execution.",
21374
+ "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.",
21375
+ "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.",
21376
+ "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.",
20103
21377
  "",
20104
21378
  "Preflight issues:",
20105
21379
  ...issues.map((issue) => `- ${issue.code}: ${issue.message}`),