@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 +12 -6
- package/dist/daemon.js +38 -22
- package/docs/05-room-workflow.md +44 -2
- package/package.json +1 -1
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:
|
|
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(
|
|
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) => !
|
|
5647
|
-
usageError("--commands must be a unique comma-separated list of
|
|
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 (!
|
|
5659
|
-
usageError("runtime command must be a supported
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
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);
|
package/docs/05-room-workflow.md
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|