@aopslabs/aops 0.3.16 → 0.3.18

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.
Files changed (42) hide show
  1. package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/aops-cli.md +13 -0
  2. package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/aops-system.md +15 -0
  3. package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/chatv3.md +16 -1
  4. package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/manifest.json +9 -9
  5. package/agent-assets-release/agent-assets/inventory.json +5 -5
  6. package/agent-assets-release/agent-assets/projection.json +1 -1
  7. package/agent-assets-release/agent-assets-release.json +40 -40
  8. package/agent-assets-release/agent-assets-release.sigstore.json +26 -29
  9. package/assets/agent-assets/core/user-guides/aops-cli.md +13 -0
  10. package/assets/agent-assets/core/user-guides/aops-system.md +15 -0
  11. package/assets/agent-assets/core/user-guides/chatv3.md +16 -1
  12. package/dist/commands/chatv3.js +297 -8
  13. package/dist/commands/community-server.js +20 -0
  14. package/dist/commands/global-update.js +353 -9
  15. package/dist/lib/agent-assets/native-fs.js +2 -2
  16. package/dist/lib/cli-error-format.js +13 -0
  17. package/dist/lib/community-native-lifecycle.js +1 -1
  18. package/dist/main.js +2 -1
  19. package/dist/utils/chatv3-session-store.js +476 -47
  20. package/native/manifest.json +1 -1
  21. package/native/qualifications/win32-x64.json +1 -1
  22. package/native/tui/darwin-arm64/aops-tui +0 -0
  23. package/native/tui/win32-x64/aops-tui.exe +0 -0
  24. package/package.json +6 -5
  25. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/SKILL.md +0 -0
  26. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/agentspace/SKILL.md +0 -0
  27. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/aops-cli-core/SKILL.md +0 -0
  28. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/chatv3/SKILL.md +0 -0
  29. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/collaborative-work/SKILL.md +0 -0
  30. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/discuss/SKILL.md +0 -0
  31. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/docman/SKILL.md +0 -0
  32. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/mission/SKILL.md +0 -0
  33. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/projectman/SKILL.md +0 -0
  34. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/sys/SKILL.md +0 -0
  35. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/view/SKILL.md +0 -0
  36. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/references/working-disciplines/SKILL.md +0 -0
  37. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/agent-assets.md +0 -0
  38. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/agentspace.md +0 -0
  39. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/docman.md +0 -0
  40. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/projectman.md +0 -0
  41. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/sys.md +0 -0
  42. /package/agent-assets-release/agent-assets/core/{ec1a663c171f7b847980a06dc84ce045493880f8a71d9d2e992d9bcaa31407ff → 387548b990d0cfef6a92805f39d18c2ae6b4bb6b1e42ea42e6c597ea7d207447}/files/user-guides/working-disciplines.md +0 -0
@@ -28,12 +28,20 @@ const CHATV3_COMMAND_TREE = {
28
28
  listen: null,
29
29
  leave: null,
30
30
  binding: new Set(['add', 'list', 'remove']),
31
- member: new Set(['list', 'remove']),
31
+ member: new Set(['list', 'remove', 'restore']),
32
32
  presence: new Set(['list', 'set']),
33
- room: new Set(['brief', 'summary']),
33
+ room: new Set(['brief', 'summary', 'create', 'list', 'members', 'join', 'leave']),
34
34
  session: new Set(['list', 'get', 'forget']),
35
35
  help: null,
36
36
  };
37
+ const CHATV3_ROOM_MEMBERS_UNSUPPORTED_HINT = 'This server does not expose room-scoped participation yet (requires chatv3 domain with the chatv3.room.members op); retry without --room or upgrade the server.';
38
+ function withRoomMembersSupportHint(error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ if (/not_found|\b404\b/i.test(message)) {
41
+ throw new Error(`${message} — ${CHATV3_ROOM_MEMBERS_UNSUPPORTED_HINT}`);
42
+ }
43
+ throw error instanceof Error ? error : new Error(message);
44
+ }
37
45
  function toInteger(value, label) {
38
46
  if (value === undefined)
39
47
  return 0;
@@ -508,6 +516,9 @@ async function loadRuntime(options, sessionId) {
508
516
  storePath: options.storePath,
509
517
  owner: options.sessionOwner,
510
518
  });
519
+ if (loaded.staleEpochKeys && loaded.staleEpochKeys.length > 0) {
520
+ process.stderr.write(`[chatv3] Dropped ${loaded.staleEpochKeys.length} stored room key(s) that no longer decrypt with the local session.key; refreshing from the server.\n`);
521
+ }
511
522
  const serverBaseUrl = normalizeNonEmpty(options.apiBaseUrl) ?? loaded.record.serverBaseUrl;
512
523
  const keyStore = new MemoryKeyStore();
513
524
  if (loaded.secrets.wrapSecret) {
@@ -941,14 +952,18 @@ export async function runChatv3Join(invite, options = {}) {
941
952
  }, { storePath, owner: options.sessionOwner });
942
953
  record = summarizeChatv3Session(saved);
943
954
  }
955
+ const selfMember = members.find((member) => member.handle === handle);
944
956
  buildEnvelope({
945
957
  options,
946
958
  command: 'chatv3.join',
947
959
  serverBaseUrl,
948
960
  input: { invite: 'redacted', handle, session: sessionId, saveSession: options.saveSession === true, mode: parsed.mode ?? 'e2e' },
949
- artifacts: compactPayload({ sessionId, channelId: channel.id, activeRoomId: rooms[0]?.id }),
961
+ artifacts: compactPayload({ sessionId, channelId: channel.id, activeRoomId: rooms[0]?.id, memberId: selfMember?.id }),
950
962
  result: {
951
963
  joined: true,
964
+ ...(selfMember
965
+ ? { member: { id: selfMember.id, handle: selfMember.handle, roleKey: selfMember.roleKey, status: selfMember.status } }
966
+ : {}),
952
967
  saved: options.saveSession === true,
953
968
  summary: buildJoinSummary({
954
969
  channel,
@@ -1112,13 +1127,47 @@ export async function runChatv3Listen(options = {}) {
1112
1127
  },
1113
1128
  });
1114
1129
  }
1130
+ export async function runChatv3RoomMembers(options = {}) {
1131
+ const sessionId = normalizeNonEmpty(options.session);
1132
+ if (!sessionId)
1133
+ throw new Error('Provide --session <id>.');
1134
+ const limit = options.limit === undefined ? undefined : toInteger(options.limit, '--limit');
1135
+ const requestedStatus = normalizeNonEmpty(options.status);
1136
+ if (requestedStatus && !['active', 'left', 'removed', 'all'].includes(requestedStatus)) {
1137
+ throw new Error('--status must be one of: active, left, removed, all.');
1138
+ }
1139
+ const status = requestedStatus === 'all' ? undefined : (requestedStatus ?? 'active');
1140
+ const runtime = await loadRuntime(options, sessionId);
1141
+ const room = resolveRoom(runtime, options.room);
1142
+ const roomMembers = await fetchRoomMembers(runtime, room.id, { status, limit });
1143
+ buildEnvelope({
1144
+ options,
1145
+ command: 'chatv3.room.members',
1146
+ serverBaseUrl: runtime.record.serverBaseUrl,
1147
+ input: compactPayload({ session: sessionId, room: options.room ?? room.slug, status: status ?? 'all', limit }),
1148
+ artifacts: { sessionId, channelId: runtime.channel.id, roomId: room.id },
1149
+ result: {
1150
+ room: { id: room.id, slug: room.slug, title: room.title },
1151
+ statusFilter: status ?? 'all',
1152
+ members: roomMembers,
1153
+ memberCount: roomMembers.length,
1154
+ },
1155
+ });
1156
+ }
1115
1157
  export async function runChatv3MemberList(options = {}) {
1116
1158
  const sessionId = normalizeNonEmpty(options.session);
1117
1159
  if (!sessionId)
1118
1160
  throw new Error('Provide --session <id>.');
1161
+ if (normalizeNonEmpty(options.room)) {
1162
+ await runChatv3RoomMembers(options);
1163
+ return;
1164
+ }
1119
1165
  const limit = options.limit === undefined ? undefined : toInteger(options.limit, '--limit');
1120
1166
  const runtime = await loadRuntime(options, sessionId);
1121
- const members = await runtime.client.listMembers(runtime.channel.id, { status: options.status, limit });
1167
+ const members = await runtime.client.listMembers(runtime.channel.id, {
1168
+ status: options.status === 'left' || options.status === 'all' ? undefined : options.status,
1169
+ limit,
1170
+ });
1122
1171
  buildEnvelope({
1123
1172
  options,
1124
1173
  command: 'chatv3.member.list',
@@ -1132,9 +1181,45 @@ export async function runChatv3MemberList(options = {}) {
1132
1181
  },
1133
1182
  });
1134
1183
  }
1184
+ async function fetchRoomMembers(runtime, roomId, opts = {}) {
1185
+ const query = new URLSearchParams();
1186
+ if (normalizeNonEmpty(opts.status))
1187
+ query.set('status', String(opts.status));
1188
+ if (opts.limit !== undefined && opts.limit > 0)
1189
+ query.set('limit', String(opts.limit));
1190
+ const qs = query.toString();
1191
+ return runtime.client.http
1192
+ .get(`/rooms/${roomId}/members${qs ? `?${qs}` : ''}`)
1193
+ .catch(withRoomMembersSupportHint);
1194
+ }
1195
+ async function updateRoomMemberStatus(runtime, roomId, memberId, status) {
1196
+ return runtime.client.http
1197
+ .request('PATCH', `/rooms/${roomId}/members/${memberId}`, { status })
1198
+ .catch(withRoomMembersSupportHint);
1199
+ }
1135
1200
  export async function runChatv3MemberRemove(options = {}) {
1136
1201
  const memberId = requireField(options.member, '--member');
1137
1202
  const { sessionId, runtime } = await loadRuntimeForChannelGuard(options);
1203
+ if (normalizeNonEmpty(options.room)) {
1204
+ const room = resolveRoom(runtime, options.room);
1205
+ const membership = await updateRoomMemberStatus(runtime, room.id, memberId, 'removed');
1206
+ const roomMembers = await fetchRoomMembers(runtime, room.id).catch(() => []);
1207
+ buildEnvelope({
1208
+ options,
1209
+ command: 'chatv3.room.member.remove',
1210
+ serverBaseUrl: runtime.record.serverBaseUrl,
1211
+ input: compactPayload({ session: sessionId, channel: options.channel, member: memberId, room: options.room }),
1212
+ artifacts: { sessionId, channelId: runtime.channel.id, roomId: room.id, memberId },
1213
+ result: {
1214
+ removedFromRoom: true,
1215
+ room: { id: room.id, slug: room.slug, title: room.title },
1216
+ membership,
1217
+ members: roomMembers,
1218
+ memberCount: roomMembers.length,
1219
+ },
1220
+ });
1221
+ return;
1222
+ }
1138
1223
  const beforeMembers = await runtime.client.listMembers(runtime.channel.id).catch(() => []);
1139
1224
  const target = beforeMembers.find((member) => member.id === memberId);
1140
1225
  if (target && target.channelId !== runtime.channel.id) {
@@ -1161,6 +1246,166 @@ export async function runChatv3MemberRemove(options = {}) {
1161
1246
  },
1162
1247
  });
1163
1248
  }
1249
+ export async function runChatv3MemberRestore(options = {}) {
1250
+ const memberId = requireField(options.member, '--member');
1251
+ const { sessionId, runtime } = await loadRuntimeForChannelGuard(options);
1252
+ if (normalizeNonEmpty(options.room)) {
1253
+ const room = resolveRoom(runtime, options.room);
1254
+ const membership = await updateRoomMemberStatus(runtime, room.id, memberId, 'active');
1255
+ const roomMembers = await fetchRoomMembers(runtime, room.id).catch(() => []);
1256
+ buildEnvelope({
1257
+ options,
1258
+ command: 'chatv3.room.member.restore',
1259
+ serverBaseUrl: runtime.record.serverBaseUrl,
1260
+ input: compactPayload({ session: sessionId, channel: options.channel, member: memberId, room: options.room }),
1261
+ artifacts: { sessionId, channelId: runtime.channel.id, roomId: room.id, memberId },
1262
+ result: {
1263
+ restoredToRoom: true,
1264
+ room: { id: room.id, slug: room.slug, title: room.title },
1265
+ membership,
1266
+ members: roomMembers,
1267
+ memberCount: roomMembers.length,
1268
+ },
1269
+ });
1270
+ return;
1271
+ }
1272
+ const member = await runtime.client.updateMember(memberId, { status: 'active' });
1273
+ if (member.channelId !== runtime.channel.id) {
1274
+ throw new Error(`Server returned member ${member.id} from channel ${member.channelId}; expected ${runtime.channel.id}.`);
1275
+ }
1276
+ const members = await runtime.client.listMembers(runtime.channel.id).catch(() => [member]);
1277
+ buildEnvelope({
1278
+ options,
1279
+ command: 'chatv3.member.restore',
1280
+ serverBaseUrl: runtime.record.serverBaseUrl,
1281
+ input: compactPayload({ session: sessionId, channel: options.channel, member: memberId }),
1282
+ artifacts: { sessionId, channelId: runtime.channel.id, memberId },
1283
+ result: {
1284
+ restored: true,
1285
+ member,
1286
+ members,
1287
+ memberCount: members.length,
1288
+ },
1289
+ });
1290
+ }
1291
+ export async function runChatv3RoomCreate(options = {}) {
1292
+ const sessionId = normalizeNonEmpty(options.session);
1293
+ if (!sessionId)
1294
+ throw new Error('Provide --session <id>.');
1295
+ const slug = requireField(options.slug, '--slug');
1296
+ const title = normalizeNonEmpty(options.title) ?? slug;
1297
+ const kind = normalizeNonEmpty(options.kind) ?? 'session';
1298
+ if (kind !== 'session' && kind !== 'task')
1299
+ throw new Error('--kind must be "session" or "task".');
1300
+ const runtime = await loadRuntime(options, sessionId);
1301
+ const room = await runtime.client.createRoom({
1302
+ channel: {
1303
+ id: runtime.channel.id,
1304
+ tenantId: runtime.channel.tenantId,
1305
+ spaceId: runtime.channel.spaceId,
1306
+ encryptionMode: runtime.channel.encryptionMode,
1307
+ },
1308
+ slug,
1309
+ title,
1310
+ kind,
1311
+ purpose: normalizeNonEmpty(options.purpose),
1312
+ });
1313
+ runtime.rooms = await runtime.client.listRooms(runtime.channel.id, { status: 'active' }).catch(() => [...runtime.rooms, room]);
1314
+ if (runtime.channel.encryptionMode === 'server-encrypted') {
1315
+ await refreshServerEpochKeys(runtime);
1316
+ }
1317
+ await persistRuntimeSnapshot(runtime, room.id);
1318
+ const roomMembers = await fetchRoomMembers(runtime, room.id).catch(() => []);
1319
+ buildEnvelope({
1320
+ options,
1321
+ command: 'chatv3.room.create',
1322
+ serverBaseUrl: runtime.record.serverBaseUrl,
1323
+ input: compactPayload({ session: sessionId, slug, title, kind, purpose: options.purpose }),
1324
+ artifacts: { sessionId, channelId: runtime.channel.id, roomId: room.id },
1325
+ result: {
1326
+ room,
1327
+ members: roomMembers,
1328
+ memberCount: roomMembers.length,
1329
+ note: 'New rooms start with only the creator as participant; others join explicitly or on first send.',
1330
+ },
1331
+ });
1332
+ }
1333
+ export async function runChatv3RoomList(options = {}) {
1334
+ const sessionId = normalizeNonEmpty(options.session);
1335
+ if (!sessionId)
1336
+ throw new Error('Provide --session <id>.');
1337
+ const runtime = await loadRuntime(options, sessionId);
1338
+ buildEnvelope({
1339
+ options,
1340
+ command: 'chatv3.room.list',
1341
+ serverBaseUrl: runtime.record.serverBaseUrl,
1342
+ input: { session: sessionId },
1343
+ artifacts: { sessionId, channelId: runtime.channel.id },
1344
+ result: {
1345
+ rooms: runtime.rooms.map((room) => compactPayload({
1346
+ id: room.id,
1347
+ slug: room.slug,
1348
+ title: room.title,
1349
+ kind: room.kind,
1350
+ status: room.status,
1351
+ currentEpoch: room.currentEpoch,
1352
+ lastSeq: room.lastSeq,
1353
+ createdBy: room.createdBy,
1354
+ })),
1355
+ roomCount: runtime.rooms.length,
1356
+ activeRoomId: runtime.record.activeRoomId,
1357
+ },
1358
+ });
1359
+ }
1360
+ export async function runChatv3RoomJoin(options = {}) {
1361
+ const sessionId = normalizeNonEmpty(options.session);
1362
+ if (!sessionId)
1363
+ throw new Error('Provide --session <id>.');
1364
+ const runtime = await loadRuntime(options, sessionId);
1365
+ const room = resolveRoom(runtime, options.room);
1366
+ const membership = await runtime.client.http
1367
+ .post(`/rooms/${room.id}/join`, {})
1368
+ .catch(withRoomMembersSupportHint);
1369
+ await persistRuntimeSnapshot(runtime, room.id);
1370
+ const roomMembers = await fetchRoomMembers(runtime, room.id).catch(() => []);
1371
+ buildEnvelope({
1372
+ options,
1373
+ command: 'chatv3.room.join',
1374
+ serverBaseUrl: runtime.record.serverBaseUrl,
1375
+ input: compactPayload({ session: sessionId, room: options.room ?? room.slug }),
1376
+ artifacts: { sessionId, channelId: runtime.channel.id, roomId: room.id },
1377
+ result: {
1378
+ joined: membership.status === 'active',
1379
+ membership,
1380
+ members: roomMembers,
1381
+ memberCount: roomMembers.length,
1382
+ },
1383
+ });
1384
+ }
1385
+ export async function runChatv3RoomLeave(options = {}) {
1386
+ const sessionId = normalizeNonEmpty(options.session);
1387
+ if (!sessionId)
1388
+ throw new Error('Provide --session <id>.');
1389
+ const runtime = await loadRuntime(options, sessionId);
1390
+ const room = resolveRoom(runtime, options.room);
1391
+ const membership = await runtime.client.http
1392
+ .post(`/rooms/${room.id}/leave`, {})
1393
+ .catch(withRoomMembersSupportHint);
1394
+ buildEnvelope({
1395
+ options,
1396
+ command: 'chatv3.room.leave',
1397
+ serverBaseUrl: runtime.record.serverBaseUrl,
1398
+ input: compactPayload({ session: sessionId, room: options.room ?? room.slug }),
1399
+ artifacts: { sessionId, channelId: runtime.channel.id, roomId: room.id },
1400
+ result: {
1401
+ left: membership.status === 'left',
1402
+ membership,
1403
+ note: membership.status === 'removed'
1404
+ ? 'Membership stays "removed" (a room manager removed this member); re-join requires a manager restore.'
1405
+ : 'Left the room roster; re-join any time with "room join" or by sending a message.',
1406
+ },
1407
+ });
1408
+ }
1164
1409
  export async function runChatv3PresenceList(options = {}) {
1165
1410
  const sessionId = normalizeNonEmpty(options.session);
1166
1411
  if (!sessionId)
@@ -1636,20 +1881,64 @@ export function makeChatv3Command(commandName = 'chat') {
1636
1881
  .option('--latest-seq <seq>', 'Explicit latest seq override; default room lastSeq')).action(async (options) => {
1637
1882
  await runChatv3RoomSummary(options);
1638
1883
  });
1884
+ applyChatv3Options(room.command('create')
1885
+ .description('Create a room in the session channel (creator becomes its first participant)')
1886
+ .requiredOption('--session <id>', 'Local ChatV3 session id')
1887
+ .requiredOption('--slug <slug>', 'Room slug (unique per channel)')
1888
+ .option('--title <title>', 'Room title; defaults to the slug')
1889
+ .option('--purpose <text>', 'Short room purpose')
1890
+ .option('--kind <session|task>', 'Room kind', 'session')).action(async (options) => {
1891
+ await runChatv3RoomCreate(options);
1892
+ });
1893
+ applyChatv3Options(room.command('list')
1894
+ .description('List active rooms of the session channel')
1895
+ .requiredOption('--session <id>', 'Local ChatV3 session id')).action(async (options) => {
1896
+ await runChatv3RoomList(options);
1897
+ });
1898
+ applyChatv3Options(room.command('members')
1899
+ .description('List the room-scoped participant roster (active participants by default)')
1900
+ .requiredOption('--session <id>', 'Local ChatV3 session id')
1901
+ .option('--room <slugOrId>', 'Room slug or id; default active/general room')
1902
+ .option('--status <active|left|removed|all>', 'Participation status filter; default active, all returns every row')
1903
+ .option('--limit <n>', 'Optional page limit')).action(async (options) => {
1904
+ await runChatv3RoomMembers(options);
1905
+ });
1906
+ applyChatv3Options(room.command('join')
1907
+ .description('Join a room roster (disjoin later with "room leave"); rejected after a room-level removal')
1908
+ .requiredOption('--session <id>', 'Local ChatV3 session id')
1909
+ .option('--room <slugOrId>', 'Room slug or id; default active/general room')).action(async (options) => {
1910
+ await runChatv3RoomJoin(options);
1911
+ });
1912
+ applyChatv3Options(room.command('leave')
1913
+ .description('Leave a room roster (disjoin); the channel membership stays intact')
1914
+ .requiredOption('--session <id>', 'Local ChatV3 session id')
1915
+ .option('--room <slugOrId>', 'Room slug or id; default active/general room')).action(async (options) => {
1916
+ await runChatv3RoomLeave(options);
1917
+ });
1639
1918
  const member = cmd.command('member').description('ChatV3 channel member roster');
1640
1919
  applyChatv3Options(member.command('list')
1641
1920
  .requiredOption('--session <id>', 'Local ChatV3 session id')
1642
- .option('--status <active|removed>', 'Member status filter')
1643
- .option('--limit <n>', 'Optional member page limit')).action(async (options) => {
1921
+ .option('--status <active|removed>', 'Member status filter (with --room: active|left|removed|all, default active)')
1922
+ .option('--limit <n>', 'Optional member page limit')
1923
+ .option('--room <slugOrId>', 'List the room-scoped participant roster instead of the channel roster (active by default)')).action(async (options) => {
1644
1924
  await runChatv3MemberList(options);
1645
1925
  });
1646
1926
  applyChatv3Options(member.command('remove')
1647
- .description('Remove a member from a ChatV3 channel using an owner/operator session')
1927
+ .description('Remove a member from a ChatV3 channel (owner/operator), or with --room kick them from one room (room creator or owner/operator)')
1648
1928
  .option('--session <id>', 'Local ChatV3 session id; required unless --channel matches exactly one local session')
1649
1929
  .option('--channel <idOrSlug>', 'Expected channel id/slug; required when --session is omitted and used as a guard when provided')
1650
- .requiredOption('--member <id>', 'Member id to remove')).action(async (options) => {
1930
+ .requiredOption('--member <id>', 'Member id to remove')
1931
+ .option('--room <slugOrId>', 'Kick from this room only (participation), keeping channel membership')).action(async (options) => {
1651
1932
  await runChatv3MemberRemove(options);
1652
1933
  });
1934
+ applyChatv3Options(member.command('restore')
1935
+ .description('Restore a removed member (channel-level, owner/operator), or with --room re-add them to one room')
1936
+ .option('--session <id>', 'Local ChatV3 session id; required unless --channel matches exactly one local session')
1937
+ .option('--channel <idOrSlug>', 'Expected channel id/slug; required when --session is omitted and used as a guard when provided')
1938
+ .requiredOption('--member <id>', 'Member id to restore')
1939
+ .option('--room <slugOrId>', 'Re-add to this room only (after a room-level kick)')).action(async (options) => {
1940
+ await runChatv3MemberRestore(options);
1941
+ });
1653
1942
  const presence = cmd.command('presence').description('ChatV3 room presence');
1654
1943
  applyChatv3Options(presence.command('list')
1655
1944
  .requiredOption('--session <id>', 'Local ChatV3 session id')
@@ -18,6 +18,15 @@ import { inspectCommunityNativeApplicationRecoveryStatus } from '../lib/communit
18
18
  import { inspectCommunityNativeDatabaseRecoveryStatus, restoreCommunityNativeDatabaseForUpdate, } from '../lib/community-native-database-recovery.js';
19
19
  import { inspectCommunityNativePostgres, removeCommunityNativePostgresContainerForReset, removeCommunityNativeManagedPostgres, } from '../lib/community-native-postgres.js';
20
20
  import { stopCommunityNativeCockpit } from '../lib/community-cockpit-lifecycle.js';
21
+ function resolveReadyTimeoutMs(options) {
22
+ if (options.readyTimeoutMs === undefined)
23
+ return undefined;
24
+ const value = Number(options.readyTimeoutMs);
25
+ if (!Number.isSafeInteger(value) || value < 1_000 || value > 900_000) {
26
+ throw new Error('community_native_ready_timeout_invalid:expected_1000_to_900000_ms');
27
+ }
28
+ return value;
29
+ }
21
30
  function nativePublicOrigin(state) {
22
31
  return state.server.host === '127.0.0.1'
23
32
  ? `http://127.0.0.1:${state.server.publicPort}`
@@ -431,6 +440,7 @@ async function runWithOperationJournal(params) {
431
440
  code: 'community_server_operation_failed',
432
441
  phase: 'operation',
433
442
  details: {
443
+ operationId: journal.record().id,
434
444
  operation: params.operation,
435
445
  journalPhase: journal.record().phase,
436
446
  journalStep: journal.record().step,
@@ -640,6 +650,7 @@ export async function runCommunityServerSetup(options, dependencies = {}) {
640
650
  mode: resolveCommunityNativeLaunchMode(options),
641
651
  createPostgresSecret: options.createPostgresSecret,
642
652
  progress: options.progressSink,
653
+ readyTimeoutMs: resolveReadyTimeoutMs(options),
643
654
  signal,
644
655
  });
645
656
  assertInstanceDirectoryIdentity(instanceIdentity);
@@ -770,6 +781,7 @@ export async function runCommunityServerStart(options, dependencies = {}) {
770
781
  ...installSelection(options),
771
782
  adoptSourceRoot: runtime.resolveNativeInstalledNpmSourceRoot(),
772
783
  mode: resolveCommunityNativeLaunchMode(options),
784
+ readyTimeoutMs: resolveReadyTimeoutMs(options),
773
785
  signal,
774
786
  }));
775
787
  writeResult({
@@ -843,6 +855,7 @@ export async function runCommunityServerRestart(options, dependencies = {}) {
843
855
  return startCommunityNativeInstall({
844
856
  ...installSelection(options),
845
857
  mode: resolveCommunityNativeLaunchMode(options),
858
+ readyTimeoutMs: resolveReadyTimeoutMs(options),
846
859
  signal,
847
860
  });
848
861
  });
@@ -1128,6 +1141,7 @@ export async function runCommunityServerUpdate(options, dependencies = {}) {
1128
1141
  dataRoot: locked.paths.dataRoot,
1129
1142
  mode: resolveCommunityNativeLaunchMode(options),
1130
1143
  requireApplicationUpdate: true,
1144
+ readyTimeoutMs: resolveReadyTimeoutMs(options),
1131
1145
  signal,
1132
1146
  });
1133
1147
  });
@@ -1339,6 +1353,7 @@ export async function runCommunityServerRollbackApplication(options, dependencie
1339
1353
  updateId: options.updateId,
1340
1354
  sourceRoot: options.sourceRoot,
1341
1355
  mode: resolveCommunityNativeLaunchMode(options),
1356
+ readyTimeoutMs: resolveReadyTimeoutMs(options),
1342
1357
  signal,
1343
1358
  });
1344
1359
  });
@@ -1965,18 +1980,21 @@ export function makeCommunityServerCommand(identity = {}) {
1965
1980
  .option('--port <number>', 'Host port', '5900')
1966
1981
  .option('--foreground', 'Keep the native server attached to this terminal')
1967
1982
  .option('--detach', 'Start the native server in the background (default)')
1983
+ .option('--ready-timeout-ms <milliseconds>', 'Native Server readiness budget from 1000 to 900000 ms; default 120000')
1968
1984
  .option('--preview', 'Validate and print the setup contract without mutation')
1969
1985
  .option('--apply', 'Apply the selected setup contract')
1970
1986
  .action((options) => runCommunityServerSetup(options, dependencies));
1971
1987
  common(command.command('start').alias('up').description('Start the installed native npm/source host or OCI stack'))
1972
1988
  .option('--foreground', 'Keep the native server attached to this terminal')
1973
1989
  .option('--detach', 'Start the native server in the background (default)')
1990
+ .option('--ready-timeout-ms <milliseconds>', 'Native Server readiness budget from 1000 to 900000 ms; default 120000')
1974
1991
  .action((options) => runCommunityServerStart(options, dependencies));
1975
1992
  common(command.command('stop').alias('down').description('Stop the server without deleting data'))
1976
1993
  .action((options) => runCommunityServerStop(options, dependencies));
1977
1994
  common(command.command('restart').description('Restart the installed server'))
1978
1995
  .option('--foreground', 'Keep the restarted native server attached to this terminal')
1979
1996
  .option('--detach', 'Restart the native server in the background (default)')
1997
+ .option('--ready-timeout-ms <milliseconds>', 'Native Server readiness budget from 1000 to 900000 ms; default 120000')
1980
1998
  .action((options) => runCommunityServerRestart(options, dependencies));
1981
1999
  common(command.command('status').description('Show install and runtime status')).action(runCommunityServerStatus);
1982
2000
  common(command.command('health').description('Check the installed server health without mutation')).action(runCommunityServerHealth);
@@ -2000,6 +2018,7 @@ export function makeCommunityServerCommand(identity = {}) {
2000
2018
  .option('--source-root <path>', 'Optional Community checkout for a native update; defaults to the installed official npm runtime')
2001
2019
  .option('--foreground', 'Keep the updated native server attached to this terminal')
2002
2020
  .option('--detach', 'Start the updated native server in the background (default)')
2021
+ .option('--ready-timeout-ms <milliseconds>', 'Native Server readiness budget from 1000 to 900000 ms; default 120000')
2003
2022
  .action((options) => runCommunityServerUpdate(options, dependencies));
2004
2023
  common(command.command('recover').description('Recover one exact failed or unhealthy update from its verified backup'))
2005
2024
  .requiredOption('--update-id <id>', 'Exact update ID recorded by the failed or unhealthy update')
@@ -2021,6 +2040,7 @@ export function makeCommunityServerCommand(identity = {}) {
2021
2040
  .requiredOption('--source-root <path>', 'Exact prior public release checkout; the CLI never downloads or changes it')
2022
2041
  .option('--foreground', 'Keep the rolled-back native server attached to this terminal')
2023
2042
  .option('--detach', 'Start the rolled-back native server in the background (default)')
2043
+ .option('--ready-timeout-ms <milliseconds>', 'Native Server readiness budget from 1000 to 900000 ms; default 120000')
2024
2044
  .action((options, child) => runCommunityServerRollbackApplication(mergeCommunityCommandOptions(options, child), dependencies));
2025
2045
  common(command.command('backup').description('Create and verify a custom-format PostgreSQL backup plus receipt'))
2026
2046
  .action((options) => runCommunityServerBackup(options, dependencies));