@geminixiang/mikan 1.0.0-beta.37 → 1.0.0-beta.39

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 (201) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/adapter.js +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +3 -6
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +16 -39
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/github/bot.d.ts +2 -0
  10. package/dist/adapters/github/bot.d.ts.map +1 -1
  11. package/dist/adapters/github/bot.js +15 -11
  12. package/dist/adapters/github/bot.js.map +1 -1
  13. package/dist/adapters/github/github-ops.d.ts +1 -1
  14. package/dist/adapters/github/github-ops.d.ts.map +1 -1
  15. package/dist/adapters/github/github-ops.js +2 -1
  16. package/dist/adapters/github/github-ops.js.map +1 -1
  17. package/dist/adapters/github/repo.d.ts +2 -1
  18. package/dist/adapters/github/repo.d.ts.map +1 -1
  19. package/dist/adapters/github/repo.js +2 -3
  20. package/dist/adapters/github/repo.js.map +1 -1
  21. package/dist/adapters/github/types.d.ts +1 -1
  22. package/dist/adapters/github/types.d.ts.map +1 -1
  23. package/dist/adapters/github/types.js.map +1 -1
  24. package/dist/adapters/intake.js +1 -1
  25. package/dist/adapters/intake.js.map +1 -1
  26. package/dist/adapters/shared.d.ts +17 -5
  27. package/dist/adapters/shared.d.ts.map +1 -1
  28. package/dist/adapters/shared.js +44 -8
  29. package/dist/adapters/shared.js.map +1 -1
  30. package/dist/adapters/slack/blocks.d.ts +14 -0
  31. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  32. package/dist/adapters/slack/blocks.js +34 -0
  33. package/dist/adapters/slack/blocks.js.map +1 -1
  34. package/dist/adapters/slack/bot.d.ts +19 -6
  35. package/dist/adapters/slack/bot.d.ts.map +1 -1
  36. package/dist/adapters/slack/bot.js +88 -21
  37. package/dist/adapters/slack/bot.js.map +1 -1
  38. package/dist/adapters/slack/types.d.ts +1 -1
  39. package/dist/adapters/slack/types.js.map +1 -1
  40. package/dist/adapters/telegram/bot.d.ts +5 -6
  41. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  42. package/dist/adapters/telegram/bot.js +29 -51
  43. package/dist/adapters/telegram/bot.js.map +1 -1
  44. package/dist/adapters/types.d.ts +19 -1
  45. package/dist/adapters/types.d.ts.map +1 -1
  46. package/dist/adapters/types.js.map +1 -1
  47. package/dist/agent.d.ts +0 -1
  48. package/dist/agent.d.ts.map +1 -1
  49. package/dist/agent.js +79 -101
  50. package/dist/agent.js.map +1 -1
  51. package/dist/cli/ext-dev.d.ts.map +1 -1
  52. package/dist/cli/ext-dev.js +8 -8
  53. package/dist/cli/ext-dev.js.map +1 -1
  54. package/dist/cli/ext.js +3 -3
  55. package/dist/cli/ext.js.map +1 -1
  56. package/dist/cli/office.js +2 -2
  57. package/dist/cli/office.js.map +1 -1
  58. package/dist/commands/auto-reply.d.ts.map +1 -1
  59. package/dist/commands/auto-reply.js +1 -2
  60. package/dist/commands/auto-reply.js.map +1 -1
  61. package/dist/commands/login.js +1 -1
  62. package/dist/commands/login.js.map +1 -1
  63. package/dist/commands/manifest.d.ts.map +1 -1
  64. package/dist/commands/manifest.js +3 -1
  65. package/dist/commands/manifest.js.map +1 -1
  66. package/dist/commands/model.d.ts.map +1 -1
  67. package/dist/commands/model.js +3 -7
  68. package/dist/commands/model.js.map +1 -1
  69. package/dist/commands/sandbox.d.ts.map +1 -1
  70. package/dist/commands/sandbox.js +7 -7
  71. package/dist/commands/sandbox.js.map +1 -1
  72. package/dist/commands/session-view.d.ts.map +1 -1
  73. package/dist/commands/session-view.js +1 -2
  74. package/dist/commands/session-view.js.map +1 -1
  75. package/dist/commands/types.d.ts +2 -1
  76. package/dist/commands/types.d.ts.map +1 -1
  77. package/dist/commands/types.js.map +1 -1
  78. package/dist/config.d.ts +10 -18
  79. package/dist/config.d.ts.map +1 -1
  80. package/dist/config.js +16 -15
  81. package/dist/config.js.map +1 -1
  82. package/dist/execution-resolver.d.ts +4 -6
  83. package/dist/execution-resolver.d.ts.map +1 -1
  84. package/dist/execution-resolver.js +10 -19
  85. package/dist/execution-resolver.js.map +1 -1
  86. package/dist/harness/extensions/loader.js +1 -1
  87. package/dist/harness/extensions/loader.js.map +1 -1
  88. package/dist/harness/skills.d.ts +8 -0
  89. package/dist/harness/skills.d.ts.map +1 -1
  90. package/dist/harness/skills.js +46 -4
  91. package/dist/harness/skills.js.map +1 -1
  92. package/dist/harness/types.d.ts +2 -0
  93. package/dist/harness/types.d.ts.map +1 -1
  94. package/dist/harness/types.js.map +1 -1
  95. package/dist/index.d.ts +2 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +1 -0
  98. package/dist/index.js.map +1 -1
  99. package/dist/main.js +16 -16
  100. package/dist/main.js.map +1 -1
  101. package/dist/{office-address.d.ts → office/address.d.ts} +13 -14
  102. package/dist/office/address.d.ts.map +1 -0
  103. package/dist/{office-address.js → office/address.js} +13 -18
  104. package/dist/office/address.js.map +1 -0
  105. package/dist/office/index.d.ts +11 -0
  106. package/dist/office/index.d.ts.map +1 -0
  107. package/dist/office/index.js +11 -0
  108. package/dist/office/index.js.map +1 -0
  109. package/dist/office/layout.d.ts +20 -0
  110. package/dist/office/layout.d.ts.map +1 -0
  111. package/dist/office/layout.js +88 -0
  112. package/dist/office/layout.js.map +1 -0
  113. package/dist/{office-migration.d.ts → office/migration.d.ts} +3 -3
  114. package/dist/office/migration.d.ts.map +1 -0
  115. package/dist/{office-migration.js → office/migration.js} +8 -12
  116. package/dist/office/migration.js.map +1 -0
  117. package/dist/{office-registry.d.ts → office/registry.d.ts} +18 -24
  118. package/dist/office/registry.d.ts.map +1 -0
  119. package/dist/{office-registry.js → office/registry.js} +22 -83
  120. package/dist/office/registry.js.map +1 -0
  121. package/dist/office/types.d.ts +71 -0
  122. package/dist/office/types.d.ts.map +1 -0
  123. package/dist/office/types.js +2 -0
  124. package/dist/office/types.js.map +1 -0
  125. package/dist/packages/admin.js +5 -5
  126. package/dist/packages/admin.js.map +1 -1
  127. package/dist/packages/inspect.d.ts.map +1 -1
  128. package/dist/packages/inspect.js +3 -8
  129. package/dist/packages/inspect.js.map +1 -1
  130. package/dist/packages/materialize.js +1 -1
  131. package/dist/packages/materialize.js.map +1 -1
  132. package/dist/packages/resolve.js +5 -8
  133. package/dist/packages/resolve.js.map +1 -1
  134. package/dist/packages/types.d.ts +2 -5
  135. package/dist/packages/types.d.ts.map +1 -1
  136. package/dist/packages/types.js.map +1 -1
  137. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  138. package/dist/runtime/conversation-runtime.js +13 -15
  139. package/dist/runtime/conversation-runtime.js.map +1 -1
  140. package/dist/runtime/session-lifecycle.js +1 -1
  141. package/dist/runtime/session-lifecycle.js.map +1 -1
  142. package/dist/runtime/types.d.ts +3 -2
  143. package/dist/runtime/types.d.ts.map +1 -1
  144. package/dist/runtime/types.js.map +1 -1
  145. package/dist/sandbox/identity.js +1 -1
  146. package/dist/sandbox/identity.js.map +1 -1
  147. package/dist/settings-mutation.d.ts +3 -2
  148. package/dist/settings-mutation.d.ts.map +1 -1
  149. package/dist/settings-mutation.js +6 -7
  150. package/dist/settings-mutation.js.map +1 -1
  151. package/dist/tools/generate-image.d.ts +8 -2
  152. package/dist/tools/generate-image.d.ts.map +1 -1
  153. package/dist/tools/generate-image.js +5 -3
  154. package/dist/tools/generate-image.js.map +1 -1
  155. package/dist/tools/index.d.ts +5 -0
  156. package/dist/tools/index.d.ts.map +1 -1
  157. package/dist/tools/index.js +3 -5
  158. package/dist/tools/index.js.map +1 -1
  159. package/dist/trigger.d.ts +1 -1
  160. package/dist/trigger.d.ts.map +1 -1
  161. package/dist/trigger.js +5 -10
  162. package/dist/trigger.js.map +1 -1
  163. package/dist/types.d.ts +3 -22
  164. package/dist/types.d.ts.map +1 -1
  165. package/dist/types.js.map +1 -1
  166. package/dist/vault/index.js +1 -1
  167. package/dist/vault/index.js.map +1 -1
  168. package/dist/vault/injection.d.ts +3 -1
  169. package/dist/vault/injection.d.ts.map +1 -1
  170. package/dist/vault/injection.js +2 -2
  171. package/dist/vault/injection.js.map +1 -1
  172. package/dist/web/admin/portal.d.ts.map +1 -1
  173. package/dist/web/admin/portal.js +67 -80
  174. package/dist/web/admin/portal.js.map +1 -1
  175. package/dist/web/admin/types.d.ts +2 -1
  176. package/dist/web/admin/types.d.ts.map +1 -1
  177. package/dist/web/admin/types.js.map +1 -1
  178. package/dist/web/server.d.ts +1 -1
  179. package/dist/web/server.d.ts.map +1 -1
  180. package/dist/web/server.js +3 -3
  181. package/dist/web/server.js.map +1 -1
  182. package/dist/web/session-view/portal.js +1 -1
  183. package/dist/web/session-view/portal.js.map +1 -1
  184. package/dist/workspace-projection/index.d.ts +2 -4
  185. package/dist/workspace-projection/index.d.ts.map +1 -1
  186. package/dist/workspace-projection/index.js +21 -56
  187. package/dist/workspace-projection/index.js.map +1 -1
  188. package/dist/workspace-projection/types.d.ts +0 -2
  189. package/dist/workspace-projection/types.d.ts.map +1 -1
  190. package/dist/workspace-projection/types.js.map +1 -1
  191. package/package.json +41 -16
  192. package/dist/office-address.d.ts.map +0 -1
  193. package/dist/office-address.js.map +0 -1
  194. package/dist/office-migration.d.ts.map +0 -1
  195. package/dist/office-migration.js.map +0 -1
  196. package/dist/office-registry.d.ts.map +0 -1
  197. package/dist/office-registry.js.map +0 -1
  198. package/dist/store.d.ts +0 -51
  199. package/dist/store.d.ts.map +0 -1
  200. package/dist/store.js +0 -185
  201. package/dist/store.js.map +0 -1
package/dist/agent.js CHANGED
@@ -2,19 +2,17 @@
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";
6
5
  import { DEFAULT_EVENT_BUDGET, formatSkillsForPrompt, loadExtensions, loadSkillsFromDir, MikanAgentSession, MikanModels, parseCommandInput, } from "./harness/index.js";
7
6
  import { runSubagent } from "./harness/subagent-runner.js";
8
7
  import { loadSubagentProfiles } from "./harness/subagent-profiles.js";
9
8
  import { mergeSubagentProgress, parseSubagentProgressSnapshot, renderSubagentDashboard, settleSubagentProgress, } from "./subagent-progress.js";
10
9
  import { createHash } from "crypto";
11
- import { existsSync, lstatSync, readdirSync } from "fs";
10
+ import { existsSync, lstatSync } from "fs";
12
11
  import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from "fs/promises";
13
12
  import { tmpdir } from "os";
14
13
  import { basename, isAbsolute, join, posix, relative, resolve, sep } from "path";
15
14
  import { resolveConversationSettings } from "./config.js";
16
15
  import { resolveWorkspaceProjection } from "./workspace-projection/index.js";
17
- import { effectiveStateDir } from "./cli/arg-grammar.js";
18
16
  import { packageSkillRuntimeDir, resolveConversationPackages } from "./packages/index.js";
19
17
  import { ActorExecutionResolver } from "./execution-resolver.js";
20
18
  import * as log from "./log.js";
@@ -191,12 +189,11 @@ function isRegularFile(path) {
191
189
  return false;
192
190
  }
193
191
  }
194
- function loadMikanSkills(address, conversationDir, workspacePath, projection) {
192
+ function loadMikanSkills(office, workspacePath, projection) {
195
193
  const skillMap = new Map();
196
- // conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)
197
- // hostWorkspacePath is the parent directory on host
198
- // workspacePath is the container path (e.g., /workspace)
199
- const hostWorkspacePath = join(conversationDir, "..");
194
+ // workspacePath is the runtime-side root (e.g. /workspace); host paths under
195
+ // the workspace root translate onto it for prompt references.
196
+ const hostWorkspacePath = office.workspace.root;
200
197
  // Helper to translate host paths to container paths
201
198
  const translatePath = (hostPath) => {
202
199
  if (hostPath.startsWith(hostWorkspacePath)) {
@@ -214,11 +211,7 @@ function loadMikanSkills(address, conversationDir, workspacePath, projection) {
214
211
  // so the agent can read the files — including any scripts or templates the
215
212
  // skill ships alongside its SKILL.md, which inlining could never carry.
216
213
  const mounted = workspacePath !== hostWorkspacePath;
217
- for (const { slug, dir } of resolveConversationPackages({
218
- address,
219
- stateDir: effectiveStateDir(),
220
- conversationDir,
221
- }).skillDirs) {
214
+ for (const { slug, dir } of resolveConversationPackages({ office }).skillDirs) {
222
215
  const runtimeDir = packageSkillRuntimeDir(slug);
223
216
  for (const skill of loadSkillsFromDir({ dir, source: `package:${slug}` }).skills) {
224
217
  // In host mode there is no mount, so the host path is already the path
@@ -242,58 +235,32 @@ function loadMikanSkills(address, conversationDir, workspacePath, projection) {
242
235
  }
243
236
  // Load conversation-specific skills (override workspace skills on collision).
244
237
  // Host prompt construction must never follow an agent-created symlink out of
245
- // the conversation office.
238
+ // the conversation office, so this load rejects symlinks per entry — on
239
+ // exactly the paths it reads. Vendored node_modules and dot directories are
240
+ // never read and never disqualify anything.
246
241
  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);
257
- }
258
- return Array.from(skillMap.values());
259
- }
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())
242
+ const conversationSkills = loadSkillsFromDir({
243
+ dir: conversationSkillsDir,
244
+ source: "channel",
245
+ rejectSymlinks: true,
246
+ });
247
+ const skippedSkillLinks = [];
248
+ for (const diagnostic of conversationSkills.diagnostics) {
249
+ if (diagnostic.code !== "symlink")
278
250
  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
- }
251
+ log.logWarning("Skipping conversation skill entry (symlink)", diagnostic.path);
252
+ skippedSkillLinks.push(translatePath(diagnostic.path));
291
253
  }
292
- return true;
254
+ for (const skill of conversationSkills.skills) {
255
+ skill.filePath = translatePath(skill.filePath);
256
+ skill.baseDir = translatePath(skill.baseDir);
257
+ skillMap.set(skill.name, skill);
258
+ }
259
+ return { skills: Array.from(skillMap.values()), skippedSkillLinks };
293
260
  }
294
- function buildRuntimePaths(runtimeWorkspaceRoot, address) {
261
+ function buildRuntimePaths(runtimeWorkspaceRoot, office) {
295
262
  const workspaceRoot = runtimeWorkspaceRoot.replace(/\/+$/, "") || "/";
296
- const conversationPath = posix.join(workspaceRoot, officeDirName(address));
263
+ const conversationPath = posix.join(workspaceRoot, office.key);
297
264
  return {
298
265
  workspaceRoot,
299
266
  conversationPath,
@@ -344,8 +311,8 @@ export function resolveTriggerAttribution(message) {
344
311
  return `@${message.userName}`;
345
312
  return undefined;
346
313
  }
347
- function buildSystemPrompt(workspacePath, address, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, projection) {
348
- const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, address);
314
+ function buildSystemPrompt(workspacePath, office, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, projection, skippedSkillLinks = []) {
315
+ const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, office);
349
316
  const sandboxType = sandboxConfig.type;
350
317
  const isContainerLike = sandboxType === "container" || sandboxType === "image";
351
318
  const isFirecracker = sandboxType === "firecracker";
@@ -403,7 +370,7 @@ Channels: ${channelMappings}
403
370
 
404
371
  Users: ${userMappings}
405
372
 
406
- When mentioning users, use <@username> format (e.g., <@mario>).
373
+ When mentioning users, write <@userName> using the exact userName from the Users table above (e.g., <@mario>). Never invent handles from other platforms (GitHub, email); the platform adapter converts <@userName> to the platform's native mention form.
407
374
 
408
375
  ## Environment
409
376
  ${envDescription}
@@ -417,7 +384,7 @@ ${projection.layout === "conversation"
417
384
  : `${workspaceRoot}/
418
385
  ├── MEMORY.md # Global memory (all conversations)
419
386
  ├── skills/ # Global CLI tools you create
420
- └── ${officeDirName(address)}/ # This conversation`}
387
+ └── ${office.key}/ # This conversation`}
421
388
  ├── MEMORY.md # Conversation-specific memory
422
389
  ├── log.jsonl # Human-readable message history (no tool results)
423
390
  ├── sessions/ # Structured session history used for context reconstruction
@@ -450,7 +417,9 @@ Scripts are in: {baseDir}/
450
417
  \`name\` and \`description\` are required. Use \`{baseDir}\` as placeholder for the skill's directory path.
451
418
 
452
419
  ### Available Skills
453
- ${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}
420
+ ${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}${skippedSkillLinks.length > 0
421
+ ? `\n\nNote: these skill entries were skipped because they are symlinks, which the host never follows when reading this conversation's skills: ${skippedSkillLinks.join(", ")}. Replace each with a real file or directory (for example \`cp -rL\`) to load it.`
422
+ : ""}
454
423
 
455
424
  ## Events
456
425
  Use the \`event\` tool to schedule immediate, one-shot, or periodic follow-ups. It writes to the host-side mikan control plane and fills routing fields for the current conversation automatically.
@@ -912,9 +881,9 @@ function buildThreadSessionName(message) {
912
881
  const userLabel = message?.userName || message?.user || "unknown";
913
882
  return `[${userLabel}]: ${text}`;
914
883
  }
915
- function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath) {
884
+ function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspace) {
916
885
  const executionResolver = vaultManager && sandboxConfig.type !== "host"
917
- ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath)
886
+ ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspace)
918
887
  : undefined;
919
888
  // activeExecutor is replaced at the start of each run() call when executionResolver
920
889
  // is present, so the stable `executor` wrapper always delegates to the latest resolved value.
@@ -947,7 +916,7 @@ function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner,
947
916
  return {
948
917
  executionResolver,
949
918
  executor,
950
- getPathContext: () => executor.getPathContext(hostWorkspacePath),
919
+ getPathContext: () => executor.getPathContext(workspace.root),
951
920
  async resolveExecutorForRun(context) {
952
921
  if (!executionResolver)
953
922
  return;
@@ -962,10 +931,10 @@ function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner,
962
931
  * notify posts through the platform bots when main.ts provides a notifier.
963
932
  */
964
933
  function buildExtensionHostServices(params) {
965
- const { workspaceDir, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, runSubagentService, } = params;
966
- const eventStore = HostEventStore.fromWorkspaceDir(workspaceDir);
934
+ const { workspace, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, runSubagentService, } = params;
935
+ const eventStore = HostEventStore.fromWorkspaceDir(workspace.root);
967
936
  return {
968
- stateDir: effectiveStateDir(),
937
+ stateDir: workspace.stateDir,
969
938
  scheduleStore: {
970
939
  write: async (filename, payload) => {
971
940
  await eventStore.write(filename, payload);
@@ -988,7 +957,10 @@ function buildExtensionHostServices(params) {
988
957
  };
989
958
  }
990
959
  async function createConfiguredAgentSession(params) {
991
- const { address, conversationId, conversationDir, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, } = params;
960
+ const { office, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, } = params;
961
+ const { address } = office;
962
+ const conversationId = address.conversationId;
963
+ const workspaceDir = office.workspace.root;
992
964
  // Host-only dirs under the state dir: extension code runs in the mikan
993
965
  // process, so it must never load from workspace paths — those are mounted
994
966
  // into sandbox containers and agent-writable (sandbox escape otherwise).
@@ -1004,11 +976,7 @@ async function createConfiguredAgentSession(params) {
1004
976
  // contributed theirs. Both subagent entry points read this at call time, so
1005
977
  // they always agree on which profiles are launchable.
1006
978
  let runnableSubagentProfiles = loadedProfiles.profiles;
1007
- const resolvedPackages = resolveConversationPackages({
1008
- address,
1009
- stateDir: effectiveStateDir(),
1010
- conversationDir,
1011
- });
979
+ const resolvedPackages = resolveConversationPackages({ office });
1012
980
  for (const error of resolvedPackages.errors) {
1013
981
  log.logWarning(`Package unavailable: ${error.source}`, error.message);
1014
982
  }
@@ -1017,7 +985,7 @@ async function createConfiguredAgentSession(params) {
1017
985
  roots: resolvedPackages.extensionRoots,
1018
986
  context: { address, conversationId, workspaceDir, model, thinkingLevel },
1019
987
  services: buildExtensionHostServices({
1020
- workspaceDir,
988
+ workspace: office.workspace,
1021
989
  vaultManager,
1022
990
  platformNotifier,
1023
991
  platformReactor,
@@ -1094,10 +1062,11 @@ function reloadSessionMessages(session, conversationId) {
1094
1062
  }
1095
1063
  }
1096
1064
  async function prepareRunContext(params) {
1097
- const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, setReactFunction, bindPlatformToolPacks, } = params;
1065
+ const { message, responder, platform, office, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, setImageUploadFunction, setReactFunction, bindPlatformToolPacks, } = params;
1066
+ const conversationId = office.address.conversationId;
1098
1067
  let pathContext = params.pathContext;
1099
1068
  const sessionConversation = conversationIdOf(message.sessionKey);
1100
- await mkdir(join(conversationDir, "scratch"), { recursive: true });
1069
+ await mkdir(join(office.dir, "scratch"), { recursive: true });
1101
1070
  if (executionResolver) {
1102
1071
  await resolveExecutorForRun({
1103
1072
  address: message.address,
@@ -1107,11 +1076,12 @@ async function prepareRunContext(params) {
1107
1076
  pathContext = getPathContext();
1108
1077
  }
1109
1078
  reloadSessionMessages(session, conversationId);
1110
- const projection = resolveWorkspaceProjection(join(conversationDir, ".."), message.address);
1079
+ const projection = resolveWorkspaceProjection(office);
1111
1080
  const memory = await getMemory(projection);
1112
- const skills = mergeExtensionSkills(loadMikanSkills(message.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection), params.extensionSkills ?? []);
1081
+ const conversationSkillLoad = loadMikanSkills(office, pathContext.runtimeWorkspaceRoot, projection);
1082
+ const skills = mergeExtensionSkills(conversationSkillLoad.skills, params.extensionSkills ?? []);
1113
1083
  const triggerAttribution = resolveTriggerAttribution(message);
1114
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, message.address, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, projection);
1084
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, office, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, projection, conversationSkillLoad.skippedSkillLinks);
1115
1085
  session.agent.state.systemPrompt = systemPrompt;
1116
1086
  // Cache diagnosis: a byte-stable system prompt is the precondition for
1117
1087
  // provider prompt caching. If this hash changes between turns of one
@@ -1133,6 +1103,11 @@ async function prepareRunContext(params) {
1133
1103
  const runtimePath = normalizeAttachRuntimePath(filePath, pathContext.runtimeWorkspaceRoot);
1134
1104
  await withStagedRuntimeFile(executor, runtimePath, (stagedPath) => responder.uploadFile(stagedPath, title));
1135
1105
  });
1106
+ // generate_image writes its file host-side, so it uploads by host path —
1107
+ // no staging through the sandbox, where the file may not be mounted.
1108
+ setImageUploadFunction(async (hostPath, title) => {
1109
+ await responder.uploadFile(hostPath, title);
1110
+ });
1136
1111
  // The react tool is available only when the responder supports reactions
1137
1112
  // (interactive turns on platforms with reaction support); otherwise unset
1138
1113
  // so the tool reports it is unavailable rather than silently no-op'ing.
@@ -1526,26 +1501,32 @@ function attachSessionEventHandlers(params) {
1526
1501
  * This is a stateless factory function.
1527
1502
  */
1528
1503
  export async function createRunner(options) {
1529
- const { sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, resourceController, sessionView, platformNotifier, platformReactor, platformUploader, platformBlockKit, platformToolPackFactories, } = options;
1530
- const agentConfig = resolveConversationSettings({ address: options.address, conversationDir });
1531
- const workspaceBase = join(conversationDir, "..");
1532
- const projection = resolveWorkspaceProjection(workspaceBase, options.address);
1504
+ const { sandboxConfig, sessionKey, office, sessionScope, vaultManager, provisioner, resourceController, sessionView, platformNotifier, platformReactor, platformUploader, platformBlockKit, platformToolPackFactories, } = options;
1505
+ const conversationId = office.address.conversationId;
1506
+ const conversationDir = office.dir;
1507
+ const workspaceDir = office.workspace.root;
1508
+ const agentConfig = resolveConversationSettings(office);
1509
+ const projection = resolveWorkspaceProjection(office);
1533
1510
  assertSandboxSupportsWorkspacePolicy(sandboxConfig, projection.doorPolicy);
1534
- const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, workspaceBase);
1535
- let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);
1511
+ const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, office.workspace);
1512
+ let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceDir);
1536
1513
  const modelRegistry = options.models ?? MikanModels.create();
1537
1514
  if (modelRegistry.getError()) {
1538
1515
  log.logWarning("models.json load error", modelRegistry.getError());
1539
1516
  }
1540
1517
  const model = modelRegistry.resolve(agentConfig.provider, agentConfig.model);
1541
1518
  // Create tools (per-runner, with per-runner upload function setter)
1542
- const { tools, setUploadFunction, setReactFunction, bindPlatformToolPacks, setEventContext, setSandboxContext, } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, resourceController: resourceController ?? provisioner }, platformToolPackFactories ?? [], {
1519
+ const { tools, setUploadFunction, setImageUploadFunction, setReactFunction, bindPlatformToolPacks, setEventContext, setSandboxContext, } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, resourceController: resourceController ?? provisioner }, platformToolPackFactories ?? [], {
1543
1520
  model,
1544
1521
  getApiKey: () => modelRegistry.getApiKeyForProvider(model.provider),
1522
+ // The conversation's own office dir: mounted into the sandbox, unlike
1523
+ // the workspace base — generated images must land somewhere the agent
1524
+ // (and a future run) can still reach.
1525
+ outputDir: conversationDir,
1545
1526
  });
1546
1527
  // Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
1547
1528
  const memory = await getMemory(projection);
1548
- const skills = loadMikanSkills(options.address, conversationDir, pathContext.runtimeWorkspaceRoot, projection);
1529
+ const { skills, skippedSkillLinks } = loadMikanSkills(office, pathContext.runtimeWorkspaceRoot, projection);
1549
1530
  const emptyPlatform = {
1550
1531
  name: "chat",
1551
1532
  formattingGuide: "",
@@ -1553,7 +1534,7 @@ export async function createRunner(options) {
1553
1534
  users: [],
1554
1535
  trustModel: "membership",
1555
1536
  };
1556
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, options.address, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills, projection);
1537
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, office, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills, projection, skippedSkillLinks);
1557
1538
  // Create session manager and settings manager. Top-level/private sessions
1558
1539
  // use the conversation's current pointer; scoped sessions use fixed files.
1559
1540
  // Platform-specific scope behavior is resolved before runner creation.
@@ -1567,10 +1548,7 @@ export async function createRunner(options) {
1567
1548
  const sessionUuid = extractSessionUuid(contextFile);
1568
1549
  const chatSessionManager = new ChatHistorySync();
1569
1550
  const { session, extensionSkills, extensionRegistry, disposeExtensions } = await createConfiguredAgentSession({
1570
- address: options.address,
1571
- conversationId,
1572
- conversationDir,
1573
- workspaceDir,
1551
+ office,
1574
1552
  systemPrompt,
1575
1553
  model,
1576
1554
  thinkingLevel: agentConfig.thinkingLevel,
@@ -1600,8 +1578,7 @@ export async function createRunner(options) {
1600
1578
  message,
1601
1579
  responder,
1602
1580
  platform,
1603
- conversationId,
1604
- conversationDir,
1581
+ office,
1605
1582
  sessionUuid,
1606
1583
  runState,
1607
1584
  executor,
@@ -1613,6 +1590,7 @@ export async function createRunner(options) {
1613
1590
  setEventContext,
1614
1591
  setSandboxContext,
1615
1592
  setUploadFunction,
1593
+ setImageUploadFunction,
1616
1594
  setReactFunction,
1617
1595
  bindPlatformToolPacks,
1618
1596
  pathContext,
@@ -1724,8 +1702,7 @@ export async function createRunner(options) {
1724
1702
  message,
1725
1703
  responder,
1726
1704
  platform,
1727
- conversationId,
1728
- conversationDir,
1705
+ office,
1729
1706
  sessionUuid,
1730
1707
  runState,
1731
1708
  executor,
@@ -1737,13 +1714,14 @@ export async function createRunner(options) {
1737
1714
  setEventContext,
1738
1715
  setSandboxContext,
1739
1716
  setUploadFunction,
1717
+ setImageUploadFunction,
1740
1718
  setReactFunction,
1741
1719
  bindPlatformToolPacks,
1742
1720
  pathContext,
1743
1721
  });
1744
1722
  pathContext = prepared.pathContext;
1745
1723
  try {
1746
- const conversationMemoryPath = posix.join(prepared.pathContext.runtimeWorkspaceRoot, officeDirName(message.address), "MEMORY.md");
1724
+ const conversationMemoryPath = posix.join(prepared.pathContext.runtimeWorkspaceRoot, office.key, "MEMORY.md");
1747
1725
  const dreamTools = sessionDreamTools(session.agent.state.tools, conversationMemoryPath);
1748
1726
  const outcome = await session.prompt(sessionDreamPrompt(conversationMemoryPath, dreamTools.map((tool) => tool.name)), {
1749
1727
  origin: {