@geminixiang/mikan 1.0.0-beta.32 → 1.0.0-beta.34

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 (257) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/adapter.d.ts +11 -1
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js +18 -1
  5. package/dist/adapter.js.map +1 -1
  6. package/dist/adapters/discord/bot.d.ts +1 -1
  7. package/dist/adapters/discord/bot.d.ts.map +1 -1
  8. package/dist/adapters/discord/bot.js +26 -20
  9. package/dist/adapters/discord/bot.js.map +1 -1
  10. package/dist/adapters/discord/context.d.ts +2 -1
  11. package/dist/adapters/discord/context.d.ts.map +1 -1
  12. package/dist/adapters/discord/context.js +7 -3
  13. package/dist/adapters/discord/context.js.map +1 -1
  14. package/dist/adapters/github/bot.d.ts +1 -1
  15. package/dist/adapters/github/bot.d.ts.map +1 -1
  16. package/dist/adapters/github/bot.js +10 -5
  17. package/dist/adapters/github/bot.js.map +1 -1
  18. package/dist/adapters/github/context.d.ts +2 -1
  19. package/dist/adapters/github/context.d.ts.map +1 -1
  20. package/dist/adapters/github/context.js +7 -4
  21. package/dist/adapters/github/context.js.map +1 -1
  22. package/dist/adapters/github/repo.d.ts.map +1 -1
  23. package/dist/adapters/github/repo.js +2 -1
  24. package/dist/adapters/github/repo.js.map +1 -1
  25. package/dist/adapters/intake.js +5 -4
  26. package/dist/adapters/intake.js.map +1 -1
  27. package/dist/adapters/shared.d.ts +8 -7
  28. package/dist/adapters/shared.d.ts.map +1 -1
  29. package/dist/adapters/shared.js +18 -16
  30. package/dist/adapters/shared.js.map +1 -1
  31. package/dist/adapters/slack/bot.d.ts +4 -1
  32. package/dist/adapters/slack/bot.d.ts.map +1 -1
  33. package/dist/adapters/slack/bot.js +76 -54
  34. package/dist/adapters/slack/bot.js.map +1 -1
  35. package/dist/adapters/slack/context.d.ts +2 -1
  36. package/dist/adapters/slack/context.d.ts.map +1 -1
  37. package/dist/adapters/slack/context.js +7 -3
  38. package/dist/adapters/slack/context.js.map +1 -1
  39. package/dist/adapters/slack/types.d.ts +2 -1
  40. package/dist/adapters/slack/types.d.ts.map +1 -1
  41. package/dist/adapters/slack/types.js.map +1 -1
  42. package/dist/adapters/telegram/bot.d.ts +1 -1
  43. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  44. package/dist/adapters/telegram/bot.js +17 -10
  45. package/dist/adapters/telegram/bot.js.map +1 -1
  46. package/dist/adapters/telegram/context.d.ts +1 -0
  47. package/dist/adapters/telegram/context.d.ts.map +1 -1
  48. package/dist/adapters/telegram/context.js +7 -3
  49. package/dist/adapters/telegram/context.js.map +1 -1
  50. package/dist/adapters/types.d.ts +3 -2
  51. package/dist/adapters/types.d.ts.map +1 -1
  52. package/dist/adapters/types.js.map +1 -1
  53. package/dist/agent.d.ts +1 -0
  54. package/dist/agent.d.ts.map +1 -1
  55. package/dist/agent.js +122 -50
  56. package/dist/agent.js.map +1 -1
  57. package/dist/cli/boot.d.ts.map +1 -1
  58. package/dist/cli/boot.js +4 -1
  59. package/dist/cli/boot.js.map +1 -1
  60. package/dist/cli/ext-dev.d.ts.map +1 -1
  61. package/dist/cli/ext-dev.js +22 -14
  62. package/dist/cli/ext-dev.js.map +1 -1
  63. package/dist/cli/ext.d.ts.map +1 -1
  64. package/dist/cli/ext.js +5 -3
  65. package/dist/cli/ext.js.map +1 -1
  66. package/dist/cli/office.d.ts +2 -0
  67. package/dist/cli/office.d.ts.map +1 -0
  68. package/dist/cli/office.js +89 -0
  69. package/dist/cli/office.js.map +1 -0
  70. package/dist/cli/types.d.ts +3 -1
  71. package/dist/cli/types.d.ts.map +1 -1
  72. package/dist/cli/types.js.map +1 -1
  73. package/dist/commands/auto-reply.d.ts.map +1 -1
  74. package/dist/commands/auto-reply.js +2 -1
  75. package/dist/commands/auto-reply.js.map +1 -1
  76. package/dist/commands/extensions.js +1 -1
  77. package/dist/commands/extensions.js.map +1 -1
  78. package/dist/commands/login.d.ts.map +1 -1
  79. package/dist/commands/login.js +6 -3
  80. package/dist/commands/login.js.map +1 -1
  81. package/dist/commands/model.d.ts.map +1 -1
  82. package/dist/commands/model.js +7 -4
  83. package/dist/commands/model.js.map +1 -1
  84. package/dist/commands/new.d.ts.map +1 -1
  85. package/dist/commands/new.js +6 -2
  86. package/dist/commands/new.js.map +1 -1
  87. package/dist/commands/sandbox.d.ts.map +1 -1
  88. package/dist/commands/sandbox.js +5 -20
  89. package/dist/commands/sandbox.js.map +1 -1
  90. package/dist/commands/session-view.d.ts.map +1 -1
  91. package/dist/commands/session-view.js +2 -1
  92. package/dist/commands/session-view.js.map +1 -1
  93. package/dist/commands/types.d.ts +5 -4
  94. package/dist/commands/types.d.ts.map +1 -1
  95. package/dist/commands/types.js.map +1 -1
  96. package/dist/config.d.ts +15 -4
  97. package/dist/config.d.ts.map +1 -1
  98. package/dist/config.js +55 -24
  99. package/dist/config.js.map +1 -1
  100. package/dist/events.d.ts.map +1 -1
  101. package/dist/events.js +4 -2
  102. package/dist/events.js.map +1 -1
  103. package/dist/execution-resolver.d.ts.map +1 -1
  104. package/dist/execution-resolver.js +22 -13
  105. package/dist/execution-resolver.js.map +1 -1
  106. package/dist/harness/extensions/loader.d.ts +2 -1
  107. package/dist/harness/extensions/loader.d.ts.map +1 -1
  108. package/dist/harness/extensions/loader.js +5 -3
  109. package/dist/harness/extensions/loader.js.map +1 -1
  110. package/dist/harness/extensions/types.d.ts +3 -0
  111. package/dist/harness/extensions/types.d.ts.map +1 -1
  112. package/dist/harness/extensions/types.js.map +1 -1
  113. package/dist/harness/session-store.d.ts +2 -0
  114. package/dist/harness/session-store.d.ts.map +1 -1
  115. package/dist/harness/session-store.js +2 -0
  116. package/dist/harness/session-store.js.map +1 -1
  117. package/dist/harness/subagent-profiles.d.ts.map +1 -1
  118. package/dist/harness/subagent-profiles.js +9 -0
  119. package/dist/harness/subagent-profiles.js.map +1 -1
  120. package/dist/harness/subagent-runner.js +6 -4
  121. package/dist/harness/subagent-runner.js.map +1 -1
  122. package/dist/harness/types.d.ts +4 -2
  123. package/dist/harness/types.d.ts.map +1 -1
  124. package/dist/harness/types.js.map +1 -1
  125. package/dist/index.d.ts +2 -1
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +1 -0
  128. package/dist/index.js.map +1 -1
  129. package/dist/main.js +47 -0
  130. package/dist/main.js.map +1 -1
  131. package/dist/observability/sentry.d.ts +2 -0
  132. package/dist/observability/sentry.d.ts.map +1 -1
  133. package/dist/observability/sentry.js +7 -0
  134. package/dist/observability/sentry.js.map +1 -1
  135. package/dist/office-address.d.ts +32 -0
  136. package/dist/office-address.d.ts.map +1 -0
  137. package/dist/office-address.js +118 -0
  138. package/dist/office-address.js.map +1 -0
  139. package/dist/office-migration.d.ts +38 -0
  140. package/dist/office-migration.d.ts.map +1 -0
  141. package/dist/office-migration.js +213 -0
  142. package/dist/office-migration.js.map +1 -0
  143. package/dist/office-registry.d.ts +81 -0
  144. package/dist/office-registry.d.ts.map +1 -0
  145. package/dist/office-registry.js +705 -0
  146. package/dist/office-registry.js.map +1 -0
  147. package/dist/packages/admin.js +5 -3
  148. package/dist/packages/admin.js.map +1 -1
  149. package/dist/packages/inspect.d.ts.map +1 -1
  150. package/dist/packages/inspect.js +5 -2
  151. package/dist/packages/inspect.js.map +1 -1
  152. package/dist/packages/materialize.d.ts +2 -1
  153. package/dist/packages/materialize.d.ts.map +1 -1
  154. package/dist/packages/materialize.js +6 -16
  155. package/dist/packages/materialize.js.map +1 -1
  156. package/dist/packages/resolve.d.ts.map +1 -1
  157. package/dist/packages/resolve.js +8 -9
  158. package/dist/packages/resolve.js.map +1 -1
  159. package/dist/packages/types.d.ts +3 -2
  160. package/dist/packages/types.d.ts.map +1 -1
  161. package/dist/packages/types.js.map +1 -1
  162. package/dist/provisioner.d.ts +7 -0
  163. package/dist/provisioner.d.ts.map +1 -1
  164. package/dist/provisioner.js +17 -0
  165. package/dist/provisioner.js.map +1 -1
  166. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  167. package/dist/runtime/conversation-runtime.js +88 -67
  168. package/dist/runtime/conversation-runtime.js.map +1 -1
  169. package/dist/runtime/session-lifecycle.d.ts +22 -12
  170. package/dist/runtime/session-lifecycle.d.ts.map +1 -1
  171. package/dist/runtime/session-lifecycle.js +85 -53
  172. package/dist/runtime/session-lifecycle.js.map +1 -1
  173. package/dist/runtime/types.d.ts +14 -4
  174. package/dist/runtime/types.d.ts.map +1 -1
  175. package/dist/runtime/types.js.map +1 -1
  176. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  177. package/dist/sandbox/cloudflare.js +1 -0
  178. package/dist/sandbox/cloudflare.js.map +1 -1
  179. package/dist/sandbox/container.d.ts.map +1 -1
  180. package/dist/sandbox/container.js +1 -0
  181. package/dist/sandbox/container.js.map +1 -1
  182. package/dist/sandbox/firecracker.d.ts.map +1 -1
  183. package/dist/sandbox/firecracker.js +1 -0
  184. package/dist/sandbox/firecracker.js.map +1 -1
  185. package/dist/sandbox/gondolin.d.ts.map +1 -1
  186. package/dist/sandbox/gondolin.js +1 -0
  187. package/dist/sandbox/gondolin.js.map +1 -1
  188. package/dist/sandbox/host.d.ts.map +1 -1
  189. package/dist/sandbox/host.js +1 -0
  190. package/dist/sandbox/host.js.map +1 -1
  191. package/dist/sandbox/identity.d.ts +35 -7
  192. package/dist/sandbox/identity.d.ts.map +1 -1
  193. package/dist/sandbox/identity.js +31 -5
  194. package/dist/sandbox/identity.js.map +1 -1
  195. package/dist/sandbox/image.d.ts.map +1 -1
  196. package/dist/sandbox/image.js +1 -0
  197. package/dist/sandbox/image.js.map +1 -1
  198. package/dist/sandbox/index.d.ts +2 -0
  199. package/dist/sandbox/index.d.ts.map +1 -1
  200. package/dist/sandbox/index.js +12 -0
  201. package/dist/sandbox/index.js.map +1 -1
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/sandbox/types.d.ts.map +1 -1
  204. package/dist/sandbox/types.js.map +1 -1
  205. package/dist/sessions/chat-history-sync.d.ts.map +1 -1
  206. package/dist/sessions/chat-history-sync.js +33 -4
  207. package/dist/sessions/chat-history-sync.js.map +1 -1
  208. package/dist/sessions/store.d.ts +13 -1
  209. package/dist/sessions/store.d.ts.map +1 -1
  210. package/dist/sessions/store.js +78 -9
  211. package/dist/sessions/store.js.map +1 -1
  212. package/dist/settings-mutation.d.ts +3 -3
  213. package/dist/settings-mutation.d.ts.map +1 -1
  214. package/dist/settings-mutation.js +4 -4
  215. package/dist/settings-mutation.js.map +1 -1
  216. package/dist/store.d.ts +5 -0
  217. package/dist/store.d.ts.map +1 -1
  218. package/dist/store.js +13 -5
  219. package/dist/store.js.map +1 -1
  220. package/dist/tools/event.d.ts.map +1 -1
  221. package/dist/tools/event.js +5 -2
  222. package/dist/tools/event.js.map +1 -1
  223. package/dist/tools/subagent.d.ts.map +1 -1
  224. package/dist/tools/subagent.js +6 -3
  225. package/dist/tools/subagent.js.map +1 -1
  226. package/dist/trigger.d.ts.map +1 -1
  227. package/dist/trigger.js +6 -3
  228. package/dist/trigger.js.map +1 -1
  229. package/dist/types.d.ts +78 -9
  230. package/dist/types.d.ts.map +1 -1
  231. package/dist/types.js.map +1 -1
  232. package/dist/vault/index.d.ts +16 -0
  233. package/dist/vault/index.d.ts.map +1 -1
  234. package/dist/vault/index.js +31 -1
  235. package/dist/vault/index.js.map +1 -1
  236. package/dist/web/admin/portal.d.ts +13 -0
  237. package/dist/web/admin/portal.d.ts.map +1 -1
  238. package/dist/web/admin/portal.js +156 -173
  239. package/dist/web/admin/portal.js.map +1 -1
  240. package/dist/web/admin/types.d.ts +3 -3
  241. package/dist/web/admin/types.d.ts.map +1 -1
  242. package/dist/web/admin/types.js.map +1 -1
  243. package/dist/web/session-view/portal.d.ts.map +1 -1
  244. package/dist/web/session-view/portal.js +24 -14
  245. package/dist/web/session-view/portal.js.map +1 -1
  246. package/dist/web/session-view/service.d.ts +1 -1
  247. package/dist/web/session-view/service.d.ts.map +1 -1
  248. package/dist/web/session-view/service.js +48 -11
  249. package/dist/web/session-view/service.js.map +1 -1
  250. package/dist/workspace-projection/index.d.ts +10 -3
  251. package/dist/workspace-projection/index.d.ts.map +1 -1
  252. package/dist/workspace-projection/index.js +129 -45
  253. package/dist/workspace-projection/index.js.map +1 -1
  254. package/dist/workspace-projection/types.d.ts +14 -3
  255. package/dist/workspace-projection/types.d.ts.map +1 -1
  256. package/dist/workspace-projection/types.js.map +1 -1
  257. package/package.json +2 -1
package/dist/agent.js CHANGED
@@ -2,21 +2,23 @@
2
2
  * Agent runner: prompt construction, path translation, run lifecycle, and createRunner.
3
3
  * Single module — do not split helpers that only this file uses.
4
4
  */
5
+ import { officeDirName } from "./office-address.js";
5
6
  import { DEFAULT_EVENT_BUDGET, formatSkillsForPrompt, loadExtensions, loadSkillsFromDir, MikanAgentSession, MikanModels, parseCommandInput, } from "./harness/index.js";
6
7
  import { runSubagent } from "./harness/subagent-runner.js";
7
8
  import { loadSubagentProfiles } from "./harness/subagent-profiles.js";
8
9
  import { mergeSubagentProgress, parseSubagentProgressSnapshot, renderSubagentDashboard, settleSubagentProgress, } from "./subagent-progress.js";
9
10
  import { createHash } from "crypto";
10
- import { existsSync } from "fs";
11
+ import { existsSync, lstatSync, readdirSync } from "fs";
11
12
  import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from "fs/promises";
12
13
  import { tmpdir } from "os";
13
14
  import { basename, isAbsolute, join, posix, relative, resolve, sep } from "path";
14
15
  import { resolveConversationSettings } from "./config.js";
16
+ import { resolveWorkspaceProjection } from "./workspace-projection/index.js";
15
17
  import { effectiveStateDir } from "./cli/arg-grammar.js";
16
18
  import { packageSkillRuntimeDir, resolveConversationPackages } from "./packages/index.js";
17
19
  import { ActorExecutionResolver } from "./execution-resolver.js";
18
20
  import * as log from "./log.js";
19
- import { createExecutor, getUnresolvedSandboxPathContext, } from "./sandbox/index.js";
21
+ import { assertSandboxSupportsWorkspacePolicy, createExecutor, getUnresolvedSandboxPathContext, } from "./sandbox/index.js";
20
22
  import { addLifecycleBreadcrumb, metricAttributes, reportUserFacingError, updateActiveSpanAttribution, } from "./observability/sentry.js";
21
23
  import { ChatHistorySync } from "./sessions/chat-history-sync.js";
22
24
  import { formatHistoryLine } from "./sessions/history-line.js";
@@ -149,11 +151,10 @@ export async function buildPromptPayload(message, workspacePath, pathContext, re
149
151
  }
150
152
  return { userMessage, imageAttachments };
151
153
  }
152
- async function getMemory(conversationDir) {
154
+ async function getMemory(projection) {
153
155
  const parts = [];
154
- // Read workspace-level memory (shared across all conversations)
155
- const workspaceMemoryPath = join(conversationDir, "..", "MEMORY.md");
156
- if (existsSync(workspaceMemoryPath)) {
156
+ const workspaceMemoryPath = projection.promptSources.globalMemoryPath;
157
+ if (workspaceMemoryPath && isRegularFile(workspaceMemoryPath)) {
157
158
  try {
158
159
  const content = (await readFile(workspaceMemoryPath, "utf-8")).trim();
159
160
  if (content) {
@@ -164,9 +165,8 @@ async function getMemory(conversationDir) {
164
165
  log.logWarning("Failed to read workspace memory", `${workspaceMemoryPath}: ${error}`);
165
166
  }
166
167
  }
167
- // Read conversation-specific memory
168
- const conversationMemoryPath = join(conversationDir, "MEMORY.md");
169
- if (existsSync(conversationMemoryPath)) {
168
+ const conversationMemoryPath = projection.promptSources.conversationMemoryPath;
169
+ if (isRegularFile(conversationMemoryPath)) {
170
170
  try {
171
171
  const content = (await readFile(conversationMemoryPath, "utf-8")).trim();
172
172
  if (content) {
@@ -182,7 +182,16 @@ async function getMemory(conversationDir) {
182
182
  }
183
183
  return parts.join("\n\n");
184
184
  }
185
- function loadMikanSkills(conversationDir, workspacePath) {
185
+ function isRegularFile(path) {
186
+ try {
187
+ const stats = lstatSync(path);
188
+ return stats.isFile() && !stats.isSymbolicLink();
189
+ }
190
+ catch {
191
+ return false;
192
+ }
193
+ }
194
+ function loadMikanSkills(address, conversationDir, workspacePath, projection) {
186
195
  const skillMap = new Map();
187
196
  // conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)
188
197
  // hostWorkspacePath is the parent directory on host
@@ -206,7 +215,7 @@ function loadMikanSkills(conversationDir, workspacePath) {
206
215
  // skill ships alongside its SKILL.md, which inlining could never carry.
207
216
  const mounted = workspacePath !== hostWorkspacePath;
208
217
  for (const { slug, dir } of resolveConversationPackages({
209
- conversationId: basename(conversationDir),
218
+ address,
210
219
  stateDir: effectiveStateDir(),
211
220
  conversationDir,
212
221
  }).skillDirs) {
@@ -221,26 +230,70 @@ function loadMikanSkills(conversationDir, workspacePath) {
221
230
  skillMap.set(skill.name, skill);
222
231
  }
223
232
  }
224
- // Load workspace-level skills (global)
225
- const workspaceSkillsDir = join(hostWorkspacePath, "skills");
226
- for (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: "workspace" }).skills) {
227
- // Translate paths to container paths for system prompt
228
- skill.filePath = translatePath(skill.filePath);
229
- skill.baseDir = translatePath(skill.baseDir);
230
- skillMap.set(skill.name, skill);
233
+ // Load workspace-level skills only when the office projection authorizes it.
234
+ const workspaceSkillsDir = projection.promptSources.globalSkillsDir;
235
+ if (workspaceSkillsDir) {
236
+ for (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: "workspace" })
237
+ .skills) {
238
+ skill.filePath = translatePath(skill.filePath);
239
+ skill.baseDir = translatePath(skill.baseDir);
240
+ skillMap.set(skill.name, skill);
241
+ }
231
242
  }
232
- // Load conversation-specific skills (override workspace skills on collision)
233
- const conversationSkillsDir = join(conversationDir, "skills");
234
- for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: "channel" }).skills) {
235
- skill.filePath = translatePath(skill.filePath);
236
- skill.baseDir = translatePath(skill.baseDir);
237
- skillMap.set(skill.name, skill);
243
+ // Load conversation-specific skills (override workspace skills on collision).
244
+ // Host prompt construction must never follow an agent-created symlink out of
245
+ // the conversation office.
246
+ const conversationSkillsDir = projection.promptSources.conversationSkillsDir;
247
+ if (isSafePromptSkillTree(conversationSkillsDir, projection.promptSources.conversationDir)) {
248
+ for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: "channel" })
249
+ .skills) {
250
+ skill.filePath = translatePath(skill.filePath);
251
+ skill.baseDir = translatePath(skill.baseDir);
252
+ skillMap.set(skill.name, skill);
253
+ }
254
+ }
255
+ else if (existsSync(conversationSkillsDir)) {
256
+ log.logWarning("Ignoring unsafe conversation skill tree", conversationSkillsDir);
238
257
  }
239
258
  return Array.from(skillMap.values());
240
259
  }
241
- function buildRuntimePaths(runtimeWorkspaceRoot, conversationId) {
260
+ export function isSafePromptSkillTree(dir, conversationDir) {
261
+ if (!existsSync(dir))
262
+ return true;
263
+ if (dir !== conversationDir && !dir.startsWith(`${conversationDir}${sep}`))
264
+ return false;
265
+ const stack = [dir];
266
+ while (stack.length > 0) {
267
+ const current = stack.pop();
268
+ let stats;
269
+ try {
270
+ stats = lstatSync(current);
271
+ }
272
+ catch {
273
+ return false;
274
+ }
275
+ if (stats.isSymbolicLink())
276
+ return false;
277
+ if (!stats.isDirectory())
278
+ continue;
279
+ let entries;
280
+ try {
281
+ entries = readdirSync(current, { withFileTypes: true });
282
+ }
283
+ catch {
284
+ return false;
285
+ }
286
+ for (const entry of entries) {
287
+ if (entry.isSymbolicLink())
288
+ return false;
289
+ stack.push(join(current, entry.name));
290
+ }
291
+ }
292
+ return true;
293
+ }
294
+ function buildRuntimePaths(runtimeWorkspaceRoot, address) {
242
295
  const workspaceRoot = runtimeWorkspaceRoot.replace(/\/+$/, "") || "/";
243
- const conversationPath = posix.join(workspaceRoot, conversationId);
296
+ const conversationPath = posix.join(workspaceRoot, officeDirName(address));
244
297
  return {
245
298
  workspaceRoot,
246
299
  conversationPath,
@@ -291,8 +344,8 @@ export function resolveTriggerAttribution(message) {
291
344
  return `@${message.userName}`;
292
345
  return undefined;
293
346
  }
294
- function buildSystemPrompt(workspacePath, conversationId, conversationKind, currentUserId, memory, sandboxConfig, platform, skills) {
295
- const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, conversationId);
347
+ function buildSystemPrompt(workspacePath, address, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, projection) {
348
+ const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, address);
296
349
  const sandboxType = sandboxConfig.type;
297
350
  const isContainerLike = sandboxType === "container" || sandboxType === "image";
298
351
  const isFirecracker = sandboxType === "firecracker";
@@ -310,6 +363,20 @@ function buildSystemPrompt(workspacePath, conversationId, conversationKind, curr
310
363
  // system prompt stays byte-stable across a conversation's turns and keeps the
311
364
  // provider prompt cache warm (a changing system prefix invalidates the cache
312
365
  // for the whole request, including the far larger conversation history).
366
+ const workspaceLayout = projection.layout === "full"
367
+ ? `${workspaceRoot}/ contains the complete trusted workspace.`
368
+ : projection.layout === "shared-support"
369
+ ? `${workspaceRoot}/ contains shared MEMORY.md, skills/, events/, and this conversation's directory.`
370
+ : `${conversationPath}/ is the only conversation workspace mounted; global memory, skills, and events are not available.`;
371
+ const skillStorageGuidance = projection.doorPolicy === "trusted"
372
+ ? `Store shared skills in \`${workspaceRoot}/skills/<name>/\` or conversation-specific skills in \`${conversationPath}/skills/<name>/\`.`
373
+ : `Store skills in \`${conversationPath}/skills/<name>/\`; this office cannot access workspace-global skills.`;
374
+ const memoryGuidance = projection.doorPolicy === "trusted"
375
+ ? `Write important shared knowledge to \`${workspaceRoot}/MEMORY.md\` and conversation-specific knowledge to \`${conversationPath}/MEMORY.md\`.`
376
+ : `Write durable knowledge only to \`${conversationPath}/MEMORY.md\`; this office cannot access workspace-global memory.`;
377
+ const systemLogPath = projection.layout === "conversation"
378
+ ? `${conversationPath}/SYSTEM.md`
379
+ : `${workspaceRoot}/SYSTEM.md`;
313
380
  const slackBlockKitInstructions = platform.name === "slack"
314
381
  ? `
315
382
  ## Slack Rendering
@@ -344,10 +411,13 @@ ${envDescription}
344
411
  - Do not use host-only paths unless you are running in host mode and verified they exist.
345
412
 
346
413
  ## Workspace Layout
347
- ${workspaceRoot}/
414
+ ${workspaceLayout}
415
+ ${projection.layout === "conversation"
416
+ ? `${conversationPath}/ # This conversation`
417
+ : `${workspaceRoot}/
348
418
  ├── MEMORY.md # Global memory (all conversations)
349
419
  ├── skills/ # Global CLI tools you create
350
- └── ${conversationId}/ # This conversation
420
+ └── ${officeDirName(address)}/ # This conversation`}
351
421
  ├── MEMORY.md # Conversation-specific memory
352
422
  ├── log.jsonl # Human-readable message history (no tool results)
353
423
  ├── sessions/ # Structured session history used for context reconstruction
@@ -362,7 +432,7 @@ ${workspaceRoot}/
362
432
  You can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).
363
433
 
364
434
  ### Creating Skills
365
- Store in \`${workspaceRoot}/skills/<name>/\` (global) or \`${conversationPath}/skills/<name>/\` (conversation-specific).
435
+ ${skillStorageGuidance}
366
436
  Each skill directory needs a \`SKILL.md\` with YAML frontmatter:
367
437
 
368
438
  \`\`\`markdown
@@ -394,16 +464,14 @@ When events trigger, messages are prefixed like \`[EVENT:filename:type:time]\`.
394
464
  For periodic events where there's nothing to report, respond with exactly \`[SILENT]\`. Debounce external triggers; prefer one summarized event over many.
395
465
 
396
466
  ## Memory
397
- Write to MEMORY.md files to persist context across conversations.
398
- - Global (${workspaceRoot}/MEMORY.md): skills, preferences, project info
399
- - Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work
400
- Update when you learn something important or when asked to remember something.
467
+ ${memoryGuidance}
468
+ Update it when you learn something important or when asked to remember something.
401
469
 
402
470
  ### Current Memory
403
471
  ${memory}
404
472
 
405
473
  ## System Configuration Log
406
- Maintain ${workspaceRoot}/SYSTEM.md to log all environment modifications:
474
+ Maintain ${systemLogPath} to log all environment modifications:
407
475
  - Installed packages (apt install, npm install, uv pip install)
408
476
  - Environment variables set
409
477
  - Config files modified (~/.gitconfig, cron jobs, etc.)
@@ -920,7 +988,7 @@ function buildExtensionHostServices(params) {
920
988
  };
921
989
  }
922
990
  async function createConfiguredAgentSession(params) {
923
- const { conversationId, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, } = params;
991
+ const { address, conversationId, conversationDir, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, } = params;
924
992
  // Host-only dirs under the state dir: extension code runs in the mikan
925
993
  // process, so it must never load from workspace paths — those are mounted
926
994
  // into sandbox containers and agent-writable (sandbox escape otherwise).
@@ -937,9 +1005,9 @@ async function createConfiguredAgentSession(params) {
937
1005
  // they always agree on which profiles are launchable.
938
1006
  let runnableSubagentProfiles = loadedProfiles.profiles;
939
1007
  const resolvedPackages = resolveConversationPackages({
940
- conversationId,
1008
+ address,
941
1009
  stateDir: effectiveStateDir(),
942
- conversationDir: join(workspaceDir, conversationId),
1010
+ conversationDir,
943
1011
  });
944
1012
  for (const error of resolvedPackages.errors) {
945
1013
  log.logWarning(`Package unavailable: ${error.source}`, error.message);
@@ -947,7 +1015,7 @@ async function createConfiguredAgentSession(params) {
947
1015
  const extensionsResult = await loadExtensions({
948
1016
  dirs: resolvedPackages.extensionDirs,
949
1017
  roots: resolvedPackages.extensionRoots,
950
- context: { conversationId, workspaceDir, model, thinkingLevel },
1018
+ context: { address, conversationId, workspaceDir, model, thinkingLevel },
951
1019
  services: buildExtensionHostServices({
952
1020
  workspaceDir,
953
1021
  vaultManager,
@@ -1032,18 +1100,18 @@ async function prepareRunContext(params) {
1032
1100
  await mkdir(join(conversationDir, "scratch"), { recursive: true });
1033
1101
  if (executionResolver) {
1034
1102
  await resolveExecutorForRun({
1035
- platform: platform.name,
1103
+ address: message.address,
1036
1104
  userId: message.userId,
1037
- conversationId,
1038
1105
  trustModel: platform.trustModel,
1039
1106
  });
1040
1107
  pathContext = getPathContext();
1041
1108
  }
1042
1109
  reloadSessionMessages(session, conversationId);
1043
- const memory = await getMemory(conversationDir);
1044
- const skills = mergeExtensionSkills(loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot), params.extensionSkills ?? []);
1110
+ const projection = resolveWorkspaceProjection(join(conversationDir, ".."), message.address);
1111
+ const memory = await getMemory(projection);
1112
+ const skills = mergeExtensionSkills(loadMikanSkills(message.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection), params.extensionSkills ?? []);
1045
1113
  const triggerAttribution = resolveTriggerAttribution(message);
1046
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills);
1114
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, message.address, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, projection);
1047
1115
  session.agent.state.systemPrompt = systemPrompt;
1048
1116
  // Cache diagnosis: a byte-stable system prompt is the precondition for
1049
1117
  // provider prompt caching. If this hash changes between turns of one
@@ -1459,8 +1527,10 @@ function attachSessionEventHandlers(params) {
1459
1527
  */
1460
1528
  export async function createRunner(options) {
1461
1529
  const { sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, resourceController, sessionView, platformNotifier, platformReactor, platformUploader, platformBlockKit, platformToolPackFactories, } = options;
1462
- const agentConfig = resolveConversationSettings(conversationDir);
1530
+ const agentConfig = resolveConversationSettings({ address: options.address, conversationDir });
1463
1531
  const workspaceBase = join(conversationDir, "..");
1532
+ const projection = resolveWorkspaceProjection(workspaceBase, options.address);
1533
+ assertSandboxSupportsWorkspacePolicy(sandboxConfig, projection.doorPolicy);
1464
1534
  const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, workspaceBase);
1465
1535
  let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);
1466
1536
  const modelRegistry = options.models ?? MikanModels.create();
@@ -1474,8 +1544,8 @@ export async function createRunner(options) {
1474
1544
  getApiKey: () => modelRegistry.getApiKeyForProvider(model.provider),
1475
1545
  });
1476
1546
  // Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
1477
- const memory = await getMemory(conversationDir);
1478
- const skills = loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot);
1547
+ const memory = await getMemory(projection);
1548
+ const skills = loadMikanSkills(options.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection);
1479
1549
  const emptyPlatform = {
1480
1550
  name: "chat",
1481
1551
  formattingGuide: "",
@@ -1483,7 +1553,7 @@ export async function createRunner(options) {
1483
1553
  users: [],
1484
1554
  trustModel: "membership",
1485
1555
  };
1486
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills);
1556
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, options.address, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills, projection);
1487
1557
  // Create session manager and settings manager. Top-level/private sessions
1488
1558
  // use the conversation's current pointer; scoped sessions use fixed files.
1489
1559
  // Platform-specific scope behavior is resolved before runner creation.
@@ -1497,7 +1567,9 @@ export async function createRunner(options) {
1497
1567
  const sessionUuid = extractSessionUuid(contextFile);
1498
1568
  const chatSessionManager = new ChatHistorySync();
1499
1569
  const { session, extensionSkills, extensionRegistry, disposeExtensions } = await createConfiguredAgentSession({
1570
+ address: options.address,
1500
1571
  conversationId,
1572
+ conversationDir,
1501
1573
  workspaceDir,
1502
1574
  systemPrompt,
1503
1575
  model,
@@ -1671,7 +1743,7 @@ export async function createRunner(options) {
1671
1743
  });
1672
1744
  pathContext = prepared.pathContext;
1673
1745
  try {
1674
- const conversationMemoryPath = posix.join(prepared.pathContext.runtimeWorkspaceRoot, conversationId, "MEMORY.md");
1746
+ const conversationMemoryPath = posix.join(prepared.pathContext.runtimeWorkspaceRoot, officeDirName(message.address), "MEMORY.md");
1675
1747
  const dreamTools = sessionDreamTools(session.agent.state.tools, conversationMemoryPath);
1676
1748
  const outcome = await session.prompt(sessionDreamPrompt(conversationMemoryPath, dreamTools.map((tool) => tool.name)), {
1677
1749
  origin: {