@granular-software/sdk 0.4.49 → 0.4.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -60,11 +60,11 @@ var __export = (target, all) => {
60
60
  for (var name in all)
61
61
  __defProp(target, name, { get: all[name], enumerable: true });
62
62
  };
63
- var __copyProps = (to, from, except, desc) => {
64
- if (from && typeof from === "object" || typeof from === "function") {
65
- for (let key of __getOwnPropNames(from))
63
+ var __copyProps = (to, from2, except, desc) => {
64
+ if (from2 && typeof from2 === "object" || typeof from2 === "function") {
65
+ for (let key of __getOwnPropNames(from2))
66
66
  if (!__hasOwnProp.call(to, key) && key !== except)
67
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
67
+ __defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
68
68
  }
69
69
  return to;
70
70
  };
@@ -8889,7 +8889,7 @@ createChalk({ level: stderrColor ? stderrColor.level : 0 });
8889
8889
  var source_default = chalk;
8890
8890
 
8891
8891
  // ../../node_modules/mimic-function/index.js
8892
- var copyProperty = (to, from, property, ignoreNonConfigurable) => {
8892
+ var copyProperty = (to, from2, property, ignoreNonConfigurable) => {
8893
8893
  if (property === "length" || property === "prototype") {
8894
8894
  return;
8895
8895
  }
@@ -8897,7 +8897,7 @@ var copyProperty = (to, from, property, ignoreNonConfigurable) => {
8897
8897
  return;
8898
8898
  }
8899
8899
  const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
8900
- const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
8900
+ const fromDescriptor = Object.getOwnPropertyDescriptor(from2, property);
8901
8901
  if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
8902
8902
  return;
8903
8903
  }
@@ -8906,8 +8906,8 @@ var copyProperty = (to, from, property, ignoreNonConfigurable) => {
8906
8906
  var canCopyProperty = function(toDescriptor, fromDescriptor) {
8907
8907
  return toDescriptor === void 0 || toDescriptor.configurable || toDescriptor.writable === fromDescriptor.writable && toDescriptor.enumerable === fromDescriptor.enumerable && toDescriptor.configurable === fromDescriptor.configurable && (toDescriptor.writable || toDescriptor.value === fromDescriptor.value);
8908
8908
  };
8909
- var changePrototype = (to, from) => {
8910
- const fromPrototype = Object.getPrototypeOf(from);
8909
+ var changePrototype = (to, from2) => {
8910
+ const fromPrototype = Object.getPrototypeOf(from2);
8911
8911
  if (fromPrototype === Object.getPrototypeOf(to)) {
8912
8912
  return;
8913
8913
  }
@@ -8917,20 +8917,20 @@ var wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/
8917
8917
  ${fromBody}`;
8918
8918
  var toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, "toString");
8919
8919
  var toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, "name");
8920
- var changeToString = (to, from, name) => {
8920
+ var changeToString = (to, from2, name) => {
8921
8921
  const withName = name === "" ? "" : `with ${name.trim()}() `;
8922
- const newToString = wrappedToString.bind(null, withName, from.toString());
8922
+ const newToString = wrappedToString.bind(null, withName, from2.toString());
8923
8923
  Object.defineProperty(newToString, "name", toStringName);
8924
8924
  const { writable, enumerable, configurable } = toStringDescriptor;
8925
8925
  Object.defineProperty(to, "toString", { value: newToString, writable, enumerable, configurable });
8926
8926
  };
8927
- function mimicFunction(to, from, { ignoreNonConfigurable = false } = {}) {
8927
+ function mimicFunction(to, from2, { ignoreNonConfigurable = false } = {}) {
8928
8928
  const { name } = to;
8929
- for (const property of Reflect.ownKeys(from)) {
8930
- copyProperty(to, from, property, ignoreNonConfigurable);
8929
+ for (const property of Reflect.ownKeys(from2)) {
8930
+ copyProperty(to, from2, property, ignoreNonConfigurable);
8931
8931
  }
8932
- changePrototype(to, from);
8933
- changeToString(to, from, name);
8932
+ changePrototype(to, from2);
8933
+ changeToString(to, from2, name);
8934
8934
  return to;
8935
8935
  }
8936
8936
 
@@ -9880,7 +9880,7 @@ function relationshipEdgesForClass(className, rels) {
9880
9880
  return edges;
9881
9881
  }
9882
9882
  function toPascalCase(name) {
9883
- return name.charAt(0).toUpperCase() + name.slice(1);
9883
+ return name.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
9884
9884
  }
9885
9885
 
9886
9886
  // src/cli/agent-docs/concept-blocks.ts
@@ -14065,16 +14065,107 @@ var StateMachineStateSchema = external_exports.union([
14065
14065
  external_exports.string(),
14066
14066
  external_exports.object({
14067
14067
  name: external_exports.string().min(1),
14068
+ label: external_exports.string().optional(),
14069
+ description: external_exports.string().optional(),
14068
14070
  isFinal: external_exports.boolean().optional()
14069
14071
  }).strict()
14070
14072
  ]);
14073
+ var StateTransitionInputBindingSchema = external_exports.lazy(
14074
+ () => external_exports.union([
14075
+ external_exports.null(),
14076
+ external_exports.string(),
14077
+ external_exports.number(),
14078
+ external_exports.boolean(),
14079
+ external_exports.array(StateTransitionInputBindingSchema),
14080
+ external_exports.object({
14081
+ const: external_exports.unknown()
14082
+ }).strict(),
14083
+ external_exports.object({
14084
+ from: external_exports.literal("object"),
14085
+ path: external_exports.string().min(1),
14086
+ editable: external_exports.boolean().optional()
14087
+ }).strict(),
14088
+ external_exports.object({
14089
+ from: external_exports.literal("field"),
14090
+ name: external_exports.string().min(1),
14091
+ editable: external_exports.boolean().optional()
14092
+ }).strict(),
14093
+ external_exports.object({
14094
+ from: external_exports.literal("relationship"),
14095
+ name: external_exports.string().min(1),
14096
+ path: external_exports.string().min(1).optional(),
14097
+ many: external_exports.boolean().optional(),
14098
+ editable: external_exports.boolean().optional()
14099
+ }).strict(),
14100
+ external_exports.object({
14101
+ from: external_exports.literal("session"),
14102
+ path: external_exports.string().min(1),
14103
+ editable: external_exports.boolean().optional()
14104
+ }).strict(),
14105
+ external_exports.object({
14106
+ from: external_exports.literal("actor"),
14107
+ path: external_exports.string().min(1),
14108
+ editable: external_exports.boolean().optional()
14109
+ }).strict(),
14110
+ external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
14111
+ ])
14112
+ );
14113
+ var StateTransitionActionSchema = external_exports.object({
14114
+ effect: external_exports.string().min(1),
14115
+ input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
14116
+ }).strict();
14117
+ var StateTransitionAssigneeSchema = external_exports.object({
14118
+ kind: external_exports.string().min(1),
14119
+ from: StateTransitionInputBindingSchema.optional(),
14120
+ role: external_exports.string().optional(),
14121
+ label: external_exports.string().optional()
14122
+ }).strict();
14123
+ var StateTransitionRelatedStateRequirementSchema = external_exports.object({
14124
+ relationship: external_exports.string().min(1),
14125
+ machine: external_exports.string().min(1),
14126
+ state: external_exports.string().min(1),
14127
+ className: external_exports.string().min(1).optional(),
14128
+ label: external_exports.string().optional(),
14129
+ mode: external_exports.enum(["every", "some", "any"]).optional()
14130
+ }).strict();
14131
+ var StateTransitionRequirementsSchema = external_exports.object({
14132
+ fields: external_exports.array(external_exports.string().min(1)).optional(),
14133
+ relationships: external_exports.array(external_exports.string().min(1)).optional(),
14134
+ relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
14135
+ }).strict();
14136
+ var StateTransitionPermissionSchema = external_exports.union([
14137
+ external_exports.string().min(1),
14138
+ external_exports.object({
14139
+ profile: external_exports.string().min(1).optional(),
14140
+ profileId: external_exports.string().min(1).optional(),
14141
+ label: external_exports.string().optional(),
14142
+ reason: external_exports.string().optional()
14143
+ }).strict()
14144
+ ]);
14145
+ var StateTransitionExpectedOutcomeSchema = external_exports.union([
14146
+ external_exports.string().min(1),
14147
+ external_exports.object({
14148
+ machine: external_exports.string().min(1).optional(),
14149
+ state: external_exports.string().min(1),
14150
+ summary: external_exports.string().optional()
14151
+ }).strict()
14152
+ ]);
14071
14153
  var StateMachineTransitionSchema = external_exports.object({
14072
14154
  name: external_exports.string().min(1),
14073
14155
  from: external_exports.string().min(1),
14074
- to: external_exports.string().min(1)
14156
+ to: external_exports.string().min(1),
14157
+ label: external_exports.string().optional(),
14158
+ description: external_exports.string().optional(),
14159
+ action: StateTransitionActionSchema.optional(),
14160
+ assignee: StateTransitionAssigneeSchema.optional(),
14161
+ requirements: StateTransitionRequirementsSchema.optional(),
14162
+ permission: StateTransitionPermissionSchema.optional(),
14163
+ risk: external_exports.enum(["low", "medium", "high"]).optional(),
14164
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
14075
14165
  }).strict();
14076
14166
  external_exports.object({
14077
14167
  name: external_exports.string().min(1),
14168
+ stateField: external_exports.string().min(1).optional(),
14078
14169
  entryState: external_exports.string().min(1),
14079
14170
  states: external_exports.array(StateMachineStateSchema).min(1),
14080
14171
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -14141,6 +14232,16 @@ var PoliciesSchema = external_exports.object({
14141
14232
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
14142
14233
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
14143
14234
  }).strict();
14235
+ var CreatesSchema = external_exports.union([
14236
+ external_exports.string().min(1),
14237
+ external_exports.object({
14238
+ className: external_exports.string().min(1),
14239
+ idPath: external_exports.string().min(1).optional(),
14240
+ pathPath: external_exports.string().min(1).optional(),
14241
+ statePath: external_exports.string().min(1).optional(),
14242
+ classStateHandle: external_exports.boolean().optional()
14243
+ }).strict()
14244
+ ]);
14144
14245
  external_exports.object({
14145
14246
  postCondition: external_exports.union([
14146
14247
  external_exports.string(),
@@ -14171,6 +14272,7 @@ external_exports.object({
14171
14272
  mode: external_exports.string().optional()
14172
14273
  }).strict()
14173
14274
  ]).optional(),
14275
+ creates: CreatesSchema.optional(),
14174
14276
  access: external_exports.enum(["read", "write", "ui"]).optional(),
14175
14277
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
14176
14278
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -14398,6 +14500,7 @@ function mergeMethodSummaryPatch(target, patch) {
14398
14500
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
14399
14501
  if (patch.effectBehaviors !== void 0)
14400
14502
  target.effectBehaviors = patch.effectBehaviors;
14503
+ if (patch.creates !== void 0) target.creates = patch.creates;
14401
14504
  if (patch.static !== void 0) target.static = patch.static;
14402
14505
  }
14403
14506
  function toPascalCase2(value) {
@@ -14458,29 +14561,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
14458
14561
  }
14459
14562
  return Object.keys(result).length > 0 ? result : null;
14460
14563
  }
14461
- function buildEffectBehaviorDocs(effectBehaviors) {
14462
- if (!effectBehaviors) {
14463
- return [];
14564
+ function normalizeCreationSummary(metamodels) {
14565
+ if (!isObject(metamodels)) return null;
14566
+ let raw = metamodels.creates;
14567
+ if (typeof raw === "string" && raw.trim().length > 0) {
14568
+ const trimmed = raw.trim();
14569
+ if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
14570
+ try {
14571
+ raw = JSON.parse(trimmed);
14572
+ } catch {
14573
+ return { className: trimmed };
14574
+ }
14575
+ } else {
14576
+ return { className: trimmed };
14577
+ }
14578
+ }
14579
+ if (typeof raw === "string" && raw.trim().length > 0) {
14580
+ return { className: raw.trim() };
14464
14581
  }
14582
+ if (!isObject(raw)) return null;
14583
+ const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
14584
+ if (!className) return null;
14585
+ return {
14586
+ className,
14587
+ ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
14588
+ ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
14589
+ ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
14590
+ ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
14591
+ };
14592
+ }
14593
+ function buildEffectBehaviorDocs(effectBehaviors, creates) {
14465
14594
  const docs = [];
14466
- if (effectBehaviors.approvalRequired?.required) {
14595
+ if (creates) {
14596
+ docs.push(
14597
+ `Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
14598
+ );
14599
+ }
14600
+ if (effectBehaviors?.approvalRequired?.required) {
14467
14601
  docs.push(
14468
14602
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
14469
14603
  );
14470
14604
  }
14471
- if (effectBehaviors.postCondition) {
14605
+ if (effectBehaviors?.postCondition) {
14472
14606
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
14473
14607
  if (effectBehaviors.postCondition.description) {
14474
14608
  docs.push(effectBehaviors.postCondition.description);
14475
14609
  }
14476
14610
  }
14477
- if (effectBehaviors.dryRun?.enabled) {
14611
+ if (effectBehaviors?.dryRun?.enabled) {
14478
14612
  docs.push("Supports dry run.");
14479
14613
  if (effectBehaviors.dryRun.description) {
14480
14614
  docs.push(effectBehaviors.dryRun.description);
14481
14615
  }
14482
14616
  }
14483
- if (effectBehaviors.reverse) {
14617
+ if (effectBehaviors?.reverse) {
14484
14618
  if (effectBehaviors.reverse.handler) {
14485
14619
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
14486
14620
  } else {
@@ -14539,13 +14673,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
14539
14673
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
14540
14674
  });
14541
14675
  }
14676
+ if (spec.creates !== void 0) {
14677
+ mutations.push({
14678
+ label: `set creates on ${toolPath}`,
14679
+ query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
14680
+ JSON.stringify(spec.creates)
14681
+ )}) { done } } } }`
14682
+ });
14683
+ }
14542
14684
  return mutations;
14543
14685
  }
14544
14686
  function readMethodEffectBehaviors(rawMethod) {
14687
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
14545
14688
  return {
14546
- effectBehaviors: normalizeEffectBehaviorSummary(
14547
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
14548
- )
14689
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
14690
+ creates: normalizeCreationSummary(metamodels)
14549
14691
  };
14550
14692
  }
14551
14693
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -14567,6 +14709,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
14567
14709
  {
14568
14710
  key: "approvalRequired",
14569
14711
  description: "Boolean or `{ required, reason, mode }`."
14712
+ },
14713
+ {
14714
+ key: "creates",
14715
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
14570
14716
  }
14571
14717
  ]
14572
14718
  },
@@ -14686,7 +14832,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
14686
14832
  ...methodIR,
14687
14833
  docs: [
14688
14834
  ...methodIR.docs,
14689
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
14835
+ ...buildEffectBehaviorDocs(
14836
+ methodSummary.effectBehaviors,
14837
+ methodSummary.creates
14838
+ )
14690
14839
  ]
14691
14840
  };
14692
14841
  }
@@ -15305,15 +15454,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
15305
15454
 
15306
15455
  // ../metamodel-state-machine/src/index.ts
15307
15456
  function normalizeStateMachines(values) {
15457
+ const parseJsonRecord = (value) => {
15458
+ if (value && typeof value === "object" && !Array.isArray(value)) {
15459
+ return value;
15460
+ }
15461
+ if (typeof value !== "string" || !value.trim()) return null;
15462
+ try {
15463
+ const parsed = JSON.parse(value);
15464
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
15465
+ } catch {
15466
+ return null;
15467
+ }
15468
+ };
15469
+ const parseJsonValue = (value) => {
15470
+ if (value === null || typeof value === "undefined") return null;
15471
+ if (typeof value !== "string") return value;
15472
+ if (!value.trim()) return null;
15473
+ try {
15474
+ return JSON.parse(value);
15475
+ } catch {
15476
+ return value;
15477
+ }
15478
+ };
15308
15479
  return (values || []).map((machine) => {
15309
15480
  const states = (machine?.states || []).map((state) => ({
15310
15481
  name: String(state?.name || ""),
15311
- isFinal: Boolean(state?.is_final)
15482
+ label: typeof state?.label === "string" ? state.label : null,
15483
+ description: typeof state?.description === "string" ? state.description : null,
15484
+ isFinal: Boolean(state?.is_final ?? state?.isFinal)
15312
15485
  })).filter((state) => state.name.length > 0);
15313
15486
  const transitions = (machine?.transitions || []).map((transition) => ({
15314
15487
  name: String(transition?.name || ""),
15315
15488
  from: String(transition?.from?.name || ""),
15316
- to: String(transition?.to?.name || "")
15489
+ to: String(transition?.to?.name || ""),
15490
+ label: typeof transition?.label === "string" ? transition.label : null,
15491
+ description: typeof transition?.description === "string" ? transition.description : null,
15492
+ action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
15493
+ assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
15494
+ requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
15495
+ permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
15496
+ risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
15497
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
15317
15498
  })).filter(
15318
15499
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
15319
15500
  );
@@ -15335,6 +15516,15 @@ function transitionTypeName(className, machineName) {
15335
15516
  function pathTypeName(className, machineName) {
15336
15517
  return `${stateTypeName(className, machineName)}Path`;
15337
15518
  }
15519
+ function methodToken(value) {
15520
+ const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
15521
+ return token || "state";
15522
+ }
15523
+ function transitionActionsForMachine(machine) {
15524
+ return Object.fromEntries(
15525
+ (machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
15526
+ );
15527
+ }
15338
15528
  function normalizeStateDefinitions(machine) {
15339
15529
  const finalStates = new Set(machine.finalStates || []);
15340
15530
  const states = /* @__PURE__ */ new Map();
@@ -15348,6 +15538,8 @@ function normalizeStateDefinitions(machine) {
15348
15538
  }
15349
15539
  states.set(rawState.name, {
15350
15540
  name: rawState.name,
15541
+ label: rawState.label,
15542
+ description: rawState.description,
15351
15543
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
15352
15544
  });
15353
15545
  }
@@ -15359,6 +15551,44 @@ function normalizeStateDefinitions(machine) {
15359
15551
  }
15360
15552
  return [...states.values()];
15361
15553
  }
15554
+ function transitionMetadataGraphqlArgs(transition) {
15555
+ const args = [];
15556
+ if (typeof transition.label === "string") {
15557
+ args.push(`label: ${JSON.stringify(transition.label)}`);
15558
+ }
15559
+ if (typeof transition.description === "string") {
15560
+ args.push(`description: ${JSON.stringify(transition.description)}`);
15561
+ }
15562
+ if (transition.action) {
15563
+ args.push(
15564
+ `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
15565
+ );
15566
+ }
15567
+ if (transition.assignee) {
15568
+ args.push(
15569
+ `assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
15570
+ );
15571
+ }
15572
+ if (transition.requirements) {
15573
+ args.push(
15574
+ `requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
15575
+ );
15576
+ }
15577
+ if (transition.permission) {
15578
+ args.push(
15579
+ `permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
15580
+ );
15581
+ }
15582
+ if (transition.risk) {
15583
+ args.push(`risk: ${JSON.stringify(transition.risk)}`);
15584
+ }
15585
+ if (transition.expectedOutcome) {
15586
+ args.push(
15587
+ `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
15588
+ );
15589
+ }
15590
+ return args.length > 0 ? `, ${args.join(", ")}` : "";
15591
+ }
15362
15592
  function buildStateMachineModelMutations(modelPath, machines) {
15363
15593
  const mutations = [];
15364
15594
  for (const machine of machines || []) {
@@ -15369,12 +15599,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
15369
15599
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
15370
15600
  });
15371
15601
  for (const state of normalizeStateDefinitions(machine)) {
15372
- if (state.name === machine.entryState && !state.isFinal) continue;
15602
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
15603
+ continue;
15373
15604
  mutations.push({
15374
15605
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
15375
15606
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
15376
15607
  machine.name
15377
- )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
15608
+ )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
15378
15609
  });
15379
15610
  }
15380
15611
  for (const transition of machine.transitions || []) {
@@ -15386,18 +15617,27 @@ function buildStateMachineModelMutations(modelPath, machines) {
15386
15617
  transition.name
15387
15618
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
15388
15619
  transition.to
15389
- )}) { name } } } }`
15620
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
15390
15621
  });
15391
15622
  }
15392
15623
  }
15393
15624
  return mutations;
15394
15625
  }
15395
15626
  function buildMachineTypes(classSummary, machine) {
15627
+ const stateGlossary = machine.states.map((state) => {
15628
+ const label = state.label && state.label !== state.name ? state.label : null;
15629
+ const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
15630
+ const finalMarker = state.isFinal ? " Final state." : "";
15631
+ return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
15632
+ });
15396
15633
  return [
15397
15634
  {
15398
15635
  kind: "union",
15399
15636
  name: stateTypeName(classSummary.name, machine.name),
15400
- docs: [`Allowed states for ${classSummary.name}.${machine.name}.`],
15637
+ docs: [
15638
+ `Allowed states for ${classSummary.name}.${machine.name}.`,
15639
+ ...stateGlossary
15640
+ ],
15401
15641
  members: machine.states.map((state) => state.name)
15402
15642
  },
15403
15643
  {
@@ -15413,7 +15653,7 @@ function buildMachineMethods(classSummary, machine) {
15413
15653
  const transitionName = transitionTypeName(classSummary.name, machine.name);
15414
15654
  pathTypeName(classSummary.name, machine.name);
15415
15655
  const docsPrefix = `${classSummary.name}.${machine.name}`;
15416
- return [
15656
+ const methods = [
15417
15657
  {
15418
15658
  name: `get_${machine.name}`,
15419
15659
  docs: [`Get the current ${docsPrefix} state.`],
@@ -15509,6 +15749,99 @@ function buildMachineMethods(classSummary, machine) {
15509
15749
  }
15510
15750
  }
15511
15751
  ];
15752
+ const creationMethods = (classSummary.methods || []).filter(
15753
+ (method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
15754
+ );
15755
+ for (const state of machine.states) {
15756
+ const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
15757
+ if (!stateNameValue) continue;
15758
+ const token = methodToken(stateNameValue);
15759
+ methods.push(
15760
+ {
15761
+ name: `reach_${machine.name}_to_${token}`,
15762
+ docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
15763
+ static: false,
15764
+ params: [],
15765
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
15766
+ runtime: {
15767
+ kind: "state_machine",
15768
+ machineName: machine.name,
15769
+ className: classSummary.name,
15770
+ stateTypeName: stateName,
15771
+ transitionTypeName: transitionName,
15772
+ operation: "reach",
15773
+ targetState: stateNameValue,
15774
+ transitionActions: transitionActionsForMachine(machine)
15775
+ }
15776
+ },
15777
+ {
15778
+ name: `prepare_${machine.name}_to_${token}`,
15779
+ docs: [
15780
+ `Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
15781
+ ],
15782
+ static: false,
15783
+ params: [],
15784
+ returnType: "Promise<SessionArtifactRecord>",
15785
+ runtime: {
15786
+ kind: "state_machine",
15787
+ machineName: machine.name,
15788
+ className: classSummary.name,
15789
+ stateTypeName: stateName,
15790
+ transitionTypeName: transitionName,
15791
+ operation: "prepare_reach",
15792
+ targetState: stateNameValue,
15793
+ transitionActions: transitionActionsForMachine(machine)
15794
+ }
15795
+ }
15796
+ );
15797
+ for (const creationMethod of creationMethods) {
15798
+ const creationRuntime = {
15799
+ kind: "state_machine",
15800
+ machineName: machine.name,
15801
+ className: classSummary.name,
15802
+ stateTypeName: stateName,
15803
+ transitionTypeName: transitionName,
15804
+ operation: "prepare_create_reach",
15805
+ targetState: stateNameValue,
15806
+ transitionActions: transitionActionsForMachine(machine),
15807
+ creation: {
15808
+ methodName: creationMethod.name,
15809
+ effectKey: creationMethod.effectKey || creationMethod.name,
15810
+ inputSchema: creationMethod.inputSchema,
15811
+ outputSchema: creationMethod.outputSchema,
15812
+ creates: creationMethod.creates
15813
+ }
15814
+ };
15815
+ const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
15816
+ methods.push({
15817
+ name: viaName,
15818
+ docs: [
15819
+ `Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
15820
+ ],
15821
+ static: true,
15822
+ params: [
15823
+ { name: "input", type: "Record<string, any>", optional: true }
15824
+ ],
15825
+ returnType: "Promise<SessionArtifactRecord>",
15826
+ runtime: creationRuntime
15827
+ });
15828
+ if (creationMethods.length === 1) {
15829
+ methods.push({
15830
+ name: `prepare_${machine.name}_to_${token}`,
15831
+ docs: [
15832
+ `Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
15833
+ ],
15834
+ static: true,
15835
+ params: [
15836
+ { name: "input", type: "Record<string, any>", optional: true }
15837
+ ],
15838
+ returnType: "Promise<SessionArtifactRecord>",
15839
+ runtime: creationRuntime
15840
+ });
15841
+ }
15842
+ }
15843
+ }
15844
+ return methods;
15512
15845
  }
15513
15846
  function readStateMachineSummaries(rawClass) {
15514
15847
  return {
@@ -15531,8 +15864,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15531
15864
  type StateMachineMutation {
15532
15865
  name: String!
15533
15866
  state_machine: StateMachine!
15534
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
15535
- add_transition(name: String!, from: String!, to: String!): StateMachineMutation!
15867
+ add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
15868
+ 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!
15536
15869
  activate_transition(name: String!): StateMachineMutation!
15537
15870
  }
15538
15871
 
@@ -15549,6 +15882,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15549
15882
  type StateMachineSnapshotMutation {
15550
15883
  snapshot: StateMachineSnapshot!
15551
15884
  activate_transition(name: String!): StateMachineSnapshotMutation!
15885
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
15552
15886
  }
15553
15887
 
15554
15888
  type StateMachine {
@@ -15568,6 +15902,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15568
15902
 
15569
15903
  type StateMachineState {
15570
15904
  name: String!
15905
+ label: String
15906
+ description: String
15571
15907
  is_final: Boolean!
15572
15908
  }
15573
15909
 
@@ -15575,6 +15911,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15575
15911
  name: String!
15576
15912
  from: StateMachineState!
15577
15913
  to: StateMachineState!
15914
+ label: String
15915
+ description: String
15916
+ action_json: String
15917
+ assignee_json: String
15918
+ requirements_json: String
15919
+ permission_json: String
15920
+ risk: String
15921
+ expected_outcome_json: String
15578
15922
  }
15579
15923
 
15580
15924
  type StateMachinePath {
@@ -15627,23 +15971,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15627
15971
  StateMachineMutation: {
15628
15972
  name: (value) => value.name,
15629
15973
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
15630
- add_state: async (value, { name, is_final }) => {
15974
+ add_state: async (value, { name, is_final, label, description }) => {
15631
15975
  await run(
15632
15976
  value.target.add_state_machine_state(
15633
15977
  value.name,
15634
15978
  name,
15635
- is_final ?? false
15979
+ is_final ?? false,
15980
+ label,
15981
+ description
15636
15982
  )
15637
15983
  );
15638
15984
  return value;
15639
15985
  },
15640
- add_transition: async (value, { name, from, to }) => {
15986
+ add_transition: async (value, {
15987
+ name,
15988
+ from: from2,
15989
+ to,
15990
+ label,
15991
+ description,
15992
+ action_json,
15993
+ assignee_json,
15994
+ requirements_json,
15995
+ permission_json,
15996
+ risk,
15997
+ expected_outcome_json
15998
+ }) => {
15641
15999
  await run(
15642
16000
  value.target.add_state_machine_transition(
15643
16001
  value.name,
15644
16002
  name,
15645
- from,
15646
- to
16003
+ from2,
16004
+ to,
16005
+ {
16006
+ label,
16007
+ description,
16008
+ actionJson: action_json,
16009
+ assigneeJson: assignee_json,
16010
+ requirementsJson: requirements_json,
16011
+ permissionJson: permission_json,
16012
+ risk,
16013
+ expectedOutcomeJson: expected_outcome_json
16014
+ }
15647
16015
  )
15648
16016
  );
15649
16017
  return value;
@@ -15662,16 +16030,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15662
16030
  value.target.activate_state_machine_transition(value.name, name)
15663
16031
  );
15664
16032
  return value;
16033
+ },
16034
+ observe_state: async (value, { state, force, source }) => {
16035
+ await run(
16036
+ value.target.observe_state_machine_state(
16037
+ value.name,
16038
+ state,
16039
+ force === true,
16040
+ source
16041
+ )
16042
+ );
16043
+ return value;
15665
16044
  }
15666
16045
  },
15667
16046
  StateMachineState: {
15668
16047
  name: (value) => value.name,
16048
+ label: (value) => value.label || null,
16049
+ description: (value) => value.description || null,
15669
16050
  is_final: (value) => value.is_final
15670
16051
  },
15671
16052
  StateMachineTransition: {
15672
16053
  name: (value) => value.name,
15673
16054
  from: (value) => value.from_state || { name: value.from, is_final: false },
15674
- to: (value) => value.to_state || { name: value.to, is_final: false }
16055
+ to: (value) => value.to_state || { name: value.to, is_final: false },
16056
+ label: (value) => value.label || null,
16057
+ description: (value) => value.description || null,
16058
+ action_json: (value) => value.action_json || null,
16059
+ assignee_json: (value) => value.assignee_json || null,
16060
+ requirements_json: (value) => value.requirements_json || null,
16061
+ permission_json: (value) => value.permission_json || null,
16062
+ risk: (value) => value.risk || null,
16063
+ expected_outcome_json: (value) => value.expected_outcome_json || null
15675
16064
  },
15676
16065
  StateMachinePath: {
15677
16066
  states: (value) => value.states,
@@ -15738,6 +16127,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15738
16127
  name
15739
16128
  from { name }
15740
16129
  to { name }
16130
+ label
16131
+ description
16132
+ action_json
16133
+ assignee_json
16134
+ requirements_json
16135
+ permission_json
16136
+ risk
16137
+ expected_outcome_json
15741
16138
  }
15742
16139
  }`
15743
16140
  ]
@@ -16426,7 +16823,7 @@ ${effectMetamodelTable}
16426
16823
  | \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImport\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge, returning an \`importId\` is enough up front, and background processing is acceptable. |
16427
16824
  | \`graphql(query, variables?)\` | **GraphQL** \u2014 see dedicated subsection below. |
16428
16825
  | \`defineRelationship\`, \`getRelationships\`, \`attach\`, \`detach\`, \`listRelated\` | Imperative relationship operations (same ideas as manifest \`defineRelationship\`). |
16429
- | \`sessions.list()\`, \`sessions.create()\`, \`sessions.connect()\`, \`sessions.reopen()\`, \`sessions.close()\` | Session lifecycle for this environment. |
16826
+ | \`sessions.list({ status?, sessionScope?, limit?, offset? })\`, \`sessions.create({ sessionScope? })\`, \`sessions.connect()\`, \`sessions.reopen()\`, \`sessions.close()\` | Scoped, bounded session history and lifecycle for this environment. |
16430
16827
 
16431
16828
  ### \`Session\` (live runtime connection)
16432
16829
 
@@ -16474,7 +16871,7 @@ Use \`environment.graphql(query, variables?)\` when you need **query/mutation ac
16474
16871
  | \`granular simulate\` | Open simulator in browser. |
16475
16872
  | \`granular simulate --print-url\` | Print a deep-linkable simulator URL without opening the browser. |
16476
16873
  | \`granular connect test --json\` | Verify auth and environment connectivity with a real session. |
16477
- | \`granular session create/list/heap/doc --json\` | Create, enumerate, and inspect real session state. |
16874
+ | \`granular session create/list/heap/doc --json\` | Create, enumerate, and inspect real session state. \`session list\` defaults to 25 rows; use \`--session-scope\`, \`--subject-id\`, \`--limit\`, and \`--offset\` for deterministic paging. |
16478
16875
  | \`granular graphql --query '...' --json\` | Run a GraphQL query or mutation against the live graph. |
16479
16876
  | \`granular effects list/diff --json\` | Inspect declared versus live ready effects. |
16480
16877
  | \`granular job run --file ./job.ts --json\` | Execute a real runtime job from the terminal or CI. |
@@ -17154,8 +17551,8 @@ Use this for command execution, environment setup, and shipping flows.
17154
17551
  | \`granular graphql --query '...' --json\` | Query the live graph through the environment GraphQL API |
17155
17552
  | \`granular effects list --json\` | Inspect declared and live effects for an environment |
17156
17553
  | \`granular effects diff --json\` | Compare declared effects to live ready handlers |
17157
- | \`granular session create --json\` | Create a fresh session for an environment |
17158
- | \`granular session list --json\` | List indexed sessions for an environment |
17554
+ | \`granular session create --session-scope <scope> --json\` | Create a fresh, application-scoped session for an environment |
17555
+ | \`granular session list --session-scope <scope> --limit 25 --offset 0 --json\` | List one bounded page of indexed sessions |
17159
17556
  | \`granular session heap --json\` | Inspect session heap |
17160
17557
  | \`granular session doc --json\` | Inspect the Automerge-backed session document |
17161
17558
  | \`granular job run --file ./job.ts\` | Execute a real job against the ontology runtime |
@@ -17238,8 +17635,8 @@ Use this for runtime debugging after the ontology builds but behavior does not m
17238
17635
 
17239
17636
  | Goal | Preferred path |
17240
17637
  | --- | --- |
17241
- | Create or rotate a session | \`granular session create --json\` |
17242
- | List known sessions | \`granular session list --json\` |
17638
+ | Create or rotate a session | \`granular session create --session-scope <scope> --json\` |
17639
+ | List known sessions | \`granular session list --session-scope <scope> --limit 25 --offset 0 --json\` |
17243
17640
  | Inspect heap | \`granular session heap --json\` |
17244
17641
  | Inspect full document | \`granular session doc --json\` |
17245
17642
  | Verify connectivity | \`granular connect test\` |
@@ -19145,6 +19542,9 @@ function rpcTimeoutMsForMethod(method) {
19145
19542
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
19146
19543
  case "client.heartbeat":
19147
19544
  case "effects.publishCatalog":
19545
+ case "effects.resetCatalog":
19546
+ case "effects.addCatalog":
19547
+ case "effects.removeCatalog":
19148
19548
  case "effects.refresh":
19149
19549
  return EFFECT_CONTROL_RPC_TIMEOUT_MS;
19150
19550
  case "harness.run":
@@ -19169,16 +19569,37 @@ var WSClient = class {
19169
19569
  tokenRefreshTimer = null;
19170
19570
  isExplicitlyDisconnected = false;
19171
19571
  reconnectAttempts = 0;
19572
+ connectPromise = null;
19573
+ connectionEpoch = 0;
19574
+ cancelConnectAttempt = null;
19172
19575
  options;
19173
19576
  constructor(options) {
19174
19577
  this.options = options;
19175
19578
  this.url = options.url;
19176
19579
  this.sessionId = options.sessionId;
19177
19580
  this.token = options.token;
19581
+ if (options.initialDocumentSnapshot) {
19582
+ this.seedDocumentSnapshot(options.initialDocumentSnapshot);
19583
+ }
19178
19584
  }
19179
19585
  get currentSessionId() {
19180
19586
  return this.sessionId;
19181
19587
  }
19588
+ seedDocumentSnapshot(document) {
19589
+ if (!document || typeof document !== "object" || Array.isArray(document)) {
19590
+ return;
19591
+ }
19592
+ try {
19593
+ this.doc = document instanceof Uint8Array ? Automerge__namespace.load(document) : Automerge__namespace.from(document);
19594
+ this.syncState = Automerge__namespace.initSyncState();
19595
+ this.emit("sync", this.doc);
19596
+ } catch (error2) {
19597
+ console.warn("[Granular] Failed to seed cached session document", error2);
19598
+ }
19599
+ }
19600
+ saveDocumentSnapshot() {
19601
+ return Automerge__namespace.save(this.doc);
19602
+ }
19182
19603
  clearTokenRefreshTimer() {
19183
19604
  if (this.tokenRefreshTimer) {
19184
19605
  clearTimeout(this.tokenRefreshTimer);
@@ -19288,8 +19709,23 @@ var WSClient = class {
19288
19709
  * Connect to the WebSocket server
19289
19710
  * @returns {Promise<void>} Resolves when connection is open
19290
19711
  */
19291
- async connect() {
19712
+ async connect(options = {}) {
19713
+ if (this.ws?.readyState === READY_STATE_OPEN) return;
19714
+ if (this.connectPromise) return this.connectPromise;
19715
+ const connectPromise = this.connectAttempt(options.signal);
19716
+ this.connectPromise = connectPromise;
19717
+ try {
19718
+ await connectPromise;
19719
+ } finally {
19720
+ if (this.connectPromise === connectPromise) {
19721
+ this.connectPromise = null;
19722
+ }
19723
+ }
19724
+ }
19725
+ async connectAttempt(signal) {
19726
+ if (signal?.aborted) throw new Error("WebSocket connect aborted");
19292
19727
  const token = await this.resolveTokenForConnect();
19728
+ if (signal?.aborted) throw new Error("WebSocket connect aborted");
19293
19729
  this.isExplicitlyDisconnected = false;
19294
19730
  this.scheduleTokenRefresh();
19295
19731
  if (this.reconnectTimer) {
@@ -19301,7 +19737,7 @@ var WSClient = class {
19301
19737
  try {
19302
19738
  const wsModule = await import('ws');
19303
19739
  WebSocketClass = wsModule.default || wsModule;
19304
- } catch (e) {
19740
+ } catch {
19305
19741
  }
19306
19742
  }
19307
19743
  if (!WebSocketClass) {
@@ -19309,83 +19745,97 @@ var WSClient = class {
19309
19745
  'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
19310
19746
  );
19311
19747
  }
19748
+ const epoch = ++this.connectionEpoch;
19749
+ const wsUrl = new URL(this.url);
19750
+ wsUrl.searchParams.set("sessionId", this.sessionId);
19751
+ wsUrl.searchParams.set("token", token);
19752
+ const socket = new WebSocketClass(wsUrl.toString());
19753
+ this.ws = socket;
19312
19754
  return new Promise((resolve2, reject) => {
19313
- try {
19314
- const wsUrl = new URL(this.url);
19315
- wsUrl.searchParams.set("sessionId", this.sessionId);
19316
- wsUrl.searchParams.set("token", token);
19317
- this.ws = new WebSocketClass(wsUrl.toString());
19318
- if (!this.ws) throw new Error("Failed to create WebSocket");
19319
- const socket = this.ws;
19320
- if (typeof socket.on === "function") {
19321
- socket.on("open", () => {
19322
- if (this.reconnectTimer) {
19323
- clearTimeout(this.reconnectTimer);
19324
- this.reconnectTimer = null;
19325
- }
19326
- this.reconnectAttempts = 0;
19327
- this.emit("open", {});
19328
- resolve2();
19329
- });
19330
- socket.on("message", (data) => {
19331
- try {
19332
- const message = JSON.parse(data.toString());
19333
- this.handleMessage(message);
19334
- } catch (error2) {
19335
- console.error("[Granular] Failed to parse message:", error2);
19336
- }
19337
- });
19338
- socket.on("error", (error2) => {
19339
- this.emit("error", error2);
19340
- if (socket.readyState !== READY_STATE_OPEN) {
19341
- reject(error2);
19342
- }
19343
- });
19344
- socket.on("close", (code, reason) => {
19345
- this.handleDisconnect({
19346
- code,
19347
- reason: this.normalizeReason(reason),
19348
- // ws does not provide wasClean on Node-style close callback
19349
- wasClean: code === 1e3
19350
- });
19351
- });
19755
+ let settled = false;
19756
+ const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
19757
+ const finish = (error2) => {
19758
+ if (settled) return;
19759
+ settled = true;
19760
+ if (this.cancelConnectAttempt === handleAbort) {
19761
+ this.cancelConnectAttempt = null;
19762
+ }
19763
+ signal?.removeEventListener("abort", handleAbort);
19764
+ if (error2) {
19765
+ reject(error2 instanceof Error ? error2 : new Error(String(error2)));
19352
19766
  } else {
19353
- this.ws.onopen = () => {
19354
- if (this.reconnectTimer) {
19355
- clearTimeout(this.reconnectTimer);
19356
- this.reconnectTimer = null;
19357
- }
19358
- this.reconnectAttempts = 0;
19359
- this.emit("open", {});
19360
- resolve2();
19361
- };
19362
- this.ws.onmessage = (event) => {
19363
- try {
19364
- const data = event.data;
19365
- const message = JSON.parse(data.toString());
19366
- this.handleMessage(message);
19367
- } catch (error2) {
19368
- console.error("[Granular] Failed to parse message:", error2);
19369
- }
19370
- };
19371
- this.ws.onerror = (event) => {
19372
- const error2 = new Error("WebSocket error");
19373
- error2.event = event;
19374
- this.emit("error", error2);
19375
- if (this.ws?.readyState !== READY_STATE_OPEN) {
19376
- reject(error2);
19377
- }
19378
- };
19379
- this.ws.onclose = (event) => {
19380
- this.handleDisconnect({
19381
- code: event.code,
19382
- reason: event.reason,
19383
- wasClean: event.wasClean
19384
- });
19385
- };
19767
+ resolve2();
19386
19768
  }
19387
- } catch (error2) {
19388
- reject(error2);
19769
+ };
19770
+ const closeStaleSocket = () => {
19771
+ try {
19772
+ socket.close(1e3, "Stale connection attempt");
19773
+ } catch {
19774
+ }
19775
+ };
19776
+ const handleAbort = () => {
19777
+ if (isCurrent()) {
19778
+ this.connectionEpoch += 1;
19779
+ this.ws = null;
19780
+ }
19781
+ closeStaleSocket();
19782
+ finish(new Error("WebSocket connect aborted"));
19783
+ };
19784
+ this.cancelConnectAttempt = handleAbort;
19785
+ const handleOpen = () => {
19786
+ if (!isCurrent()) {
19787
+ closeStaleSocket();
19788
+ return;
19789
+ }
19790
+ this.reconnectAttempts = 0;
19791
+ this.emit("open", {});
19792
+ finish();
19793
+ };
19794
+ const handleMessage = (data) => {
19795
+ if (!isCurrent()) return;
19796
+ try {
19797
+ const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
19798
+ this.handleMessage(JSON.parse(text));
19799
+ } catch (error2) {
19800
+ console.error("[Granular] Failed to parse message:", error2);
19801
+ }
19802
+ };
19803
+ const handleError = (error2) => {
19804
+ if (!isCurrent()) return;
19805
+ const typedError = error2 instanceof Error ? error2 : new Error("WebSocket error");
19806
+ this.emit("error", typedError);
19807
+ if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
19808
+ };
19809
+ const handleClose = (close) => {
19810
+ if (!isCurrent()) return;
19811
+ if (!settled) {
19812
+ finish(
19813
+ new Error(
19814
+ `WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
19815
+ )
19816
+ );
19817
+ }
19818
+ this.handleDisconnect({
19819
+ code: close.code,
19820
+ reason: this.normalizeReason(close.reason),
19821
+ wasClean: close.wasClean
19822
+ });
19823
+ };
19824
+ signal?.addEventListener("abort", handleAbort, { once: true });
19825
+ const nodeSocket = socket;
19826
+ if (typeof nodeSocket.on === "function") {
19827
+ nodeSocket.on("open", handleOpen);
19828
+ nodeSocket.on("message", handleMessage);
19829
+ nodeSocket.on("error", handleError);
19830
+ nodeSocket.on(
19831
+ "close",
19832
+ (code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
19833
+ );
19834
+ } else {
19835
+ socket.onopen = handleOpen;
19836
+ socket.onmessage = (event) => handleMessage(event.data);
19837
+ socket.onerror = handleError;
19838
+ socket.onclose = (event) => handleClose(event);
19389
19839
  }
19390
19840
  });
19391
19841
  }
@@ -19403,9 +19853,58 @@ var WSClient = class {
19403
19853
  return void 0;
19404
19854
  }
19405
19855
  rejectPending(error2) {
19406
- this.messageQueue.forEach((pending) => pending.reject(error2));
19856
+ this.messageQueue.forEach((pending) => {
19857
+ clearTimeout(pending.timeout);
19858
+ pending.reject(error2);
19859
+ });
19407
19860
  this.messageQueue = [];
19408
19861
  }
19862
+ emitReconnectErrorMessage(error2) {
19863
+ const reconnectInfo = {
19864
+ error: error2,
19865
+ sessionId: this.sessionId,
19866
+ timestamp: Date.now()
19867
+ };
19868
+ this.emit("reconnect_error", reconnectInfo);
19869
+ if (this.options.onReconnectError) {
19870
+ try {
19871
+ this.options.onReconnectError(reconnectInfo);
19872
+ } catch (callbackError) {
19873
+ console.error(
19874
+ "[Granular] onReconnectError callback failed:",
19875
+ callbackError
19876
+ );
19877
+ }
19878
+ }
19879
+ }
19880
+ scheduleReconnectAttempt() {
19881
+ if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
19882
+ const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
19883
+ const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
19884
+ if (this.reconnectAttempts >= maxReconnectAttempts) {
19885
+ this.emitReconnectErrorMessage(
19886
+ `WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
19887
+ );
19888
+ return null;
19889
+ }
19890
+ this.reconnectAttempts += 1;
19891
+ const reconnectDelayMs = Math.min(
19892
+ 3e4,
19893
+ baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
19894
+ );
19895
+ this.reconnectTimer = setTimeout(() => {
19896
+ this.reconnectTimer = null;
19897
+ console.log("[Granular] Attempting reconnect...");
19898
+ this.connect().catch((error2) => {
19899
+ console.error("[Granular] Reconnect failed:", error2);
19900
+ this.emitReconnectErrorMessage(
19901
+ error2 instanceof Error ? error2.message : String(error2)
19902
+ );
19903
+ this.scheduleReconnectAttempt();
19904
+ });
19905
+ }, reconnectDelayMs);
19906
+ return reconnectDelayMs;
19907
+ }
19409
19908
  buildDisconnectError(info2) {
19410
19909
  const details = [
19411
19910
  info2.code !== void 0 ? `code=${info2.code}` : void 0,
@@ -19415,8 +19914,6 @@ var WSClient = class {
19415
19914
  return new Error(`WebSocket disconnected${suffix}`);
19416
19915
  }
19417
19916
  handleDisconnect(close = {}) {
19418
- const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
19419
- 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;
19420
19917
  const unexpected = !this.isExplicitlyDisconnected;
19421
19918
  const info2 = {
19422
19919
  code: close.code,
@@ -19436,32 +19933,9 @@ var WSClient = class {
19436
19933
  const disconnectError = this.buildDisconnectError(info2);
19437
19934
  this.rejectPending(disconnectError);
19438
19935
  this.emit("disconnect", info2);
19439
- if (this.reconnectAttempts >= maxReconnectAttempts) {
19440
- const reconnectInfo = {
19441
- error: `WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`,
19442
- sessionId: this.sessionId,
19443
- timestamp: Date.now()
19444
- };
19445
- this.emit("reconnect_error", reconnectInfo);
19446
- if (this.options.onReconnectError) {
19447
- try {
19448
- this.options.onReconnectError(reconnectInfo);
19449
- } catch (callbackError) {
19450
- console.error(
19451
- "[Granular] onReconnectError callback failed:",
19452
- callbackError
19453
- );
19454
- }
19455
- }
19456
- return;
19457
- }
19458
- this.reconnectAttempts += 1;
19459
- const reconnectDelayMs = Math.min(
19460
- 3e4,
19461
- baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
19462
- );
19463
- info2.reconnectScheduled = true;
19464
- info2.reconnectDelayMs = reconnectDelayMs;
19936
+ const reconnectDelayMs = this.scheduleReconnectAttempt();
19937
+ info2.reconnectScheduled = reconnectDelayMs !== null;
19938
+ if (reconnectDelayMs !== null) info2.reconnectDelayMs = reconnectDelayMs;
19465
19939
  if (this.options.onUnexpectedClose) {
19466
19940
  try {
19467
19941
  this.options.onUnexpectedClose(info2);
@@ -19472,28 +19946,6 @@ var WSClient = class {
19472
19946
  );
19473
19947
  }
19474
19948
  }
19475
- this.reconnectTimer = setTimeout(() => {
19476
- console.log("[Granular] Attempting reconnect...");
19477
- this.connect().catch((error2) => {
19478
- console.error("[Granular] Reconnect failed:", error2);
19479
- const reconnectInfo = {
19480
- error: error2 instanceof Error ? error2.message : String(error2),
19481
- sessionId: this.sessionId,
19482
- timestamp: Date.now()
19483
- };
19484
- this.emit("reconnect_error", reconnectInfo);
19485
- if (this.options.onReconnectError) {
19486
- try {
19487
- this.options.onReconnectError(reconnectInfo);
19488
- } catch (callbackError) {
19489
- console.error(
19490
- "[Granular] onReconnectError callback failed:",
19491
- callbackError
19492
- );
19493
- }
19494
- }
19495
- });
19496
- }, reconnectDelayMs);
19497
19949
  }
19498
19950
  }
19499
19951
  handleMessage(message) {
@@ -19604,6 +20056,7 @@ var WSClient = class {
19604
20056
  const response = message;
19605
20057
  const pending = this.messageQueue.find((q) => q.id === response.id);
19606
20058
  if (pending) {
20059
+ clearTimeout(pending.timeout);
19607
20060
  if (response.type === "rpc_error") {
19608
20061
  pending.reject(
19609
20062
  new Error(
@@ -19649,16 +20102,22 @@ var WSClient = class {
19649
20102
  id
19650
20103
  };
19651
20104
  return new Promise((resolve2, reject) => {
19652
- this.messageQueue.push({ resolve: resolve2, reject, id });
19653
- this.ws.send(JSON.stringify(request));
19654
20105
  const timeoutMs = rpcTimeoutMsForMethod(method);
19655
- setTimeout(() => {
20106
+ const timeout = setTimeout(() => {
19656
20107
  const pending = this.messageQueue.find((q) => q.id === id);
19657
20108
  if (pending) {
19658
20109
  this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
19659
20110
  reject(new Error(`RPC timeout: ${method}`));
19660
20111
  }
19661
20112
  }, timeoutMs);
20113
+ this.messageQueue.push({ resolve: resolve2, reject, id, timeout });
20114
+ try {
20115
+ this.ws.send(JSON.stringify(request));
20116
+ } catch (error2) {
20117
+ clearTimeout(timeout);
20118
+ this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
20119
+ reject(error2 instanceof Error ? error2 : new Error(String(error2)));
20120
+ }
19662
20121
  });
19663
20122
  }
19664
20123
  async handleIncomingRpc(request) {
@@ -19744,15 +20203,18 @@ var WSClient = class {
19744
20203
  /**
19745
20204
  * Disconnect the WebSocket and clear state
19746
20205
  */
19747
- disconnect() {
20206
+ disconnect(options = {}) {
19748
20207
  this.isExplicitlyDisconnected = true;
20208
+ this.cancelConnectAttempt?.();
20209
+ this.cancelConnectAttempt = null;
20210
+ this.connectionEpoch += 1;
19749
20211
  if (this.reconnectTimer) {
19750
20212
  clearTimeout(this.reconnectTimer);
19751
20213
  this.reconnectTimer = null;
19752
20214
  }
19753
20215
  this.clearTokenRefreshTimer();
19754
20216
  if (this.ws) {
19755
- this.ws.close(1e3, "Client disconnect");
20217
+ this.ws.close(1e3, options.reason || "Client disconnect");
19756
20218
  this.ws = null;
19757
20219
  }
19758
20220
  this.rejectPending(new Error("Client explicitly disconnected"));
@@ -19848,8 +20310,12 @@ function normalizePrompt(rawValue) {
19848
20310
  const source = promptRecord || raw;
19849
20311
  const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
19850
20312
  if (!id) return null;
20313
+ const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
20314
+ const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
19851
20315
  return {
19852
20316
  id,
20317
+ ...jobId ? { jobId } : {},
20318
+ ...turnId ? { turnId } : {},
19853
20319
  type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
19854
20320
  title: typeof source.title === "string" ? source.title : "Input required",
19855
20321
  message: typeof source.message === "string" ? source.message : "",
@@ -19890,6 +20356,9 @@ function resolvePromptAnswer(prompt3, answer) {
19890
20356
 
19891
20357
  // src/session.ts
19892
20358
  var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
20359
+ function toPascalCase3(value) {
20360
+ return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
20361
+ }
19893
20362
  function withPromptTranscriptTimeout(promise) {
19894
20363
  let timeout = null;
19895
20364
  return Promise.race([
@@ -19933,6 +20402,9 @@ var Session = class {
19933
20402
  this.initialQuota = options.initialQuota || null;
19934
20403
  this.setupEventHandlers();
19935
20404
  this.setupToolInvokeHandler();
20405
+ this.currentDomainRevision = this.extractDomainRevisionFromDoc(
20406
+ this.client.doc
20407
+ );
19936
20408
  }
19937
20409
  extractDomainRevisionFromDoc(doc) {
19938
20410
  const domain = doc?.domain;
@@ -20452,9 +20924,7 @@ var Session = class {
20452
20924
  if (classes && Object.keys(classes).length > 0) {
20453
20925
  let docs2 = "# Domain Documentation\n\n";
20454
20926
  docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
20455
- const classNames = Object.keys(classes).map(
20456
- (c) => c.charAt(0).toUpperCase() + c.slice(1)
20457
- );
20927
+ const classNames = Object.keys(classes).map(toPascalCase3);
20458
20928
  const globalNames = (globalTools || []).map((t) => t.name);
20459
20929
  const importLines = [
20460
20930
  ...classNames.map(
@@ -20468,7 +20938,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
20468
20938
 
20469
20939
  `;
20470
20940
  for (const [className, cls] of Object.entries(classes)) {
20471
- const TsName = className.charAt(0).toUpperCase() + className.slice(1);
20941
+ const TsName = toPascalCase3(className);
20472
20942
  docs2 += `## ${TsName}
20473
20943
 
20474
20944
  `;
@@ -20840,6 +21310,7 @@ function normalizeJobAgentMessageEnvelope(data) {
20840
21310
  }
20841
21311
  return {
20842
21312
  jobId: d.jobId,
21313
+ ...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
20843
21314
  message: {
20844
21315
  messageId: d.messageId,
20845
21316
  kind: d.kind === "artifacts" ? "artifacts" : "text",
@@ -21458,6 +21929,28 @@ function asString(value) {
21458
21929
  function trimString(value) {
21459
21930
  return typeof value === "string" ? value.trim() : "";
21460
21931
  }
21932
+ function compactJson(value, maxLength = 320) {
21933
+ if (value === void 0 || value === null) return void 0;
21934
+ try {
21935
+ const json = JSON.stringify(value);
21936
+ if (!json || json === "undefined") return void 0;
21937
+ return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
21938
+ } catch {
21939
+ return String(value);
21940
+ }
21941
+ }
21942
+ function artifactRecordsById(liveDoc) {
21943
+ const artifacts = asRecord3(liveDoc?.artifacts);
21944
+ const byId = asRecord3(artifacts?.byId) || {};
21945
+ return Object.fromEntries(
21946
+ Object.entries(byId).map(([artifactId, value]) => {
21947
+ const record = asRecord3(value);
21948
+ return record ? [artifactId, record] : null;
21949
+ }).filter(
21950
+ (entry) => Boolean(entry)
21951
+ )
21952
+ );
21953
+ }
21461
21954
  function normalizeShowRefs(value) {
21462
21955
  const record = asRecord3(value);
21463
21956
  if (!record) return void 0;
@@ -21474,9 +21967,106 @@ function normalizeShowRefs(value) {
21474
21967
  entryPaths: normalizeRefs(record.entryPaths),
21475
21968
  listNames: normalizeRefs(record.listNames),
21476
21969
  variableNames: normalizeRefs(record.variableNames),
21477
- fileIds: normalizeRefs(record.fileIds)
21970
+ fileIds: normalizeRefs(record.fileIds),
21971
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
21972
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
21973
+ tables: Array.isArray(record.tables) ? record.tables.filter(
21974
+ (table2) => Boolean(
21975
+ table2 && typeof table2 === "object" && !Array.isArray(table2) && Array.isArray(table2.columns) && Array.isArray(table2.rows)
21976
+ )
21977
+ ) : void 0
21478
21978
  };
21479
- return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
21979
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
21980
+ }
21981
+ function normalizeActionSuggestions(value) {
21982
+ if (!Array.isArray(value)) return void 0;
21983
+ const suggestions = [];
21984
+ for (const item of value) {
21985
+ const record = asRecord3(item);
21986
+ if (!record) continue;
21987
+ const label = trimString(record.label);
21988
+ if (!label) continue;
21989
+ const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
21990
+ suggestions.push({
21991
+ suggestionId,
21992
+ label,
21993
+ ...typeof record.description === "string" ? { description: record.description } : {},
21994
+ ...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
21995
+ ...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
21996
+ ...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
21997
+ });
21998
+ }
21999
+ return suggestions.length ? suggestions : void 0;
22000
+ }
22001
+ var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
22002
+ var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
22003
+ var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
22004
+ var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
22005
+ function normalizeConversationMessageActions(value) {
22006
+ if (!Array.isArray(value) || value.length === 0) return void 0;
22007
+ const actions = [];
22008
+ for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
22009
+ const record = asRecord3(item);
22010
+ const kind = record?.kind;
22011
+ const label = trimString(record?.label ?? record?.title);
22012
+ const status = record?.status;
22013
+ if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
22014
+ continue;
22015
+ }
22016
+ actions.push({
22017
+ kind,
22018
+ label,
22019
+ ...status === "done" || status === "queued" || status === "failed" ? { status } : {}
22020
+ });
22021
+ }
22022
+ return actions.length ? actions : void 0;
22023
+ }
22024
+ function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
22025
+ if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
22026
+ return void 0;
22027
+ }
22028
+ const parts = [];
22029
+ const canonicalActionsById = new Map(
22030
+ (canonicalActions || []).map((action) => [
22031
+ `${action.kind}:${action.label}`,
22032
+ action
22033
+ ])
22034
+ );
22035
+ const seenActionIds = /* @__PURE__ */ new Set();
22036
+ let textLength = 0;
22037
+ for (const item of value) {
22038
+ const record = asRecord3(item);
22039
+ if (!record) return void 0;
22040
+ if (record.type === "text") {
22041
+ if (typeof record.text !== "string" || record.text.length === 0) {
22042
+ return void 0;
22043
+ }
22044
+ textLength += record.text.length;
22045
+ if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
22046
+ parts.push({ type: "text", text: record.text });
22047
+ continue;
22048
+ }
22049
+ if (record.type !== "action") return void 0;
22050
+ const action = asRecord3(record.action);
22051
+ const kind = action?.kind;
22052
+ const label = trimString(action?.label);
22053
+ if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
22054
+ return void 0;
22055
+ }
22056
+ const actionId = `${kind}:${label}`;
22057
+ const canonicalAction = canonicalActionsById.get(actionId);
22058
+ if (!canonicalAction) return void 0;
22059
+ if (seenActionIds.has(actionId)) continue;
22060
+ seenActionIds.add(actionId);
22061
+ parts.push({
22062
+ type: "action",
22063
+ action: canonicalAction
22064
+ });
22065
+ }
22066
+ const orderedText = parts.filter(
22067
+ (part) => part.type === "text"
22068
+ ).map((part) => part.text).join("");
22069
+ return orderedText === canonicalContent ? parts : void 0;
21480
22070
  }
21481
22071
  function stringifyTranscriptValue(value, fallback2 = "") {
21482
22072
  if (typeof value === "string") {
@@ -21496,12 +22086,139 @@ function stringifyTranscriptValue(value, fallback2 = "") {
21496
22086
  return String(value);
21497
22087
  }
21498
22088
  }
21499
- function buildArtifactHistory(show) {
22089
+ function latestInputEditSummary(metadata) {
22090
+ const lastInputEdit = asRecord3(metadata.lastInputEdit);
22091
+ if (!lastInputEdit) return null;
22092
+ const source = asString(lastInputEdit.source) || "unknown";
22093
+ const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
22094
+ const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
22095
+ (key) => typeof key === "string" && key.trim().length > 0
22096
+ ).slice(0, 6) : [];
22097
+ const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
22098
+ (key) => typeof key === "string" && key.trim().length > 0
22099
+ ).slice(0, 6) : [];
22100
+ const changed = [
22101
+ inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
22102
+ relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
22103
+ ].filter(Boolean);
22104
+ return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
22105
+ }
22106
+ function artifactIssueSummary(record) {
22107
+ const validation = asRecord3(record.validation);
22108
+ if (!validation) return null;
22109
+ const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
22110
+ if (issues.length > 0) {
22111
+ return `issues=${issues.map((issue) => {
22112
+ const code = asString(issue.code) || asString(issue.kind) || "issue";
22113
+ const path7 = asString(issue.path);
22114
+ const message = trimString(issue.message);
22115
+ return `${code}${path7 ? ` at ${path7}` : ""}${message ? ` (${message})` : ""}`;
22116
+ }).join("; ")}`;
22117
+ }
22118
+ const error2 = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
22119
+ return error2 ? `validation=${error2}` : null;
22120
+ }
22121
+ function artifactExecutionSummary(metadata) {
22122
+ const execution = asRecord3(metadata.execution);
22123
+ if (!execution) return null;
22124
+ const result = asRecord3(execution.result);
22125
+ const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
22126
+ const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
22127
+ const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
22128
+ const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
22129
+ const error2 = trimString(execution.error);
22130
+ const pieces = [
22131
+ awaiting ? `awaiting=${awaiting}` : null,
22132
+ pendingTransition ? `pendingTransition=${pendingTransition}` : null,
22133
+ approvalTarget ? `approvalTarget=${approvalTarget}` : null,
22134
+ error2 ? `executionError=${error2}` : null
22135
+ ].filter(Boolean);
22136
+ return pieces.length ? pieces.join("; ") : null;
22137
+ }
22138
+ function artifactStatePathSummary(metadata) {
22139
+ const statePlan = asRecord3(metadata.statePlan);
22140
+ if (!statePlan) return null;
22141
+ const machineName = asString(statePlan.machineName);
22142
+ const targetState = asString(statePlan.targetState);
22143
+ const objectPath = asString(statePlan.objectPath);
22144
+ const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
22145
+ const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
22146
+ const pieces = [
22147
+ machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
22148
+ objectPath ? `objectPath=${objectPath}` : null,
22149
+ approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
22150
+ approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
22151
+ ].filter(Boolean);
22152
+ return pieces.length ? pieces.join("; ") : null;
22153
+ }
22154
+ function artifactSummaryLine(artifactId, record) {
22155
+ if (!record) return `- ${artifactId}: unavailable in session artifact store`;
22156
+ const label = trimString(record.label) || artifactId;
22157
+ const kind = asString(record.kind) || "artifact";
22158
+ const status = asString(record.status) || "unknown";
22159
+ const createdByJobId = asString(record.createdByJobId);
22160
+ const target = asRecord3(record.target);
22161
+ const metadata = asRecord3(record.metadata) || {};
22162
+ const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
22163
+ (id) => typeof id === "string" && id.trim().length > 0
22164
+ ).slice(0, 8) : [];
22165
+ const relationships = compactJson(record.relationships, 220);
22166
+ const pieces = [
22167
+ `kind=${kind}`,
22168
+ `status=${status}`,
22169
+ createdByJobId ? `createdByJob=${createdByJobId}` : null,
22170
+ target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
22171
+ artifactStatePathSummary(metadata),
22172
+ artifactExecutionSummary(metadata),
22173
+ artifactIssueSummary(record),
22174
+ latestInputEditSummary(metadata),
22175
+ subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
22176
+ relationships ? `relationships=${relationships}` : null
22177
+ ].filter(Boolean);
22178
+ return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
22179
+ }
22180
+ function buildArtifactHistory(show, artifactsById) {
21500
22181
  if (!show) return void 0;
21501
- return `[Agent message]
22182
+ const artifactIds = show.sessionArtifactIds || [];
22183
+ const actionSuggestions = show.actionSuggestions || [];
22184
+ if (artifactIds.length === 0 && actionSuggestions.length === 0) {
22185
+ return `[Agent message]
21502
22186
  ${stringifyTranscriptValue({ show }, "")}`;
22187
+ }
22188
+ const lines = artifactIds.slice(0, 8).map(
22189
+ (artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
22190
+ );
22191
+ if (artifactIds.length > 8) {
22192
+ lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
22193
+ }
22194
+ if (actionSuggestions.length > 0) {
22195
+ if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
22196
+ for (const suggestion of actionSuggestions.slice(0, 8)) {
22197
+ lines.push(
22198
+ `- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
22199
+ );
22200
+ }
22201
+ if (actionSuggestions.length > 8) {
22202
+ lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
22203
+ }
22204
+ }
22205
+ const otherRefs = {
22206
+ entryPaths: show.entryPaths,
22207
+ listNames: show.listNames,
22208
+ variableNames: show.variableNames,
22209
+ fileIds: show.fileIds
22210
+ };
22211
+ const hasOtherRefs = Object.values(otherRefs).some(
22212
+ (value) => Array.isArray(value) && value.length > 0
22213
+ );
22214
+ const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
22215
+ return [
22216
+ title,
22217
+ ...lines,
22218
+ hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
22219
+ ].filter(Boolean).join("\n");
21503
22220
  }
21504
- function normalizeConversationMessage(raw) {
22221
+ function normalizeConversationMessage(raw, artifactsById) {
21505
22222
  const record = asRecord3(raw);
21506
22223
  if (!record) return null;
21507
22224
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -21509,10 +22226,18 @@ function normalizeConversationMessage(raw) {
21509
22226
  const content = trimString(
21510
22227
  record.content ?? record.reply ?? record.message ?? record.text
21511
22228
  );
22229
+ const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
22230
+ const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
21512
22231
  const show = normalizeShowRefs(record.show);
21513
22232
  const id = asString(record.id) || crypto.randomUUID();
21514
22233
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
21515
- if (!content && !show) return null;
22234
+ if (!content && !show && !actions?.length) return null;
22235
+ const artifactHistory = buildArtifactHistory(show, artifactsById);
22236
+ const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
22237
+ ${content}
22238
+
22239
+ ${artifactHistory}` : content ? `[Assistant reply]
22240
+ ${content}` : artifactHistory : void 0;
21516
22241
  return {
21517
22242
  id,
21518
22243
  role,
@@ -21521,8 +22246,9 @@ function normalizeConversationMessage(raw) {
21521
22246
  jobId: asString(record.jobId),
21522
22247
  promptId: asString(record.promptId),
21523
22248
  show,
21524
- historyContent: role === "assistant" ? content ? `[Assistant reply]
21525
- ${content}` : buildArtifactHistory(show) : void 0,
22249
+ actions,
22250
+ parts,
22251
+ historyContent,
21526
22252
  source: "conversation"
21527
22253
  };
21528
22254
  }
@@ -21565,7 +22291,7 @@ ${assistantContent}`,
21565
22291
  return entries;
21566
22292
  });
21567
22293
  }
21568
- function normalizeAgentMessageEntries(jobId, rawMessages) {
22294
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
21569
22295
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
21570
22296
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
21571
22297
  ).flatMap((message) => {
@@ -21596,14 +22322,14 @@ ${reply}`,
21596
22322
  timestamp,
21597
22323
  jobId,
21598
22324
  show,
21599
- historyContent: buildArtifactHistory(show),
22325
+ historyContent: buildArtifactHistory(show, artifactsById),
21600
22326
  source: "job_agent_message"
21601
22327
  });
21602
22328
  }
21603
22329
  return entries;
21604
22330
  });
21605
22331
  }
21606
- function buildJobFallbackEntries(jobId, job2, sessionHeap) {
22332
+ function buildJobFallbackEntries(jobId, job2, sessionHeap, artifactsById) {
21607
22333
  const timestamp = asNumber(job2.finishedAt) || asNumber(job2.startedAt) || asNumber(job2.submittedAt) || 0;
21608
22334
  const resultPreview = stringifyTranscriptValue(
21609
22335
  job2.result,
@@ -21641,7 +22367,7 @@ ${responseText}`,
21641
22367
  timestamp,
21642
22368
  jobId,
21643
22369
  show,
21644
- historyContent: buildArtifactHistory(show),
22370
+ historyContent: buildArtifactHistory(show, artifactsById),
21645
22371
  source: "job_result"
21646
22372
  });
21647
22373
  }
@@ -21695,10 +22421,11 @@ function buildJobCodeEntry(jobId, job2) {
21695
22421
  function buildSessionTranscript(input) {
21696
22422
  const liveDoc = input.liveDoc || null;
21697
22423
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
22424
+ const artifactsById = artifactRecordsById(liveDoc);
21698
22425
  const transcript = [];
21699
22426
  const conversationMessages = asArray(
21700
22427
  asRecord3(liveDoc?.conversation)?.messages
21701
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
22428
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
21702
22429
  const conversationPromptIds = new Set(
21703
22430
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
21704
22431
  );
@@ -21725,7 +22452,8 @@ function buildSessionTranscript(input) {
21725
22452
  if (!assistantConversationJobIds.has(jobId)) {
21726
22453
  const agentEntries = normalizeAgentMessageEntries(
21727
22454
  jobId,
21728
- job2.agentMessages
22455
+ job2.agentMessages,
22456
+ artifactsById
21729
22457
  );
21730
22458
  if (agentEntries.length > 0) {
21731
22459
  transcript.push(...agentEntries);
@@ -21734,7 +22462,8 @@ function buildSessionTranscript(input) {
21734
22462
  ...buildJobFallbackEntries(
21735
22463
  jobId,
21736
22464
  job2,
21737
- sessionHeap
22465
+ sessionHeap,
22466
+ artifactsById
21738
22467
  )
21739
22468
  );
21740
22469
  }
@@ -21982,15 +22711,50 @@ function toRecordSearchResult(className, node) {
21982
22711
  return [];
21983
22712
  }
21984
22713
  ) : [];
22714
+ const graphPathId = extractRecordIdFromGraphPath(path7, className);
22715
+ const realIdField = fields.find(
22716
+ (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
22717
+ );
22718
+ const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
22719
+ const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
22720
+ if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path7)) {
22721
+ return null;
22722
+ }
22723
+ const fallbackLabel = displayLabelFromFields(fields);
22724
+ const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path7) ? rawLabel : fallbackLabel || rawLabel || id;
21985
22725
  return {
21986
22726
  path: path7,
21987
22727
  className,
21988
- id: extractRecordIdFromGraphPath(path7, className),
21989
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path7, className),
22728
+ id,
22729
+ label,
21990
22730
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
21991
22731
  fields
21992
22732
  };
21993
22733
  }
22734
+ function isPlaceholderRecordLabel(label, id, path7) {
22735
+ const normalizedLabel = normalizeGraphPathSegment(label);
22736
+ return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path7);
22737
+ }
22738
+ function displayLabelFromFields(fields) {
22739
+ const preferredFieldNames = [
22740
+ "name",
22741
+ "title",
22742
+ "label",
22743
+ "display_name",
22744
+ "file_name",
22745
+ "number",
22746
+ "code"
22747
+ ];
22748
+ for (const preferred of preferredFieldNames) {
22749
+ const match = fields.find(
22750
+ (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
22751
+ );
22752
+ if (typeof match?.value === "string") {
22753
+ return match.value.trim();
22754
+ }
22755
+ }
22756
+ return null;
22757
+ }
21994
22758
  function normalizeRecordSearchText(value) {
21995
22759
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
21996
22760
  }
@@ -22132,7 +22896,12 @@ async function recordOpenAIUsageSpend(options) {
22132
22896
  const metadata = {
22133
22897
  ...options.metadata || {},
22134
22898
  ...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
22135
- usageContext: context
22899
+ usageContext: context,
22900
+ pricingContextTier: options.usage.pricingContextTier,
22901
+ cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
22902
+ cacheWriteTokens: options.usage.cacheWriteTokens,
22903
+ cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
22904
+ longContextThresholdTokens: options.usage.longContextThresholdTokens
22136
22905
  };
22137
22906
  const response = await fetch(
22138
22907
  `${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
@@ -22203,6 +22972,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
22203
22972
  }
22204
22973
 
22205
22974
  // src/client.ts
22975
+ var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
22976
+ var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
22977
+ var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
22978
+ function boundedSessionListInteger(value, name, fallback2, minimum, maximum) {
22979
+ if (value === void 0) return fallback2;
22980
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
22981
+ throw new RangeError(
22982
+ `Session list ${name} must be an integer between ${minimum} and ${maximum}.`
22983
+ );
22984
+ }
22985
+ return value;
22986
+ }
22206
22987
  var STANDARD_MODULES_OPERATIONS = [
22207
22988
  {
22208
22989
  create: "entity",
@@ -22239,6 +23020,26 @@ var STANDARD_MODULES_OPERATIONS = [
22239
23020
  var BUILTIN_MODULES = {
22240
23021
  standard_modules: STANDARD_MODULES_OPERATIONS
22241
23022
  };
23023
+ function stateNameFromMethodName(methodName) {
23024
+ const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
23025
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
23026
+ }
23027
+ function appendQueryOptions(searchParams, query) {
23028
+ for (const [key, value] of Object.entries(query || {})) {
23029
+ if (value === null || typeof value === "undefined" || value === "") {
23030
+ continue;
23031
+ }
23032
+ if (value instanceof Date) {
23033
+ searchParams.set(key, value.toISOString());
23034
+ continue;
23035
+ }
23036
+ if (Array.isArray(value)) {
23037
+ if (value.length > 0) searchParams.set(key, value.join(","));
23038
+ continue;
23039
+ }
23040
+ searchParams.set(key, String(value));
23041
+ }
23042
+ }
22242
23043
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
22243
23044
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
22244
23045
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -22269,8 +23070,20 @@ function bodyInitFromSessionFileUpload(body) {
22269
23070
  return body;
22270
23071
  }
22271
23072
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
23073
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
22272
23074
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
22273
23075
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
23076
+ function chunkItems(items, batchSize) {
23077
+ const chunks = [];
23078
+ for (let offset = 0; offset < items.length; offset += batchSize) {
23079
+ chunks.push(items.slice(offset, offset + batchSize));
23080
+ }
23081
+ return chunks;
23082
+ }
23083
+ function isUnsupportedEffectCatalogMutation(error2) {
23084
+ const message = error2 instanceof Error ? error2.message : String(error2);
23085
+ 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");
23086
+ }
22274
23087
  function planRecordObjectsChunks(records, batchSize) {
22275
23088
  const total = records.length;
22276
23089
  const size = Math.max(1, Math.min(batchSize, total));
@@ -22282,6 +23095,23 @@ function planRecordObjectsChunks(records, batchSize) {
22282
23095
  }
22283
23096
  return plans;
22284
23097
  }
23098
+ function preserveRecordObjectRealId(record) {
23099
+ const realId = record.id.trim();
23100
+ if (!realId) {
23101
+ return record;
23102
+ }
23103
+ const fields = record.fields || {};
23104
+ if (typeof fields.real_id === "string" && fields.real_id.trim()) {
23105
+ return record;
23106
+ }
23107
+ return {
23108
+ ...record,
23109
+ fields: {
23110
+ ...fields,
23111
+ real_id: realId
23112
+ }
23113
+ };
23114
+ }
22285
23115
  function computeEffectKey2(effect) {
22286
23116
  const attachedClass = effect.className?.trim();
22287
23117
  if (!attachedClass) {
@@ -22492,7 +23322,7 @@ var Environment = class _Environment {
22492
23322
  }
22493
23323
  get sessions() {
22494
23324
  return {
22495
- list: async (options) => this.listSessions(options?.status || "active"),
23325
+ list: async (options = {}) => this.listSessions(options),
22496
23326
  create: async (options) => this.createSession(options),
22497
23327
  connect: async (sessionId, options) => this.connectSession(sessionId, options),
22498
23328
  reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
@@ -22519,11 +23349,105 @@ var Environment = class _Environment {
22519
23349
  getAwaitingCount: async () => this.getAwaitingRecordCount()
22520
23350
  };
22521
23351
  }
23352
+ /**
23353
+ * Mirror product-owned workflow state into Granular without making Granular
23354
+ * own the customer application's state machine.
23355
+ */
23356
+ async recordState(input) {
23357
+ const { machine, state, ...target } = input;
23358
+ if (!machine.trim()) {
23359
+ throw new Error("State update requires a machine name");
23360
+ }
23361
+ if (!state.trim()) {
23362
+ throw new Error("State update requires a state");
23363
+ }
23364
+ return this.recordObject({
23365
+ className: target.className,
23366
+ id: target.id,
23367
+ ...target.label ? { label: target.label } : {},
23368
+ ...target.fields ? { fields: target.fields } : {},
23369
+ ...target.relationships ? { relationships: target.relationships } : {},
23370
+ states: {
23371
+ [machine.trim()]: {
23372
+ state: state.trim(),
23373
+ ...target.source ? { source: target.source } : {},
23374
+ ...target.cause ? { cause: target.cause } : {},
23375
+ ...target.actorId ? { actorId: target.actorId } : {},
23376
+ ...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
23377
+ ...target.force !== void 0 ? { force: target.force } : {},
23378
+ ...target.metadata ? { metadata: target.metadata } : {}
23379
+ }
23380
+ }
23381
+ });
23382
+ }
23383
+ /**
23384
+ * Mirror product-owned workflow state into Granular without making Granular
23385
+ * own the customer application's state machine.
23386
+ *
23387
+ * Example:
23388
+ * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
23389
+ */
23390
+ state(target) {
23391
+ const observe = async (machineName, stateName, input = {}) => {
23392
+ const observedState = input.observedState || input.state || stateName;
23393
+ if (!observedState) {
23394
+ throw new Error("State observation requires a target state");
23395
+ }
23396
+ return this.recordState({
23397
+ ...target,
23398
+ machine: machineName,
23399
+ state: observedState,
23400
+ ...input.source ? { source: input.source } : {},
23401
+ ...input.cause ? { cause: input.cause } : {},
23402
+ ...input.actorId ? { actorId: input.actorId } : {},
23403
+ ...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
23404
+ ...input.force !== void 0 ? { force: input.force } : {},
23405
+ ...input.metadata ? { metadata: input.metadata } : {}
23406
+ });
23407
+ };
23408
+ return new Proxy(
23409
+ {},
23410
+ {
23411
+ get: (_target, machineProperty) => {
23412
+ if (typeof machineProperty !== "string") return void 0;
23413
+ return new Proxy(
23414
+ {},
23415
+ {
23416
+ get: (_machineTarget, stateProperty) => {
23417
+ if (stateProperty === "to") {
23418
+ return (stateName, input) => observe(machineProperty, stateName, input || {});
23419
+ }
23420
+ if (typeof stateProperty !== "string") return void 0;
23421
+ return (input) => observe(
23422
+ machineProperty,
23423
+ stateNameFromMethodName(stateProperty),
23424
+ input || {}
23425
+ );
23426
+ }
23427
+ }
23428
+ );
23429
+ }
23430
+ }
23431
+ );
23432
+ }
22522
23433
  get feedback() {
22523
23434
  return {
22524
23435
  list: async () => this.listFeedback()
22525
23436
  };
22526
23437
  }
23438
+ get manualActions() {
23439
+ return {
23440
+ record: (input) => this.recordManualAction(input),
23441
+ list: (options = {}) => this.listManualActions(options),
23442
+ suggest: (options = {}) => this.suggestManualActions(options)
23443
+ };
23444
+ }
23445
+ get artifactApprovals() {
23446
+ return {
23447
+ list: (options = {}) => this.listArtifactApprovals(options),
23448
+ decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
23449
+ };
23450
+ }
22527
23451
  /**
22528
23452
  * Sessionless environments do not own a live transport, so disconnecting the
22529
23453
  * environment handle itself is a no-op. This keeps the public surface
@@ -22533,17 +23457,12 @@ var Environment = class _Environment {
22533
23457
  */
22534
23458
  async disconnect() {
22535
23459
  }
22536
- async listSessions(status = "active") {
22537
- if (status === "all") {
22538
- const [active, closed] = await Promise.all([
22539
- this.granular.listOpenSessions({ environmentId: this.environmentId }),
22540
- this.granular.listClosedSessions({ environmentId: this.environmentId })
22541
- ]);
22542
- return [...active, ...closed].sort(
22543
- (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
22544
- );
22545
- }
22546
- return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
23460
+ async listSessions(optionsOrStatus = {}) {
23461
+ const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
23462
+ return this.granular.listSessions({
23463
+ ...options,
23464
+ environmentId: this.environmentId
23465
+ });
22547
23466
  }
22548
23467
  async getUserEnvironmentState(options = {}) {
22549
23468
  return this.granular.getUserEnvironmentState({
@@ -22561,6 +23480,7 @@ var Environment = class _Environment {
22561
23480
  return this.granular.createSession({
22562
23481
  environmentId: this.environmentId,
22563
23482
  clientId: options?.clientId,
23483
+ sessionScope: options?.sessionScope,
22564
23484
  initialHeap: options?.initialHeap
22565
23485
  });
22566
23486
  }
@@ -22602,6 +23522,50 @@ var Environment = class _Environment {
22602
23522
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
22603
23523
  return Array.isArray(response.items) ? response.items : [];
22604
23524
  }
23525
+ async recordManualAction(input) {
23526
+ const body = {
23527
+ ...input,
23528
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
23529
+ };
23530
+ return this.controlPlaneRequest(
23531
+ `/control/environments/${this.environmentId}/manual-actions`,
23532
+ {
23533
+ method: "POST",
23534
+ body: JSON.stringify(body)
23535
+ }
23536
+ );
23537
+ }
23538
+ async listManualActions(options = {}) {
23539
+ const query = new URLSearchParams();
23540
+ appendQueryOptions(query, options);
23541
+ const suffix = query.toString() ? `?${query.toString()}` : "";
23542
+ return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
23543
+ }
23544
+ async suggestManualActions(options = {}) {
23545
+ const query = new URLSearchParams();
23546
+ appendQueryOptions(query, options);
23547
+ const suffix = query.toString() ? `?${query.toString()}` : "";
23548
+ return this.controlPlaneRequest(
23549
+ `/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
23550
+ );
23551
+ }
23552
+ async listArtifactApprovals(options = {}) {
23553
+ const query = new URLSearchParams();
23554
+ appendQueryOptions(query, options);
23555
+ const suffix = query.toString() ? `?${query.toString()}` : "";
23556
+ return this.controlPlaneRequest(
23557
+ `/control/environments/${this.environmentId}/artifact-approvals${suffix}`
23558
+ );
23559
+ }
23560
+ async decideArtifactApproval(approvalTaskId, input) {
23561
+ return this.controlPlaneRequest(
23562
+ `/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
23563
+ {
23564
+ method: "POST",
23565
+ body: JSON.stringify(input)
23566
+ }
23567
+ );
23568
+ }
22605
23569
  getRuntimeBaseUrl() {
22606
23570
  return deriveRuntimeBaseUrl(this._apiEndpoint);
22607
23571
  }
@@ -23426,10 +24390,11 @@ var Environment = class _Environment {
23426
24390
  if (!Array.isArray(records) || records.length === 0) {
23427
24391
  return [];
23428
24392
  }
24393
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
23429
24394
  const batchSize = Math.max(
23430
24395
  1,
23431
24396
  Math.min(
23432
- records.length,
24397
+ recordsToWrite.length,
23433
24398
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
23434
24399
  )
23435
24400
  );
@@ -23437,8 +24402,8 @@ var Environment = class _Environment {
23437
24402
  MAX_RECORD_OBJECTS_CONCURRENCY,
23438
24403
  Math.max(1, options?.concurrency ?? 1)
23439
24404
  );
23440
- const plans = planRecordObjectsChunks(records, batchSize);
23441
- const total = records.length;
24405
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
24406
+ const total = recordsToWrite.length;
23442
24407
  const results = new Array(total);
23443
24408
  const onChunk = options?.onChunkComplete;
23444
24409
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -23509,12 +24474,13 @@ var Environment = class _Environment {
23509
24474
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
23510
24475
  */
23511
24476
  async enqueueRecordImport(records, options = {}) {
24477
+ const recordsToImport = records.map(preserveRecordObjectRealId);
23512
24478
  return this.controlPlaneRequest(
23513
24479
  `/control/environments/${this.environmentId}/record-imports`,
23514
24480
  {
23515
24481
  method: "POST",
23516
24482
  body: JSON.stringify({
23517
- records,
24483
+ records: recordsToImport,
23518
24484
  batchSize: options.batchSize,
23519
24485
  setupRunId: options.setupRunId,
23520
24486
  writeMode: options.writeMode
@@ -23622,11 +24588,7 @@ var EnvironmentSession = class extends Session {
23622
24588
  }
23623
24589
  buildSessionDataUrl(path7, query) {
23624
24590
  const searchParams = new URLSearchParams();
23625
- for (const [key, value] of Object.entries(query || {})) {
23626
- if (value !== null && typeof value !== "undefined" && value !== "") {
23627
- searchParams.set(key, String(value));
23628
- }
23629
- }
24591
+ appendQueryOptions(searchParams, query);
23630
24592
  const queryString = searchParams.toString();
23631
24593
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path7}${queryString ? `?${queryString}` : ""}`;
23632
24594
  }
@@ -23709,9 +24671,108 @@ var EnvironmentSession = class extends Session {
23709
24671
  ),
23710
24672
  get: (jobId) => this.sessionDataRequest(
23711
24673
  `/jobs/${encodeURIComponent(jobId)}`
24674
+ ),
24675
+ latest: async (options = {}) => {
24676
+ const page = await this.sessionDataRequest("/jobs", {
24677
+ status: options.status || "all",
24678
+ latest: true,
24679
+ limit: 1
24680
+ });
24681
+ return page.items[0] || null;
24682
+ }
24683
+ };
24684
+ }
24685
+ get artifacts() {
24686
+ return {
24687
+ list: (options = {}) => {
24688
+ const queryOptions = { ...options };
24689
+ if (options.target) {
24690
+ queryOptions.targetClassName = options.target.className;
24691
+ queryOptions.targetId = options.target.id;
24692
+ delete queryOptions.target;
24693
+ }
24694
+ return this.sessionDataRequest("/artifacts", queryOptions);
24695
+ },
24696
+ listForLatestJob: (options = {}) => this.artifacts.list({
24697
+ ...options,
24698
+ latestJob: true
24699
+ }),
24700
+ get: (artifactId) => this.sessionDataRequest(
24701
+ `/artifacts/${encodeURIComponent(artifactId)}`
24702
+ ),
24703
+ create: (artifact) => this.sessionDataRequest(
24704
+ "/artifacts",
24705
+ void 0,
24706
+ {
24707
+ method: "POST",
24708
+ body: artifact
24709
+ }
24710
+ ),
24711
+ updateInputs: (artifactId, patch) => this.sessionDataRequest(
24712
+ `/artifacts/${encodeURIComponent(artifactId)}`,
24713
+ void 0,
24714
+ {
24715
+ method: "PATCH",
24716
+ body: patch
24717
+ }
24718
+ ),
24719
+ validate: (artifactId) => this.sessionDataRequest(
24720
+ `/artifacts/${encodeURIComponent(artifactId)}/validate`,
24721
+ void 0,
24722
+ { method: "POST" }
24723
+ ),
24724
+ execute: (artifactId, options) => this.sessionDataRequest(
24725
+ `/artifacts/${encodeURIComponent(artifactId)}/execute`,
24726
+ void 0,
24727
+ { method: "POST", body: options }
24728
+ ),
24729
+ approve: (artifactId, options) => this.sessionDataRequest(
24730
+ `/artifacts/${encodeURIComponent(artifactId)}/approve`,
24731
+ void 0,
24732
+ { method: "POST", body: options }
24733
+ ),
24734
+ cancel: (artifactId) => this.sessionDataRequest(
24735
+ `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
24736
+ void 0,
24737
+ { method: "POST" }
23712
24738
  )
23713
24739
  };
23714
24740
  }
24741
+ get manualActions() {
24742
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
24743
+ return {
24744
+ record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
24745
+ "/manual-actions",
24746
+ void 0,
24747
+ {
24748
+ method: "POST",
24749
+ body: { ...input, sessionId: this.sessionId }
24750
+ }
24751
+ ) : this.environment.manualActions.record({
24752
+ ...input,
24753
+ sessionId: this.sessionId
24754
+ }),
24755
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
24756
+ ...options,
24757
+ sessionId: this.sessionId
24758
+ }),
24759
+ suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
24760
+ "/manual-actions/suggestions",
24761
+ options
24762
+ ) : this.environment.manualActions.suggest(options)
24763
+ };
24764
+ }
24765
+ get artifactApprovals() {
24766
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
24767
+ return {
24768
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
24769
+ decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
24770
+ `/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
24771
+ void 0,
24772
+ { method: "POST", body: input }
24773
+ ) : this.environment.artifactApprovals.decide(approvalTaskId, input)
24774
+ };
24775
+ }
23715
24776
  get files() {
23716
24777
  return {
23717
24778
  list: (options = {}) => this.sessionDataRequest(
@@ -23792,13 +24853,16 @@ var EnvironmentSession = class extends Session {
23792
24853
  get transcript() {
23793
24854
  return {
23794
24855
  list: async (options = {}) => {
23795
- const [messages, jobs, entries, lists] = await Promise.all([
24856
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
23796
24857
  this.collectAllSessionItems(this.messages.list),
23797
24858
  this.collectAllSessionItems(
23798
24859
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
23799
24860
  ),
23800
24861
  this.collectAllSessionItems(this.heap.entries.list),
23801
- this.collectAllSessionItems(this.heap.lists.list)
24862
+ this.collectAllSessionItems(this.heap.lists.list),
24863
+ this.collectAllSessionItems(
24864
+ (pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
24865
+ )
23802
24866
  ]);
23803
24867
  const liveDoc = {
23804
24868
  conversation: { messages },
@@ -23812,6 +24876,21 @@ var EnvironmentSession = class extends Session {
23812
24876
  (entry) => Boolean(entry)
23813
24877
  )
23814
24878
  )
24879
+ },
24880
+ artifacts: {
24881
+ byId: Object.fromEntries(
24882
+ artifacts.map((artifact) => {
24883
+ return artifact?.artifactId ? [
24884
+ artifact.artifactId,
24885
+ artifact
24886
+ ] : null;
24887
+ }).filter(
24888
+ (entry) => Boolean(entry)
24889
+ )
24890
+ ),
24891
+ order: artifacts.map((artifact) => artifact?.artifactId).filter(
24892
+ (artifactId) => Boolean(artifactId)
24893
+ )
23815
24894
  }
23816
24895
  };
23817
24896
  const heap = normalizeHeapSnapshot({
@@ -23888,6 +24967,12 @@ var EnvironmentSession = class extends Session {
23888
24967
  async recordObject(options) {
23889
24968
  return this.environment.recordObject(options);
23890
24969
  }
24970
+ async recordState(input) {
24971
+ return this.environment.recordState(input);
24972
+ }
24973
+ state(target) {
24974
+ return this.environment.state(target);
24975
+ }
23891
24976
  async recordObjects(records, options) {
23892
24977
  return this.environment.recordObjects(records, options);
23893
24978
  }
@@ -23958,7 +25043,7 @@ var EnvironmentSession = class extends Session {
23958
25043
  * Close only the socket transport without sending `client.goodbye`.
23959
25044
  */
23960
25045
  disconnectTransport() {
23961
- this.client.disconnect();
25046
+ this.client.disconnect({ reason: "Transport detach" });
23962
25047
  }
23963
25048
  /**
23964
25049
  * Backwards-compatible alias for `disconnect()`.
@@ -24353,16 +25438,71 @@ var Granular = class _Granular {
24353
25438
  };
24354
25439
  }
24355
25440
  /**
24356
- * List active (open) sessions for an environment each session is one agent conversation thread.
25441
+ * List indexed sessions using ownership filters and bounded pagination.
25442
+ */
25443
+ async listSessions(options) {
25444
+ const environmentId = options.environmentId?.trim();
25445
+ const sandboxId = options.sandboxId?.trim();
25446
+ const subjectId = options.subjectId?.trim();
25447
+ if (!environmentId && !sandboxId && !subjectId) {
25448
+ throw new Error(
25449
+ "listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
25450
+ );
25451
+ }
25452
+ const status = options.status || "active";
25453
+ const allowedStatuses = /* @__PURE__ */ new Set([
25454
+ "active",
25455
+ "closed",
25456
+ "expired",
25457
+ "failed",
25458
+ "timeout",
25459
+ "all"
25460
+ ]);
25461
+ if (!allowedStatuses.has(status)) {
25462
+ throw new Error(`Unsupported session status: ${String(status)}`);
25463
+ }
25464
+ const limit = boundedSessionListInteger(
25465
+ options.limit,
25466
+ "limit",
25467
+ DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
25468
+ 1,
25469
+ MAX_CONVERSATION_SESSION_LIST_LIMIT
25470
+ );
25471
+ const offset = boundedSessionListInteger(
25472
+ options.offset,
25473
+ "offset",
25474
+ 0,
25475
+ 0,
25476
+ MAX_CONVERSATION_SESSION_LIST_OFFSET
25477
+ );
25478
+ const query = new URLSearchParams({
25479
+ limit: String(limit),
25480
+ offset: String(offset)
25481
+ });
25482
+ if (environmentId) query.set("environmentId", environmentId);
25483
+ if (sandboxId) query.set("sandboxId", sandboxId);
25484
+ if (subjectId) query.set("userId", subjectId);
25485
+ if (options.sessionScope?.trim()) {
25486
+ query.set("sessionScope", options.sessionScope.trim());
25487
+ }
25488
+ if (status !== "all") query.set("status", status);
25489
+ const res = await this.request(
25490
+ `/control/sessions?${query.toString()}`
25491
+ );
25492
+ const items = Array.isArray(res.items) ? res.items : [];
25493
+ return items.map((row) => this.normalizeConversationSession(row));
25494
+ }
25495
+ /**
25496
+ * List active (open) sessions for an environment.
24357
25497
  */
24358
25498
  async listOpenSessions(filters) {
24359
- return this.listSessionsForEnvironment(filters.environmentId, "active");
25499
+ return this.listSessions({ ...filters, status: "active" });
24360
25500
  }
24361
25501
  /**
24362
25502
  * List closed sessions for an environment (conversations that have disconnected).
24363
25503
  */
24364
25504
  async listClosedSessions(filters) {
24365
- return this.listSessionsForEnvironment(filters.environmentId, "closed");
25505
+ return this.listSessions({ ...filters, status: "closed" });
24366
25506
  }
24367
25507
  async getUserEnvironmentState(options) {
24368
25508
  const query = new URLSearchParams({
@@ -24397,14 +25537,6 @@ var Granular = class _Granular {
24397
25537
  });
24398
25538
  return result.readAtBySessionId || {};
24399
25539
  }
24400
- async listSessionsForEnvironment(environmentId, status) {
24401
- const query = new URLSearchParams({ environmentId, status });
24402
- const res = await this.request(
24403
- `/control/sessions?${query.toString()}`
24404
- );
24405
- const items = Array.isArray(res.items) ? res.items : [];
24406
- return items.map((row) => this.normalizeConversationSession(row));
24407
- }
24408
25540
  normalizeConversationSession(row) {
24409
25541
  const sessionId = String(row.sessionId ?? row.session_id ?? "");
24410
25542
  const environmentId = String(row.environmentId ?? row.environment_id ?? "");
@@ -24463,6 +25595,7 @@ var Granular = class _Granular {
24463
25595
  */
24464
25596
  async createSession(options) {
24465
25597
  const clientId = options.clientId || `client_${Date.now()}`;
25598
+ const sessionScope = options.sessionScope?.trim() || void 0;
24466
25599
  await this.activateEnvironment(options.environmentId);
24467
25600
  const envData = await this.environments.get(options.environmentId);
24468
25601
  const environment = this.bindEnvironmentHandle(envData);
@@ -24471,6 +25604,8 @@ var Granular = class _Granular {
24471
25604
  body: JSON.stringify({
24472
25605
  environmentId: options.environmentId,
24473
25606
  clientId,
25607
+ sessionScope,
25608
+ capabilities: sessionScope ? { sessionScope } : void 0,
24474
25609
  initialHeap: options.initialHeap
24475
25610
  })
24476
25611
  });
@@ -24715,15 +25850,43 @@ var Granular = class _Granular {
24715
25850
  const effects2 = Array.from(
24716
25851
  this.getSandboxEffectMap(host.sandboxId).values()
24717
25852
  ).map((effect) => this.serializeEffect(effect));
24718
- const result = await withTimeout(
24719
- host.wsClient.call("effects.publishCatalog", {
24720
- effects: effects2
24721
- }),
24722
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
24723
- `effects.publishCatalog for sandbox ${host.sandboxId}`
24724
- );
24725
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
24726
- const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
25853
+ let acceptedCount = 0;
25854
+ const rejected = [];
25855
+ try {
25856
+ await withTimeout(
25857
+ host.wsClient.call("effects.resetCatalog", {}),
25858
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
25859
+ `effects.resetCatalog for sandbox ${host.sandboxId}`
25860
+ );
25861
+ for (const batch of chunkItems(effects2, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
25862
+ const result = await withTimeout(
25863
+ host.wsClient.call("effects.addCatalog", {
25864
+ effects: batch
25865
+ }),
25866
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
25867
+ `effects.addCatalog for sandbox ${host.sandboxId}`
25868
+ );
25869
+ acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
25870
+ if (Array.isArray(result?.rejected)) {
25871
+ rejected.push(...result.rejected);
25872
+ }
25873
+ }
25874
+ } catch (error2) {
25875
+ if (!isUnsupportedEffectCatalogMutation(error2)) {
25876
+ throw error2;
25877
+ }
25878
+ const result = await withTimeout(
25879
+ host.wsClient.call("effects.publishCatalog", {
25880
+ effects: effects2
25881
+ }),
25882
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
25883
+ `effects.publishCatalog for sandbox ${host.sandboxId}`
25884
+ );
25885
+ acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
25886
+ if (Array.isArray(result?.rejected)) {
25887
+ rejected.push(...result.rejected);
25888
+ }
25889
+ }
24727
25890
  if (acceptedCount === 0 && rejected.length > 0) {
24728
25891
  const detail = rejected.map(
24729
25892
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -25764,15 +26927,22 @@ async function resolveEnvironmentData(granular, options) {
25764
26927
  const ontologyId = await resolveOntologyId(granular, options.ontology);
25765
26928
  const environmentName = options.environment ?? "dev";
25766
26929
  const environments = await granular.environments.list(ontologyId);
25767
- const existing = environments.find(
25768
- (environment) => matchesEnvironmentName(environment, environmentName)
26930
+ const matchingEnvironments = environments.filter(
26931
+ (environment) => matchesEnvironmentName(environment, environmentName) && (!options.subjectId || environment.subjectId === options.subjectId)
25769
26932
  );
26933
+ if (matchingEnvironments.length > 1) {
26934
+ throw new Error(
26935
+ `Environment slot \`${environmentName}\` matches ${matchingEnvironments.length} subject environments. Pass --environment-id or --subject-id so session history cannot resolve to an arbitrary user.`
26936
+ );
26937
+ }
26938
+ const existing = matchingEnvironments[0];
25770
26939
  if (existing) {
25771
26940
  return existing;
25772
26941
  }
25773
26942
  if (!options.createIfMissing) {
26943
+ const subjectSuffix = options.subjectId ? ` for subject \`${options.subjectId}\`` : "";
25774
26944
  throw new Error(
25775
- `No environment named \`${environmentName}\` found for ontology \`${ontologyId}\`. Run \`granular connect test\` or \`granular session create\` first, or pass \`--environment-id\`.`
26945
+ `No environment named \`${environmentName}\`${subjectSuffix} found for ontology \`${ontologyId}\`. Run \`granular connect test\` or \`granular session create\` first, or pass \`--environment-id\`.`
25776
26946
  );
25777
26947
  }
25778
26948
  const connection = await granular.connect({
@@ -25783,17 +26953,15 @@ async function resolveEnvironmentData(granular, options) {
25783
26953
  });
25784
26954
  return await granular.environments.get(connection.environmentId);
25785
26955
  }
25786
- async function listSessionsForEnvironment(granular, environmentId, status) {
25787
- if (status === "all") {
25788
- const [active, closed] = await Promise.all([
25789
- granular.listOpenSessions({ environmentId }),
25790
- granular.listClosedSessions({ environmentId })
25791
- ]);
25792
- return [...active, ...closed].sort((left, right) => {
25793
- return Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt);
25794
- });
25795
- }
25796
- return status === "closed" ? granular.listClosedSessions({ environmentId }) : granular.listOpenSessions({ environmentId });
26956
+ async function listSessionsForEnvironment(granular, environmentId, options) {
26957
+ return granular.listSessions({
26958
+ environmentId,
26959
+ status: options.status,
26960
+ sessionScope: options.sessionScope,
26961
+ subjectId: options.subjectId,
26962
+ limit: options.limit,
26963
+ offset: options.offset
26964
+ });
25797
26965
  }
25798
26966
  async function connectRuntime(options) {
25799
26967
  if (options.sessionId) {
@@ -25906,6 +27074,19 @@ async function connectTestCommand(options) {
25906
27074
  }
25907
27075
 
25908
27076
  // src/cli/commands/session.ts
27077
+ function parseSessionListInteger(value, name) {
27078
+ const fallback2 = name === "limit" ? 25 : 0;
27079
+ const minimum = name === "limit" ? 1 : 0;
27080
+ const maximum = name === "limit" ? 500 : 1e5;
27081
+ if (value === void 0 || value === "") return fallback2;
27082
+ const parsed = typeof value === "number" ? value : Number.parseInt(value.trim(), 10);
27083
+ if (!Number.isInteger(parsed) || String(parsed) !== String(value).trim() || parsed < minimum || parsed > maximum) {
27084
+ throw new Error(
27085
+ `--${name} must be an integer between ${minimum} and ${maximum}.`
27086
+ );
27087
+ }
27088
+ return parsed;
27089
+ }
25909
27090
  function printValue(value, emitJson) {
25910
27091
  const json = JSON.stringify(value, null, 2);
25911
27092
  if (emitJson) {
@@ -25990,7 +27171,8 @@ async function sessionCreateCommand(options) {
25990
27171
  createIfMissing: true
25991
27172
  });
25992
27173
  const environment = await granular.createSession({
25993
- environmentId: envData.environmentId
27174
+ environmentId: envData.environmentId,
27175
+ sessionScope: options.sessionScope
25994
27176
  });
25995
27177
  try {
25996
27178
  const payload = {
@@ -25999,6 +27181,7 @@ async function sessionCreateCommand(options) {
25999
27181
  environmentId: environment.environmentId,
26000
27182
  environment: environment.tag || envData.tag?.name || envData.buildPolicy.tagName || requestedEnvironment,
26001
27183
  sessionId: environment.sessionId,
27184
+ sessionScope: options.sessionScope?.trim() || null,
26002
27185
  subjectId: environment.subjectId,
26003
27186
  versionId: environment.versionId
26004
27187
  };
@@ -26013,6 +27196,7 @@ async function sessionCreateCommand(options) {
26013
27196
  Environment: payload.environment,
26014
27197
  "Environment ID": payload.environmentId,
26015
27198
  "Session ID": payload.sessionId,
27199
+ "Session scope": payload.sessionScope || "unscoped",
26016
27200
  "Subject ID": payload.subjectId,
26017
27201
  "Version ID": payload.versionId
26018
27202
  });
@@ -26028,6 +27212,21 @@ async function sessionCreateCommand(options) {
26028
27212
  async function sessionListCommand(options) {
26029
27213
  const emitJson = options.json === true;
26030
27214
  const status = options.status ?? "active";
27215
+ const allowedStatuses = /* @__PURE__ */ new Set([
27216
+ "active",
27217
+ "closed",
27218
+ "expired",
27219
+ "failed",
27220
+ "timeout",
27221
+ "all"
27222
+ ]);
27223
+ if (!allowedStatuses.has(status)) {
27224
+ throw new Error(
27225
+ "--status must be one of active, closed, expired, failed, timeout, or all."
27226
+ );
27227
+ }
27228
+ const limit = parseSessionListInteger(options.limit, "limit");
27229
+ const offset = parseSessionListInteger(options.offset, "offset");
26031
27230
  const requestedEnvironment = options.environment ?? "dev";
26032
27231
  if (!emitJson) {
26033
27232
  printHeader();
@@ -26037,18 +27236,33 @@ async function sessionListCommand(options) {
26037
27236
  ontology: options.ontology,
26038
27237
  environment: requestedEnvironment,
26039
27238
  environmentId: options.environmentId,
27239
+ subjectId: options.subjectId,
26040
27240
  createIfMissing: false
26041
27241
  });
26042
27242
  const items = await listSessionsForEnvironment(
26043
27243
  granular,
26044
27244
  environmentData.environmentId,
26045
- status
27245
+ {
27246
+ status,
27247
+ sessionScope: options.sessionScope?.trim() || void 0,
27248
+ subjectId: options.subjectId?.trim() || void 0,
27249
+ limit,
27250
+ offset
27251
+ }
26046
27252
  );
26047
27253
  const payload = {
26048
27254
  ontologyId: environmentData.sandboxId,
26049
27255
  environmentId: environmentData.environmentId,
26050
27256
  environment: environmentData.tag?.name || environmentData.buildPolicy.tagName || requestedEnvironment,
26051
27257
  status,
27258
+ sessionScope: options.sessionScope?.trim() || null,
27259
+ page: {
27260
+ limit,
27261
+ offset,
27262
+ returned: items.length,
27263
+ mayHaveMore: items.length === limit,
27264
+ nextOffset: items.length === limit ? offset + items.length : null
27265
+ },
26052
27266
  items
26053
27267
  };
26054
27268
  if (emitJson) {
@@ -26071,6 +27285,9 @@ async function sessionListCommand(options) {
26071
27285
  item.lastSeenAt
26072
27286
  ])
26073
27287
  );
27288
+ info(
27289
+ `Showing ${items.length} session${items.length === 1 ? "" : "s"} from offset ${offset}.` + (items.length === limit ? ` Use --offset ${offset + items.length} for the next page.` : "")
27290
+ );
26074
27291
  console.log();
26075
27292
  }
26076
27293
 
@@ -26977,6 +28194,9 @@ session.command("create").description(
26977
28194
  "--permissions <list>",
26978
28195
  "Comma-separated permission profile to ensure when a named environment needs to be created",
26979
28196
  "allow-all"
28197
+ ).option(
28198
+ "--session-scope <scope>",
28199
+ "Application-owned history scope to persist on the new session"
26980
28200
  ).option("--json", "Print machine-readable JSON").action(
26981
28201
  async (options) => {
26982
28202
  try {
@@ -26987,7 +28207,7 @@ session.command("create").description(
26987
28207
  }
26988
28208
  }
26989
28209
  );
26990
- session.command("list").description("List indexed sessions for an environment").option(
28210
+ session.command("list").description("List one bounded page of indexed sessions for an environment").option(
26991
28211
  "--ontology <ontologyId>",
26992
28212
  "Override the ontology id from .granularrc"
26993
28213
  ).option(
@@ -26997,11 +28217,17 @@ session.command("list").description("List indexed sessions for an environment").
26997
28217
  ).option(
26998
28218
  "--environment-id <environmentId>",
26999
28219
  "List sessions for this exact environment id"
28220
+ ).option(
28221
+ "--subject-id <subjectId>",
28222
+ "Resolve and constrain history to this internal Granular subject id"
28223
+ ).option(
28224
+ "--session-scope <scope>",
28225
+ "Return only sessions owned by this application history scope"
27000
28226
  ).option(
27001
28227
  "--status <status>",
27002
- "Session status filter: active|closed|all",
28228
+ "Session status: active|closed|expired|failed|timeout|all",
27003
28229
  "active"
27004
- ).option("--json", "Print machine-readable JSON").action(
28230
+ ).option("--limit <count>", "Rows to return (1-500)", "25").option("--offset <count>", "Rows to skip (0-100000)", "0").option("--json", "Print machine-readable JSON").action(
27005
28231
  async (options) => {
27006
28232
  try {
27007
28233
  await sessionListCommand(options);