@ours.network/cowork 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4455,9 +4455,15 @@ var LowerCrockfordUlidSchema = external_exports.string().regex(
4455
4455
  );
4456
4456
  var ContainerIdSchema2 = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
4457
4457
  var RuntimeCommandNameSchema = external_exports.union([external_exports.enum(RUNTIME_COMMAND_NAMES), ConsumerCommandNameSchema]);
4458
+ var RuntimeCommandNamespacePatternSchema = external_exports.string().max(128).regex(/^(?:[a-z0-9][a-z0-9-]*\.)+\*$/);
4459
+ var RuntimeCommandGrantPatternSchema = external_exports.union([
4460
+ RuntimeCommandNameSchema,
4461
+ external_exports.literal("*"),
4462
+ RuntimeCommandNamespacePatternSchema
4463
+ ]);
4458
4464
  var RuntimeCommandGrantSchema = external_exports.object({
4459
4465
  caller_cid: ContainerIdSchema2,
4460
- command: RuntimeCommandNameSchema
4466
+ command: RuntimeCommandGrantPatternSchema
4461
4467
  }).strict();
4462
4468
  function isStrictRfc3339(value) {
4463
4469
  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/.exec(value);
@@ -4506,7 +4512,7 @@ var RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
4506
4512
  var ROOM_ROLE = "room";
4507
4513
  var RuntimeRoleCommandGrantSchema = external_exports.object({
4508
4514
  role: RoleSchema,
4509
- commands: external_exports.array(RuntimeCommandNameSchema).superRefine((commands, context) => {
4515
+ commands: external_exports.array(RuntimeCommandGrantPatternSchema).superRefine((commands, context) => {
4510
4516
  const seen = /* @__PURE__ */ new Set();
4511
4517
  for (const [index, command] of commands.entries()) {
4512
4518
  if (seen.has(command)) {
@@ -5643,8 +5649,8 @@ async function roomRequest(command, args) {
5643
5649
  const role = requiredFlag(parsed, "--role", "room role-command-set");
5644
5650
  const value = requiredFlag(parsed, "--commands", "room role-command-set");
5645
5651
  const commands = value === "none" ? [] : value.split(",");
5646
- if (new Set(commands).size !== commands.length || commands.some((item) => !RuntimeCommandNameSchema.safeParse(item).success)) {
5647
- usageError("--commands must be a unique comma-separated list of built-in command names or none");
5652
+ if (new Set(commands).size !== commands.length || commands.some((item) => !RuntimeCommandGrantPatternSchema.safeParse(item).success)) {
5653
+ usageError("--commands must be a unique comma-separated list of command names, * or namespace.* patterns, or none");
5648
5654
  }
5649
5655
  return { method: "room.command.role.set", params: { room_id: roomId, role, commands } };
5650
5656
  }
@@ -5655,8 +5661,8 @@ async function roomRequest(command, args) {
5655
5661
  3,
5656
5662
  `room ${command}`
5657
5663
  );
5658
- if (!RuntimeCommandNameSchema.safeParse(runtimeCommand).success) {
5659
- usageError("runtime command must be a supported built-in command name");
5664
+ if (!RuntimeCommandGrantPatternSchema.safeParse(runtimeCommand).success) {
5665
+ usageError("runtime command must be a supported command name, * or namespace.* pattern");
5660
5666
  }
5661
5667
  return {
5662
5668
  method: command === "command-grant" ? "room.command.grant" : "room.command.revoke",
package/dist/daemon.js CHANGED
@@ -11625,6 +11625,9 @@ var init_ours_runtime = __esm({
11625
11625
  });
11626
11626
 
11627
11627
  // src/command-names.ts
11628
+ function commandGrantMatches(pattern, command) {
11629
+ return pattern === command || pattern === "*" || pattern.endsWith(".*") && command.startsWith(pattern.slice(0, -1)) && command.length > pattern.length - 1;
11630
+ }
11628
11631
  var SHARED_ROOM_COMMANDS, RUNTIME_COMMAND_NAMES;
11629
11632
  var init_command_names = __esm({
11630
11633
  "src/command-names.ts"() {
@@ -12050,7 +12053,7 @@ function refineMessageThread(message, context) {
12050
12053
  });
12051
12054
  }
12052
12055
  }
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;
12056
+ 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, RuntimeCommandNamespacePatternSchema, RuntimeCommandGrantPatternSchema, 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;
12054
12057
  var init_contracts = __esm({
12055
12058
  "src/contracts.ts"() {
12056
12059
  "use strict";
@@ -12076,9 +12079,15 @@ var init_contracts = __esm({
12076
12079
  );
12077
12080
  ContainerIdSchema2 = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
12078
12081
  RuntimeCommandNameSchema = external_exports.union([external_exports.enum(RUNTIME_COMMAND_NAMES), ConsumerCommandNameSchema]);
12082
+ RuntimeCommandNamespacePatternSchema = external_exports.string().max(128).regex(/^(?:[a-z0-9][a-z0-9-]*\.)+\*$/);
12083
+ RuntimeCommandGrantPatternSchema = external_exports.union([
12084
+ RuntimeCommandNameSchema,
12085
+ external_exports.literal("*"),
12086
+ RuntimeCommandNamespacePatternSchema
12087
+ ]);
12079
12088
  RuntimeCommandGrantSchema = external_exports.object({
12080
12089
  caller_cid: ContainerIdSchema2,
12081
- command: RuntimeCommandNameSchema
12090
+ command: RuntimeCommandGrantPatternSchema
12082
12091
  }).strict();
12083
12092
  Rfc3339Schema = external_exports.string().refine(isStrictRfc3339, "must be a valid RFC3339 timestamp");
12084
12093
  RoomNameSchema = external_exports.string().refine(
@@ -12108,7 +12117,7 @@ var init_contracts = __esm({
12108
12117
  ROOM_ROLE = "room";
12109
12118
  RuntimeRoleCommandGrantSchema = external_exports.object({
12110
12119
  role: RoleSchema,
12111
- commands: external_exports.array(RuntimeCommandNameSchema).superRefine((commands, context) => {
12120
+ commands: external_exports.array(RuntimeCommandGrantPatternSchema).superRefine((commands, context) => {
12112
12121
  const seen = /* @__PURE__ */ new Set();
12113
12122
  for (const [index, command] of commands.entries()) {
12114
12123
  if (seen.has(command)) {
@@ -12973,7 +12982,7 @@ function apiDocsAsset(pathname) {
12973
12982
  return void 0;
12974
12983
  }
12975
12984
  }
12976
- var RPC_PATH, OPENAPI_DOCUMENT_PATH, API_DOCS_PATH, API_DOCS_SCRIPT_PATH, API_DOCS_STYLESHEET_PATH, API_VERSION, EXAMPLE_ROOM_ID, roomIdProperty, roleProperty, restRoleProperty, missionTextProperty, roomNameProperty, inviteModeProperty, notifyProperty, PRIVATE_ROOM_RPC_METHODS, ROOM_RPC_METHODS, RPC_ERROR_CODES, DOCS_PAGE, DOCS_STYLESHEET, DOCS_SCRIPT;
12985
+ var RPC_PATH, OPENAPI_DOCUMENT_PATH, API_DOCS_PATH, API_DOCS_SCRIPT_PATH, API_DOCS_STYLESHEET_PATH, API_VERSION, commandGrantPatternProperty, EXAMPLE_ROOM_ID, roomIdProperty, roleProperty, restRoleProperty, missionTextProperty, roomNameProperty, inviteModeProperty, notifyProperty, PRIVATE_ROOM_RPC_METHODS, ROOM_RPC_METHODS, RPC_ERROR_CODES, DOCS_PAGE, DOCS_STYLESHEET, DOCS_SCRIPT;
12977
12986
  var init_openapi = __esm({
12978
12987
  "src/openapi.ts"() {
12979
12988
  "use strict";
@@ -12984,6 +12993,15 @@ var init_openapi = __esm({
12984
12993
  API_DOCS_SCRIPT_PATH = "/docs/ui.js";
12985
12994
  API_DOCS_STYLESHEET_PATH = "/docs/ui.css";
12986
12995
  API_VERSION = "1";
12996
+ commandGrantPatternProperty = {
12997
+ anyOf: [
12998
+ { type: "string", enum: [...RUNTIME_COMMAND_NAMES] },
12999
+ { type: "string", maxLength: 128, pattern: "^consumer\\.[a-z0-9][a-z0-9.-]*[a-z0-9]$" },
13000
+ { type: "string", enum: ["*"] },
13001
+ { type: "string", maxLength: 128, pattern: "^(?:[a-z0-9][a-z0-9-]*\\.)+\\*$" }
13002
+ ],
13003
+ description: "Exact command, * for all runtime commands, or terminal namespace.*. Patterns also cover future matching commands."
13004
+ };
12987
13005
  EXAMPLE_ROOM_ID = "01jd7q4h9m2v8xk3znbc5regty";
12988
13006
  roomIdProperty = {
12989
13007
  type: "string",
@@ -13048,7 +13066,7 @@ var init_openapi = __esm({
13048
13066
  {
13049
13067
  method: "room.command.definition.put",
13050
13068
  summary: "Register or replace a consumer command",
13051
- description: "Registers a consumer.* command for the ours catalog using an exact host-configured handler reference. Replacement clears grants for that name. Local definitions cannot be replaced here. A published:false result means desired state is committed; use reload to retry publication.",
13069
+ description: "Registers a consumer.* command for the ours catalog using an exact host-configured handler reference. Replacement clears exact grants for that name; wildcard grants remain effective. Local definitions cannot be replaced here. A published:false result means desired state is committed; use reload to retry publication.",
13052
13070
  params: params({ room_id: roomIdProperty, expected_revision: { type: "integer", minimum: 0 }, definition: {
13053
13071
  type: "object",
13054
13072
  additionalProperties: false,
@@ -13061,7 +13079,7 @@ var init_openapi = __esm({
13061
13079
  {
13062
13080
  method: "room.command.definition.delete",
13063
13081
  summary: "Delete a REST consumer command",
13064
- description: "Deletes the desired definition and its CID/role grants. Requires the current registry revision. Local commands must be removed from their file and reloaded.",
13082
+ description: "Deletes the desired definition and its exact CID/role grants; wildcard grants remain stored. Requires the current registry revision. Local commands must be removed from their file and reloaded.",
13065
13083
  params: params({ room_id: roomIdProperty, expected_revision: { type: "integer", minimum: 0 }, name: { type: "string" } }, ["room_id", "expected_revision", "name"]),
13066
13084
  result: "Updated revision, published and definitions.",
13067
13085
  example: { room_id: EXAMPLE_ROOM_ID, expected_revision: 1, name: "consumer.orders" }
@@ -13231,7 +13249,7 @@ var init_openapi = __esm({
13231
13249
  {
13232
13250
  method: "room.command.grants",
13233
13251
  summary: "List runtime-command grants",
13234
- description: "Lists the default-deny grants that bind one active participant CID to one room runtime command.",
13252
+ description: "Lists the default-deny grants that bind one active participant CID to one room runtime command name or stored wildcard pattern.",
13235
13253
  params: params({ room_id: roomIdProperty }, ["room_id"]),
13236
13254
  result: "An array of `{caller_cid, command}` grants.",
13237
13255
  example: { room_id: EXAMPLE_ROOM_ID }
@@ -13247,13 +13265,11 @@ var init_openapi = __esm({
13247
13265
  {
13248
13266
  method: "room.command.role.set",
13249
13267
  summary: "Set a role runtime-command policy",
13250
- description: "Atomically replaces the command policy for one exact role. An empty command list removes the policy without removing independent per-CID grants.",
13268
+ description: "Atomically replaces the command policy for one exact role. An empty command list removes the policy without removing independent per-CID grants. Accepts exact names, * and namespace.* patterns.",
13251
13269
  params: params({
13252
13270
  room_id: roomIdProperty,
13253
13271
  role: { type: "string", minLength: 1, description: "Exact admitted seat role." },
13254
- commands: { type: "array", uniqueItems: true, items: {
13255
- anyOf: [{ type: "string", enum: [...RUNTIME_COMMAND_NAMES] }, { type: "string", maxLength: 128, pattern: "^consumer\\.[a-z0-9][a-z0-9.-]*[a-z0-9]$" }]
13256
- } }
13272
+ commands: { type: "array", uniqueItems: true, items: commandGrantPatternProperty }
13257
13273
  }, ["room_id", "role", "commands"]),
13258
13274
  result: "The complete sorted role-policy list after the idempotent update.",
13259
13275
  example: { room_id: EXAMPLE_ROOM_ID, role: "Owner", commands: ["list-members", "remove-member"] }
@@ -13261,11 +13277,11 @@ var init_openapi = __esm({
13261
13277
  {
13262
13278
  method: "room.command.grant",
13263
13279
  summary: "Authorize a runtime command caller",
13264
- description: "Idempotently grants one command to one exact active participant CID. Display names and roles are never authority.",
13280
+ description: "Idempotently grants one command name or dynamic pattern to one exact active participant CID. Display names and caller-supplied roles are never authority. * and room.* include permission administration and room lifecycle commands; * also covers future or replaced consumer commands.",
13265
13281
  params: params({
13266
13282
  room_id: roomIdProperty,
13267
13283
  caller_cid: { type: "string", pattern: "^[0-9A-Fa-f]{64}$", description: "Authenticated caller CID." },
13268
- command: { anyOf: [{ type: "string", enum: [...RUNTIME_COMMAND_NAMES] }, { type: "string", maxLength: 128, pattern: "^consumer\\.[a-z0-9][a-z0-9.-]*[a-z0-9]$" }] }
13284
+ command: commandGrantPatternProperty
13269
13285
  }, ["room_id", "caller_cid", "command"]),
13270
13286
  result: "The complete sorted grant list after the idempotent update.",
13271
13287
  example: { room_id: EXAMPLE_ROOM_ID, caller_cid: "A".repeat(64), command: "list-members" }
@@ -13273,11 +13289,11 @@ var init_openapi = __esm({
13273
13289
  {
13274
13290
  method: "room.command.revoke",
13275
13291
  summary: "Revoke a runtime command caller",
13276
- description: "Idempotently removes one exact CID/command grant. Revocation is persisted before subsequent command dispatch can acquire the room mutex.",
13292
+ description: "Idempotently removes only the exact stored CID/name or CID/pattern entry; overlapping grants remain effective. Revocation is persisted before subsequent command dispatch can acquire the room mutex.",
13277
13293
  params: params({
13278
13294
  room_id: roomIdProperty,
13279
13295
  caller_cid: { type: "string", pattern: "^[0-9A-Fa-f]{64}$", description: "Authenticated caller CID." },
13280
- command: { anyOf: [{ type: "string", enum: [...RUNTIME_COMMAND_NAMES] }, { type: "string", maxLength: 128, pattern: "^consumer\\.[a-z0-9][a-z0-9.-]*[a-z0-9]$" }] }
13296
+ command: commandGrantPatternProperty
13281
13297
  }, ["room_id", "caller_cid", "command"]),
13282
13298
  result: "The complete sorted grant list after the idempotent update.",
13283
13299
  example: { room_id: EXAMPLE_ROOM_ID, caller_cid: "A".repeat(64), command: "remove-member" }
@@ -15375,12 +15391,12 @@ var init_command_routes = __esm({
15375
15391
  RuntimeCommandGrantParams = external_exports.object({
15376
15392
  room_id: external_exports.string(),
15377
15393
  caller_cid: ContainerIdSchema2,
15378
- command: RuntimeCommandNameSchema
15394
+ command: RuntimeCommandGrantPatternSchema
15379
15395
  }).strict();
15380
15396
  RuntimeRoleCommandGrantParams = external_exports.object({
15381
15397
  room_id: external_exports.string(),
15382
15398
  role: external_exports.string(),
15383
- commands: external_exports.array(RuntimeCommandNameSchema)
15399
+ commands: external_exports.array(RuntimeCommandGrantPatternSchema)
15384
15400
  }).strict();
15385
15401
  ParticipantRemoveParams = external_exports.object({
15386
15402
  room_id: external_exports.string(),
@@ -16265,9 +16281,9 @@ var init_service = __esm({
16265
16281
  return seat;
16266
16282
  }
16267
16283
  hasRuntimeCommandGrant(room, callerCid, command) {
16268
- if (room.command_grants.some((grant) => grant.caller_cid === callerCid && grant.command === command)) return true;
16284
+ if (room.command_grants.some((grant) => grant.caller_cid === callerCid && commandGrantMatches(grant.command, command))) return true;
16269
16285
  const caller = room.seats.find((seat) => seat.state === "active" && seat.identity === callerCid);
16270
- return caller !== void 0 && room.role_command_grants.some((grant) => grant.role === caller.role && grant.commands.includes(command));
16286
+ return caller !== void 0 && room.role_command_grants.some((grant) => grant.role === caller.role && grant.commands.some((pattern) => commandGrantMatches(pattern, command)));
16271
16287
  }
16272
16288
  async beginRemovalUnlocked(room, seat, notify) {
16273
16289
  if (seat.state === "pending" || isCancelledExternalSeat(seat)) {
@@ -16575,7 +16591,7 @@ var init_service = __esm({
16575
16591
  }
16576
16592
  /** List the operator-managed runtime-command grants for one room. */
16577
16593
  assertRegisteredConsumerName(room, name) {
16578
- if (name.startsWith("consumer.") && !(room.consumer_commands ?? []).some((definition) => definition.name === name)) {
16594
+ if (name.startsWith("consumer.") && !name.endsWith(".*") && !(room.consumer_commands ?? []).some((definition) => definition.name === name)) {
16579
16595
  throw new RoomServiceError("consumer command is not registered");
16580
16596
  }
16581
16597
  }
@@ -16719,7 +16735,7 @@ var init_service = __esm({
16719
16735
  return saved.role_command_grants.map((grant) => ({ role: grant.role, commands: [...grant.commands] }));
16720
16736
  });
16721
16737
  }
16722
- /** Grant one exact command to one active authenticated room identity. Idempotent. */
16738
+ /** Grant one command selector to one active authenticated room identity. Idempotent. */
16723
16739
  async grantRuntimeCommand(roomId, input) {
16724
16740
  const id = LowerCrockfordUlidSchema.parse(roomId);
16725
16741
  const request = RuntimeCommandGrantInputSchema.parse(input);
@@ -16738,7 +16754,7 @@ var init_service = __esm({
16738
16754
  return saved.command_grants.map((grant) => ({ ...grant }));
16739
16755
  });
16740
16756
  }
16741
- /** Revoke one exact command grant. Absence is already the requested state. */
16757
+ /** Remove only the exact stored selector; overlapping grants remain effective. */
16742
16758
  async revokeRuntimeCommand(roomId, input) {
16743
16759
  const id = LowerCrockfordUlidSchema.parse(roomId);
16744
16760
  const request = RuntimeCommandGrantInputSchema.parse(input);
@@ -14,9 +14,51 @@ Update the display name with `ours-cowork room settings <room-id> --name "New na
14
14
 
15
15
  Runtime commands are default-deny. Per-CID grants use `command-grant` and `command-revoke`. An operator may instead register a durable role policy with `role-command-set <room-id> --role <label> --commands <comma-list>` and inspect it with `role-command-grants`; use `--commands none` to remove it. A role policy authorizes only an authenticated, active seat whose durable admission role exactly matches the policy. Display names, message labels, pending seats, removed seats, and caller-supplied role text never confer authority. Removing a role policy does not remove an independently configured per-CID grant.
16
16
 
17
+ ### Wildcard command grants
18
+
19
+ CID grants and role policies accept exact command names, `*`, and a terminal
20
+ `namespace.*` pattern. Quote patterns in the shell:
21
+
22
+ ```sh
23
+ ours-cowork room command-grant <room-id> <caller-cid> '*'
24
+ ours-cowork room command-grant <room-id> <caller-cid> 'room.*'
25
+ ours-cowork room role-command-set <room-id> --role Reviewer --commands 'room.briefing.*,consumer.orders.*'
26
+ ours-cowork room command-revoke <room-id> <caller-cid> 'room.*'
27
+ ```
28
+
29
+ Use the same strings in the `command` field of `room.command.grant` / `revoke`
30
+ and the `commands` array of `room.command.role.set` over RPC or ours transport.
31
+ Invocation still requires a concrete command name; patterns are permissions only.
32
+
33
+ `*` covers all room runtime commands, including `start_thread`, `list-members`,
34
+ `remove-member`, shared `room.*` commands and registered `consumer.*` commands.
35
+ `room.*` covers only names beginning with `room.`; it excludes those dedicated
36
+ commands and consumer commands. `room.command.*` includes deeper descendants.
37
+ Namespace segments use lowercase ASCII letters, digits and hyphens, start with a
38
+ letter or digit, and are separated by dots; a namespace pattern is at most 128
39
+ characters. Empty segments, internal stars, `room*`, `*.show`, regex, whitespace
40
+ and case folding are unsupported. A valid namespace need not exist yet.
41
+
42
+ Patterns are stored and listed verbatim, and match future commands dynamically.
43
+ Consumer replacement/deletion still clears exact grants for the changed name,
44
+ but retains wildcard grants: once a matching consumer is registered and published
45
+ again, the wildcard authorizes it. Registration and publication checks still apply.
46
+
47
+ `*` and `room.*` include permission administration and room close/delete commands,
48
+ so their holders can delegate privileges. Granting them requires the same management
49
+ access or existing permission to invoke the relevant grant command. Patterns never
50
+ bypass authenticated CID, active membership, room scope, lifecycle checks, or
51
+ participant result visibility; they do not authorize host/global management APIs.
52
+
53
+ Granting an identical CID/pattern is idempotent. Revocation removes only the exact
54
+ stored entry, not all matching names: revoking `room.show` does not override a retained
55
+ `room.*`, and revoking `room.*` leaves independent exact or role grants in effect.
56
+ Patterns survive restart, and per-CID grants are removed with participant removal
57
+ just like exact grants. Empty role command lists remove the role policy.
58
+
17
59
  ## Scoped reply threads
18
60
 
19
- The dedicated `start_thread` runtime command creates a scoped reply thread after an operator grants that exact command name. Discover and invoke it through the SDK's generic command APIs, then use the SDK's native reply field:
61
+ The dedicated `start_thread` runtime command creates a scoped reply thread after an operator grants `start_thread` or `*`. Discover and invoke it through the SDK's generic command APIs, then use the SDK's native reply field:
20
62
 
21
63
  ```ts
22
64
  const definitions = await client.listContactCommands({ contact: roomCid });
@@ -66,7 +108,7 @@ Every web action has an equivalent CLI fallback in the room commands above and i
66
108
 
67
109
  Room-scoped operations also appear in the room identity's ours catalog with their RPC names: `room.settings`, `room.briefing.role.set`, `room.briefing.role.delete`, `room.invite`, `room.participant.remove`, `room.revoke`, `room.recover`, `room.recover.confirm`, `room.show`, `room.participants`, `room.command.grants`, `room.command.role.grants`, `room.command.role.set`, `room.command.grant`, `room.command.revoke`, `room.history`, `room.message`, `room.say`, `room.role.rest.add`, `room.role.rest.remove`, `room.accept`, `room.rebind`, `room.close`, and `room.delete`.
68
110
 
69
- Use the RPC arguments without `room_id`; the receiving room fixes the target. For example, an operator grants `ours-cowork room command-grant <room-id> <caller-cid> room.settings`, then that active member calls `room.settings` with `{"status":"review"}` using ours command transport. A grant for one name grants none of the other names. The SDK returns a correlated result containing `{ok:true,result:<service value>}` or `{ok:false,error:<code>}`. History returns one page; follow `seq` with `after` to fetch more.
111
+ Use the RPC arguments without `room_id`; the receiving room fixes the target. For example, an operator grants `ours-cowork room command-grant <room-id> <caller-cid> room.settings`, then that active member calls `room.settings` with `{"status":"review"}` using ours command transport. An exact-name grant grants none of the other names. The SDK returns a correlated result containing `{ok:true,result:<service value>}` or `{ok:false,error:<code>}`. History returns one page; follow `seq` with `after` to fetch more.
70
112
 
71
113
  `start_thread`, `list-members`, and `remove-member` are dedicated runtime commands rather than shared management routes. `list-members` retains its contact-safe roster. `remove-member` retains its epoch, confirm and no-self-removal gates. The separate `room.participant.remove` command instead grants the full operator removal behavior. `room.show` returns only public room settings and mission content; `room.participants` returns participant IDs, roles, and states. Runtime `room.history` returns only messages visible to the authenticated active seat, with viewer-local cursors, and rejects operator view. Runtime `room.message` and `room.say` return only an accepted message-ID receipt. Host management routes retain their full operator results. Policy-administration commands can delegate more privileges; `room.message` and `room.say` authorize room/role authorship. Assign these permissions deliberately. Command results may include invite material; do not relay them into chat.
72
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/cowork",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Durable ours mission rooms on the shared ours daemon.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",