@geminixiang/mikan 1.0.0-beta.61 → 1.0.0-beta.63

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 (104) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +16 -14
  3. package/dist/adapters/commands/sandbox.d.ts.map +1 -1
  4. package/dist/adapters/commands/sandbox.js +30 -53
  5. package/dist/adapters/commands/sandbox.js.map +1 -1
  6. package/dist/adapters/commands/types.d.ts +3 -3
  7. package/dist/adapters/commands/types.d.ts.map +1 -1
  8. package/dist/adapters/commands/types.js.map +1 -1
  9. package/dist/adapters/slack/bot.d.ts +15 -3
  10. package/dist/adapters/slack/bot.d.ts.map +1 -1
  11. package/dist/adapters/slack/bot.js +50 -14
  12. package/dist/adapters/slack/bot.js.map +1 -1
  13. package/dist/adapters/web/admin/portal.d.ts +1 -1
  14. package/dist/adapters/web/admin/portal.d.ts.map +1 -1
  15. package/dist/adapters/web/admin/portal.js +61 -146
  16. package/dist/adapters/web/admin/portal.js.map +1 -1
  17. package/dist/adapters/web/admin/types.d.ts +6 -3
  18. package/dist/adapters/web/admin/types.d.ts.map +1 -1
  19. package/dist/adapters/web/admin/types.js.map +1 -1
  20. package/dist/adapters/web/server.d.ts +3 -0
  21. package/dist/adapters/web/server.d.ts.map +1 -1
  22. package/dist/adapters/web/server.js +4 -6
  23. package/dist/adapters/web/server.js.map +1 -1
  24. package/dist/cli/boot.js +1 -1
  25. package/dist/cli/boot.js.map +1 -1
  26. package/dist/cli/office.d.ts.map +1 -1
  27. package/dist/cli/office.js +66 -0
  28. package/dist/cli/office.js.map +1 -1
  29. package/dist/config.d.ts +13 -10
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +35 -54
  32. package/dist/config.js.map +1 -1
  33. package/dist/env-manifest.js +3 -3
  34. package/dist/env-manifest.js.map +1 -1
  35. package/dist/events/index.d.ts +56 -34
  36. package/dist/events/index.d.ts.map +1 -1
  37. package/dist/events/index.js +114 -23
  38. package/dist/events/index.js.map +1 -1
  39. package/dist/events/scheduler.d.ts +34 -0
  40. package/dist/events/scheduler.d.ts.map +1 -0
  41. package/dist/events/scheduler.js +231 -0
  42. package/dist/events/scheduler.js.map +1 -0
  43. package/dist/harness/execution-resolver.d.ts.map +1 -1
  44. package/dist/harness/execution-resolver.js +1 -1
  45. package/dist/harness/execution-resolver.js.map +1 -1
  46. package/dist/harness/mcp.d.ts.map +1 -1
  47. package/dist/harness/mcp.js +1 -3
  48. package/dist/harness/mcp.js.map +1 -1
  49. package/dist/harness/open-connector.d.ts +21 -10
  50. package/dist/harness/open-connector.d.ts.map +1 -1
  51. package/dist/harness/open-connector.js +109 -135
  52. package/dist/harness/open-connector.js.map +1 -1
  53. package/dist/harness/prompt.d.ts.map +1 -1
  54. package/dist/harness/prompt.js +19 -23
  55. package/dist/harness/prompt.js.map +1 -1
  56. package/dist/harness/runner.d.ts.map +1 -1
  57. package/dist/harness/runner.js +26 -11
  58. package/dist/harness/runner.js.map +1 -1
  59. package/dist/harness/tools/event.d.ts +1 -1
  60. package/dist/harness/tools/event.d.ts.map +1 -1
  61. package/dist/harness/tools/event.js +19 -22
  62. package/dist/harness/tools/event.js.map +1 -1
  63. package/dist/harness/tools/index.d.ts +2 -1
  64. package/dist/harness/tools/index.d.ts.map +1 -1
  65. package/dist/harness/tools/index.js +2 -3
  66. package/dist/harness/tools/index.js.map +1 -1
  67. package/dist/main.js +17 -9
  68. package/dist/main.js.map +1 -1
  69. package/dist/office/index.d.ts +3 -2
  70. package/dist/office/index.d.ts.map +1 -1
  71. package/dist/office/index.js +3 -3
  72. package/dist/office/index.js.map +1 -1
  73. package/dist/office/projection.d.ts +19 -2
  74. package/dist/office/projection.d.ts.map +1 -1
  75. package/dist/office/projection.js +73 -100
  76. package/dist/office/projection.js.map +1 -1
  77. package/dist/office/types.d.ts +13 -10
  78. package/dist/office/types.d.ts.map +1 -1
  79. package/dist/office/types.js.map +1 -1
  80. package/dist/runtime/conversation-runtime.js +1 -0
  81. package/dist/runtime/conversation-runtime.js.map +1 -1
  82. package/dist/runtime/types.d.ts +4 -1
  83. package/dist/runtime/types.d.ts.map +1 -1
  84. package/dist/runtime/types.js.map +1 -1
  85. package/dist/sandbox/index.d.ts +9 -1
  86. package/dist/sandbox/index.d.ts.map +1 -1
  87. package/dist/sandbox/index.js +16 -12
  88. package/dist/sandbox/index.js.map +1 -1
  89. package/dist/sandbox/provisioner.d.ts +8 -0
  90. package/dist/sandbox/provisioner.d.ts.map +1 -1
  91. package/dist/sandbox/provisioner.js +36 -0
  92. package/dist/sandbox/provisioner.js.map +1 -1
  93. package/dist/settings-mutation.d.ts +5 -29
  94. package/dist/settings-mutation.d.ts.map +1 -1
  95. package/dist/settings-mutation.js +7 -13
  96. package/dist/settings-mutation.js.map +1 -1
  97. package/dist/types.d.ts +11 -27
  98. package/dist/types.d.ts.map +1 -1
  99. package/dist/types.js.map +1 -1
  100. package/package.json +2 -1
  101. package/dist/events/watcher.d.ts +0 -43
  102. package/dist/events/watcher.d.ts.map +0 -1
  103. package/dist/events/watcher.js +0 -395
  104. package/dist/events/watcher.js.map +0 -1
@@ -40,5 +40,5 @@ export declare function readSkillsFromDir(skillsDir: string, source: SkillEntry[
40
40
  * event-format module (files that fail validation stay visible with a null
41
41
  * payload so operators can delete them).
42
42
  */
43
- export declare function listAllEvents(store: EventStore): Promise<EventSummary[]>;
43
+ export declare function listOfficeEvents(store: EventStore): Promise<EventSummary[]>;
44
44
  //# sourceMappingURL=portal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/adapters/web/admin/portal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,qBAAa,uBAAwB,SAAQ,kBAAkB,CAAC,UAAU,CAAC;IACzE,MAAM,CAAC,IAAI,EAAE;QACX,QAAQ,EAAE,YAAY,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,UAAU,CAUb;CACF;AAsCD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,OAAO,CAAC,CA6BlB;AA0GD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,UAAU,GAChB,sBAAsB,CAexB;AA8gCD,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,cAAc,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAwBD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,UAAU,EAAE,CAuB/F;AAqRD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAiB9E"}
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/adapters/web/admin/portal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,qBAAa,uBAAwB,SAAQ,kBAAkB,CAAC,UAAU,CAAC;IACzE,MAAM,CAAC,IAAI,EAAE;QACX,QAAQ,EAAE,YAAY,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,UAAU,CAUb;CACF;AAoCD,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAYzD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAwGD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,UAAU,GAChB,sBAAsB,CAexB;AA28BD,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,cAAc,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAwBD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,UAAU,EAAE,CAuB/F;AAqRD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBjF"}
@@ -16,11 +16,11 @@ export class InMemoryAdminTokenStore extends InMemoryTokenStore {
16
16
  });
17
17
  }
18
18
  }
19
- import { loadConversationWorkspaceOverride, loadGlobalSettings, loadScopeMcpServers, resolveConversationSettings, } from "../../../config.js";
19
+ import { loadOfficeVisibilityOverride, loadGlobalSettings, loadScopeMcpServers, resolveConversationSettings, } from "../../../config.js";
20
20
  import { findMcpPreset, listMcpPresets, materializeMcpPreset } from "../../../harness/mcp.js";
21
21
  import { loadMcpTools } from "../../../harness/mcp.js";
22
22
  import { isValidMcpServerName, parseStandardMcpServers, redactMcpUrl, } from "../../../harness/mcp.js";
23
- import { applyConversationSettings, applyConversationWorkspacePolicy, applyGlobalSettings, applyGlobalWorkspacePolicy, } from "../../../settings-mutation.js";
23
+ import { applyConversationSettings, applyOfficeVisibility, applyGlobalSettings, } from "../../../settings-mutation.js";
24
24
  import { escapeHtml, jsonResponse as jsonRes, readJsonBody, renderPortalShell, } from "../portal-shell.js";
25
25
  import { resolveExistingSessionFile } from "../session-view/portal.js";
26
26
  import { PRODUCT_NAME } from "../../../platform-messages.js";
@@ -115,8 +115,8 @@ async function routePostApiRequest(req, res, url, services) {
115
115
  switch (url.pathname) {
116
116
  case "/admin/api/conversations/model":
117
117
  return serveConversationModelUpdate(res, body, services, token);
118
- case "/admin/api/conversations/sandbox":
119
- return serveConversationSandboxUpdate(res, body, services, token);
118
+ case "/admin/api/conversations/visibility":
119
+ return serveConversationVisibilityUpdate(res, body, services, token);
120
120
  case "/admin/api/conversations/slack":
121
121
  return serveConversationSlackUpdate(res, body, services, token);
122
122
  case "/admin/api/conversations/session-link":
@@ -129,8 +129,6 @@ async function routePostApiRequest(req, res, url, services) {
129
129
  return serveMcpServerMutation(res, body, services, token);
130
130
  case "/admin/api/settings/model":
131
131
  return serveGlobalModelUpdate(res, body, services);
132
- case "/admin/api/settings/workspace":
133
- return serveGlobalWorkspaceUpdate(res, body, services);
134
132
  case "/admin/api/settings/sandbox":
135
133
  return serveGlobalSandboxUpdate(res, body, services);
136
134
  case "/admin/api/settings/slack":
@@ -443,7 +441,6 @@ function serveConversationState(res, url, services, token) {
443
441
  const globalConfig = loadGlobalSettings();
444
442
  const conversationConfig = resolveConversationSettings(office);
445
443
  const conversationWorkspace = resolveWorkspaceProjection(office);
446
- const globalWorkspaceSettings = globalConfig.sandbox?.workspace;
447
444
  jsonRes(res, 200, {
448
445
  conversationId,
449
446
  provider: conversationConfig.provider,
@@ -452,13 +449,10 @@ function serveConversationState(res, url, services, token) {
452
449
  globalProvider: globalConfig.provider,
453
450
  globalModel: globalConfig.model,
454
451
  globalThinkingLevel: globalConfig.thinkingLevel,
455
- workspaceDoorPolicy: conversationWorkspace.doorPolicy,
456
- workspaceLayout: conversationWorkspace.layout,
457
- workspaceVisibility: conversationWorkspace.visibility,
458
- workspaceOverride: doorPolicyChoiceKey(loadConversationWorkspaceOverride(office)),
459
- globalWorkspaceDoorPolicy: globalWorkspaceSettings?.doorPolicy ?? "isolated",
460
- globalWorkspaceLayout: globalWorkspaceSettings?.layout ?? "conversation",
461
- globalWorkspaceVisibility: globalWorkspaceSettings?.visibility ?? "public",
452
+ officeVisibility: conversationWorkspace.visibility,
453
+ officeVisibilitySource: conversationWorkspace.source,
454
+ officeVisibilityOverride: loadOfficeVisibilityOverride(office),
455
+ officeLegacyFull: conversationWorkspace.legacyFull,
462
456
  slack: {
463
457
  replyMode: conversationConfig.slack?.replyMode ?? globalConfig.slack?.replyMode ?? "top-level",
464
458
  globalReplyMode: globalConfig.slack?.replyMode ?? "top-level",
@@ -476,9 +470,6 @@ function serveGlobalSettings(res) {
476
470
  sandboxMemory: config.sandbox?.memory ?? null,
477
471
  sandboxBoostCpus: config.sandbox?.boost?.cpus ?? null,
478
472
  sandboxBoostMemory: config.sandbox?.boost?.memory ?? null,
479
- workspaceDoorPolicy: config.sandbox?.workspace?.doorPolicy ?? null,
480
- workspaceLayout: config.sandbox?.workspace?.layout ?? null,
481
- workspaceVisibility: config.sandbox?.workspace?.visibility ?? null,
482
473
  defaultSharedVault: config.sandbox?.defaultSharedVault ?? null,
483
474
  slack: {
484
475
  replyMode: config.slack?.replyMode ?? "top-level",
@@ -543,48 +534,9 @@ function serveConversationModelUpdate(res, body, services, token) {
543
534
  jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
544
535
  }
545
536
  }
546
- /**
547
- * Wire values for a door-policy selection; "default" clears the office's
548
- * override. `trusted-shared-support` keeps the historical read-write shared
549
- * MEMORY.md; `trusted-shared-support-private` is the same layout with the
550
- * shared MEMORY.md mounted read-only (modeled on Claude Tag's private-
551
- * channel memory: read the shared pool, never write into it).
552
- */
553
- function parseDoorPolicyChoice(value) {
554
- switch (value) {
555
- case "default":
556
- return { choice: null };
557
- case "isolated":
558
- return { choice: { doorPolicy: "isolated" } };
559
- case "trusted-shared-support":
560
- return { choice: { doorPolicy: "trusted", layout: "shared-support", visibility: "public" } };
561
- case "trusted-shared-support-private":
562
- return {
563
- choice: { doorPolicy: "trusted", layout: "shared-support", visibility: "private" },
564
- };
565
- case "trusted-full":
566
- return { choice: { doorPolicy: "trusted", layout: "full" } };
567
- default:
568
- return undefined;
569
- }
570
- }
571
- function doorPolicyChoiceKey(choice) {
572
- if (!choice)
573
- return "default";
574
- if (choice.doorPolicy === "isolated")
575
- return "isolated";
576
- if (choice.layout === "full")
577
- return "trusted-full";
578
- return choice.visibility === "private"
579
- ? "trusted-shared-support-private"
580
- : "trusted-shared-support";
581
- }
582
- function serveConversationSandboxUpdate(res, body, services, token) {
583
- const parsed = parseDoorPolicyChoice(body.doorPolicy);
584
- if (!parsed) {
585
- jsonRes(res, 400, {
586
- error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', 'trusted-shared-support-private', or 'trusted-full'",
587
- });
537
+ function serveConversationVisibilityUpdate(res, body, services, token) {
538
+ if (body.visibility !== "private" && body.visibility !== "default") {
539
+ jsonRes(res, 400, { error: "visibility must be 'private' or 'default'" });
588
540
  return;
589
541
  }
590
542
  const target = requireConversationWorkspace(res, body, services, token);
@@ -592,7 +544,7 @@ function serveConversationSandboxUpdate(res, body, services, token) {
592
544
  return;
593
545
  const { scope, workspace } = target;
594
546
  try {
595
- const result = applyConversationWorkspacePolicy(services.runtime, workspace.office(scope.address), parsed.choice);
547
+ const result = applyOfficeVisibility(services.runtime, workspace.office(scope.address), body.visibility === "private" ? "private" : null);
596
548
  if (!result.ok) {
597
549
  jsonRes(res, 409, { error: "Conversation is busy; retry when the current run finishes" });
598
550
  return;
@@ -603,19 +555,6 @@ function serveConversationSandboxUpdate(res, body, services, token) {
603
555
  jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
604
556
  }
605
557
  }
606
- function serveGlobalWorkspaceUpdate(res, body, services) {
607
- const parsed = parseDoorPolicyChoice(body.doorPolicy);
608
- if (!parsed) {
609
- jsonRes(res, 400, {
610
- error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', 'trusted-shared-support-private', or 'trusted-full'",
611
- });
612
- return;
613
- }
614
- respondWithSettingsUpdate(res, () => {
615
- const result = applyGlobalWorkspacePolicy(services.runtime, parsed.choice);
616
- return { ok: true, staleConversations: result.staleConversations.length };
617
- });
618
- }
619
558
  function serveConversationSlackUpdate(res, body, services, token) {
620
559
  const replyMode = body.replyMode;
621
560
  if (replyMode !== "top-level" && replyMode !== "thread") {
@@ -1289,12 +1228,14 @@ function serveSkillFile(res, url, services, token) {
1289
1228
  * event-format module (files that fail validation stay visible with a null
1290
1229
  * payload so operators can delete them).
1291
1230
  */
1292
- export async function listAllEvents(store) {
1231
+ export async function listOfficeEvents(store) {
1293
1232
  const entries = await store.list();
1294
1233
  return entries.map((entry) => {
1295
1234
  const payload = entry.payload;
1296
1235
  return {
1297
1236
  name: entry.filename,
1237
+ officePlatform: store.address.platform,
1238
+ officeConversationId: store.address.conversationId,
1298
1239
  size: entry.size,
1299
1240
  mtimeMs: entry.mtimeMs,
1300
1241
  type: payload?.type ?? null,
@@ -1307,31 +1248,45 @@ export async function listAllEvents(store) {
1307
1248
  };
1308
1249
  });
1309
1250
  }
1310
- function requireAdminEventStore(res, services) {
1251
+ function requireAdminEventStore(res, services, office) {
1311
1252
  if (!services.eventStore) {
1312
1253
  jsonRes(res, 503, { error: "Working directory not available" });
1313
1254
  return null;
1314
1255
  }
1315
- return services.eventStore;
1256
+ return services.eventStore(office);
1316
1257
  }
1258
+ /** Every registered office's events, each read through its own store. */
1317
1259
  async function serveEventsList(res, services) {
1318
- const store = requireAdminEventStore(res, services);
1319
- if (!store)
1260
+ const workspace = requireAdminWorkspace(res, services);
1261
+ if (!workspace)
1320
1262
  return;
1321
- jsonRes(res, 200, { events: await listAllEvents(store) });
1263
+ if (!services.eventStore) {
1264
+ jsonRes(res, 503, { error: "Working directory not available" });
1265
+ return;
1266
+ }
1267
+ const events = [];
1268
+ for (const record of listRegisteredOffices(workspace.stateDir)) {
1269
+ events.push(...(await listOfficeEvents(services.eventStore(workspace.office(record)))));
1270
+ }
1271
+ jsonRes(res, 200, { events });
1322
1272
  }
1323
- /** Per-conversation listing — filter all events by conversationId match. */
1273
+ /** Per-conversation listing through that office's confined store. */
1324
1274
  async function serveConversationEventsList(res, url, services, token) {
1325
1275
  const scope = resolveConversationFromQuery(url, token);
1326
1276
  if (scope.error) {
1327
1277
  jsonRes(res, 403, { error: scope.error });
1328
1278
  return;
1329
1279
  }
1330
- const store = requireAdminEventStore(res, services);
1280
+ const workspace = requireAdminWorkspace(res, services);
1281
+ if (!workspace)
1282
+ return;
1283
+ const store = requireAdminEventStore(res, services, workspace.office(scope.address));
1331
1284
  if (!store)
1332
1285
  return;
1333
- const events = (await listAllEvents(store)).filter((e) => e.conversationId === scope.conversationId);
1334
- jsonRes(res, 200, { conversationId: scope.conversationId, events });
1286
+ jsonRes(res, 200, {
1287
+ conversationId: scope.conversationId,
1288
+ events: await listOfficeEvents(store),
1289
+ });
1335
1290
  }
1336
1291
  /** Delete a single event file scoped to the caller's conversation. */
1337
1292
  async function serveConversationEventDelete(res, body, services, token) {
@@ -1348,28 +1303,20 @@ async function serveConversationEventDelete(res, body, services, token) {
1348
1303
  jsonRes(res, 400, { error: "Invalid name" });
1349
1304
  return;
1350
1305
  }
1351
- const store = requireAdminEventStore(res, services);
1352
- if (!store)
1353
- return;
1354
- let payload;
1355
- try {
1356
- payload = (await store.read(name)).payload;
1357
- }
1358
- catch {
1359
- jsonRes(res, 404, { error: "Event not found" });
1306
+ const workspace = requireAdminWorkspace(res, services);
1307
+ if (!workspace)
1360
1308
  return;
1361
- }
1362
- // The store normalizes the legacy `channelId` alias into `conversationId`.
1363
- if (payload?.conversationId !== scope.conversationId) {
1364
- jsonRes(res, 403, { error: "Event does not belong to this conversation." });
1309
+ const store = requireAdminEventStore(res, services, workspace.office(scope.address));
1310
+ if (!store)
1365
1311
  return;
1366
- }
1312
+ // The store is confined to this office: another office's filename is "not found".
1367
1313
  try {
1368
1314
  await store.delete(name);
1369
1315
  jsonRes(res, 200, { ok: true });
1370
1316
  }
1371
1317
  catch (err) {
1372
- jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
1318
+ const message = err instanceof Error ? err.message : String(err);
1319
+ jsonRes(res, /not found/.test(message) ? 404 : 500, { error: message });
1373
1320
  }
1374
1321
  }
1375
1322
  // ── Utilities ──────────────────────────────────────────────────────────────────
@@ -1840,23 +1787,12 @@ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
1840
1787
  function renderReplyOptions(slack) {
1841
1788
  return renderOptions(['top-level','thread'].map((m) => [m, m]), (slack && slack.replyMode) || 'top-level');
1842
1789
  }
1843
- function renderDoorOptions(value, channel) {
1844
- return renderOptions([
1845
- ['default', 'Automatic — follows ' + channel + ' platform channel (public shares, private reads only, DMs isolated)'],
1846
- ['isolated', 'isolated — own office only'],
1847
- ['trusted-shared-support', 'trusted / shared-support (public) — office + shared memory, skills, events — read-write'],
1848
- ['trusted-shared-support-private', 'trusted / shared-support (private) — same, but shared MEMORY.md is read-only'],
1849
- ['trusted-full', 'trusted / full — entire workspace'],
1850
- ], value);
1851
- }
1852
-
1853
1790
  function renderSettings(data) {
1854
1791
  const thinkingOpts = renderThinkingOptions(data.thinkingLevel);
1855
1792
  const replyModeOpts = renderReplyOptions(data.slack);
1856
1793
  const globalReplyMode = (data.slack && data.slack.globalReplyMode) || 'top-level';
1857
1794
  const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');
1858
1795
  const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');
1859
- const doorPolicyOpts = renderDoorOptions(data.workspaceOverride || 'default', 'the');
1860
1796
  return [
1861
1797
  '<div class="config-grid">',
1862
1798
  renderConfigCard('Model', [
@@ -1865,11 +1801,19 @@ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
1865
1801
  '<p class="muted-note">Global default: ' + escHtml(globalModelLabel) + '</p>',
1866
1802
  ].join(''), 'saveModel', 'Save model', 'model-save-result'),
1867
1803
 
1868
- renderConfigCard('Office data policy', [
1869
- '<div class="config-row"><label>Door policy</label><select id="m-door-policy">' + doorPolicyOpts + '</select></div>',
1870
- '<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout + ' / ' + (data.workspaceVisibility || 'public')) + '</p>',
1871
- '<p class="muted-note">Changing the policy rebuilds the office sandbox container with the new mounts on its next message; the container contents are preserved.</p>',
1872
- ].join(''), 'saveDoorPolicy', 'Save door policy', 'mount-save-result'),
1804
+ renderConfigCard('Office visibility', [
1805
+ '<div class="config-row"><label>Visibility</label><select id="m-visibility">' +
1806
+ renderOptions([
1807
+ ['default', 'Follow the Slack conversation type (default)'],
1808
+ ['private', 'Private — treat this public channel as private'],
1809
+ ], data.officeVisibilityOverride === 'private' ? 'private' : 'default') + '</select></div>',
1810
+ '<p class="muted-note">Effective: <strong>' + escHtml(data.officeVisibility) + '</strong> · ' + escHtml(
1811
+ data.officeVisibilitySource === 'override' ? 'set here'
1812
+ : data.officeVisibilitySource === 'platform' ? 'from the Slack conversation type'
1813
+ : 'conversation type unknown; private until observed') + '</p>',
1814
+ '<p class="muted-note">Public offices can be read by every other office and may write shared MEMORY.md and skills. Private offices (private channels, DMs) are visible only to themselves and read shared knowledge without writing it. Nothing can be made more visible than Slack allows.</p>',
1815
+ (data.officeLegacyFull ? '<p class="muted-note">This office still declares the retired <code>full</code> door policy; it no longer widens access.</p>' : ''),
1816
+ ].join(''), 'saveVisibility', 'Save visibility', 'mount-save-result'),
1873
1817
  renderConfigCard('Slack', [
1874
1818
  '<div class="config-row"><label>Reply mode</label><select id="m-slack-reply-mode">' + replyModeOpts + '</select></div>',
1875
1819
  '<p class="muted-note">Global default: ' + escHtml(globalReplyMode) + '</p>',
@@ -1898,10 +1842,10 @@ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
1898
1842
  await saveConversationSetting(btn, result, 'slack', { replyMode }, 'Save Slack');
1899
1843
  }
1900
1844
 
1901
- async function saveDoorPolicy(btn) {
1902
- const doorPolicy = document.getElementById('m-door-policy').value;
1845
+ async function saveVisibility(btn) {
1846
+ const visibility = document.getElementById('m-visibility').value;
1903
1847
  const result = document.getElementById('mount-save-result');
1904
- await saveConversationSetting(btn, result, 'sandbox', { doorPolicy }, 'Save door policy', loadSettings);
1848
+ await saveConversationSetting(btn, result, 'visibility', { visibility }, 'Save visibility', loadSettings);
1905
1849
  }
1906
1850
 
1907
1851
  function saveConversationSetting(btn, result, setting, values, label, onSaved) {
@@ -2666,14 +2610,6 @@ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
2666
2610
  function renderGlobalSettings(data) {
2667
2611
  const thinkingOpts = renderThinkingOptions(data.thinkingLevel);
2668
2612
  const replyModeOpts = renderReplyOptions(data.slack);
2669
- const gDoorKey = !data.workspaceDoorPolicy
2670
- ? 'default'
2671
- : (data.workspaceDoorPolicy === 'isolated'
2672
- ? 'isolated'
2673
- : (data.workspaceLayout === 'full'
2674
- ? 'trusted-full'
2675
- : ((data.workspaceVisibility || 'public') === 'private' ? 'trusted-shared-support-private' : 'trusted-shared-support')));
2676
- const gDoorPolicyOpts = renderDoorOptions(gDoorKey, 'each');
2677
2613
  return [
2678
2614
  '<div class="config-grid">',
2679
2615
  renderConfigCard('Default model', [
@@ -2686,10 +2622,6 @@ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
2686
2622
  '<div class="config-row"><label>Boost CPUs</label><input id="g-bcpus" placeholder="2" value="' + escAttr(data.sandboxBoostCpus || '') + '"></div>',
2687
2623
  '<div class="config-row"><label>Boost Mem</label><input id="g-bmem" placeholder="4g" value="' + escAttr(data.sandboxBoostMemory || '') + '"></div>',
2688
2624
  ].join(''), 'saveGlobalSandbox', 'Save sandbox', 'g-sandbox-result'),
2689
- renderConfigCard('Office door policy', [
2690
- '<div class="config-row"><label>Default</label><select id="g-door-policy">' + gDoorPolicyOpts + '</select></div>',
2691
- '<p class="muted-note">Applies to offices without their own policy. Affected offices rebuild their sandbox container with the new mounts on the next message; container contents are preserved.</p>',
2692
- ].join(''), 'saveGlobalWorkspace', 'Save door policy', 'g-workspace-result'),
2693
2625
  renderConfigCard('Slack', [
2694
2626
  '<div class="config-row"><label>Reply mode</label><select id="g-slack-reply-mode">' + replyModeOpts + '</select></div>',
2695
2627
  ].join(''), 'saveGlobalSlack', 'Save Slack', 'g-slack-result'),
@@ -2727,23 +2659,6 @@ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
2727
2659
  await saveSetting(btn, result, 'settings/sandbox', { cpus, memory, boostCpus, boostMemory }, 'Save sandbox');
2728
2660
  }
2729
2661
 
2730
- async function saveGlobalWorkspace(btn) {
2731
- const doorPolicy = document.getElementById('g-door-policy').value;
2732
- const result = document.getElementById('g-workspace-result');
2733
- btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';
2734
- try {
2735
- const data = await apiPost('/admin/api/settings/workspace', { doorPolicy });
2736
- result.style.display = 'block'; result.className = 'inline-result ok';
2737
- result.textContent = data.staleConversations > 0
2738
- ? 'Saved ✓ (' + data.staleConversations + ' busy conversation(s) refresh after their current run)'
2739
- : 'Saved ✓';
2740
- } catch (err) {
2741
- result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;
2742
- } finally {
2743
- btn.disabled = false; btn.textContent = 'Save door policy';
2744
- }
2745
- }
2746
-
2747
2662
  async function saveGlobalSlack(btn) {
2748
2663
  const replyMode = document.getElementById('g-slack-reply-mode').value;
2749
2664
  const result = document.getElementById('g-slack-result');