@ours.network/cowork 1.1.3 → 1.2.0

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/daemon.js CHANGED
@@ -18,9 +18,9 @@ var __export = (target, all) => {
18
18
  };
19
19
  var __copyProps = (to, from, except, desc) => {
20
20
  if (from && typeof from === "object" || typeof from === "function") {
21
- for (let key of __getOwnPropNames(from))
22
- if (!__hasOwnProp.call(to, key) && key !== except)
23
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ for (let key2 of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
23
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
24
24
  }
25
25
  return to;
26
26
  };
@@ -69,9 +69,9 @@ var init_util = __esm({
69
69
  };
70
70
  util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object) => {
71
71
  const keys = [];
72
- for (const key in object) {
73
- if (Object.prototype.hasOwnProperty.call(object, key)) {
74
- keys.push(key);
72
+ for (const key2 in object) {
73
+ if (Object.prototype.hasOwnProperty.call(object, key2)) {
74
+ keys.push(key2);
75
75
  }
76
76
  }
77
77
  return keys;
@@ -496,10 +496,10 @@ var init_parseUtil = __esm({
496
496
  static async mergeObjectAsync(status, pairs) {
497
497
  const syncPairs = [];
498
498
  for (const pair of pairs) {
499
- const key = await pair.key;
499
+ const key2 = await pair.key;
500
500
  const value = await pair.value;
501
501
  syncPairs.push({
502
- key,
502
+ key: key2,
503
503
  value
504
504
  });
505
505
  }
@@ -508,17 +508,17 @@ var init_parseUtil = __esm({
508
508
  static mergeObjectSync(status, pairs) {
509
509
  const finalObject = {};
510
510
  for (const pair of pairs) {
511
- const { key, value } = pair;
512
- if (key.status === "aborted")
511
+ const { key: key2, value } = pair;
512
+ if (key2.status === "aborted")
513
513
  return INVALID;
514
514
  if (value.status === "aborted")
515
515
  return INVALID;
516
- if (key.status === "dirty")
516
+ if (key2.status === "dirty")
517
517
  status.dirty();
518
518
  if (value.status === "dirty")
519
519
  status.dirty();
520
- if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) {
521
- finalObject[key.value] = value.value;
520
+ if (key2.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) {
521
+ finalObject[key2.value] = value.value;
522
522
  }
523
523
  }
524
524
  return { status: status.value, value: finalObject };
@@ -650,9 +650,9 @@ function floatSafeRemainder(val, step) {
650
650
  function deepPartialify(schema) {
651
651
  if (schema instanceof ZodObject) {
652
652
  const newShape = {};
653
- for (const key in schema.shape) {
654
- const fieldSchema = schema.shape[key];
655
- newShape[key] = ZodOptional.create(deepPartialify(fieldSchema));
653
+ for (const key2 in schema.shape) {
654
+ const fieldSchema = schema.shape[key2];
655
+ newShape[key2] = ZodOptional.create(deepPartialify(fieldSchema));
656
656
  }
657
657
  return new ZodObject({
658
658
  ...schema._def,
@@ -680,14 +680,14 @@ function mergeValues(a, b) {
680
680
  return { valid: true, data: a };
681
681
  } else if (aType === ZodParsedType.object && bType === ZodParsedType.object) {
682
682
  const bKeys = util.objectKeys(b);
683
- const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1);
683
+ const sharedKeys = util.objectKeys(a).filter((key2) => bKeys.indexOf(key2) !== -1);
684
684
  const newObj = { ...a, ...b };
685
- for (const key of sharedKeys) {
686
- const sharedValue = mergeValues(a[key], b[key]);
685
+ for (const key2 of sharedKeys) {
686
+ const sharedValue = mergeValues(a[key2], b[key2]);
687
687
  if (!sharedValue.valid) {
688
688
  return { valid: false };
689
689
  }
690
- newObj[key] = sharedValue.data;
690
+ newObj[key2] = sharedValue.data;
691
691
  }
692
692
  return { valid: true, data: newObj };
693
693
  } else if (aType === ZodParsedType.array && bType === ZodParsedType.array) {
@@ -754,12 +754,12 @@ var init_types = __esm({
754
754
  init_parseUtil();
755
755
  init_util();
756
756
  ParseInputLazyPath = class {
757
- constructor(parent, value, path, key) {
757
+ constructor(parent, value, path, key2) {
758
758
  this._cachedPath = [];
759
759
  this.parent = parent;
760
760
  this.data = value;
761
761
  this._path = path;
762
- this._key = key;
762
+ this._key = key2;
763
763
  }
764
764
  get path() {
765
765
  if (!this._cachedPath.length) {
@@ -2437,29 +2437,29 @@ var init_types = __esm({
2437
2437
  const { shape, keys: shapeKeys } = this._getCached();
2438
2438
  const extraKeys = [];
2439
2439
  if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) {
2440
- for (const key in ctx.data) {
2441
- if (!shapeKeys.includes(key)) {
2442
- extraKeys.push(key);
2440
+ for (const key2 in ctx.data) {
2441
+ if (!shapeKeys.includes(key2)) {
2442
+ extraKeys.push(key2);
2443
2443
  }
2444
2444
  }
2445
2445
  }
2446
2446
  const pairs = [];
2447
- for (const key of shapeKeys) {
2448
- const keyValidator = shape[key];
2449
- const value = ctx.data[key];
2447
+ for (const key2 of shapeKeys) {
2448
+ const keyValidator = shape[key2];
2449
+ const value = ctx.data[key2];
2450
2450
  pairs.push({
2451
- key: { status: "valid", value: key },
2452
- value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)),
2453
- alwaysSet: key in ctx.data
2451
+ key: { status: "valid", value: key2 },
2452
+ value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key2)),
2453
+ alwaysSet: key2 in ctx.data
2454
2454
  });
2455
2455
  }
2456
2456
  if (this._def.catchall instanceof ZodNever) {
2457
2457
  const unknownKeys = this._def.unknownKeys;
2458
2458
  if (unknownKeys === "passthrough") {
2459
- for (const key of extraKeys) {
2459
+ for (const key2 of extraKeys) {
2460
2460
  pairs.push({
2461
- key: { status: "valid", value: key },
2462
- value: { status: "valid", value: ctx.data[key] }
2461
+ key: { status: "valid", value: key2 },
2462
+ value: { status: "valid", value: ctx.data[key2] }
2463
2463
  });
2464
2464
  }
2465
2465
  } else if (unknownKeys === "strict") {
@@ -2476,15 +2476,15 @@ var init_types = __esm({
2476
2476
  }
2477
2477
  } else {
2478
2478
  const catchall = this._def.catchall;
2479
- for (const key of extraKeys) {
2480
- const value = ctx.data[key];
2479
+ for (const key2 of extraKeys) {
2480
+ const value = ctx.data[key2];
2481
2481
  pairs.push({
2482
- key: { status: "valid", value: key },
2482
+ key: { status: "valid", value: key2 },
2483
2483
  value: catchall._parse(
2484
- new ParseInputLazyPath(ctx, value, ctx.path, key)
2484
+ new ParseInputLazyPath(ctx, value, ctx.path, key2)
2485
2485
  //, ctx.child(key), value, getParsedType(value)
2486
2486
  ),
2487
- alwaysSet: key in ctx.data
2487
+ alwaysSet: key2 in ctx.data
2488
2488
  });
2489
2489
  }
2490
2490
  }
@@ -2492,10 +2492,10 @@ var init_types = __esm({
2492
2492
  return Promise.resolve().then(async () => {
2493
2493
  const syncPairs = [];
2494
2494
  for (const pair of pairs) {
2495
- const key = await pair.key;
2495
+ const key2 = await pair.key;
2496
2496
  const value = await pair.value;
2497
2497
  syncPairs.push({
2498
- key,
2498
+ key: key2,
2499
2499
  value,
2500
2500
  alwaysSet: pair.alwaysSet
2501
2501
  });
@@ -2620,8 +2620,8 @@ var init_types = __esm({
2620
2620
  // }) as any;
2621
2621
  // return merged;
2622
2622
  // }
2623
- setKey(key, schema) {
2624
- return this.augment({ [key]: schema });
2623
+ setKey(key2, schema) {
2624
+ return this.augment({ [key2]: schema });
2625
2625
  }
2626
2626
  // merge<Incoming extends AnyZodObject>(
2627
2627
  // merging: Incoming
@@ -2652,9 +2652,9 @@ var init_types = __esm({
2652
2652
  }
2653
2653
  pick(mask) {
2654
2654
  const shape = {};
2655
- for (const key of util.objectKeys(mask)) {
2656
- if (mask[key] && this.shape[key]) {
2657
- shape[key] = this.shape[key];
2655
+ for (const key2 of util.objectKeys(mask)) {
2656
+ if (mask[key2] && this.shape[key2]) {
2657
+ shape[key2] = this.shape[key2];
2658
2658
  }
2659
2659
  }
2660
2660
  return new _ZodObject({
@@ -2664,9 +2664,9 @@ var init_types = __esm({
2664
2664
  }
2665
2665
  omit(mask) {
2666
2666
  const shape = {};
2667
- for (const key of util.objectKeys(this.shape)) {
2668
- if (!mask[key]) {
2669
- shape[key] = this.shape[key];
2667
+ for (const key2 of util.objectKeys(this.shape)) {
2668
+ if (!mask[key2]) {
2669
+ shape[key2] = this.shape[key2];
2670
2670
  }
2671
2671
  }
2672
2672
  return new _ZodObject({
@@ -2682,12 +2682,12 @@ var init_types = __esm({
2682
2682
  }
2683
2683
  partial(mask) {
2684
2684
  const newShape = {};
2685
- for (const key of util.objectKeys(this.shape)) {
2686
- const fieldSchema = this.shape[key];
2687
- if (mask && !mask[key]) {
2688
- newShape[key] = fieldSchema;
2685
+ for (const key2 of util.objectKeys(this.shape)) {
2686
+ const fieldSchema = this.shape[key2];
2687
+ if (mask && !mask[key2]) {
2688
+ newShape[key2] = fieldSchema;
2689
2689
  } else {
2690
- newShape[key] = fieldSchema.optional();
2690
+ newShape[key2] = fieldSchema.optional();
2691
2691
  }
2692
2692
  }
2693
2693
  return new _ZodObject({
@@ -2697,16 +2697,16 @@ var init_types = __esm({
2697
2697
  }
2698
2698
  required(mask) {
2699
2699
  const newShape = {};
2700
- for (const key of util.objectKeys(this.shape)) {
2701
- if (mask && !mask[key]) {
2702
- newShape[key] = this.shape[key];
2700
+ for (const key2 of util.objectKeys(this.shape)) {
2701
+ if (mask && !mask[key2]) {
2702
+ newShape[key2] = this.shape[key2];
2703
2703
  } else {
2704
- const fieldSchema = this.shape[key];
2704
+ const fieldSchema = this.shape[key2];
2705
2705
  let newField = fieldSchema;
2706
2706
  while (newField instanceof ZodOptional) {
2707
2707
  newField = newField._def.innerType;
2708
2708
  }
2709
- newShape[key] = newField;
2709
+ newShape[key2] = newField;
2710
2710
  }
2711
2711
  }
2712
2712
  return new _ZodObject({
@@ -3083,11 +3083,11 @@ var init_types = __esm({
3083
3083
  const pairs = [];
3084
3084
  const keyType = this._def.keyType;
3085
3085
  const valueType = this._def.valueType;
3086
- for (const key in ctx.data) {
3086
+ for (const key2 in ctx.data) {
3087
3087
  pairs.push({
3088
- key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)),
3089
- value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)),
3090
- alwaysSet: key in ctx.data
3088
+ key: keyType._parse(new ParseInputLazyPath(ctx, key2, ctx.path, key2)),
3089
+ value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key2], ctx.path, key2)),
3090
+ alwaysSet: key2 in ctx.data
3091
3091
  });
3092
3092
  }
3093
3093
  if (ctx.common.async) {
@@ -3135,9 +3135,9 @@ var init_types = __esm({
3135
3135
  }
3136
3136
  const keyType = this._def.keyType;
3137
3137
  const valueType = this._def.valueType;
3138
- const pairs = [...ctx.data.entries()].map(([key, value], index) => {
3138
+ const pairs = [...ctx.data.entries()].map(([key2, value], index) => {
3139
3139
  return {
3140
- key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])),
3140
+ key: keyType._parse(new ParseInputLazyPath(ctx, key2, ctx.path, [index, "key"])),
3141
3141
  value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"]))
3142
3142
  };
3143
3143
  });
@@ -3145,30 +3145,30 @@ var init_types = __esm({
3145
3145
  const finalMap = /* @__PURE__ */ new Map();
3146
3146
  return Promise.resolve().then(async () => {
3147
3147
  for (const pair of pairs) {
3148
- const key = await pair.key;
3148
+ const key2 = await pair.key;
3149
3149
  const value = await pair.value;
3150
- if (key.status === "aborted" || value.status === "aborted") {
3150
+ if (key2.status === "aborted" || value.status === "aborted") {
3151
3151
  return INVALID;
3152
3152
  }
3153
- if (key.status === "dirty" || value.status === "dirty") {
3153
+ if (key2.status === "dirty" || value.status === "dirty") {
3154
3154
  status.dirty();
3155
3155
  }
3156
- finalMap.set(key.value, value.value);
3156
+ finalMap.set(key2.value, value.value);
3157
3157
  }
3158
3158
  return { status: status.value, value: finalMap };
3159
3159
  });
3160
3160
  } else {
3161
3161
  const finalMap = /* @__PURE__ */ new Map();
3162
3162
  for (const pair of pairs) {
3163
- const key = pair.key;
3163
+ const key2 = pair.key;
3164
3164
  const value = pair.value;
3165
- if (key.status === "aborted" || value.status === "aborted") {
3165
+ if (key2.status === "aborted" || value.status === "aborted") {
3166
3166
  return INVALID;
3167
3167
  }
3168
- if (key.status === "dirty" || value.status === "dirty") {
3168
+ if (key2.status === "dirty" || value.status === "dirty") {
3169
3169
  status.dirty();
3170
3170
  }
3171
- finalMap.set(key.value, value.value);
3171
+ finalMap.set(key2.value, value.value);
3172
3172
  }
3173
3173
  return { status: status.value, value: finalMap };
3174
3174
  }
@@ -4275,15 +4275,15 @@ var require_code = __commonJS({
4275
4275
  return JSON.stringify(x).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4276
4276
  }
4277
4277
  exports.safeStringify = safeStringify;
4278
- function getProperty(key) {
4279
- return typeof key == "string" && exports.IDENTIFIER.test(key) ? new _Code(`.${key}`) : _`[${key}]`;
4278
+ function getProperty(key2) {
4279
+ return typeof key2 == "string" && exports.IDENTIFIER.test(key2) ? new _Code(`.${key2}`) : _`[${key2}]`;
4280
4280
  }
4281
4281
  exports.getProperty = getProperty;
4282
- function getEsmExportName(key) {
4283
- if (typeof key == "string" && exports.IDENTIFIER.test(key)) {
4284
- return new _Code(`${key}`);
4282
+ function getEsmExportName(key2) {
4283
+ if (typeof key2 == "string" && exports.IDENTIFIER.test(key2)) {
4284
+ return new _Code(`${key2}`);
4285
4285
  }
4286
- throw new Error(`CodeGen: invalid export name: ${key}, use explicit $id name mapping`);
4286
+ throw new Error(`CodeGen: invalid export name: ${key2}, use explicit $id name mapping`);
4287
4287
  }
4288
4288
  exports.getEsmExportName = getEsmExportName;
4289
4289
  function regexpCode(rx) {
@@ -4910,11 +4910,11 @@ var require_codegen = __commonJS({
4910
4910
  // returns code for object literal for the passed argument list of key-value pairs
4911
4911
  object(...keyValues) {
4912
4912
  const code = ["{"];
4913
- for (const [key, value] of keyValues) {
4913
+ for (const [key2, value] of keyValues) {
4914
4914
  if (code.length > 1)
4915
4915
  code.push(",");
4916
- code.push(key);
4917
- if (key !== value || this.opts.es5) {
4916
+ code.push(key2);
4917
+ if (key2 !== value || this.opts.es5) {
4918
4918
  code.push(":");
4919
4919
  (0, code_1.addCodeArg)(code, value);
4920
4920
  }
@@ -5189,17 +5189,17 @@ var require_util = __commonJS({
5189
5189
  if (typeof schema === "boolean")
5190
5190
  return;
5191
5191
  const rules = self.RULES.keywords;
5192
- for (const key in schema) {
5193
- if (!rules[key])
5194
- checkStrictMode(it, `unknown keyword: "${key}"`);
5192
+ for (const key2 in schema) {
5193
+ if (!rules[key2])
5194
+ checkStrictMode(it, `unknown keyword: "${key2}"`);
5195
5195
  }
5196
5196
  }
5197
5197
  exports.checkUnknownRules = checkUnknownRules;
5198
5198
  function schemaHasRules(schema, rules) {
5199
5199
  if (typeof schema == "boolean")
5200
5200
  return !schema;
5201
- for (const key in schema)
5202
- if (rules[key])
5201
+ for (const key2 in schema)
5202
+ if (rules[key2])
5203
5203
  return true;
5204
5204
  return false;
5205
5205
  }
@@ -5207,8 +5207,8 @@ var require_util = __commonJS({
5207
5207
  function schemaHasRulesButRef(schema, RULES) {
5208
5208
  if (typeof schema == "boolean")
5209
5209
  return !schema;
5210
- for (const key in schema)
5211
- if (key !== "$ref" && RULES.all[key])
5210
+ for (const key2 in schema)
5211
+ if (key2 !== "$ref" && RULES.all[key2])
5212
5212
  return true;
5213
5213
  return false;
5214
5214
  }
@@ -5786,8 +5786,8 @@ var require_defaults = __commonJS({
5786
5786
  function assignDefaults(it, ty) {
5787
5787
  const { properties, items } = it.schema;
5788
5788
  if (ty === "object" && properties) {
5789
- for (const key in properties) {
5790
- assignDefault(it, key, properties[key].default);
5789
+ for (const key2 in properties) {
5790
+ assignDefault(it, key2, properties[key2].default);
5791
5791
  }
5792
5792
  } else if (ty === "array" && Array.isArray(items)) {
5793
5793
  items.forEach((sch, i) => assignDefault(it, i, sch.default));
@@ -6171,8 +6171,8 @@ var require_fast_deep_equal = __commonJS({
6171
6171
  for (i = length; i-- !== 0; )
6172
6172
  if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
6173
6173
  for (i = length; i-- !== 0; ) {
6174
- var key = keys[i];
6175
- if (!equal(a[key], b[key])) return false;
6174
+ var key2 = keys[i];
6175
+ if (!equal(a[key2], b[key2])) return false;
6176
6176
  }
6177
6177
  return true;
6178
6178
  }
@@ -6244,20 +6244,20 @@ var require_json_schema_traverse = __commonJS({
6244
6244
  function _traverse(opts, pre, post, schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
6245
6245
  if (schema && typeof schema == "object" && !Array.isArray(schema)) {
6246
6246
  pre(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
6247
- for (var key in schema) {
6248
- var sch = schema[key];
6247
+ for (var key2 in schema) {
6248
+ var sch = schema[key2];
6249
6249
  if (Array.isArray(sch)) {
6250
- if (key in traverse.arrayKeywords) {
6250
+ if (key2 in traverse.arrayKeywords) {
6251
6251
  for (var i = 0; i < sch.length; i++)
6252
- _traverse(opts, pre, post, sch[i], jsonPtr + "/" + key + "/" + i, rootSchema, jsonPtr, key, schema, i);
6252
+ _traverse(opts, pre, post, sch[i], jsonPtr + "/" + key2 + "/" + i, rootSchema, jsonPtr, key2, schema, i);
6253
6253
  }
6254
- } else if (key in traverse.propsKeywords) {
6254
+ } else if (key2 in traverse.propsKeywords) {
6255
6255
  if (sch && typeof sch == "object") {
6256
6256
  for (var prop in sch)
6257
- _traverse(opts, pre, post, sch[prop], jsonPtr + "/" + key + "/" + escapeJsonPtr(prop), rootSchema, jsonPtr, key, schema, prop);
6257
+ _traverse(opts, pre, post, sch[prop], jsonPtr + "/" + key2 + "/" + escapeJsonPtr(prop), rootSchema, jsonPtr, key2, schema, prop);
6258
6258
  }
6259
- } else if (key in traverse.keywords || opts.allKeys && !(key in traverse.skipKeywords)) {
6260
- _traverse(opts, pre, post, sch, jsonPtr + "/" + key, rootSchema, jsonPtr, key, schema);
6259
+ } else if (key2 in traverse.keywords || opts.allKeys && !(key2 in traverse.skipKeywords)) {
6260
+ _traverse(opts, pre, post, sch, jsonPtr + "/" + key2, rootSchema, jsonPtr, key2, schema);
6261
6261
  }
6262
6262
  }
6263
6263
  post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
@@ -6314,10 +6314,10 @@ var require_resolve = __commonJS({
6314
6314
  "$dynamicAnchor"
6315
6315
  ]);
6316
6316
  function hasRef(schema) {
6317
- for (const key in schema) {
6318
- if (REF_KEYWORDS.has(key))
6317
+ for (const key2 in schema) {
6318
+ if (REF_KEYWORDS.has(key2))
6319
6319
  return true;
6320
- const sch = schema[key];
6320
+ const sch = schema[key2];
6321
6321
  if (Array.isArray(sch) && sch.some(hasRef))
6322
6322
  return true;
6323
6323
  if (typeof sch == "object" && hasRef(sch))
@@ -6327,14 +6327,14 @@ var require_resolve = __commonJS({
6327
6327
  }
6328
6328
  function countKeys(schema) {
6329
6329
  let count = 0;
6330
- for (const key in schema) {
6331
- if (key === "$ref")
6330
+ for (const key2 in schema) {
6331
+ if (key2 === "$ref")
6332
6332
  return Infinity;
6333
6333
  count++;
6334
- if (SIMPLE_INLINED.has(key))
6334
+ if (SIMPLE_INLINED.has(key2))
6335
6335
  continue;
6336
- if (typeof schema[key] == "object") {
6337
- (0, util_1.eachItem)(schema[key], (sch) => count += countKeys(sch));
6336
+ if (typeof schema[key2] == "object") {
6337
+ (0, util_1.eachItem)(schema[key2], (sch) => count += countKeys(sch));
6338
6338
  }
6339
6339
  if (count === Infinity)
6340
6340
  return Infinity;
@@ -6523,8 +6523,8 @@ var require_validate = __commonJS({
6523
6523
  function schemaCxtHasRules({ schema, self }) {
6524
6524
  if (typeof schema == "boolean")
6525
6525
  return !schema;
6526
- for (const key in schema)
6527
- if (self.RULES.all[key])
6526
+ for (const key2 in schema)
6527
+ if (self.RULES.all[key2])
6528
6528
  return true;
6529
6529
  return false;
6530
6530
  }
@@ -8562,7 +8562,7 @@ var require_core = __commonJS({
8562
8562
  }
8563
8563
  }
8564
8564
  // Adds schema to the instance
8565
- addSchema(schema, key, _meta, _validateSchema = this.opts.validateSchema) {
8565
+ addSchema(schema, key2, _meta, _validateSchema = this.opts.validateSchema) {
8566
8566
  if (Array.isArray(schema)) {
8567
8567
  for (const sch of schema)
8568
8568
  this.addSchema(sch, void 0, _meta, _validateSchema);
@@ -8576,15 +8576,15 @@ var require_core = __commonJS({
8576
8576
  throw new Error(`schema ${schemaId} must be string`);
8577
8577
  }
8578
8578
  }
8579
- key = (0, resolve_1.normalizeId)(key || id);
8580
- this._checkUnique(key);
8581
- this.schemas[key] = this._addSchema(schema, _meta, key, _validateSchema, true);
8579
+ key2 = (0, resolve_1.normalizeId)(key2 || id);
8580
+ this._checkUnique(key2);
8581
+ this.schemas[key2] = this._addSchema(schema, _meta, key2, _validateSchema, true);
8582
8582
  return this;
8583
8583
  }
8584
8584
  // Add schema that will be used to validate other schemas
8585
8585
  // options in META_IGNORE_OPTIONS are alway set to false
8586
- addMetaSchema(schema, key, _validateSchema = this.opts.validateSchema) {
8587
- this.addSchema(schema, key, true, _validateSchema);
8586
+ addMetaSchema(schema, key2, _validateSchema = this.opts.validateSchema) {
8587
+ this.addSchema(schema, key2, true, _validateSchema);
8588
8588
  return this;
8589
8589
  }
8590
8590
  // Validate schema against its meta-schema
@@ -8740,14 +8740,14 @@ var require_core = __commonJS({
8740
8740
  let keywords = metaSchema;
8741
8741
  for (const seg of segments)
8742
8742
  keywords = keywords[seg];
8743
- for (const key in rules) {
8744
- const rule = rules[key];
8743
+ for (const key2 in rules) {
8744
+ const rule = rules[key2];
8745
8745
  if (typeof rule != "object")
8746
8746
  continue;
8747
8747
  const { $data } = rule.definition;
8748
- const schema = keywords[key];
8748
+ const schema = keywords[key2];
8749
8749
  if ($data && schema)
8750
- keywords[key] = schemaOrData(schema);
8750
+ keywords[key2] = schemaOrData(schema);
8751
8751
  }
8752
8752
  }
8753
8753
  return metaSchema;
@@ -8820,10 +8820,10 @@ var require_core = __commonJS({
8820
8820
  Ajv2.MissingRefError = ref_error_1.default;
8821
8821
  exports.default = Ajv2;
8822
8822
  function checkOptions(checkOpts, options, msg, log = "error") {
8823
- for (const key in checkOpts) {
8824
- const opt = key;
8823
+ for (const key2 in checkOpts) {
8824
+ const opt = key2;
8825
8825
  if (opt in options)
8826
- this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`);
8826
+ this.logger[log](`${msg}: option ${key2}. ${checkOpts[opt]}`);
8827
8827
  }
8828
8828
  }
8829
8829
  function getSchEnv(keyRef) {
@@ -8837,8 +8837,8 @@ var require_core = __commonJS({
8837
8837
  if (Array.isArray(optsSchemas))
8838
8838
  this.addSchema(optsSchemas);
8839
8839
  else
8840
- for (const key in optsSchemas)
8841
- this.addSchema(optsSchemas[key], key);
8840
+ for (const key2 in optsSchemas)
8841
+ this.addSchema(optsSchemas[key2], key2);
8842
8842
  }
8843
8843
  function addInitialFormats() {
8844
8844
  for (const name in this.opts.formats) {
@@ -9886,11 +9886,11 @@ var require_dependencies = __commonJS({
9886
9886
  function splitDependencies({ schema }) {
9887
9887
  const propertyDeps = {};
9888
9888
  const schemaDeps = {};
9889
- for (const key in schema) {
9890
- if (key === "__proto__")
9889
+ for (const key2 in schema) {
9890
+ if (key2 === "__proto__")
9891
9891
  continue;
9892
- const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps;
9893
- deps[key] = schema[key];
9892
+ const deps = Array.isArray(schema[key2]) ? propertyDeps : schemaDeps;
9893
+ deps[key2] = schema[key2];
9894
9894
  }
9895
9895
  return [propertyDeps, schemaDeps];
9896
9896
  }
@@ -9967,13 +9967,13 @@ var require_propertyNames = __commonJS({
9967
9967
  if ((0, util_1.alwaysValidSchema)(it, schema))
9968
9968
  return;
9969
9969
  const valid = gen.name("valid");
9970
- gen.forIn("key", data, (key) => {
9971
- cxt.setParams({ propertyName: key });
9970
+ gen.forIn("key", data, (key2) => {
9971
+ cxt.setParams({ propertyName: key2 });
9972
9972
  cxt.subschema({
9973
9973
  keyword: "propertyNames",
9974
- data: key,
9974
+ data: key2,
9975
9975
  dataTypes: ["string"],
9976
- propertyName: key,
9976
+ propertyName: key2,
9977
9977
  compositeRule: true
9978
9978
  }, valid);
9979
9979
  gen.if((0, codegen_1.not)(valid), () => {
@@ -10022,38 +10022,38 @@ var require_additionalProperties = __commonJS({
10022
10022
  checkAdditionalProperties();
10023
10023
  cxt.ok((0, codegen_1._)`${errsCount} === ${names_1.default.errors}`);
10024
10024
  function checkAdditionalProperties() {
10025
- gen.forIn("key", data, (key) => {
10025
+ gen.forIn("key", data, (key2) => {
10026
10026
  if (!props.length && !patProps.length)
10027
- additionalPropertyCode(key);
10027
+ additionalPropertyCode(key2);
10028
10028
  else
10029
- gen.if(isAdditional(key), () => additionalPropertyCode(key));
10029
+ gen.if(isAdditional(key2), () => additionalPropertyCode(key2));
10030
10030
  });
10031
10031
  }
10032
- function isAdditional(key) {
10032
+ function isAdditional(key2) {
10033
10033
  let definedProp;
10034
10034
  if (props.length > 8) {
10035
10035
  const propsSchema = (0, util_1.schemaRefOrVal)(it, parentSchema.properties, "properties");
10036
- definedProp = (0, code_1.isOwnProperty)(gen, propsSchema, key);
10036
+ definedProp = (0, code_1.isOwnProperty)(gen, propsSchema, key2);
10037
10037
  } else if (props.length) {
10038
- definedProp = (0, codegen_1.or)(...props.map((p) => (0, codegen_1._)`${key} === ${p}`));
10038
+ definedProp = (0, codegen_1.or)(...props.map((p) => (0, codegen_1._)`${key2} === ${p}`));
10039
10039
  } else {
10040
10040
  definedProp = codegen_1.nil;
10041
10041
  }
10042
10042
  if (patProps.length) {
10043
- definedProp = (0, codegen_1.or)(definedProp, ...patProps.map((p) => (0, codegen_1._)`${(0, code_1.usePattern)(cxt, p)}.test(${key})`));
10043
+ definedProp = (0, codegen_1.or)(definedProp, ...patProps.map((p) => (0, codegen_1._)`${(0, code_1.usePattern)(cxt, p)}.test(${key2})`));
10044
10044
  }
10045
10045
  return (0, codegen_1.not)(definedProp);
10046
10046
  }
10047
- function deleteAdditional(key) {
10048
- gen.code((0, codegen_1._)`delete ${data}[${key}]`);
10047
+ function deleteAdditional(key2) {
10048
+ gen.code((0, codegen_1._)`delete ${data}[${key2}]`);
10049
10049
  }
10050
- function additionalPropertyCode(key) {
10050
+ function additionalPropertyCode(key2) {
10051
10051
  if (opts.removeAdditional === "all" || opts.removeAdditional && schema === false) {
10052
- deleteAdditional(key);
10052
+ deleteAdditional(key2);
10053
10053
  return;
10054
10054
  }
10055
10055
  if (schema === false) {
10056
- cxt.setParams({ additionalProperty: key });
10056
+ cxt.setParams({ additionalProperty: key2 });
10057
10057
  cxt.error();
10058
10058
  if (!allErrors)
10059
10059
  gen.break();
@@ -10062,22 +10062,22 @@ var require_additionalProperties = __commonJS({
10062
10062
  if (typeof schema == "object" && !(0, util_1.alwaysValidSchema)(it, schema)) {
10063
10063
  const valid = gen.name("valid");
10064
10064
  if (opts.removeAdditional === "failing") {
10065
- applyAdditionalSchema(key, valid, false);
10065
+ applyAdditionalSchema(key2, valid, false);
10066
10066
  gen.if((0, codegen_1.not)(valid), () => {
10067
10067
  cxt.reset();
10068
- deleteAdditional(key);
10068
+ deleteAdditional(key2);
10069
10069
  });
10070
10070
  } else {
10071
- applyAdditionalSchema(key, valid);
10071
+ applyAdditionalSchema(key2, valid);
10072
10072
  if (!allErrors)
10073
10073
  gen.if((0, codegen_1.not)(valid), () => gen.break());
10074
10074
  }
10075
10075
  }
10076
10076
  }
10077
- function applyAdditionalSchema(key, valid, errors) {
10077
+ function applyAdditionalSchema(key2, valid, errors) {
10078
10078
  const subschema = {
10079
10079
  keyword: "additionalProperties",
10080
- dataProp: key,
10080
+ dataProp: key2,
10081
10081
  dataPropType: util_1.Type.Str
10082
10082
  };
10083
10083
  if (errors === false) {
@@ -10202,19 +10202,19 @@ var require_patternProperties = __commonJS({
10202
10202
  }
10203
10203
  }
10204
10204
  function validateProperties(pat) {
10205
- gen.forIn("key", data, (key) => {
10206
- gen.if((0, codegen_1._)`${(0, code_1.usePattern)(cxt, pat)}.test(${key})`, () => {
10205
+ gen.forIn("key", data, (key2) => {
10206
+ gen.if((0, codegen_1._)`${(0, code_1.usePattern)(cxt, pat)}.test(${key2})`, () => {
10207
10207
  const alwaysValid = alwaysValidPatterns.includes(pat);
10208
10208
  if (!alwaysValid) {
10209
10209
  cxt.subschema({
10210
10210
  keyword: "patternProperties",
10211
10211
  schemaProp: pat,
10212
- dataProp: key,
10212
+ dataProp: key2,
10213
10213
  dataPropType: util_2.Type.Str
10214
10214
  }, valid);
10215
10215
  }
10216
10216
  if (it.opts.unevaluated && props !== true) {
10217
- gen.assign((0, codegen_1._)`${props}[${key}]`, true);
10217
+ gen.assign((0, codegen_1._)`${props}[${key2}]`, true);
10218
10218
  } else if (!alwaysValid && !it.allErrors) {
10219
10219
  gen.if((0, codegen_1.not)(valid), () => gen.break());
10220
10220
  }
@@ -11120,8 +11120,8 @@ var init_consumer_commands = __esm({
11120
11120
  const visit = (value, depth = 0) => {
11121
11121
  if (depth > 16) throw new Error("consumer input schema is too deeply nested");
11122
11122
  if (!value || typeof value !== "object") return;
11123
- for (const [key, child] of Object.entries(value)) {
11124
- if (["$ref", "$id", "$async", "pattern", "patternProperties", "format"].includes(key)) {
11123
+ for (const [key2, child] of Object.entries(value)) {
11124
+ if (["$ref", "$id", "$async", "pattern", "patternProperties", "format"].includes(key2)) {
11125
11125
  throw new Error("consumer input schema contains an unsupported keyword");
11126
11126
  }
11127
11127
  visit(child, depth + 1);
@@ -11275,7 +11275,7 @@ function rejectRemovedEnvironment(env) {
11275
11275
  }
11276
11276
  function rejectRemovedConfig(value, path) {
11277
11277
  if (value === null || typeof value !== "object" || Array.isArray(value)) return;
11278
- const removed = ["brokerUrl", "daemon"].filter((key) => Object.hasOwn(value, key));
11278
+ const removed = ["brokerUrl", "daemon"].filter((key2) => Object.hasOwn(value, key2));
11279
11279
  if (removed.length === 0) return;
11280
11280
  throw new CoworkConfigError(
11281
11281
  `cowork config at ${path} contains removed ${removed.join(" and ")} ${removed.length === 1 ? "key" : "keys"}: ours-cowork now attaches only to the shared ours daemon. Remove those keys and configure the daemon with @ours.network/cli.`
@@ -11656,6 +11656,7 @@ var init_command_names = __esm({
11656
11656
  "room.role.rest.remove"
11657
11657
  ];
11658
11658
  RUNTIME_COMMAND_NAMES = [
11659
+ "start_thread",
11659
11660
  "list-members",
11660
11661
  "remove-member",
11661
11662
  ...SHARED_ROOM_COMMANDS
@@ -11663,6 +11664,93 @@ var init_command_names = __esm({
11663
11664
  }
11664
11665
  });
11665
11666
 
11667
+ // src/thread-contracts.ts
11668
+ var ParticipantIdSchema, ContainerIdSchema, TopicTextSchema, InputTopicSchema, StoredTopicSchema, IdempotencyKeySchema, ThreadMemberSchema, ThreadRootSchema, ThreadScopeSchema, StartThreadInputSchema, ThreadFailure;
11669
+ var init_thread_contracts = __esm({
11670
+ "src/thread-contracts.ts"() {
11671
+ "use strict";
11672
+ init_zod();
11673
+ ParticipantIdSchema = external_exports.string().regex(
11674
+ /^[0-7][0-9a-hjkmnp-tv-z]{25}$/,
11675
+ "must be a 26-character lowercase Crockford ULID"
11676
+ );
11677
+ ContainerIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
11678
+ TopicTextSchema = external_exports.string().refine(
11679
+ (value) => Array.from(value).length >= 1 && Array.from(value).length <= 120 && value.trim().length > 0 && !/[\p{Cc}\p{Cf}]/u.test(value),
11680
+ "topic must contain 1-120 Unicode characters without control or format characters"
11681
+ );
11682
+ InputTopicSchema = TopicTextSchema.transform((value) => value.trim());
11683
+ StoredTopicSchema = TopicTextSchema.refine(
11684
+ (value) => value === value.trim(),
11685
+ "stored topic must already be trimmed"
11686
+ );
11687
+ IdempotencyKeySchema = external_exports.string().regex(
11688
+ /^[A-Za-z0-9._:-]{1,128}$/,
11689
+ "must contain 1-128 portable idempotency-key characters"
11690
+ );
11691
+ ThreadMemberSchema = external_exports.object({
11692
+ participant_id: ParticipantIdSchema,
11693
+ identity: ContainerIdSchema
11694
+ }).strict();
11695
+ ThreadRootSchema = external_exports.object({
11696
+ schema_version: external_exports.literal(1),
11697
+ thread_id: ParticipantIdSchema,
11698
+ topic: StoredTopicSchema,
11699
+ creator_participant_id: ParticipantIdSchema,
11700
+ members: external_exports.array(ThreadMemberSchema).min(1),
11701
+ idempotency_key: IdempotencyKeySchema,
11702
+ fingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/, "must be a lowercase SHA-256 digest")
11703
+ }).strict().superRefine((root, context) => {
11704
+ const participantIds = /* @__PURE__ */ new Set();
11705
+ const identities = /* @__PURE__ */ new Set();
11706
+ for (const [index, member] of root.members.entries()) {
11707
+ if (participantIds.has(member.participant_id)) {
11708
+ context.addIssue({
11709
+ code: external_exports.ZodIssueCode.custom,
11710
+ path: ["members", index, "participant_id"],
11711
+ message: "thread member participant IDs must be unique"
11712
+ });
11713
+ }
11714
+ participantIds.add(member.participant_id);
11715
+ if (identities.has(member.identity)) {
11716
+ context.addIssue({
11717
+ code: external_exports.ZodIssueCode.custom,
11718
+ path: ["members", index, "identity"],
11719
+ message: "thread member identities must be unique"
11720
+ });
11721
+ }
11722
+ identities.add(member.identity);
11723
+ }
11724
+ if (!participantIds.has(root.creator_participant_id)) {
11725
+ context.addIssue({
11726
+ code: external_exports.ZodIssueCode.custom,
11727
+ path: ["creator_participant_id"],
11728
+ message: "thread creator must be a member"
11729
+ });
11730
+ }
11731
+ });
11732
+ ThreadScopeSchema = external_exports.object({
11733
+ thread_id: ParticipantIdSchema,
11734
+ parent_key: external_exports.string().regex(
11735
+ /^(?:message|file):[0-7][0-9a-hjkmnp-tv-z]{25}$/,
11736
+ "must identify an immediate message or file parent"
11737
+ ).optional()
11738
+ }).strict();
11739
+ StartThreadInputSchema = external_exports.object({
11740
+ topic: InputTopicSchema,
11741
+ participant_ids: external_exports.array(ParticipantIdSchema).min(1),
11742
+ idempotency_key: IdempotencyKeySchema
11743
+ }).strict();
11744
+ ThreadFailure = class extends Error {
11745
+ constructor(code) {
11746
+ super(code);
11747
+ this.code = code;
11748
+ this.name = "ThreadFailure";
11749
+ }
11750
+ };
11751
+ }
11752
+ });
11753
+
11666
11754
  // src/contracts.ts
11667
11755
  import { createHash } from "node:crypto";
11668
11756
  function utf8Bounded(label, maximumBytes) {
@@ -11804,6 +11892,18 @@ function refineRelaySubject(record, context) {
11804
11892
  });
11805
11893
  }
11806
11894
  }
11895
+ function refineIntakeRejection(record, context) {
11896
+ if (record.kind !== "intake_rejection") return;
11897
+ const hasMessage = record.source_msg_id !== void 0;
11898
+ const hasFile = record.source_file_id !== void 0;
11899
+ if (hasMessage === hasFile || (record.source_kind === "message" ? !hasMessage : !hasFile)) {
11900
+ context.addIssue({
11901
+ code: external_exports.ZodIssueCode.custom,
11902
+ path: ["source_kind"],
11903
+ message: "intake rejections require exactly one numeric source field matching source_kind"
11904
+ });
11905
+ }
11906
+ }
11807
11907
  function refineFileRecord(record, context) {
11808
11908
  if (record.kind !== "file" || record.data_base64 === void 0) return;
11809
11909
  const bytes = Buffer.from(record.data_base64, "base64");
@@ -11854,13 +11954,110 @@ function refineMessageCategory(message, context) {
11854
11954
  }
11855
11955
  }
11856
11956
  }
11857
- var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_EXTERNAL_INVITE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, MAX_ROOM_IDENTITY_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, ContainerIdSchema, RuntimeCommandNameSchema, RuntimeCommandGrantSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, MAX_ROOM_IDENTITY_TITLE_CHARACTERS, SDK_IDENTITY_NAME_FORBIDDEN, SDK_IDENTITY_NAME_RESERVED, CoworkIdentityNameError, RoleSchema, ROOM_ROLE, RuntimeRoleCommandGrantSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, PostAsRoleInputSchema, RestRoleInputSchema, AcceptExternalInviteInputSchema, ListMembersCommandInputSchema, CommandIdempotencyKeySchema, RemoveMemberCommandInputSchema, RuntimeCommandGrantInputSchema, RuntimeRoleCommandGrantInputSchema, RuntimeCommandAuditSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, ReplyReferenceSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
11957
+ function refineMessageThread(message, context) {
11958
+ if (message.thread_root !== void 0) {
11959
+ if (message.scope === void 0) {
11960
+ context.addIssue({
11961
+ code: external_exports.ZodIssueCode.custom,
11962
+ path: ["scope"],
11963
+ message: "thread root messages require scope"
11964
+ });
11965
+ return;
11966
+ }
11967
+ if (message.scope.thread_id !== message.message_id || message.thread_root.thread_id !== message.message_id) {
11968
+ context.addIssue({
11969
+ code: external_exports.ZodIssueCode.custom,
11970
+ path: ["thread_root", "thread_id"],
11971
+ message: "thread root thread_id and scope thread_id must equal message_id"
11972
+ });
11973
+ }
11974
+ if (message.scope.parent_key !== void 0) {
11975
+ context.addIssue({
11976
+ code: external_exports.ZodIssueCode.custom,
11977
+ path: ["scope", "parent_key"],
11978
+ message: "parent_key is forbidden on thread root messages"
11979
+ });
11980
+ }
11981
+ if (message.category !== "chat") {
11982
+ context.addIssue({
11983
+ code: external_exports.ZodIssueCode.custom,
11984
+ path: ["category"],
11985
+ message: "thread root messages must be chat messages"
11986
+ });
11987
+ }
11988
+ if (message.text !== `Thread: ${message.thread_root.topic}`) {
11989
+ context.addIssue({
11990
+ code: external_exports.ZodIssueCode.custom,
11991
+ path: ["text"],
11992
+ message: "thread root message text must identify its topic"
11993
+ });
11994
+ }
11995
+ const creator = message.thread_root.members.find(
11996
+ (member) => member.participant_id === message.thread_root?.creator_participant_id
11997
+ );
11998
+ if (creator?.identity !== message.author.identity) {
11999
+ context.addIssue({
12000
+ code: external_exports.ZodIssueCode.custom,
12001
+ path: ["thread_root", "creator_participant_id"],
12002
+ message: "thread root creator must match the message author"
12003
+ });
12004
+ }
12005
+ if (message.author_alias !== void 0 && message.author_alias.participant_id !== message.thread_root.creator_participant_id) {
12006
+ context.addIssue({
12007
+ code: external_exports.ZodIssueCode.custom,
12008
+ path: ["author_alias", "participant_id"],
12009
+ message: "thread root author alias must identify the creator"
12010
+ });
12011
+ }
12012
+ for (const field of ["source_msg_id", "source_wire_id", "source_reply_to"]) {
12013
+ if (message[field] !== void 0) {
12014
+ context.addIssue({
12015
+ code: external_exports.ZodIssueCode.custom,
12016
+ path: [field],
12017
+ message: `${field} is forbidden on thread root messages`
12018
+ });
12019
+ }
12020
+ }
12021
+ return;
12022
+ }
12023
+ if (message.scope === void 0) return;
12024
+ if (message.scope.parent_key === void 0) {
12025
+ context.addIssue({
12026
+ code: external_exports.ZodIssueCode.custom,
12027
+ path: ["scope", "parent_key"],
12028
+ message: "thread descendants require an immediate parent_key"
12029
+ });
12030
+ }
12031
+ if (message.scope.thread_id === message.message_id) {
12032
+ context.addIssue({
12033
+ code: external_exports.ZodIssueCode.custom,
12034
+ path: ["scope", "thread_id"],
12035
+ message: "thread descendant thread_id must identify a distinct root message"
12036
+ });
12037
+ }
12038
+ if (message.source_reply_to === void 0) {
12039
+ context.addIssue({
12040
+ code: external_exports.ZodIssueCode.custom,
12041
+ path: ["source_reply_to"],
12042
+ message: "thread descendants require source_reply_to"
12043
+ });
12044
+ }
12045
+ if (message.category !== "chat") {
12046
+ context.addIssue({
12047
+ code: external_exports.ZodIssueCode.custom,
12048
+ path: ["category"],
12049
+ message: "thread descendants must be chat messages"
12050
+ });
12051
+ }
12052
+ }
12053
+ var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_EXTERNAL_INVITE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, MAX_ROOM_IDENTITY_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, ContainerIdSchema2, RuntimeCommandNameSchema, RuntimeCommandGrantSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, MAX_ROOM_IDENTITY_TITLE_CHARACTERS, SDK_IDENTITY_NAME_FORBIDDEN, SDK_IDENTITY_NAME_RESERVED, CoworkIdentityNameError, RoleSchema, ROOM_ROLE, RuntimeRoleCommandGrantSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, PostAsRoleInputSchema, RestRoleInputSchema, AcceptExternalInviteInputSchema, ListMembersCommandInputSchema, CommandIdempotencyKeySchema, RemoveMemberCommandInputSchema, RuntimeCommandGrantInputSchema, RuntimeRoleCommandGrantInputSchema, RuntimeCommandAuditSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, ReplyReferenceSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, IntakeRejectionShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, IntakeRejectionRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
11858
12054
  var init_contracts = __esm({
11859
12055
  "src/contracts.ts"() {
11860
12056
  "use strict";
11861
12057
  init_zod();
11862
12058
  init_consumer_commands();
11863
12059
  init_command_names();
12060
+ init_thread_contracts();
11864
12061
  MAX_TEXT_BYTES = 262144;
11865
12062
  MAX_FILE_BYTES = 2 * 1024 * 1024;
11866
12063
  MAX_HISTORY_PAGE_BYTES = 3 * 1024 * 1024;
@@ -11877,10 +12074,10 @@ var init_contracts = __esm({
11877
12074
  /^[0-7][0-9a-hjkmnp-tv-z]{25}$/,
11878
12075
  "must be a 26-character lowercase Crockford ULID"
11879
12076
  );
11880
- ContainerIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
12077
+ ContainerIdSchema2 = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
11881
12078
  RuntimeCommandNameSchema = external_exports.union([external_exports.enum(RUNTIME_COMMAND_NAMES), ConsumerCommandNameSchema]);
11882
12079
  RuntimeCommandGrantSchema = external_exports.object({
11883
- caller_cid: ContainerIdSchema,
12080
+ caller_cid: ContainerIdSchema2,
11884
12081
  command: RuntimeCommandNameSchema
11885
12082
  }).strict();
11886
12083
  Rfc3339Schema = external_exports.string().refine(isStrictRfc3339, "must be a valid RFC3339 timestamp");
@@ -12159,7 +12356,7 @@ var init_contracts = __esm({
12159
12356
  lifecycle_request: external_exports.object({
12160
12357
  request_id: external_exports.string().min(1).max(256),
12161
12358
  command: external_exports.enum(["room.close", "room.delete"]),
12162
- caller_cid: ContainerIdSchema,
12359
+ caller_cid: ContainerIdSchema2,
12163
12360
  accepted_at: Rfc3339Schema,
12164
12361
  state: external_exports.enum(["pending", "failed", "completed"]),
12165
12362
  error: external_exports.literal("lifecycle_failed").optional()
@@ -12240,15 +12437,15 @@ var init_contracts = __esm({
12240
12437
  }
12241
12438
  const seenCommandGrants = /* @__PURE__ */ new Set();
12242
12439
  for (const [index, grant] of room.command_grants.entries()) {
12243
- const key = `${grant.caller_cid}\0${grant.command}`;
12244
- if (seenCommandGrants.has(key)) {
12440
+ const key2 = `${grant.caller_cid}\0${grant.command}`;
12441
+ if (seenCommandGrants.has(key2)) {
12245
12442
  context.addIssue({
12246
12443
  code: external_exports.ZodIssueCode.custom,
12247
12444
  path: ["command_grants", index],
12248
12445
  message: "runtime command grants must be unique by caller CID and command"
12249
12446
  });
12250
12447
  }
12251
- seenCommandGrants.add(key);
12448
+ seenCommandGrants.add(key2);
12252
12449
  if (!room.seats.some((seat) => seat.state === "active" && seat.identity === grant.caller_cid)) {
12253
12450
  context.addIssue({
12254
12451
  code: external_exports.ZodIssueCode.custom,
@@ -12310,7 +12507,7 @@ var init_contracts = __esm({
12310
12507
  (value) => Buffer.byteLength(value, "utf8") <= MAX_EXTERNAL_INVITE_BYTES,
12311
12508
  `invite input must be at most ${MAX_EXTERNAL_INVITE_BYTES} UTF-8 bytes`
12312
12509
  ),
12313
- expected_cid: ContainerIdSchema.optional()
12510
+ expected_cid: ContainerIdSchema2.optional()
12314
12511
  }).strict();
12315
12512
  ListMembersCommandInputSchema = external_exports.object({}).strict();
12316
12513
  CommandIdempotencyKeySchema = external_exports.string().regex(
@@ -12387,7 +12584,9 @@ var init_contracts = __esm({
12387
12584
  }),
12388
12585
  source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
12389
12586
  source_wire_id: NonEmptyStringSchema.optional(),
12390
- source_reply_to: ReplyReferenceSchema.optional()
12587
+ source_reply_to: ReplyReferenceSchema.optional(),
12588
+ scope: ThreadScopeSchema.optional(),
12589
+ thread_root: ThreadRootSchema.optional()
12391
12590
  };
12392
12591
  RelayIntentShape = {
12393
12592
  kind: external_exports.literal("relay_intent"),
@@ -12395,7 +12594,12 @@ var init_contracts = __esm({
12395
12594
  file_id: LowerCrockfordUlidSchema.optional(),
12396
12595
  recipient_identity: NonEmptyStringSchema
12397
12596
  };
12398
- RelayResultStatusSchema = external_exports.enum(["queued", "send_failed", "skipped_removed"]);
12597
+ RelayResultStatusSchema = external_exports.enum([
12598
+ "queued",
12599
+ "send_failed",
12600
+ "skipped_removed",
12601
+ "skipped_reply_unavailable"
12602
+ ]);
12399
12603
  RelayResultShape = {
12400
12604
  kind: external_exports.literal("relay_result"),
12401
12605
  intent_record_id: NonEmptyStringSchema,
@@ -12433,6 +12637,18 @@ var init_contracts = __esm({
12433
12637
  source_wire_id: NonEmptyStringSchema.optional(),
12434
12638
  source_reply_to: ReplyReferenceSchema.optional()
12435
12639
  };
12640
+ IntakeRejectionShape = {
12641
+ kind: external_exports.literal("intake_rejection"),
12642
+ source_kind: external_exports.enum(["message", "file"]),
12643
+ source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
12644
+ source_file_id: external_exports.number().int().nonnegative().safe().optional(),
12645
+ source_wire_id: NonEmptyStringSchema,
12646
+ sender_identity: NonEmptyStringSchema,
12647
+ sender_participant_id: LowerCrockfordUlidSchema,
12648
+ fingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
12649
+ error: external_exports.enum(["reply_target_unavailable", "thread_files_unsupported"]),
12650
+ notification_attempt_claimed: external_exports.literal(true)
12651
+ };
12436
12652
  MembershipIntentShape = {
12437
12653
  kind: external_exports.literal("membership_intent"),
12438
12654
  action: external_exports.enum(["remove"]),
@@ -12470,6 +12686,7 @@ var init_contracts = __esm({
12470
12686
  FileRecordSchema = external_exports.object({ ...RecordCommonShape, ...FileShape }).strict();
12471
12687
  RelayIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayIntentShape }).strict();
12472
12688
  RelayResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayResultShape }).strict();
12689
+ IntakeRejectionRecordSchema = external_exports.object({ ...RecordCommonShape, ...IntakeRejectionShape }).strict();
12473
12690
  MembershipIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipIntentShape }).strict();
12474
12691
  MembershipResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipResultShape }).strict();
12475
12692
  CloseNoticeIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeIntentShape }).strict();
@@ -12479,6 +12696,7 @@ var init_contracts = __esm({
12479
12696
  FileRecordSchema,
12480
12697
  RelayIntentRecordSchema,
12481
12698
  RelayResultRecordSchema,
12699
+ IntakeRejectionRecordSchema,
12482
12700
  MembershipIntentRecordSchema,
12483
12701
  MembershipResultRecordSchema,
12484
12702
  CloseNoticeIntentRecordSchema,
@@ -12492,8 +12710,12 @@ var init_contracts = __esm({
12492
12710
  message: 'record_id must equal room_id + ":" + seq'
12493
12711
  });
12494
12712
  }
12495
- if (record.kind === "message") refineMessageCategory(record, context);
12713
+ if (record.kind === "message") {
12714
+ refineMessageCategory(record, context);
12715
+ refineMessageThread(record, context);
12716
+ }
12496
12717
  refineRelaySubject(record, context);
12718
+ refineIntakeRejection(record, context);
12497
12719
  refineFileRecord(record, context);
12498
12720
  });
12499
12721
  AppendRecordSchema = external_exports.discriminatedUnion("kind", [
@@ -12501,11 +12723,16 @@ var init_contracts = __esm({
12501
12723
  external_exports.object({ ...AppendCommonShape, ...FileShape }).strict(),
12502
12724
  external_exports.object({ ...AppendCommonShape, ...RelayIntentShape }).strict(),
12503
12725
  external_exports.object({ ...AppendCommonShape, ...RelayResultShape }).strict(),
12726
+ external_exports.object({ ...AppendCommonShape, ...IntakeRejectionShape }).strict(),
12504
12727
  external_exports.object({ ...AppendCommonShape, ...CloseNoticeIntentShape }).strict(),
12505
12728
  external_exports.object({ ...AppendCommonShape, ...CloseNoticeResultShape }).strict()
12506
12729
  ]).superRefine((record, context) => {
12507
- if (record.kind === "message") refineMessageCategory(record, context);
12730
+ if (record.kind === "message") {
12731
+ refineMessageCategory(record, context);
12732
+ refineMessageThread(record, context);
12733
+ }
12508
12734
  refineRelaySubject(record, context);
12735
+ refineIntakeRejection(record, context);
12509
12736
  refineFileRecord(record, context);
12510
12737
  });
12511
12738
  }
@@ -13842,6 +14069,34 @@ var init_packets = __esm({
13842
14069
  this.runtimeHandlers = handlers;
13843
14070
  await this.runBound(() => this.client.registerCommands([
13844
14071
  ...handlers.consumerCommands ?? [],
14072
+ ...handlers.startThread === void 0 ? [] : [{
14073
+ name: "start_thread",
14074
+ description: "Create a scoped reply thread for an explicit set of room participant IDs.",
14075
+ input_schema: {
14076
+ type: "object",
14077
+ additionalProperties: false,
14078
+ required: ["topic", "participant_ids", "idempotency_key"],
14079
+ properties: {
14080
+ topic: {
14081
+ type: "string",
14082
+ minLength: 1,
14083
+ maxLength: 120,
14084
+ pattern: "^(?![\\s\\S]*[\\p{Cc}\\p{Cf}])[\\s\\S]*\\S[\\s\\S]*$"
14085
+ },
14086
+ participant_ids: {
14087
+ type: "array",
14088
+ minItems: 1,
14089
+ uniqueItems: true,
14090
+ items: { type: "string", pattern: "^[0-7][0-9a-hjkmnp-tv-z]{25}$" }
14091
+ },
14092
+ idempotency_key: {
14093
+ type: "string",
14094
+ pattern: "^[A-Za-z0-9._:-]{1,128}$"
14095
+ }
14096
+ }
14097
+ },
14098
+ handler: handlers.startThread
14099
+ }],
13845
14100
  {
13846
14101
  name: "list-members",
13847
14102
  description: "List the room roster using contact-safe member fields.",
@@ -13866,9 +14121,10 @@ var init_packets = __esm({
13866
14121
  ...handlers.sharedCommand === void 0 ? [] : SHARED_ROOM_COMMANDS.map((name) => {
13867
14122
  const doc = [...ROOM_RPC_METHODS, ...PRIVATE_ROOM_RPC_METHODS].find((method) => method.method === name);
13868
14123
  const { room_id: _roomId, ...properties } = doc.params.properties;
14124
+ if (name === "room.history") properties.view = { const: "participant" };
13869
14125
  return {
13870
14126
  name,
13871
- description: `${doc.description} Requires an explicit grant; applies only to this room.`,
14127
+ description: `${name === "room.history" ? "Read your visible messages using viewer-local after cursors." : doc.description} Requires an explicit grant; applies only to this room.`,
13872
14128
  input_schema: {
13873
14129
  ...doc.params,
13874
14130
  properties,
@@ -14010,16 +14266,257 @@ var init_ulid = __esm({
14010
14266
  }
14011
14267
  });
14012
14268
 
14269
+ // src/reply-threading.ts
14270
+ function buildAliasIndex(rows, roomId) {
14271
+ const local = rows.filter((r) => r.room_id === roomId);
14272
+ const items = local.filter((r) => r.kind === "message" || r.kind === "file");
14273
+ const intents = local.filter((r) => r.kind === "relay_intent");
14274
+ const results = local.filter((r) => r.kind === "relay_result");
14275
+ const copies = (parent, cid) => results.filter((result) => {
14276
+ if (result.status !== "queued" || result.recipient_identity !== cid || key(result) !== key(parent) || !parent.recipient_identities.includes(cid)) return false;
14277
+ const matches = intents.filter((intent2) => intent2.record_id === result.intent_record_id);
14278
+ if (matches.length !== 1) return false;
14279
+ const intent = matches[0];
14280
+ return key(intent) === key(parent) && intent.recipient_identity === cid && parent.seq < intent.seq && intent.seq < result.seq;
14281
+ }).sort((a, b) => a.seq - b.seq);
14282
+ const wires = (result, parent) => [result.wire_id, ...parent.kind === "file" ? [result.metadata_wire_id] : []].filter(nonempty);
14283
+ const ownersFor = (wireId, cid) => items.filter((item) => item.author.identity === cid && item.source_wire_id === wireId || copies(item, cid).some((result) => wires(result, item).includes(wireId)));
14284
+ return { items, copies, wires, ownersFor };
14285
+ }
14286
+ async function readReplyRows(store, roomId) {
14287
+ const rows = [];
14288
+ let after = 0;
14289
+ for (; ; ) {
14290
+ const page = await store.read(roomId, { after, limit: 64 });
14291
+ if (page.length === 0) return rows;
14292
+ const last = page[page.length - 1];
14293
+ if (last.seq <= after) throw new Error("reply archive cursor did not advance");
14294
+ for (const r of page) {
14295
+ if (r.room_id !== roomId) throw new Error("reply archive room mismatch");
14296
+ if (r.kind === "message") rows.push({ ...r, text: "" });
14297
+ else if (r.kind === "file") rows.push({ ...r, data_base64: "" });
14298
+ else if (r.kind === "relay_intent" || r.kind === "relay_result") rows.push(r);
14299
+ }
14300
+ after = last.seq;
14301
+ }
14302
+ }
14303
+ function resolveReplyParent(rows, roomId, senderCid, wireId, beforeSeq) {
14304
+ if (wireId === void 0) return { state: "none" };
14305
+ if (!nonempty(wireId)) return { state: "unknown_parent" };
14306
+ const index = buildAliasIndex(rows, roomId);
14307
+ const candidates = index.ownersFor(wireId, senderCid);
14308
+ if (candidates.length === 0) return { state: "unknown_parent" };
14309
+ if (candidates.length !== 1) return { state: "ambiguous_parent" };
14310
+ const parent = candidates[0];
14311
+ if (parent.seq >= beforeSeq) return { state: "unknown_parent" };
14312
+ const parentKey = key(parent);
14313
+ if (index.items.filter((item) => key(item) === parentKey).length !== 1) {
14314
+ return { state: "ambiguous_parent" };
14315
+ }
14316
+ return { state: "resolved", parent: { key: parentKey, item: parent } };
14317
+ }
14318
+ function mapReplyParent(rows, roomId, logicalParent, recipientCid) {
14319
+ const index = buildAliasIndex(rows, roomId);
14320
+ if (logicalParent.item.room_id !== roomId || key(logicalParent.item) !== logicalParent.key) {
14321
+ return { state: "ambiguous_parent", parentKey: logicalParent.key };
14322
+ }
14323
+ const matches = index.items.filter((item) => key(item) === logicalParent.key);
14324
+ if (matches.length !== 1) {
14325
+ return { state: "ambiguous_parent", parentKey: logicalParent.key };
14326
+ }
14327
+ const parent = matches[0];
14328
+ const recipientCopies = index.copies(parent, recipientCid);
14329
+ const sourceWire = parent.author.identity === recipientCid && nonempty(parent.source_wire_id) ? parent.source_wire_id : void 0;
14330
+ const sourceOwners = sourceWire === void 0 ? [] : index.ownersFor(sourceWire, recipientCid);
14331
+ const wireId = sourceOwners.length === 1 && key(sourceOwners[0]) === logicalParent.key ? sourceWire : recipientCopies.map((copy) => index.wires(copy, parent)[0]).find(nonempty);
14332
+ if (!nonempty(wireId)) return { state: "missing_copy", parentKey: logicalParent.key };
14333
+ const owners = index.ownersFor(wireId, recipientCid);
14334
+ if (owners.length !== 1 || key(owners[0]) !== logicalParent.key) {
14335
+ return { state: "ambiguous_parent", parentKey: logicalParent.key };
14336
+ }
14337
+ return { state: "linked", parentKey: logicalParent.key, replyTo: { wire_id: wireId } };
14338
+ }
14339
+ function selectReply(rows, roomId, child, recipientCid) {
14340
+ if (child.room_id !== roomId) throw new Error("reply child room mismatch");
14341
+ const resolution = resolveReplyParent(
14342
+ rows,
14343
+ roomId,
14344
+ child.author.identity,
14345
+ child.source_reply_to?.wire_id,
14346
+ child.seq
14347
+ );
14348
+ if (resolution.state !== "resolved") return resolution;
14349
+ return mapReplyParent(rows, roomId, resolution.parent, recipientCid);
14350
+ }
14351
+ var key, nonempty;
14352
+ var init_reply_threading = __esm({
14353
+ "src/reply-threading.ts"() {
14354
+ "use strict";
14355
+ key = (r) => r.message_id === void 0 === (r.file_id === void 0) ? void 0 : r.message_id === void 0 ? `file:${r.file_id}` : `message:${r.message_id}`;
14356
+ nonempty = (value) => typeof value === "string" && value.length > 0;
14357
+ }
14358
+ });
14359
+
14360
+ // src/threads.ts
14361
+ import { createHash as createHash3 } from "node:crypto";
14362
+ function publicThreadMetadata(root, room) {
14363
+ let creator;
14364
+ if (room.anonymous) {
14365
+ const alias = AuthorAliasSchema.safeParse(root.author_alias);
14366
+ if (!alias.success || alias.data.participant_id !== root.thread_root.creator_participant_id) {
14367
+ throw new ThreadFailure("reply_target_unavailable");
14368
+ }
14369
+ creator = {
14370
+ identity: alias.data.participant_id,
14371
+ display_name: alias.data.alias,
14372
+ role: root.author.role
14373
+ };
14374
+ } else {
14375
+ creator = {
14376
+ identity: root.author.identity,
14377
+ display_name: root.author.display_name,
14378
+ role: root.author.role
14379
+ };
14380
+ }
14381
+ return {
14382
+ schema_version: 1,
14383
+ thread_id: root.thread_root.thread_id,
14384
+ topic: root.thread_root.topic,
14385
+ creator,
14386
+ participant_ids: root.thread_root.members.map((member) => member.participant_id),
14387
+ created_at: root.at
14388
+ };
14389
+ }
14390
+ function selectThreadMembers(room, cid, input) {
14391
+ const selected = new Set(input.participant_ids);
14392
+ const active = room.seats.filter((seat) => seat.state === "active");
14393
+ const creator = active.find((seat) => seat.identity === cid);
14394
+ if (creator === void 0 || selected.size === 0 || selected.size !== input.participant_ids.length || selected.size > active.length || !selected.has(creator.participant_id)) {
14395
+ throw new ThreadFailure("invalid_members");
14396
+ }
14397
+ const members = active.filter((seat) => selected.has(seat.participant_id));
14398
+ if (members.length !== selected.size) throw new ThreadFailure("invalid_members");
14399
+ return members.map(({ participant_id, identity }) => ({ participant_id, identity })).sort((left, right) => left.participant_id.localeCompare(right.participant_id));
14400
+ }
14401
+ function activeThreadSeat(room, root, cid) {
14402
+ return room.seats.find((seat) => seat.state === "active" && seat.identity === cid && root.members.some((member) => member.identity === cid && member.participant_id === seat.participant_id));
14403
+ }
14404
+ function threadRelayEligible(room, root, cid) {
14405
+ return activeThreadSeat(room, root, cid) !== void 0;
14406
+ }
14407
+ function publicThreadAuthor(message, root, room) {
14408
+ const member = root.members.find((member2) => member2.identity === message.author.identity);
14409
+ if (!member) throw new ThreadFailure("reply_target_unavailable");
14410
+ if (!room.anonymous) return message.author;
14411
+ const alias = AuthorAliasSchema.safeParse(message.author_alias);
14412
+ if (!alias.success || alias.data.participant_id !== member.participant_id) {
14413
+ throw new ThreadFailure("reply_target_unavailable");
14414
+ }
14415
+ return { identity: alias.data.participant_id, display_name: alias.data.alias, role: message.author.role };
14416
+ }
14417
+ function threadFingerprint(input) {
14418
+ return createHash3("sha256").update(JSON.stringify({
14419
+ topic: input.topic,
14420
+ participant_ids: [...input.participant_ids].sort()
14421
+ })).digest("hex");
14422
+ }
14423
+ function findThreadRoot(rows, id) {
14424
+ const candidates = rows.filter((row) => row.kind === "message" && (row.message_id === id || row.thread_root?.thread_id === id || row.scope?.thread_id === id && row.scope.parent_key === void 0));
14425
+ if (candidates.length === 0) return void 0;
14426
+ if (candidates.length !== 1) throw new ThreadFailure("reply_target_unavailable");
14427
+ const root = candidates[0];
14428
+ const parsedRoot = ThreadRootSchema.safeParse(root.thread_root);
14429
+ const parsedScope = ThreadScopeSchema.safeParse(root.scope);
14430
+ const parsedAlias = AuthorAliasSchema.safeParse(root.author_alias);
14431
+ const creator = parsedRoot.success ? parsedRoot.data.members.find((member) => member.participant_id === parsedRoot.data.creator_participant_id) : void 0;
14432
+ if (!parsedRoot.success || !parsedScope.success || root.message_id !== id || parsedRoot.data.thread_id !== id || parsedScope.data.thread_id !== id || parsedScope.data.parent_key !== void 0 || root.category !== "chat" || creator?.identity !== root.author.identity || root.author_alias !== void 0 && (!parsedAlias.success || parsedAlias.data.participant_id !== parsedRoot.data.creator_participant_id) || root.source_msg_id !== void 0 || root.source_wire_id !== void 0 || root.source_reply_to !== void 0) {
14433
+ throw new ThreadFailure("reply_target_unavailable");
14434
+ }
14435
+ return root;
14436
+ }
14437
+ function classifyThreadAssociation(room, rows, source) {
14438
+ const chain = [];
14439
+ const seen = /* @__PURE__ */ new Set();
14440
+ let item = source;
14441
+ for (; ; ) {
14442
+ const key2 = item.kind === "message" ? `message:${item.message_id}` : `file:${item.file_id}`;
14443
+ if (item.room_id !== room.room_id || seen.has(key2) || chain.length > rows.length) {
14444
+ throw new ThreadFailure("reply_target_unavailable");
14445
+ }
14446
+ seen.add(key2);
14447
+ const resolved = resolveReplyParent(
14448
+ rows,
14449
+ room.room_id,
14450
+ item.author.identity,
14451
+ item.source_reply_to?.wire_id,
14452
+ item.seq
14453
+ );
14454
+ chain.push({ item, resolved });
14455
+ if (resolved.state !== "resolved") break;
14456
+ if (resolved.parent.item.seq >= item.seq) throw new ThreadFailure("reply_target_unavailable");
14457
+ item = resolved.parent.item;
14458
+ }
14459
+ let association = { state: "ordinary" };
14460
+ for (const { item: item2, resolved } of chain.reverse()) {
14461
+ const declared = item2.kind === "message" && (item2.scope !== void 0 || item2.thread_root !== void 0);
14462
+ if (!declared && association.state === "ordinary") continue;
14463
+ const scope = ThreadScopeSchema.safeParse(item2.kind === "message" ? item2.scope : void 0);
14464
+ if (item2.kind !== "message" || !scope.success || item2.category !== "chat" || rows.filter((row) => row.room_id === room.room_id && row.kind === "message" && row.message_id === item2.message_id).length !== 1) {
14465
+ throw new ThreadFailure("reply_target_unavailable");
14466
+ }
14467
+ const root = findThreadRoot(rows.filter((row) => row.room_id === room.room_id), scope.data.thread_id);
14468
+ if (!root?.thread_root) throw new ThreadFailure("reply_target_unavailable");
14469
+ publicThreadMetadata({ ...root, thread_root: root.thread_root }, room);
14470
+ publicThreadAuthor(item2, root.thread_root, room);
14471
+ if (item2.message_id !== root.message_id) {
14472
+ if (item2.thread_root !== void 0 || root.seq >= item2.seq || resolved.state !== "resolved" || scope.data.parent_key !== resolved.parent.key || association.state !== "scoped" || association.root.message_id !== root.message_id) {
14473
+ throw new ThreadFailure("reply_target_unavailable");
14474
+ }
14475
+ }
14476
+ association = { state: "scoped", root: { ...root, thread_root: root.thread_root }, scope: scope.data };
14477
+ }
14478
+ return association;
14479
+ }
14480
+ function resolveIntakeScope(room, rows, item, beforeSeq) {
14481
+ const ordinary = () => ({ recipients: [...new Set(room.seats.filter((seat) => seat.state === "active" && seat.identity !== item.sender_id).map((seat) => seat.identity))] });
14482
+ if (item.reply_to == null) return ordinary();
14483
+ const reply = item.reply_to;
14484
+ if (typeof reply.wire_id !== "string" || reply.wire_id.length === 0 || reply.wire_id.length > 256 || reply.sentence !== void 0 && (!Number.isSafeInteger(reply.sentence) || reply.sentence < 1) || room.state !== "active" || room.lifecycle_request?.state === "pending" || !room.seats.some((seat) => seat.identity === item.sender_id && seat.state === "active")) {
14485
+ throw new ThreadFailure("reply_target_unavailable");
14486
+ }
14487
+ const resolved = resolveReplyParent(rows, room.room_id, item.sender_id, reply.wire_id, beforeSeq);
14488
+ if (resolved.state !== "resolved") throw new ThreadFailure("reply_target_unavailable");
14489
+ const association = classifyThreadAssociation(room, rows, resolved.parent.item);
14490
+ if (association.state === "ordinary") return ordinary();
14491
+ const { root } = association;
14492
+ if (!activeThreadSeat(room, root.thread_root, item.sender_id)) {
14493
+ throw new ThreadFailure("reply_target_unavailable");
14494
+ }
14495
+ if ("file_id" in item) throw new ThreadFailure("thread_files_unsupported");
14496
+ return {
14497
+ recipients: root.thread_root.members.filter((member) => member.identity !== item.sender_id && activeThreadSeat(room, root.thread_root, member.identity) !== void 0).map((member) => member.identity),
14498
+ scope: { thread_id: root.message_id, parent_key: resolved.parent.key }
14499
+ };
14500
+ }
14501
+ var init_threads = __esm({
14502
+ "src/threads.ts"() {
14503
+ "use strict";
14504
+ init_reply_threading();
14505
+ init_contracts();
14506
+ init_thread_contracts();
14507
+ }
14508
+ });
14509
+
14013
14510
  // src/intake.ts
14014
14511
  import { AsyncLocalStorage } from "node:async_hooks";
14015
- import { createHash as createHash3 } from "node:crypto";
14512
+ import { createHash as createHash4 } from "node:crypto";
14016
14513
  function canonicalJson(value) {
14017
14514
  const encoded = JSON.stringify(canonicalValue(value));
14018
14515
  if (encoded === void 0) throw new TypeError("canonical JSON value is not serializable");
14019
14516
  return encoded;
14020
14517
  }
14021
- async function sendRoomBody(packet, recipientIdentity, unsigned) {
14022
- return packet.send(recipientIdentity, canonicalJson(unsigned));
14518
+ async function sendRoomBody(packet, recipientIdentity, unsigned, replyTo) {
14519
+ return packet.send(recipientIdentity, canonicalJson(unsigned), replyTo);
14023
14520
  }
14024
14521
  function sameReply2(stored, observed) {
14025
14522
  if (stored === void 0 || observed == null) return stored === void 0 && observed == null;
@@ -14027,13 +14524,25 @@ function sameReply2(stored, observed) {
14027
14524
  }
14028
14525
  async function queryStore(store, roomId, options) {
14029
14526
  if (store.query) return store.query(roomId, options);
14030
- let records = await store.read(roomId);
14031
- records = records.filter((record) => {
14527
+ const archive = [];
14528
+ let after = 0;
14529
+ for (; ; ) {
14530
+ const page = await store.read(roomId, { after, limit: JOURNAL_WORK_BATCH_SIZE });
14531
+ if (page.length === 0) break;
14532
+ for (const row of page) {
14533
+ if (row.room_id !== roomId || !Number.isSafeInteger(row.seq) || row.seq <= after) {
14534
+ throw new Error("intake archive cursor did not advance");
14535
+ }
14536
+ archive.push(row);
14537
+ after = row.seq;
14538
+ }
14539
+ }
14540
+ let records = archive.filter((record) => {
14032
14541
  const value = record;
14033
- return (options.kind === void 0 || record.kind === options.kind) && (options.messageId === void 0 || value.message_id === options.messageId) && (options.fileId === void 0 || value.file_id === options.fileId) && (options.sourceMsgId === void 0 || value.source_msg_id === options.sourceMsgId) && (options.sourceFileId === void 0 || value.source_file_id === options.sourceFileId) && (options.recipientIdentity === void 0 || value.recipient_identity === options.recipientIdentity);
14542
+ return (options.after === void 0 || record.seq > options.after) && (options.kind === void 0 || record.kind === options.kind) && (options.messageId === void 0 || value.message_id === options.messageId) && (options.fileId === void 0 || value.file_id === options.fileId) && (options.sourceMsgId === void 0 || value.source_msg_id === options.sourceMsgId) && (options.sourceFileId === void 0 || value.source_file_id === options.sourceFileId) && (options.recipientIdentity === void 0 || value.recipient_identity === options.recipientIdentity);
14034
14543
  });
14035
14544
  if (options.unresolvedResultKind) {
14036
- const completed = new Set((await store.read(roomId)).filter((record) => record.kind === options.unresolvedResultKind).map((record) => record.intent_record_id));
14545
+ const completed = new Set(archive.filter((record) => record.kind === options.unresolvedResultKind).map((record) => record.intent_record_id));
14037
14546
  records = records.filter((record) => !completed.has(record.record_id));
14038
14547
  }
14039
14548
  if (options.descending) records.reverse();
@@ -14044,15 +14553,27 @@ function canonicalValue(value) {
14044
14553
  if (value !== null && typeof value === "object") {
14045
14554
  const input = value;
14046
14555
  const output = {};
14047
- for (const key of Object.keys(input).sort()) {
14048
- if (input[key] !== void 0) output[key] = canonicalValue(input[key]);
14556
+ for (const key2 of Object.keys(input).sort()) {
14557
+ if (input[key2] !== void 0) output[key2] = canonicalValue(input[key2]);
14049
14558
  }
14050
14559
  return output;
14051
14560
  }
14052
14561
  return value;
14053
14562
  }
14054
- function unique(values) {
14055
- return [...new Set(values)];
14563
+ function inputFingerprint(item) {
14564
+ return createHash4("sha256").update(canonicalJson({
14565
+ sender: item.sender_id,
14566
+ wire: item.wire_id,
14567
+ date: item.date,
14568
+ reply: item.reply_to ?? null,
14569
+ ..."file_id" in item ? {
14570
+ kind: "file",
14571
+ id: item.file_id,
14572
+ filename: item.filename,
14573
+ mime: item.mime,
14574
+ sha256: createHash4("sha256").update(item.data).digest("hex")
14575
+ } : { kind: "message", id: item.msg_id, text: item.text }
14576
+ })).digest("hex");
14056
14577
  }
14057
14578
  function wireKind(category) {
14058
14579
  switch (category) {
@@ -14073,6 +14594,9 @@ var init_intake = __esm({
14073
14594
  init_zod();
14074
14595
  init_contracts();
14075
14596
  init_ulid();
14597
+ init_reply_threading();
14598
+ init_threads();
14599
+ init_thread_contracts();
14076
14600
  JOURNAL_WORK_BATCH_SIZE = 64;
14077
14601
  INTAKE_BATCH_SIZE = 32;
14078
14602
  IntakePump = class {
@@ -14217,28 +14741,36 @@ var init_intake = __esm({
14217
14741
  });
14218
14742
  }
14219
14743
  async processFileInboxItem(roomId, packet, item) {
14220
- const parsedName = FileNameSchema.safeParse(item.filename);
14221
- const parsedMime = FileMimeSchema.safeParse(item.mime);
14222
- if (!parsedName.success || !parsedMime.success) {
14223
- await packet.acknowledgeFile(item);
14224
- return;
14225
- }
14226
- if (item.data.length > MAX_FILE_BYTES) {
14227
- throw new RangeError(`room files must be at most ${MAX_FILE_BYTES} bytes (2 MiB)`);
14228
- }
14229
14744
  const room = await this.store.load(roomId);
14230
- const seat = room.seats.find(
14231
- (candidate) => candidate.identity === item.sender_id && candidate.state === "active"
14232
- );
14233
- if (room.state !== "active" || !seat) {
14234
- if (room.state === "active") await this.bounceRemovedSender(roomId, room, packet, item);
14745
+ const [stored] = await queryStore(this.store, roomId, { sourceFileId: item.file_id, limit: 1 });
14746
+ if (this.isRejectedReplay(stored, item)) {
14235
14747
  await packet.acknowledgeFile(item);
14236
14748
  return;
14237
14749
  }
14238
- const [storedFile] = await queryStore(this.store, roomId, { kind: "file", sourceFileId: item.file_id, limit: 1 });
14239
- let file = this.findSourceFile(storedFile === void 0 ? [] : [storedFile], item);
14750
+ let file = this.findSourceFile(stored === void 0 ? [] : [stored], item);
14240
14751
  if (!file) {
14241
- const recipientIdentities = unique(room.seats.filter((recipient) => recipient.state === "active").map((recipient) => recipient.identity).filter((identity) => identity !== seat.identity));
14752
+ const seat = room.seats.find((candidate) => candidate.identity === item.sender_id && candidate.state === "active");
14753
+ const known = room.seats.some((candidate) => candidate.identity === item.sender_id);
14754
+ if (!known || item.reply_to == null && (room.state !== "active" || !seat)) {
14755
+ if (room.state === "active") await this.bounceRemovedSender(roomId, room, packet, item);
14756
+ await packet.acknowledgeFile(item);
14757
+ return;
14758
+ }
14759
+ const disposition = await this.freshScopeUnlocked(room, item);
14760
+ if (!disposition) {
14761
+ await packet.acknowledgeFile(item);
14762
+ return;
14763
+ }
14764
+ if (!seat) throw new Error("authorized file sender has no active seat");
14765
+ const parsedName = FileNameSchema.safeParse(item.filename);
14766
+ const parsedMime = FileMimeSchema.safeParse(item.mime);
14767
+ if (!parsedName.success || !parsedMime.success) {
14768
+ await packet.acknowledgeFile(item);
14769
+ return;
14770
+ }
14771
+ if (item.data.length > MAX_FILE_BYTES) {
14772
+ throw new RangeError(`room files must be at most ${MAX_FILE_BYTES} bytes (2 MiB)`);
14773
+ }
14242
14774
  const bytes = Buffer.from(item.data);
14243
14775
  const appended = await this.store.append(roomId, {
14244
14776
  version: 1,
@@ -14251,9 +14783,9 @@ var init_intake = __esm({
14251
14783
  filename: parsedName.data,
14252
14784
  mime: parsedMime.data,
14253
14785
  size: bytes.length,
14254
- sha256: createHash3("sha256").update(bytes).digest("hex"),
14786
+ sha256: createHash4("sha256").update(bytes).digest("hex"),
14255
14787
  data_base64: bytes.toString("base64"),
14256
- recipient_identities: recipientIdentities,
14788
+ recipient_identities: disposition.recipients,
14257
14789
  source_file_id: item.file_id,
14258
14790
  ...item.wire_id === "" ? {} : { source_wire_id: item.wire_id },
14259
14791
  ...item.reply_to == null ? {} : { source_reply_to: item.reply_to }
@@ -14266,18 +14798,26 @@ var init_intake = __esm({
14266
14798
  }
14267
14799
  async processInboxItem(roomId, packet, item, acknowledge = true) {
14268
14800
  const room = await this.store.load(roomId);
14269
- const seat = room.seats.find(
14270
- (candidate) => candidate.identity === item.sender_id && candidate.state === "active"
14271
- );
14272
- if (room.state !== "active" || !seat) {
14273
- if (room.state === "active") await this.bounceRemovedSender(roomId, room, packet, item);
14801
+ const [stored] = await queryStore(this.store, roomId, { sourceMsgId: item.msg_id, limit: 1 });
14802
+ if (this.isRejectedReplay(stored, item)) {
14274
14803
  if (acknowledge) await this.acknowledgeMessage(roomId, packet, item);
14275
14804
  return;
14276
14805
  }
14277
- const [storedMessage] = await queryStore(this.store, roomId, { kind: "message", sourceMsgId: item.msg_id, limit: 1 });
14278
- let message = this.findSourceMessage(storedMessage === void 0 ? [] : [storedMessage], item);
14806
+ let message = this.findSourceMessage(stored === void 0 ? [] : [stored], item);
14279
14807
  if (!message) {
14280
- const recipientIdentities = unique(room.seats.filter((recipient) => recipient.state === "active").map((recipient) => recipient.identity).filter((identity) => identity !== seat.identity));
14808
+ const seat = room.seats.find((candidate) => candidate.identity === item.sender_id && candidate.state === "active");
14809
+ const known = room.seats.some((candidate) => candidate.identity === item.sender_id);
14810
+ if (!known || item.reply_to == null && (room.state !== "active" || !seat)) {
14811
+ if (room.state === "active") await this.bounceRemovedSender(roomId, room, packet, item);
14812
+ if (acknowledge) await this.acknowledgeMessage(roomId, packet, item);
14813
+ return;
14814
+ }
14815
+ const disposition = await this.freshScopeUnlocked(room, item);
14816
+ if (!disposition) {
14817
+ if (acknowledge) await this.acknowledgeMessage(roomId, packet, item);
14818
+ return;
14819
+ }
14820
+ if (!seat) throw new Error("authorized message sender has no active seat");
14281
14821
  const appended = await this.store.append(roomId, {
14282
14822
  version: 1,
14283
14823
  kind: "message",
@@ -14294,7 +14834,8 @@ var init_intake = __esm({
14294
14834
  ...room.anonymous && seat.alias !== void 0 ? { author_alias: { participant_id: seat.participant_id, alias: seat.alias } } : {},
14295
14835
  category: "chat",
14296
14836
  text: item.text,
14297
- recipient_identities: recipientIdentities,
14837
+ recipient_identities: disposition.recipients,
14838
+ ...disposition.scope === void 0 ? {} : { scope: disposition.scope },
14298
14839
  source_msg_id: item.msg_id,
14299
14840
  ...item.wire_id === "" ? {} : { source_wire_id: item.wire_id },
14300
14841
  ...item.reply_to == null ? {} : { source_reply_to: item.reply_to }
@@ -14305,6 +14846,78 @@ var init_intake = __esm({
14305
14846
  await this.completeMessageIntents(roomId, message);
14306
14847
  if (acknowledge) await this.acknowledgeMessage(roomId, packet, item);
14307
14848
  }
14849
+ async freshScopeUnlocked(room, item) {
14850
+ try {
14851
+ const rows = item.reply_to == null ? [] : await readReplyRows(this.store, room.room_id);
14852
+ const beforeSeq = item.reply_to == null ? 1 : await this.nextRecordSeq(room.room_id);
14853
+ return resolveIntakeScope(room, rows, item, beforeSeq);
14854
+ } catch (error) {
14855
+ if (!(error instanceof ThreadFailure)) throw error;
14856
+ await this.recordRejectionUnlocked(room, item, error);
14857
+ return void 0;
14858
+ }
14859
+ }
14860
+ async nextRecordSeq(roomId) {
14861
+ if (this.store.query) {
14862
+ const [last] = await this.store.query(roomId, { descending: true, limit: 1 });
14863
+ if (last && (last.room_id !== roomId || !Number.isSafeInteger(last.seq) || last.seq < 1)) {
14864
+ throw new Error("intake archive tail is invalid");
14865
+ }
14866
+ return (last?.seq ?? 0) + 1;
14867
+ }
14868
+ let after = 0;
14869
+ for (; ; ) {
14870
+ const page = await this.store.read(roomId, { after, limit: JOURNAL_WORK_BATCH_SIZE });
14871
+ if (page.length === 0) return after + 1;
14872
+ for (const row of page) {
14873
+ if (row.room_id !== roomId || !Number.isSafeInteger(row.seq) || row.seq <= after) {
14874
+ throw new Error("intake archive cursor did not advance");
14875
+ }
14876
+ after = row.seq;
14877
+ }
14878
+ }
14879
+ }
14880
+ isRejectedReplay(record, item) {
14881
+ if (record?.kind !== "intake_rejection") return false;
14882
+ const file = "file_id" in item;
14883
+ if (record.source_kind !== (file ? "file" : "message") || (file ? record.source_file_id !== item.file_id : record.source_msg_id !== item.msg_id) || record.source_wire_id !== item.wire_id || record.sender_identity !== item.sender_id || record.fingerprint !== inputFingerprint(item)) {
14884
+ throw new Error("inbox source does not match its durable intake rejection");
14885
+ }
14886
+ return true;
14887
+ }
14888
+ async recordRejectionUnlocked(room, item, error) {
14889
+ const seat = room.seats.find((seat2) => seat2.identity === item.sender_id && seat2.state === "active") ?? room.seats.find((seat2) => seat2.identity === item.sender_id);
14890
+ if (!seat || typeof item.wire_id !== "string" || item.wire_id.length === 0) {
14891
+ throw new Error("intake rejection requires a known seat and source wire");
14892
+ }
14893
+ if (error.code !== "reply_target_unavailable" && error.code !== "thread_files_unsupported") throw error;
14894
+ await this.store.append(room.room_id, {
14895
+ version: 1,
14896
+ kind: "intake_rejection",
14897
+ room_id: room.room_id,
14898
+ at: this.now(),
14899
+ ..."file_id" in item ? { source_kind: "file", source_file_id: item.file_id } : { source_kind: "message", source_msg_id: item.msg_id },
14900
+ source_wire_id: item.wire_id,
14901
+ sender_identity: item.sender_id,
14902
+ sender_participant_id: seat.participant_id,
14903
+ fingerprint: inputFingerprint(item),
14904
+ error: error.code,
14905
+ notification_attempt_claimed: true
14906
+ });
14907
+ try {
14908
+ await sendRoomBody(this.packet(room.room_id), item.sender_id, {
14909
+ version: 1,
14910
+ kind: "room_msg",
14911
+ room_id: room.room_id,
14912
+ room_name: room.room_name,
14913
+ message_id: this.nextMessageId(),
14914
+ at: this.now(),
14915
+ text: error.code,
14916
+ author: { identity: room.identity_cid, display_name: room.identity_name, role: ROOM_ROLE }
14917
+ });
14918
+ } catch {
14919
+ }
14920
+ }
14308
14921
  acknowledgeMessage(roomId, packet, expected) {
14309
14922
  return packet.acknowledgeMessage(
14310
14923
  expected,
@@ -14428,23 +15041,45 @@ var init_intake = __esm({
14428
15041
  const [message] = intent.message_id === void 0 ? [] : await queryStore(this.store, roomId, { kind: "message", messageId: intent.message_id, limit: 1 });
14429
15042
  const [file] = intent.file_id === void 0 ? [] : await queryStore(this.store, roomId, { kind: "file", fileId: intent.file_id, limit: 1 });
14430
15043
  if (message === void 0 === (file === void 0)) continue;
14431
- const recipients = message?.recipient_identities ?? file.recipient_identities;
14432
- if (!recipients.includes(intent.recipient_identity)) continue;
14433
- if (!activeCids.has(intent.recipient_identity) && removedCids.has(intent.recipient_identity)) {
14434
- const skipped = await this.store.append(roomId, {
14435
- version: 1,
14436
- kind: "relay_result",
14437
- room_id: roomId,
14438
- at: this.now(),
14439
- intent_record_id: intent.record_id,
14440
- ...intent.message_id === void 0 ? {} : { message_id: intent.message_id },
14441
- ...intent.file_id === void 0 ? {} : { file_id: intent.file_id },
14442
- recipient_identity: intent.recipient_identity,
14443
- status: "skipped_removed"
14444
- });
14445
- if (skipped.kind !== "relay_result") throw new Error("storage returned the wrong relay result kind");
15044
+ const source = message ?? file;
15045
+ const replyRows = source.source_reply_to === void 0 && message?.scope === void 0 && message?.thread_root === void 0 ? [] : await readReplyRows(this.store, roomId);
15046
+ const decision = selectReply(replyRows, roomId, source, intent.recipient_identity);
15047
+ let publicThread = {};
15048
+ let scopedAuthor;
15049
+ try {
15050
+ const association = classifyThreadAssociation(room, replyRows, source);
15051
+ if (association.state === "scoped") {
15052
+ const { root, scope } = association;
15053
+ if (!message || !message.recipient_identities.includes(intent.recipient_identity) || message.seq >= intent.seq || !root.thread_root.members.some((member) => member.identity === intent.recipient_identity)) {
15054
+ throw new ThreadFailure("reply_target_unavailable");
15055
+ }
15056
+ if (!threadRelayEligible(room, root.thread_root, intent.recipient_identity)) {
15057
+ await this.skipRelay(roomId, intent, "skipped_removed");
15058
+ continue;
15059
+ }
15060
+ const metadata = publicThreadMetadata({ ...root, thread_root: root.thread_root }, room);
15061
+ scopedAuthor = publicThreadAuthor(message, root.thread_root, room);
15062
+ if (message.message_id === root.message_id) {
15063
+ publicThread = { thread: { schema_version: 1, thread_id: root.message_id }, thread_root: metadata };
15064
+ } else {
15065
+ if (decision.state !== "linked" || decision.parentKey !== scope.parent_key) {
15066
+ throw new ThreadFailure("reply_target_unavailable");
15067
+ }
15068
+ publicThread = { thread: { schema_version: 1, thread_id: root.message_id } };
15069
+ }
15070
+ } else {
15071
+ if (!source.recipient_identities.includes(intent.recipient_identity)) continue;
15072
+ if (!activeCids.has(intent.recipient_identity) && removedCids.has(intent.recipient_identity)) {
15073
+ await this.skipRelay(roomId, intent, "skipped_removed");
15074
+ continue;
15075
+ }
15076
+ }
15077
+ } catch (error) {
15078
+ if (!(error instanceof ThreadFailure)) throw error;
15079
+ await this.skipRelay(roomId, intent, "skipped_reply_unavailable");
14446
15080
  continue;
14447
15081
  }
15082
+ const replyTo = decision.replyTo;
14448
15083
  if (file !== void 0) {
14449
15084
  const uploader = file.author_alias?.alias ?? file.author.display_name;
14450
15085
  const notice = await sendRoomBody(packet, intent.recipient_identity, {
@@ -14460,7 +15095,7 @@ var init_intake = __esm({
14460
15095
  },
14461
15096
  text: `${uploader} sent a file`,
14462
15097
  at: file.at
14463
- });
15098
+ }, replyTo);
14464
15099
  if (notice.status === "send_failed") {
14465
15100
  const failed = await this.store.append(roomId, {
14466
15101
  version: 1,
@@ -14479,7 +15114,8 @@ var init_intake = __esm({
14479
15114
  intent.recipient_identity,
14480
15115
  file.filename,
14481
15116
  file.mime,
14482
- Buffer.from(file.data_base64, "base64")
15117
+ Buffer.from(file.data_base64, "base64"),
15118
+ replyTo
14483
15119
  );
14484
15120
  const appended2 = await this.store.append(roomId, {
14485
15121
  version: 1,
@@ -14503,18 +15139,19 @@ var init_intake = __esm({
14503
15139
  room_name: room.room_name,
14504
15140
  message_id: message.message_id,
14505
15141
  // An anonymous author leaves the archive only in alias form.
14506
- author: message.author_alias === void 0 ? message.author : {
15142
+ author: scopedAuthor ?? (message.author_alias === void 0 ? message.author : {
14507
15143
  identity: message.author_alias.participant_id,
14508
15144
  display_name: message.author_alias.alias,
14509
15145
  role: message.author.role
14510
- },
15146
+ }),
15147
+ ...publicThread,
14511
15148
  text: message.text,
14512
15149
  at: message.at,
14513
15150
  ...message.briefing_role === void 0 ? {} : { briefing_role: message.briefing_role },
14514
15151
  ...message.briefing_version === void 0 ? {} : { briefing_version: message.briefing_version },
14515
15152
  ...message.membership === void 0 ? {} : { membership: message.membership }
14516
15153
  };
14517
- const outcome = await sendRoomBody(packet, intent.recipient_identity, unsigned);
15154
+ const outcome = await sendRoomBody(packet, intent.recipient_identity, unsigned, replyTo);
14518
15155
  const appended = await this.store.append(roomId, {
14519
15156
  version: 1,
14520
15157
  kind: "relay_result",
@@ -14530,6 +15167,20 @@ var init_intake = __esm({
14530
15167
  }
14531
15168
  }
14532
15169
  }
15170
+ async skipRelay(roomId, intent, status) {
15171
+ const result = await this.store.append(roomId, {
15172
+ version: 1,
15173
+ kind: "relay_result",
15174
+ room_id: roomId,
15175
+ at: this.now(),
15176
+ intent_record_id: intent.record_id,
15177
+ ...intent.message_id === void 0 ? {} : { message_id: intent.message_id },
15178
+ ...intent.file_id === void 0 ? {} : { file_id: intent.file_id },
15179
+ recipient_identity: intent.recipient_identity,
15180
+ status
15181
+ });
15182
+ if (result.kind !== "relay_result") throw new Error("storage returned the wrong relay result kind");
15183
+ }
14533
15184
  findSourceMessage(records, item) {
14534
15185
  const message = records.find((record) => record.kind === "message" && record.source_msg_id === item.msg_id);
14535
15186
  if (!message) return void 0;
@@ -14723,7 +15374,7 @@ var init_command_routes = __esm({
14723
15374
  }).strict();
14724
15375
  RuntimeCommandGrantParams = external_exports.object({
14725
15376
  room_id: external_exports.string(),
14726
- caller_cid: ContainerIdSchema,
15377
+ caller_cid: ContainerIdSchema2,
14727
15378
  command: RuntimeCommandNameSchema
14728
15379
  }).strict();
14729
15380
  RuntimeRoleCommandGrantParams = external_exports.object({
@@ -14745,9 +15396,132 @@ var init_command_routes = __esm({
14745
15396
  }
14746
15397
  });
14747
15398
 
15399
+ // src/thread-history.ts
15400
+ function publicHistoryMessage(record, author = record.author) {
15401
+ return {
15402
+ version: 1,
15403
+ room_id: record.room_id,
15404
+ seq: record.seq,
15405
+ record_id: record.record_id,
15406
+ at: record.at,
15407
+ kind: "message",
15408
+ message_id: record.message_id,
15409
+ author: { identity: author.identity, display_name: author.display_name, role: author.role },
15410
+ category: record.category,
15411
+ text: record.text,
15412
+ ...record.category === "role_briefing" ? { briefing_role: record.briefing_role } : {},
15413
+ ...(record.category === "briefing" || record.category === "role_briefing") && record.briefing_version !== void 0 ? { briefing_version: record.briefing_version } : {},
15414
+ ...record.category === "membership" && record.membership ? { membership: {
15415
+ action: record.membership.action,
15416
+ epoch: record.membership.epoch,
15417
+ ...record.membership.alias === void 0 ? {} : { alias: record.membership.alias },
15418
+ ...record.membership.role === void 0 ? {} : { role: record.membership.role }
15419
+ } } : {}
15420
+ };
15421
+ }
15422
+ function projectParticipantHistory(room, records, viewerCid, page) {
15423
+ const viewer = room.seats.find((seat) => seat.state === "active" && seat.identity === viewerCid);
15424
+ if (!viewer || room.state !== "active") throw new ThreadFailure("unauthorized");
15425
+ const { after = 0, limit = 200 } = ParticipantHistoryPageSchema.parse(page);
15426
+ const replyRows = records.filter((row) => ["message", "file", "relay_intent", "relay_result"].includes(row.kind));
15427
+ const output = [];
15428
+ let ordinal = 0, bytes = 2;
15429
+ for (const record of records) {
15430
+ if (record.kind !== "message" || record.room_id !== room.room_id) continue;
15431
+ let author = record.author;
15432
+ let thread;
15433
+ let thread_root;
15434
+ try {
15435
+ const association = classifyThreadAssociation(room, replyRows, record);
15436
+ if (association.state === "scoped") {
15437
+ const { root } = association;
15438
+ if (!activeThreadSeat(room, root.thread_root, viewerCid)) continue;
15439
+ const metadata = publicThreadMetadata(root, room);
15440
+ author = publicThreadAuthor(record, root.thread_root, room);
15441
+ if (record.message_id === root.message_id) thread_root = metadata;
15442
+ thread = { schema_version: 1, thread_id: root.message_id };
15443
+ } else if (record.author_alias !== void 0) {
15444
+ const alias = AuthorAliasSchema.parse(record.author_alias);
15445
+ author = { identity: alias.participant_id, display_name: alias.alias, role: record.author.role };
15446
+ }
15447
+ } catch (error) {
15448
+ if (error instanceof ThreadFailure || error instanceof external_exports.ZodError) continue;
15449
+ throw error;
15450
+ }
15451
+ ordinal += 1;
15452
+ if (ordinal <= after) continue;
15453
+ const projected = {
15454
+ ...publicHistoryMessage(record, author),
15455
+ seq: ordinal,
15456
+ record_id: `${room.room_id}:participant:${viewer.participant_id}:${ordinal}`,
15457
+ ...thread ? { thread } : {},
15458
+ ...thread_root ? { thread_root } : {}
15459
+ };
15460
+ const size = Buffer.byteLength(JSON.stringify(projected), "utf8") + (output.length ? 1 : 0);
15461
+ if (bytes + size > MAX_HISTORY_PAGE_BYTES) {
15462
+ if (output.length === 0) throw new RangeError("one participant history record exceeds the page byte contract");
15463
+ break;
15464
+ }
15465
+ output.push(projected);
15466
+ bytes += size;
15467
+ if (output.length >= limit) break;
15468
+ }
15469
+ return output;
15470
+ }
15471
+ var PublicThreadSchema, PublicThreadMetadataSchema, PublicMessageShape, ParticipantHistoryRecordSchema, ParticipantHistoryPageSchema;
15472
+ var init_thread_history = __esm({
15473
+ "src/thread-history.ts"() {
15474
+ "use strict";
15475
+ init_zod();
15476
+ init_contracts();
15477
+ init_thread_contracts();
15478
+ init_threads();
15479
+ PublicThreadSchema = external_exports.object({ schema_version: external_exports.literal(1), thread_id: LowerCrockfordUlidSchema }).strict();
15480
+ PublicThreadMetadataSchema = PublicThreadSchema.extend({
15481
+ topic: external_exports.string(),
15482
+ creator: AuthorSnapshotSchema,
15483
+ participant_ids: external_exports.array(LowerCrockfordUlidSchema),
15484
+ created_at: Rfc3339Schema
15485
+ }).strict();
15486
+ PublicMessageShape = {
15487
+ version: external_exports.literal(1),
15488
+ room_id: LowerCrockfordUlidSchema,
15489
+ seq: external_exports.number().int().positive().safe(),
15490
+ record_id: external_exports.string(),
15491
+ at: Rfc3339Schema,
15492
+ kind: external_exports.literal("message"),
15493
+ message_id: LowerCrockfordUlidSchema,
15494
+ author: AuthorSnapshotSchema,
15495
+ category: external_exports.enum(["briefing", "role_briefing", "chat", "membership"]),
15496
+ briefing_role: RoleSchema.optional(),
15497
+ briefing_version: external_exports.number().int().positive().safe().optional(),
15498
+ membership: MembershipNoticeSchema.optional(),
15499
+ text: MessageTextSchema
15500
+ };
15501
+ ParticipantHistoryRecordSchema = external_exports.object({
15502
+ ...PublicMessageShape,
15503
+ thread: PublicThreadSchema.optional(),
15504
+ thread_root: PublicThreadMetadataSchema.optional()
15505
+ }).strict().superRefine((row, ctx) => {
15506
+ const prefix = `${row.room_id}:participant:`;
15507
+ const participantId = row.record_id.slice(prefix.length, -(String(row.seq).length + 1));
15508
+ if (!row.record_id.startsWith(prefix) || !LowerCrockfordUlidSchema.safeParse(participantId).success || row.record_id !== `${prefix}${participantId}:${row.seq}`) {
15509
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["record_id"], message: "must identify the viewer and visible ordinal" });
15510
+ }
15511
+ if (row.thread_root && (!row.thread || row.thread.thread_id !== row.thread_root.thread_id || row.message_id !== row.thread.thread_id)) {
15512
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["thread_root"], message: "must identify this thread root" });
15513
+ }
15514
+ });
15515
+ ParticipantHistoryPageSchema = external_exports.object({
15516
+ after: external_exports.number().int().nonnegative().safe().optional(),
15517
+ limit: external_exports.number().int().positive().safe().optional()
15518
+ }).strict();
15519
+ }
15520
+ });
15521
+
14748
15522
  // src/service.ts
14749
15523
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
14750
- import { createHash as createHash4 } from "node:crypto";
15524
+ import { createHash as createHash5 } from "node:crypto";
14751
15525
  function byteBoundedHistoryPage(records) {
14752
15526
  const page = [];
14753
15527
  let bytes = 2;
@@ -14804,7 +15578,7 @@ function uniqueIdentities(identities) {
14804
15578
  function currentContactIdentities(packet) {
14805
15579
  return new Set(packet.listContacts().map((contact) => contact.container_id));
14806
15580
  }
14807
- var CreateInviteInputSchema, HistoryOptionsSchema, JOURNAL_WORK_BATCH_SIZE2, DeleteRoomInputSchema, RemoveParticipantInputSchema, RoomServiceError, RoomService;
15581
+ var MAX_ROOM_MESSAGE_BYTES, CreateInviteInputSchema, HistoryOptionsSchema, JOURNAL_WORK_BATCH_SIZE2, DeleteRoomInputSchema, RemoveParticipantInputSchema, RoomServiceError, RoomService;
14808
15582
  var init_service = __esm({
14809
15583
  "src/service.ts"() {
14810
15584
  "use strict";
@@ -14816,6 +15590,11 @@ var init_service = __esm({
14816
15590
  init_consumer_commands();
14817
15591
  init_command_names();
14818
15592
  init_ulid();
15593
+ init_reply_threading();
15594
+ init_thread_contracts();
15595
+ init_thread_history();
15596
+ init_threads();
15597
+ MAX_ROOM_MESSAGE_BYTES = 262144;
14819
15598
  CreateInviteInputSchema = external_exports.object({
14820
15599
  mode: InviteModeSchema,
14821
15600
  role: RoleSchema.optional(),
@@ -15035,11 +15814,106 @@ var init_service = __esm({
15035
15814
  handler: (input, context) => this.invokeConsumerCommand(roomId, definition.name, input, context)
15036
15815
  })),
15037
15816
  sharedCommand: (name, input, context) => this.lock(roomId, () => this.sharedCommandUnlocked(roomId, name, input, context)),
15817
+ startThread: async (input, context) => {
15818
+ const result = await this.lock(roomId, () => this.startThreadCommandUnlocked(roomId, input, context));
15819
+ if (result.ok === true) await this.intake.resumePending(roomId);
15820
+ return result;
15821
+ },
15038
15822
  listMembers: (input, context) => this.lock(roomId, () => this.listMembersCommandUnlocked(roomId, input, context)),
15039
15823
  removeMember: (input, context) => this.lock(roomId, () => this.removeMemberCommandUnlocked(roomId, input, context))
15040
15824
  });
15041
15825
  this.publishedConsumerRevisions.set(roomId, registered.consumer_commands_revision ?? 0);
15042
15826
  }
15827
+ /** Called by the registered adapter while it owns the room mutex. */
15828
+ async startThreadCommandUnlocked(roomId, input, context) {
15829
+ const room = await this.store.load(roomId);
15830
+ if (room.state !== "active" || room.lifecycle_request?.state === "pending") {
15831
+ return { ok: false, error: "room_unavailable" };
15832
+ }
15833
+ const creator = room.seats.find((seat) => seat.state === "active" && seat.identity === context.sender_cid);
15834
+ if (creator === void 0 || !this.hasRuntimeCommandGrant(room, context.sender_cid, "start_thread")) {
15835
+ return { ok: false, error: "unauthorized" };
15836
+ }
15837
+ const parsed = StartThreadInputSchema.safeParse(input);
15838
+ if (!parsed.success) return { ok: false, error: "invalid_request" };
15839
+ const request = parsed.data;
15840
+ const rows = await readReplyRows(this.store, roomId);
15841
+ const prior = rows.filter((row) => row.kind === "message" && row.thread_root !== void 0 && row.author.identity === context.sender_cid && row.thread_root.idempotency_key === request.idempotency_key);
15842
+ if (prior.length > 1) throw new Error("duplicate thread roots for creator idempotency key");
15843
+ if (prior.length === 1) {
15844
+ const root2 = prior[0];
15845
+ if (root2.thread_root.creator_participant_id !== creator.participant_id) {
15846
+ return { ok: false, error: "unauthorized" };
15847
+ }
15848
+ if (root2.thread_root.fingerprint !== threadFingerprint(request)) {
15849
+ return { ok: false, error: "idempotency_conflict" };
15850
+ }
15851
+ return { ok: true, thread_id: root2.message_id, status: "accepted" };
15852
+ }
15853
+ let members;
15854
+ try {
15855
+ members = selectThreadMembers(room, context.sender_cid, request);
15856
+ } catch (error) {
15857
+ if (error instanceof ThreadFailure) return { ok: false, error: error.code };
15858
+ throw error;
15859
+ }
15860
+ const threadId = LowerCrockfordUlidSchema.parse(this.nextMessageId());
15861
+ const at = this.now();
15862
+ const threadRoot = {
15863
+ schema_version: 1,
15864
+ thread_id: threadId,
15865
+ topic: request.topic,
15866
+ creator_participant_id: creator.participant_id,
15867
+ members,
15868
+ idempotency_key: request.idempotency_key,
15869
+ fingerprint: threadFingerprint(request)
15870
+ };
15871
+ if (room.anonymous && creator.alias === void 0) {
15872
+ throw new Error("anonymous thread creator is missing its room alias");
15873
+ }
15874
+ const root = {
15875
+ version: 1,
15876
+ kind: "message",
15877
+ room_id: roomId,
15878
+ at,
15879
+ message_id: threadId,
15880
+ author: {
15881
+ identity: creator.identity,
15882
+ display_name: creator.display_name,
15883
+ role: creator.role
15884
+ },
15885
+ ...room.anonymous ? {
15886
+ author_alias: { participant_id: creator.participant_id, alias: creator.alias }
15887
+ } : {},
15888
+ category: "chat",
15889
+ text: `Thread: ${request.topic}`,
15890
+ recipient_identities: members.map((member) => member.identity),
15891
+ scope: { thread_id: threadId },
15892
+ thread_root: threadRoot
15893
+ };
15894
+ const projected = {
15895
+ version: 1,
15896
+ kind: "room_msg",
15897
+ room_id: roomId,
15898
+ room_name: room.room_name,
15899
+ message_id: threadId,
15900
+ author: room.anonymous ? {
15901
+ identity: creator.participant_id,
15902
+ display_name: creator.alias,
15903
+ role: creator.role
15904
+ } : root.author,
15905
+ text: root.text,
15906
+ at,
15907
+ thread: { schema_version: 1, thread_id: threadId },
15908
+ thread_root: publicThreadMetadata(root, room)
15909
+ };
15910
+ if (Buffer.byteLength(JSON.stringify(projected), "utf8") > MAX_ROOM_MESSAGE_BYTES) {
15911
+ return { ok: false, error: "invalid_request" };
15912
+ }
15913
+ const appended = await this.store.append(roomId, root);
15914
+ if (appended.kind !== "message") throw new Error("storage returned the wrong thread root kind");
15915
+ return { ok: true, thread_id: appended.message_id, status: "accepted" };
15916
+ }
15043
15917
  /** The SDK supplies authenticated context; arguments never select another room. */
15044
15918
  async sharedCommandUnlocked(roomId, name, input, context) {
15045
15919
  if (!SHARED_ROOM_COMMANDS.includes(name) || input === null || typeof input !== "object" || Array.isArray(input) || Object.hasOwn(input, "room_id")) return { ok: false, error: "invalid_request" };
@@ -15080,8 +15954,30 @@ var init_service = __esm({
15080
15954
  try {
15081
15955
  return await this.commandScope.run(scope, async () => {
15082
15956
  try {
15957
+ if (name === "room.history") {
15958
+ const request = ParticipantHistoryPageSchema.extend({ view: external_exports.literal("participant").optional() }).safeParse(input);
15959
+ if (!request.success) return { ok: false, error: "invalid_request" };
15960
+ const { view: _view, ...page } = request.data;
15961
+ return { ok: true, result: JSON.parse(JSON.stringify(await this.participantHistory(roomId, context.sender_cid, page))) };
15962
+ }
15963
+ if (name === "room.show" || name === "room.participants") {
15964
+ external_exports.object({}).strict().parse(input);
15965
+ const result2 = name === "room.show" ? {
15966
+ room_id: room.room_id,
15967
+ room_name: room.room_name,
15968
+ state: room.state,
15969
+ mission: { goal: room.mission.goal, briefing: room.mission.briefing, briefing_version: room.mission.briefing_version },
15970
+ anonymous: room.anonymous,
15971
+ quiet_membership: room.quiet_membership,
15972
+ membership_epoch: room.membership_epoch
15973
+ } : room.seats.map(({ participant_id, role, state }) => ({ participant_id, role, state }));
15974
+ return { ok: true, result: JSON.parse(JSON.stringify(result2)) };
15975
+ }
15083
15976
  const routes = name === "room.accept" ? createPrivateServiceRoutes(this) : createServiceRoutes(this);
15084
15977
  const result = await routes[name].run({ ...input, room_id: roomId });
15978
+ if (name === "room.message" || name === "room.say") {
15979
+ return { ok: true, result: { message_id: result.message_id, accepted: true } };
15980
+ }
15085
15981
  return { ok: true, result: JSON.parse(JSON.stringify(result)) };
15086
15982
  } catch (error) {
15087
15983
  return { ok: false, error: classifyServiceError(error) };
@@ -15252,7 +16148,7 @@ var init_service = __esm({
15252
16148
  } catch {
15253
16149
  throw new RoomServiceError("external invite is invalid or exceeds the 48 KiB decoded limit");
15254
16150
  }
15255
- const digest = createHash4("sha256").update(decoded).digest("hex");
16151
+ const digest = createHash5("sha256").update(decoded).digest("hex");
15256
16152
  const receipt = await this.lock(id, async () => {
15257
16153
  const room = await this.store.load(id);
15258
16154
  this.assertMutable(room, "accept an external invite for");
@@ -15265,7 +16161,7 @@ var init_service = __esm({
15265
16161
  } catch {
15266
16162
  throw new RoomServiceError("external invite was rejected");
15267
16163
  }
15268
- const cid = ContainerIdSchema.parse(added.container_id);
16164
+ const cid = ContainerIdSchema2.parse(added.container_id);
15269
16165
  if (request.expected_cid !== void 0 && cid !== request.expected_cid) {
15270
16166
  throw new RoomServiceError("external invite inviter CID did not match --expected-cid");
15271
16167
  }
@@ -15857,28 +16753,38 @@ var init_service = __esm({
15857
16753
  return saved.command_grants.map((grant) => ({ ...grant }));
15858
16754
  });
15859
16755
  }
16756
+ /** Internal authenticated API; numeric cursors count only this viewer's visible messages. */
16757
+ async participantHistory(roomId, viewerCid, page = {}) {
16758
+ const id = LowerCrockfordUlidSchema.parse(roomId);
16759
+ const request = ParticipantHistoryPageSchema.parse(page);
16760
+ return this.lock(id, async () => {
16761
+ const room = await this.store.load(id);
16762
+ if (room.state !== "active" || !room.seats.some((seat) => seat.state === "active" && seat.identity === viewerCid)) {
16763
+ throw new ThreadFailure("unauthorized");
16764
+ }
16765
+ const records = [];
16766
+ let after = 0;
16767
+ for (; ; ) {
16768
+ const batch = await this.store.read(id, { after, limit: JOURNAL_WORK_BATCH_SIZE2 });
16769
+ if (batch.length === 0) break;
16770
+ const last = batch[batch.length - 1];
16771
+ if (last.seq <= after || batch.some((row) => row.room_id !== id)) throw new Error("invalid participant history archive page");
16772
+ records.push(...batch);
16773
+ after = last.seq;
16774
+ }
16775
+ return projectParticipantHistory(room, records, viewerCid, request);
16776
+ });
16777
+ }
15860
16778
  async history(roomId, options = {}) {
15861
16779
  const id = LowerCrockfordUlidSchema.parse(roomId);
15862
16780
  const { view, ...page } = HistoryOptionsSchema.parse(options);
15863
16781
  const records = view === "participant" ? await queryStore2(this.store, id, { kind: "message", after: page.after, limit: page.limit }) : await this.store.read(id, page);
15864
16782
  if (view !== "participant") return byteBoundedHistoryPage(records);
15865
- const projected = records.filter((record) => record.kind === "message").map((record) => {
15866
- const {
15867
- author_alias,
15868
- recipient_identities: _recipients,
15869
- source_msg_id: _sourceMsg,
15870
- source_wire_id: _sourceWire,
15871
- ...rest
15872
- } = record;
15873
- return {
15874
- ...rest,
15875
- author: author_alias === void 0 ? record.author : {
15876
- identity: author_alias.participant_id,
15877
- display_name: author_alias.alias,
15878
- role: record.author.role
15879
- }
15880
- };
15881
- });
16783
+ const projected = records.filter((record) => record.kind === "message").map((record) => publicHistoryMessage(record, record.author_alias === void 0 ? record.author : {
16784
+ identity: record.author_alias.participant_id,
16785
+ display_name: record.author_alias.alias,
16786
+ role: record.author.role
16787
+ }));
15882
16788
  return byteBoundedHistoryPage(projected.slice(0, page.limit ?? Number.MAX_SAFE_INTEGER));
15883
16789
  }
15884
16790
  /**
@@ -16438,7 +17344,7 @@ import * as nodeFs2 from "node:fs";
16438
17344
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
16439
17345
  import { basename, dirname as dirname3, join as join4 } from "node:path";
16440
17346
  import Database from "better-sqlite3";
16441
- var DIRECTORY_MODE2, FILE_MODE2, NO_FOLLOW2, SQLITE_SCHEMA_VERSION, DEFAULT_WORK_BATCH_SIZE, utf8Decoder, CoworkStorageError, RoomQueue, CoworkStore;
17347
+ var DIRECTORY_MODE2, FILE_MODE2, NO_FOLLOW2, SQLITE_SCHEMA_VERSION, DEFAULT_WORK_BATCH_SIZE, utf8Decoder, SQLITE_V2_EXTENSION_DDL, CoworkStorageError, RoomQueue, CoworkStore;
16442
17348
  var init_storage = __esm({
16443
17349
  "src/storage.ts"() {
16444
17350
  "use strict";
@@ -16447,9 +17353,24 @@ var init_storage = __esm({
16447
17353
  DIRECTORY_MODE2 = 448;
16448
17354
  FILE_MODE2 = 384;
16449
17355
  NO_FOLLOW2 = nodeFs2.constants.O_NOFOLLOW ?? 0;
16450
- SQLITE_SCHEMA_VERSION = 1;
17356
+ SQLITE_SCHEMA_VERSION = 2;
16451
17357
  DEFAULT_WORK_BATCH_SIZE = 64;
16452
17358
  utf8Decoder = new TextDecoder("utf-8", { fatal: true });
17359
+ SQLITE_V2_EXTENSION_DDL = `
17360
+ CREATE UNIQUE INDEX IF NOT EXISTS records_thread_creation_key
17361
+ ON records(json_extract(payload_json,'$.author.identity'),
17362
+ json_extract(payload_json,'$.thread_root.idempotency_key'))
17363
+ WHERE kind='message' AND json_type(payload_json,'$.thread_root')='object';
17364
+ CREATE INDEX IF NOT EXISTS records_thread_id
17365
+ ON records(json_extract(payload_json,'$.scope.thread_id'))
17366
+ WHERE kind='message' AND json_type(payload_json,'$.scope')='object';
17367
+ DROP INDEX IF EXISTS records_source_message;
17368
+ DROP INDEX IF EXISTS records_source_file;
17369
+ CREATE UNIQUE INDEX records_source_message ON records(source_msg_id)
17370
+ WHERE source_msg_id IS NOT NULL;
17371
+ CREATE UNIQUE INDEX records_source_file ON records(source_file_id)
17372
+ WHERE source_file_id IS NOT NULL;
17373
+ `;
16453
17374
  CoworkStorageError = class extends Error {
16454
17375
  constructor(message, options) {
16455
17376
  super(message, options);
@@ -16729,7 +17650,7 @@ var init_storage = __esm({
16729
17650
  "SELECT recipient_identity FROM relay_intent_work WHERE record_seq = ? ORDER BY recipient_identity LIMIT ?"
16730
17651
  ).all(recordSeq, limit).map((row) => row.recipient_identity)));
16731
17652
  }
16732
- async briefingDeliveryTimes(roomId, key, recipientIdentities) {
17653
+ async briefingDeliveryTimes(roomId, key2, recipientIdentities) {
16733
17654
  const id = this.roomId(roomId);
16734
17655
  if (recipientIdentities.length === 0) return /* @__PURE__ */ new Map();
16735
17656
  return this.mutex(id, () => this.withDatabase(id, (db) => {
@@ -16743,9 +17664,9 @@ var init_storage = __esm({
16743
17664
  for (const recipient of recipientIdentities) {
16744
17665
  const row = lookup.get(
16745
17666
  recipient,
16746
- key.category,
16747
- key.briefingRole ?? null,
16748
- key.briefingVersion
17667
+ key2.category,
17668
+ key2.briefingRole ?? null,
17669
+ key2.briefingVersion
16749
17670
  );
16750
17671
  if (row) deliveries.set(recipient, row.at);
16751
17672
  }
@@ -16853,55 +17774,65 @@ var init_storage = __esm({
16853
17774
  try {
16854
17775
  this.secureSqliteFiles(path);
16855
17776
  db = new Database(path, { fileMustExist: !create });
17777
+ const activeDb = db;
16856
17778
  if (guardFd !== void 0) this.validateOpenPath(guardFd, path, "room archive database", "file", true);
16857
17779
  this.fs.chmodSync(path, FILE_MODE2);
16858
- db.pragma("journal_mode = WAL");
16859
- db.pragma("synchronous = FULL");
16860
- db.pragma("foreign_keys = ON");
16861
- db.pragma("busy_timeout = 5000");
17780
+ const existingVersion = create ? void 0 : activeDb.pragma("user_version", { simple: true });
17781
+ if (existingVersion !== void 0 && existingVersion !== 1 && existingVersion !== SQLITE_SCHEMA_VERSION) {
17782
+ throw new CoworkStorageError(`unsupported room archive schema version ${existingVersion}`);
17783
+ }
17784
+ activeDb.pragma("journal_mode = WAL");
17785
+ activeDb.pragma("synchronous = FULL");
17786
+ activeDb.pragma("foreign_keys = ON");
17787
+ activeDb.pragma("busy_timeout = 5000");
16862
17788
  if (create) {
16863
- db.exec(`CREATE TABLE records (
16864
- seq INTEGER PRIMARY KEY, record_id TEXT NOT NULL UNIQUE, kind TEXT NOT NULL, at TEXT NOT NULL,
16865
- payload_json TEXT NOT NULL, blob_path TEXT, message_id TEXT, file_id TEXT, intent_record_id TEXT,
16866
- recipient_identity TEXT, source_msg_id INTEGER, source_file_id INTEGER, category TEXT,
16867
- briefing_role TEXT, briefing_version INTEGER, membership_epoch INTEGER
16868
- );
16869
- CREATE TABLE record_recipients (
16870
- record_seq INTEGER NOT NULL REFERENCES records(seq) ON DELETE CASCADE,
16871
- recipient_identity TEXT NOT NULL, category TEXT, briefing_role TEXT,
16872
- briefing_version INTEGER, PRIMARY KEY(record_seq, recipient_identity)
16873
- );
16874
- CREATE TABLE relay_intent_work (
16875
- record_seq INTEGER NOT NULL REFERENCES records(seq) ON DELETE CASCADE,
16876
- recipient_identity TEXT NOT NULL, PRIMARY KEY(record_seq, recipient_identity)
16877
- );
16878
- CREATE INDEX relay_work_source ON relay_intent_work(record_seq, recipient_identity);
16879
- CREATE INDEX records_kind_seq ON records(kind, seq);
16880
- CREATE INDEX records_message ON records(message_id, kind, seq);
16881
- CREATE INDEX records_file ON records(file_id, kind, seq);
16882
- CREATE INDEX records_intent_result ON records(intent_record_id, kind);
16883
- CREATE INDEX records_relay_recipient ON records(kind, recipient_identity, seq);
16884
- CREATE UNIQUE INDEX records_source_message ON records(source_msg_id) WHERE kind='message' AND source_msg_id IS NOT NULL;
16885
- CREATE UNIQUE INDEX records_source_file ON records(source_file_id) WHERE kind='file' AND source_file_id IS NOT NULL;
16886
- CREATE INDEX records_briefing ON records(category, briefing_role, briefing_version, seq);
16887
- CREATE INDEX records_membership_epoch ON records(category, membership_epoch);
16888
- CREATE INDEX recipients_identity ON record_recipients(recipient_identity, record_seq);
16889
- CREATE INDEX recipients_briefing_delivery ON record_recipients
16890
- (recipient_identity, category, briefing_role, briefing_version, record_seq);`);
16891
- db.pragma(`user_version = ${SQLITE_SCHEMA_VERSION}`);
17789
+ activeDb.transaction(() => {
17790
+ activeDb.exec(`CREATE TABLE records (
17791
+ seq INTEGER PRIMARY KEY, record_id TEXT NOT NULL UNIQUE, kind TEXT NOT NULL, at TEXT NOT NULL,
17792
+ payload_json TEXT NOT NULL, blob_path TEXT, message_id TEXT, file_id TEXT, intent_record_id TEXT,
17793
+ recipient_identity TEXT, source_msg_id INTEGER, source_file_id INTEGER, category TEXT,
17794
+ briefing_role TEXT, briefing_version INTEGER, membership_epoch INTEGER
17795
+ );
17796
+ CREATE TABLE record_recipients (
17797
+ record_seq INTEGER NOT NULL REFERENCES records(seq) ON DELETE CASCADE,
17798
+ recipient_identity TEXT NOT NULL, category TEXT, briefing_role TEXT,
17799
+ briefing_version INTEGER, PRIMARY KEY(record_seq, recipient_identity)
17800
+ );
17801
+ CREATE TABLE relay_intent_work (
17802
+ record_seq INTEGER NOT NULL REFERENCES records(seq) ON DELETE CASCADE,
17803
+ recipient_identity TEXT NOT NULL, PRIMARY KEY(record_seq, recipient_identity)
17804
+ );
17805
+ CREATE INDEX relay_work_source ON relay_intent_work(record_seq, recipient_identity);
17806
+ CREATE INDEX records_kind_seq ON records(kind, seq);
17807
+ CREATE INDEX records_message ON records(message_id, kind, seq);
17808
+ CREATE INDEX records_file ON records(file_id, kind, seq);
17809
+ CREATE INDEX records_intent_result ON records(intent_record_id, kind);
17810
+ CREATE INDEX records_relay_recipient ON records(kind, recipient_identity, seq);
17811
+ CREATE UNIQUE INDEX records_source_message ON records(source_msg_id) WHERE kind='message' AND source_msg_id IS NOT NULL;
17812
+ CREATE UNIQUE INDEX records_source_file ON records(source_file_id) WHERE kind='file' AND source_file_id IS NOT NULL;
17813
+ CREATE INDEX records_briefing ON records(category, briefing_role, briefing_version, seq);
17814
+ CREATE INDEX records_membership_epoch ON records(category, membership_epoch);
17815
+ CREATE INDEX recipients_identity ON record_recipients(recipient_identity, record_seq);
17816
+ CREATE INDEX recipients_briefing_delivery ON record_recipients
17817
+ (recipient_identity, category, briefing_role, briefing_version, record_seq);`);
17818
+ activeDb.exec(SQLITE_V2_EXTENSION_DDL);
17819
+ activeDb.pragma(`user_version = ${SQLITE_SCHEMA_VERSION}`);
17820
+ }).immediate();
16892
17821
  this.reconciledBlobRooms.add(roomId);
16893
17822
  } else {
16894
- const version = db.pragma("user_version", { simple: true });
16895
- if (version !== SQLITE_SCHEMA_VERSION) {
16896
- throw new CoworkStorageError(`unsupported room archive schema version ${version}`);
17823
+ if (existingVersion === 1) {
17824
+ activeDb.transaction(() => {
17825
+ activeDb.exec(SQLITE_V2_EXTENSION_DDL);
17826
+ activeDb.pragma(`user_version = ${SQLITE_SCHEMA_VERSION}`);
17827
+ }).immediate();
16897
17828
  }
16898
17829
  if (!this.reconciledBlobRooms.has(roomId)) {
16899
- this.reconcileBlobDirectory(roomId, db);
17830
+ this.reconcileBlobDirectory(roomId, activeDb);
16900
17831
  this.reconciledBlobRooms.add(roomId);
16901
17832
  }
16902
17833
  }
16903
17834
  this.secureSqliteFiles(path);
16904
- const result = work(db);
17835
+ const result = work(activeDb);
16905
17836
  this.secureSqliteFiles(path);
16906
17837
  return result;
16907
17838
  } catch (error) {
@@ -18052,7 +18983,7 @@ function isIntakeNotification(event) {
18052
18983
  function createDaemonControlRoutes(control) {
18053
18984
  if (!/^[0-9a-f]{32}$/.test(control.session)) throw new TypeError("invalid daemon control session");
18054
18985
  const requireExact = (params2, keys) => {
18055
- if (Object.keys(params2).length !== keys.length || keys.some((key) => !Object.hasOwn(params2, key))) {
18986
+ if (Object.keys(params2).length !== keys.length || keys.some((key2) => !Object.hasOwn(params2, key2))) {
18056
18987
  throw new TypeError("invalid daemon control parameters");
18057
18988
  }
18058
18989
  };