@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/README.md +3 -0
- package/dist/cli.js +220 -8
- package/dist/daemon.js +1228 -297
- package/dist/web/assets/app.js +9 -9
- package/docs/05-room-workflow.md +37 -1
- package/docs/07-messaging-history.md +14 -0
- package/docs/08-backup-restore.md +1 -1
- package/docs/10-limitations.md +1 -0
- package/package.json +1 -1
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
|
|
22
|
-
if (!__hasOwnProp.call(to,
|
|
23
|
-
__defProp(to,
|
|
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
|
|
73
|
-
if (Object.prototype.hasOwnProperty.call(object,
|
|
74
|
-
keys.push(
|
|
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
|
|
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 (
|
|
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 (
|
|
516
|
+
if (key2.status === "dirty")
|
|
517
517
|
status.dirty();
|
|
518
518
|
if (value.status === "dirty")
|
|
519
519
|
status.dirty();
|
|
520
|
-
if (
|
|
521
|
-
finalObject[
|
|
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
|
|
654
|
-
const fieldSchema = schema.shape[
|
|
655
|
-
newShape[
|
|
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((
|
|
683
|
+
const sharedKeys = util.objectKeys(a).filter((key2) => bKeys.indexOf(key2) !== -1);
|
|
684
684
|
const newObj = { ...a, ...b };
|
|
685
|
-
for (const
|
|
686
|
-
const sharedValue = mergeValues(a[
|
|
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[
|
|
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,
|
|
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 =
|
|
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
|
|
2441
|
-
if (!shapeKeys.includes(
|
|
2442
|
-
extraKeys.push(
|
|
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
|
|
2448
|
-
const keyValidator = shape[
|
|
2449
|
-
const value = ctx.data[
|
|
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:
|
|
2452
|
-
value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path,
|
|
2453
|
-
alwaysSet:
|
|
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
|
|
2459
|
+
for (const key2 of extraKeys) {
|
|
2460
2460
|
pairs.push({
|
|
2461
|
-
key: { status: "valid", value:
|
|
2462
|
-
value: { status: "valid", value: ctx.data[
|
|
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
|
|
2480
|
-
const value = ctx.data[
|
|
2479
|
+
for (const key2 of extraKeys) {
|
|
2480
|
+
const value = ctx.data[key2];
|
|
2481
2481
|
pairs.push({
|
|
2482
|
-
key: { status: "valid", value:
|
|
2482
|
+
key: { status: "valid", value: key2 },
|
|
2483
2483
|
value: catchall._parse(
|
|
2484
|
-
new ParseInputLazyPath(ctx, value, ctx.path,
|
|
2484
|
+
new ParseInputLazyPath(ctx, value, ctx.path, key2)
|
|
2485
2485
|
//, ctx.child(key), value, getParsedType(value)
|
|
2486
2486
|
),
|
|
2487
|
-
alwaysSet:
|
|
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
|
|
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(
|
|
2624
|
-
return this.augment({ [
|
|
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
|
|
2656
|
-
if (mask[
|
|
2657
|
-
shape[
|
|
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
|
|
2668
|
-
if (!mask[
|
|
2669
|
-
shape[
|
|
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
|
|
2686
|
-
const fieldSchema = this.shape[
|
|
2687
|
-
if (mask && !mask[
|
|
2688
|
-
newShape[
|
|
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[
|
|
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
|
|
2701
|
-
if (mask && !mask[
|
|
2702
|
-
newShape[
|
|
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[
|
|
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[
|
|
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
|
|
3086
|
+
for (const key2 in ctx.data) {
|
|
3087
3087
|
pairs.push({
|
|
3088
|
-
key: keyType._parse(new ParseInputLazyPath(ctx,
|
|
3089
|
-
value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[
|
|
3090
|
-
alwaysSet:
|
|
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(([
|
|
3138
|
+
const pairs = [...ctx.data.entries()].map(([key2, value], index) => {
|
|
3139
3139
|
return {
|
|
3140
|
-
key: keyType._parse(new ParseInputLazyPath(ctx,
|
|
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
|
|
3148
|
+
const key2 = await pair.key;
|
|
3149
3149
|
const value = await pair.value;
|
|
3150
|
-
if (
|
|
3150
|
+
if (key2.status === "aborted" || value.status === "aborted") {
|
|
3151
3151
|
return INVALID;
|
|
3152
3152
|
}
|
|
3153
|
-
if (
|
|
3153
|
+
if (key2.status === "dirty" || value.status === "dirty") {
|
|
3154
3154
|
status.dirty();
|
|
3155
3155
|
}
|
|
3156
|
-
finalMap.set(
|
|
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
|
|
3163
|
+
const key2 = pair.key;
|
|
3164
3164
|
const value = pair.value;
|
|
3165
|
-
if (
|
|
3165
|
+
if (key2.status === "aborted" || value.status === "aborted") {
|
|
3166
3166
|
return INVALID;
|
|
3167
3167
|
}
|
|
3168
|
-
if (
|
|
3168
|
+
if (key2.status === "dirty" || value.status === "dirty") {
|
|
3169
3169
|
status.dirty();
|
|
3170
3170
|
}
|
|
3171
|
-
finalMap.set(
|
|
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(
|
|
4279
|
-
return typeof
|
|
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(
|
|
4283
|
-
if (typeof
|
|
4284
|
-
return new _Code(`${
|
|
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: ${
|
|
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 [
|
|
4913
|
+
for (const [key2, value] of keyValues) {
|
|
4914
4914
|
if (code.length > 1)
|
|
4915
4915
|
code.push(",");
|
|
4916
|
-
code.push(
|
|
4917
|
-
if (
|
|
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
|
|
5193
|
-
if (!rules[
|
|
5194
|
-
checkStrictMode(it, `unknown keyword: "${
|
|
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
|
|
5202
|
-
if (rules[
|
|
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
|
|
5211
|
-
if (
|
|
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
|
|
5790
|
-
assignDefault(it,
|
|
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
|
|
6175
|
-
if (!equal(a[
|
|
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
|
|
6248
|
-
var sch = schema[
|
|
6247
|
+
for (var key2 in schema) {
|
|
6248
|
+
var sch = schema[key2];
|
|
6249
6249
|
if (Array.isArray(sch)) {
|
|
6250
|
-
if (
|
|
6250
|
+
if (key2 in traverse.arrayKeywords) {
|
|
6251
6251
|
for (var i = 0; i < sch.length; i++)
|
|
6252
|
-
_traverse(opts, pre, post, sch[i], jsonPtr + "/" +
|
|
6252
|
+
_traverse(opts, pre, post, sch[i], jsonPtr + "/" + key2 + "/" + i, rootSchema, jsonPtr, key2, schema, i);
|
|
6253
6253
|
}
|
|
6254
|
-
} else if (
|
|
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 + "/" +
|
|
6257
|
+
_traverse(opts, pre, post, sch[prop], jsonPtr + "/" + key2 + "/" + escapeJsonPtr(prop), rootSchema, jsonPtr, key2, schema, prop);
|
|
6258
6258
|
}
|
|
6259
|
-
} else if (
|
|
6260
|
-
_traverse(opts, pre, post, sch, jsonPtr + "/" +
|
|
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
|
|
6318
|
-
if (REF_KEYWORDS.has(
|
|
6317
|
+
for (const key2 in schema) {
|
|
6318
|
+
if (REF_KEYWORDS.has(key2))
|
|
6319
6319
|
return true;
|
|
6320
|
-
const sch = schema[
|
|
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
|
|
6331
|
-
if (
|
|
6330
|
+
for (const key2 in schema) {
|
|
6331
|
+
if (key2 === "$ref")
|
|
6332
6332
|
return Infinity;
|
|
6333
6333
|
count++;
|
|
6334
|
-
if (SIMPLE_INLINED.has(
|
|
6334
|
+
if (SIMPLE_INLINED.has(key2))
|
|
6335
6335
|
continue;
|
|
6336
|
-
if (typeof schema[
|
|
6337
|
-
(0, util_1.eachItem)(schema[
|
|
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
|
|
6527
|
-
if (self.RULES.all[
|
|
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,
|
|
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
|
-
|
|
8580
|
-
this._checkUnique(
|
|
8581
|
-
this.schemas[
|
|
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,
|
|
8587
|
-
this.addSchema(schema,
|
|
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
|
|
8744
|
-
const rule = rules[
|
|
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[
|
|
8748
|
+
const schema = keywords[key2];
|
|
8749
8749
|
if ($data && schema)
|
|
8750
|
-
keywords[
|
|
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
|
|
8824
|
-
const opt =
|
|
8823
|
+
for (const key2 in checkOpts) {
|
|
8824
|
+
const opt = key2;
|
|
8825
8825
|
if (opt in options)
|
|
8826
|
-
this.logger[log](`${msg}: option ${
|
|
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
|
|
8841
|
-
this.addSchema(optsSchemas[
|
|
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
|
|
9890
|
-
if (
|
|
9889
|
+
for (const key2 in schema) {
|
|
9890
|
+
if (key2 === "__proto__")
|
|
9891
9891
|
continue;
|
|
9892
|
-
const deps = Array.isArray(schema[
|
|
9893
|
-
deps[
|
|
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, (
|
|
9971
|
-
cxt.setParams({ propertyName:
|
|
9970
|
+
gen.forIn("key", data, (key2) => {
|
|
9971
|
+
cxt.setParams({ propertyName: key2 });
|
|
9972
9972
|
cxt.subschema({
|
|
9973
9973
|
keyword: "propertyNames",
|
|
9974
|
-
data:
|
|
9974
|
+
data: key2,
|
|
9975
9975
|
dataTypes: ["string"],
|
|
9976
|
-
propertyName:
|
|
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, (
|
|
10025
|
+
gen.forIn("key", data, (key2) => {
|
|
10026
10026
|
if (!props.length && !patProps.length)
|
|
10027
|
-
additionalPropertyCode(
|
|
10027
|
+
additionalPropertyCode(key2);
|
|
10028
10028
|
else
|
|
10029
|
-
gen.if(isAdditional(
|
|
10029
|
+
gen.if(isAdditional(key2), () => additionalPropertyCode(key2));
|
|
10030
10030
|
});
|
|
10031
10031
|
}
|
|
10032
|
-
function isAdditional(
|
|
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,
|
|
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._)`${
|
|
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(${
|
|
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(
|
|
10048
|
-
gen.code((0, codegen_1._)`delete ${data}[${
|
|
10047
|
+
function deleteAdditional(key2) {
|
|
10048
|
+
gen.code((0, codegen_1._)`delete ${data}[${key2}]`);
|
|
10049
10049
|
}
|
|
10050
|
-
function additionalPropertyCode(
|
|
10050
|
+
function additionalPropertyCode(key2) {
|
|
10051
10051
|
if (opts.removeAdditional === "all" || opts.removeAdditional && schema === false) {
|
|
10052
|
-
deleteAdditional(
|
|
10052
|
+
deleteAdditional(key2);
|
|
10053
10053
|
return;
|
|
10054
10054
|
}
|
|
10055
10055
|
if (schema === false) {
|
|
10056
|
-
cxt.setParams({ additionalProperty:
|
|
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(
|
|
10065
|
+
applyAdditionalSchema(key2, valid, false);
|
|
10066
10066
|
gen.if((0, codegen_1.not)(valid), () => {
|
|
10067
10067
|
cxt.reset();
|
|
10068
|
-
deleteAdditional(
|
|
10068
|
+
deleteAdditional(key2);
|
|
10069
10069
|
});
|
|
10070
10070
|
} else {
|
|
10071
|
-
applyAdditionalSchema(
|
|
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(
|
|
10077
|
+
function applyAdditionalSchema(key2, valid, errors) {
|
|
10078
10078
|
const subschema = {
|
|
10079
10079
|
keyword: "additionalProperties",
|
|
10080
|
-
dataProp:
|
|
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, (
|
|
10206
|
-
gen.if((0, codegen_1._)`${(0, code_1.usePattern)(cxt, pat)}.test(${
|
|
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:
|
|
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}[${
|
|
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 [
|
|
11124
|
-
if (["$ref", "$id", "$async", "pattern", "patternProperties", "format"].includes(
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
12244
|
-
if (seenCommandGrants.has(
|
|
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(
|
|
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:
|
|
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([
|
|
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")
|
|
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")
|
|
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
|
|
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
|
-
|
|
14031
|
-
|
|
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(
|
|
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
|
|
14048
|
-
if (input[
|
|
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
|
|
14055
|
-
return
|
|
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
|
|
14231
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
14786
|
+
sha256: createHash4("sha256").update(bytes).digest("hex"),
|
|
14255
14787
|
data_base64: bytes.toString("base64"),
|
|
14256
|
-
recipient_identities:
|
|
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
|
|
14270
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
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
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
|
|
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
|
-
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
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
|
-
|
|
16895
|
-
|
|
16896
|
-
|
|
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,
|
|
17830
|
+
this.reconcileBlobDirectory(roomId, activeDb);
|
|
16900
17831
|
this.reconciledBlobRooms.add(roomId);
|
|
16901
17832
|
}
|
|
16902
17833
|
}
|
|
16903
17834
|
this.secureSqliteFiles(path);
|
|
16904
|
-
const result = work(
|
|
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((
|
|
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
|
};
|