@geminixiang/mikan 1.0.0-beta.84 → 1.0.0-beta.86

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 (289) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +10 -10
  3. package/dist/adapters/commands/session-view.js +1 -1
  4. package/dist/adapters/commands/session-view.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts.map +1 -1
  6. package/dist/adapters/discord/bot.js +1 -8
  7. package/dist/adapters/discord/bot.js.map +1 -1
  8. package/dist/adapters/github/activity.d.ts +3 -0
  9. package/dist/adapters/github/activity.d.ts.map +1 -0
  10. package/dist/adapters/github/activity.js +154 -0
  11. package/dist/adapters/github/activity.js.map +1 -0
  12. package/dist/adapters/github/bot.d.ts +10 -20
  13. package/dist/adapters/github/bot.d.ts.map +1 -1
  14. package/dist/adapters/github/bot.js +119 -342
  15. package/dist/adapters/github/bot.js.map +1 -1
  16. package/dist/adapters/github/client.d.ts +9 -19
  17. package/dist/adapters/github/client.d.ts.map +1 -1
  18. package/dist/adapters/github/client.js +28 -104
  19. package/dist/adapters/github/client.js.map +1 -1
  20. package/dist/adapters/github/context.d.ts.map +1 -1
  21. package/dist/adapters/github/context.js +29 -20
  22. package/dist/adapters/github/context.js.map +1 -1
  23. package/dist/adapters/github/github-ops.d.ts +2 -10
  24. package/dist/adapters/github/github-ops.d.ts.map +1 -1
  25. package/dist/adapters/github/github-ops.js +28 -91
  26. package/dist/adapters/github/github-ops.js.map +1 -1
  27. package/dist/adapters/github/policy.d.ts +19 -0
  28. package/dist/adapters/github/policy.d.ts.map +1 -0
  29. package/dist/adapters/github/policy.js +79 -0
  30. package/dist/adapters/github/policy.js.map +1 -0
  31. package/dist/adapters/github/tool-pack.d.ts +2 -2
  32. package/dist/adapters/github/tool-pack.d.ts.map +1 -1
  33. package/dist/adapters/github/tool-pack.js +4 -9
  34. package/dist/adapters/github/tool-pack.js.map +1 -1
  35. package/dist/adapters/github/tools/checks.d.ts.map +1 -1
  36. package/dist/adapters/github/tools/checks.js +6 -8
  37. package/dist/adapters/github/tools/checks.js.map +1 -1
  38. package/dist/adapters/github/tools/pr.d.ts.map +1 -1
  39. package/dist/adapters/github/tools/pr.js +7 -9
  40. package/dist/adapters/github/tools/pr.js.map +1 -1
  41. package/dist/adapters/github/types.d.ts +66 -67
  42. package/dist/adapters/github/types.d.ts.map +1 -1
  43. package/dist/adapters/github/types.js.map +1 -1
  44. package/dist/adapters/github/webhook.d.ts +2 -1
  45. package/dist/adapters/github/webhook.d.ts.map +1 -1
  46. package/dist/adapters/github/webhook.js +46 -4
  47. package/dist/adapters/github/webhook.js.map +1 -1
  48. package/dist/adapters/progressive-renderer.d.ts.map +1 -1
  49. package/dist/adapters/progressive-renderer.js +3 -0
  50. package/dist/adapters/progressive-renderer.js.map +1 -1
  51. package/dist/adapters/shared.d.ts +3 -1
  52. package/dist/adapters/shared.d.ts.map +1 -1
  53. package/dist/adapters/shared.js +45 -23
  54. package/dist/adapters/shared.js.map +1 -1
  55. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  56. package/dist/adapters/slack/blocks.js +65 -2
  57. package/dist/adapters/slack/blocks.js.map +1 -1
  58. package/dist/adapters/slack/bot.d.ts +4 -0
  59. package/dist/adapters/slack/bot.d.ts.map +1 -1
  60. package/dist/adapters/slack/bot.js +84 -36
  61. package/dist/adapters/slack/bot.js.map +1 -1
  62. package/dist/adapters/slack/jev-context.js +2 -2
  63. package/dist/adapters/slack/jev-context.js.map +1 -1
  64. package/dist/adapters/slack/response-lifecycle.js +2 -2
  65. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  66. package/dist/adapters/slack/task-status.d.ts +3 -2
  67. package/dist/adapters/slack/task-status.d.ts.map +1 -1
  68. package/dist/adapters/slack/task-status.js +12 -15
  69. package/dist/adapters/slack/task-status.js.map +1 -1
  70. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  71. package/dist/adapters/telegram/bot.js +1 -8
  72. package/dist/adapters/telegram/bot.js.map +1 -1
  73. package/dist/adapters/web/admin/portal.js +1 -1
  74. package/dist/adapters/web/admin/portal.js.map +1 -1
  75. package/dist/adapters/web/admin/provider-models.d.ts.map +1 -1
  76. package/dist/adapters/web/admin/provider-models.js +10 -3
  77. package/dist/adapters/web/admin/provider-models.js.map +1 -1
  78. package/dist/adapters/web/login/portal.js +6 -1
  79. package/dist/adapters/web/login/portal.js.map +1 -1
  80. package/dist/adapters/web/server.d.ts.map +1 -1
  81. package/dist/adapters/web/server.js +5 -3
  82. package/dist/adapters/web/server.js.map +1 -1
  83. package/dist/adapters/web/session-view/portal.d.ts +2 -1
  84. package/dist/adapters/web/session-view/portal.d.ts.map +1 -1
  85. package/dist/adapters/web/session-view/portal.js +18 -27
  86. package/dist/adapters/web/session-view/portal.js.map +1 -1
  87. package/dist/cli/boot.d.ts.map +1 -1
  88. package/dist/cli/boot.js +4 -6
  89. package/dist/cli/boot.js.map +1 -1
  90. package/dist/cli/migrate.d.ts +3 -0
  91. package/dist/cli/migrate.d.ts.map +1 -0
  92. package/dist/cli/migrate.js +74 -0
  93. package/dist/cli/migrate.js.map +1 -0
  94. package/dist/cli/office.d.ts.map +1 -1
  95. package/dist/cli/office.js +8 -129
  96. package/dist/cli/office.js.map +1 -1
  97. package/dist/cli/onboard.d.ts +0 -1
  98. package/dist/cli/onboard.d.ts.map +1 -1
  99. package/dist/cli/onboard.js +4 -7
  100. package/dist/cli/onboard.js.map +1 -1
  101. package/dist/cli/types.d.ts +2 -3
  102. package/dist/cli/types.d.ts.map +1 -1
  103. package/dist/cli/types.js.map +1 -1
  104. package/dist/env-manifest.d.ts.map +1 -1
  105. package/dist/env-manifest.js +24 -22
  106. package/dist/env-manifest.js.map +1 -1
  107. package/dist/events/index.d.ts +3 -13
  108. package/dist/events/index.d.ts.map +1 -1
  109. package/dist/events/index.js +12 -58
  110. package/dist/events/index.js.map +1 -1
  111. package/dist/events/scheduler.d.ts.map +1 -1
  112. package/dist/events/scheduler.js +3 -0
  113. package/dist/events/scheduler.js.map +1 -1
  114. package/dist/file-guards.d.ts +7 -0
  115. package/dist/file-guards.d.ts.map +1 -1
  116. package/dist/file-guards.js +63 -1
  117. package/dist/file-guards.js.map +1 -1
  118. package/dist/harness/budget-stop.d.ts +3 -0
  119. package/dist/harness/budget-stop.d.ts.map +1 -0
  120. package/dist/harness/budget-stop.js +36 -0
  121. package/dist/harness/budget-stop.js.map +1 -0
  122. package/dist/harness/execution-resolver.d.ts.map +1 -1
  123. package/dist/harness/execution-resolver.js +5 -9
  124. package/dist/harness/execution-resolver.js.map +1 -1
  125. package/dist/harness/mcp.d.ts.map +1 -1
  126. package/dist/harness/mcp.js +75 -31
  127. package/dist/harness/mcp.js.map +1 -1
  128. package/dist/harness/models.js +2 -2
  129. package/dist/harness/models.js.map +1 -1
  130. package/dist/harness/open-connector.d.ts +0 -9
  131. package/dist/harness/open-connector.d.ts.map +1 -1
  132. package/dist/harness/open-connector.js +15 -69
  133. package/dist/harness/open-connector.js.map +1 -1
  134. package/dist/harness/presenter.d.ts.map +1 -1
  135. package/dist/harness/presenter.js +33 -2
  136. package/dist/harness/presenter.js.map +1 -1
  137. package/dist/harness/prompt.d.ts +1 -1
  138. package/dist/harness/prompt.d.ts.map +1 -1
  139. package/dist/harness/prompt.js +14 -32
  140. package/dist/harness/prompt.js.map +1 -1
  141. package/dist/harness/run-events.d.ts.map +1 -1
  142. package/dist/harness/run-events.js +6 -1
  143. package/dist/harness/run-events.js.map +1 -1
  144. package/dist/harness/runner.d.ts.map +1 -1
  145. package/dist/harness/runner.js +6 -9
  146. package/dist/harness/runner.js.map +1 -1
  147. package/dist/harness/session.d.ts.map +1 -1
  148. package/dist/harness/session.js +6 -0
  149. package/dist/harness/session.js.map +1 -1
  150. package/dist/harness/tools/event.d.ts.map +1 -1
  151. package/dist/harness/tools/event.js +19 -2
  152. package/dist/harness/tools/event.js.map +1 -1
  153. package/dist/harness/tools/generate-image.d.ts.map +1 -1
  154. package/dist/harness/tools/generate-image.js +17 -3
  155. package/dist/harness/tools/generate-image.js.map +1 -1
  156. package/dist/harness/tools/jev-browser.d.ts +1 -2
  157. package/dist/harness/tools/jev-browser.d.ts.map +1 -1
  158. package/dist/harness/tools/jev-browser.js +75 -50
  159. package/dist/harness/tools/jev-browser.js.map +1 -1
  160. package/dist/harness/types.d.ts +16 -0
  161. package/dist/harness/types.d.ts.map +1 -1
  162. package/dist/harness/types.js.map +1 -1
  163. package/dist/index.d.ts +2 -3
  164. package/dist/index.d.ts.map +1 -1
  165. package/dist/index.js +2 -3
  166. package/dist/index.js.map +1 -1
  167. package/dist/main.js +40 -93
  168. package/dist/main.js.map +1 -1
  169. package/dist/memory-capture/index.d.ts.map +1 -1
  170. package/dist/memory-capture/index.js +3 -3
  171. package/dist/memory-capture/index.js.map +1 -1
  172. package/dist/migrations/actor-vaults.d.ts +3 -0
  173. package/dist/migrations/actor-vaults.d.ts.map +1 -0
  174. package/dist/migrations/actor-vaults.js +43 -0
  175. package/dist/migrations/actor-vaults.js.map +1 -0
  176. package/dist/migrations/conversation-settings.d.ts +3 -0
  177. package/dist/migrations/conversation-settings.d.ts.map +1 -0
  178. package/dist/migrations/conversation-settings.js +31 -0
  179. package/dist/migrations/conversation-settings.js.map +1 -0
  180. package/dist/migrations/index.d.ts +14 -0
  181. package/dist/migrations/index.d.ts.map +1 -0
  182. package/dist/migrations/index.js +99 -0
  183. package/dist/migrations/index.js.map +1 -0
  184. package/dist/migrations/models-json.d.ts +3 -0
  185. package/dist/migrations/models-json.d.ts.map +1 -0
  186. package/dist/migrations/models-json.js +18 -0
  187. package/dist/migrations/models-json.js.map +1 -0
  188. package/dist/migrations/office-layout.d.ts +4 -0
  189. package/dist/migrations/office-layout.d.ts.map +1 -0
  190. package/dist/migrations/office-layout.js +94 -0
  191. package/dist/migrations/office-layout.js.map +1 -0
  192. package/dist/migrations/office-sessions.d.ts +3 -0
  193. package/dist/migrations/office-sessions.d.ts.map +1 -0
  194. package/dist/migrations/office-sessions.js +70 -0
  195. package/dist/migrations/office-sessions.js.map +1 -0
  196. package/dist/migrations/sandbox-containers.d.ts +3 -0
  197. package/dist/migrations/sandbox-containers.d.ts.map +1 -0
  198. package/dist/migrations/sandbox-containers.js +36 -0
  199. package/dist/migrations/sandbox-containers.js.map +1 -0
  200. package/dist/{sessions/migrate-common.d.ts → migrations/session-files.d.ts} +1 -2
  201. package/dist/migrations/session-files.d.ts.map +1 -0
  202. package/dist/{sessions/migrate-common.js → migrations/session-files.js} +2 -2
  203. package/dist/migrations/session-files.js.map +1 -0
  204. package/dist/{sessions/migrate-v3.d.ts → migrations/sessions-v3.d.ts} +4 -3
  205. package/dist/migrations/sessions-v3.d.ts.map +1 -0
  206. package/dist/{sessions/migrate-v3.js → migrations/sessions-v3.js} +14 -4
  207. package/dist/migrations/sessions-v3.js.map +1 -0
  208. package/dist/migrations/types.d.ts +30 -0
  209. package/dist/migrations/types.d.ts.map +1 -0
  210. package/dist/migrations/types.js +2 -0
  211. package/dist/migrations/types.js.map +1 -0
  212. package/dist/migrations/workspace-events.d.ts +3 -0
  213. package/dist/migrations/workspace-events.d.ts.map +1 -0
  214. package/dist/migrations/workspace-events.js +43 -0
  215. package/dist/migrations/workspace-events.js.map +1 -0
  216. package/dist/office/index.d.ts +2 -24
  217. package/dist/office/index.d.ts.map +1 -1
  218. package/dist/office/index.js +15 -579
  219. package/dist/office/index.js.map +1 -1
  220. package/dist/office/types.d.ts +0 -10
  221. package/dist/office/types.d.ts.map +1 -1
  222. package/dist/office/types.js.map +1 -1
  223. package/dist/runtime/conversation-runtime.js +12 -12
  224. package/dist/runtime/conversation-runtime.js.map +1 -1
  225. package/dist/sandbox/identity.d.ts +2 -3
  226. package/dist/sandbox/identity.d.ts.map +1 -1
  227. package/dist/sandbox/identity.js +6 -13
  228. package/dist/sandbox/identity.js.map +1 -1
  229. package/dist/sandbox/layout.d.ts +0 -2
  230. package/dist/sandbox/layout.d.ts.map +1 -1
  231. package/dist/sandbox/layout.js +2 -2
  232. package/dist/sandbox/layout.js.map +1 -1
  233. package/dist/sandbox/provisioner.d.ts +4 -38
  234. package/dist/sandbox/provisioner.d.ts.map +1 -1
  235. package/dist/sandbox/provisioner.js +32 -456
  236. package/dist/sandbox/provisioner.js.map +1 -1
  237. package/dist/sessions/chat-history-sync.d.ts.map +1 -1
  238. package/dist/sessions/chat-history-sync.js +28 -40
  239. package/dist/sessions/chat-history-sync.js.map +1 -1
  240. package/dist/sessions/session-store.d.ts.map +1 -1
  241. package/dist/sessions/session-store.js +1 -8
  242. package/dist/sessions/session-store.js.map +1 -1
  243. package/dist/sessions/store.d.ts +2 -3
  244. package/dist/sessions/store.d.ts.map +1 -1
  245. package/dist/sessions/store.js +8 -14
  246. package/dist/sessions/store.js.map +1 -1
  247. package/dist/sessions/types.d.ts +6 -15
  248. package/dist/sessions/types.d.ts.map +1 -1
  249. package/dist/sessions/types.js.map +1 -1
  250. package/dist/settings/index.d.ts +0 -48
  251. package/dist/settings/index.d.ts.map +1 -1
  252. package/dist/settings/index.js +6 -37
  253. package/dist/settings/index.js.map +1 -1
  254. package/dist/types.d.ts +0 -29
  255. package/dist/types.d.ts.map +1 -1
  256. package/dist/types.js.map +1 -1
  257. package/dist/vault/index.d.ts +0 -7
  258. package/dist/vault/index.d.ts.map +1 -1
  259. package/dist/vault/index.js +1 -36
  260. package/dist/vault/index.js.map +1 -1
  261. package/package.json +8 -6
  262. package/dist/adapters/github/repo.d.ts +0 -8
  263. package/dist/adapters/github/repo.d.ts.map +0 -1
  264. package/dist/adapters/github/repo.js +0 -92
  265. package/dist/adapters/github/repo.js.map +0 -1
  266. package/dist/adapters/github/tools/sync.d.ts +0 -13
  267. package/dist/adapters/github/tools/sync.d.ts.map +0 -1
  268. package/dist/adapters/github/tools/sync.js +0 -29
  269. package/dist/adapters/github/tools/sync.js.map +0 -1
  270. package/dist/cli/sandbox.d.ts +0 -3
  271. package/dist/cli/sandbox.d.ts.map +0 -1
  272. package/dist/cli/sandbox.js +0 -80
  273. package/dist/cli/sandbox.js.map +0 -1
  274. package/dist/cli/sessions.d.ts +0 -2
  275. package/dist/cli/sessions.d.ts.map +0 -1
  276. package/dist/cli/sessions.js +0 -78
  277. package/dist/cli/sessions.js.map +0 -1
  278. package/dist/sessions/migrate-common.d.ts.map +0 -1
  279. package/dist/sessions/migrate-common.js.map +0 -1
  280. package/dist/sessions/migrate-pi-084.d.ts +0 -7
  281. package/dist/sessions/migrate-pi-084.d.ts.map +0 -1
  282. package/dist/sessions/migrate-pi-084.js +0 -293
  283. package/dist/sessions/migrate-pi-084.js.map +0 -1
  284. package/dist/sessions/migrate-v3.d.ts.map +0 -1
  285. package/dist/sessions/migrate-v3.js.map +0 -1
  286. package/dist/settings/migrate.d.ts +0 -16
  287. package/dist/settings/migrate.d.ts.map +0 -1
  288. package/dist/settings/migrate.js +0 -70
  289. package/dist/settings/migrate.js.map +0 -1
@@ -1,8 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
- import { legacyConversationResourceKey, sanitizeIdentitySegment } from "./identity.js";
3
- import { GUEST_HOME, GUEST_PUBLIC_OFFICES_DIR } from "./layout.js";
4
2
  import { createHash } from "node:crypto";
5
3
  import { readFileSync, statSync } from "node:fs";
4
+ import { isAbsolute, relative } from "node:path";
6
5
  import { promisify } from "node:util";
7
6
  import * as log from "../log.js";
8
7
  import { reportUserFacingError } from "../observability/index.js";
@@ -21,29 +20,16 @@ function isDockerNotFoundError(err) {
21
20
  /error: no such [^\n]+/.test(haystack));
22
21
  }
23
22
  import { errorMessage } from "../unknown-values.js";
24
- function bindSpecToMount(bindSpec) {
25
- const readOnly = bindSpec.endsWith(":ro");
26
- const spec = readOnly ? bindSpec.slice(0, -3) : bindSpec;
27
- const separator = spec.indexOf(":");
28
- return {
29
- source: spec.slice(0, separator),
30
- target: spec.slice(separator + 1),
31
- readOnly: readOnly ? true : undefined,
32
- };
33
- }
34
23
  export class DockerContainerManager {
35
24
  static { this.MANAGED_LABEL = "mikan.managed=true"; }
36
25
  static { this.IMAGE_MODE_LABEL = "mikan.sandbox=image"; }
37
26
  static { this.VAULT_ID_LABEL_KEY = "mikan.vault-id"; }
38
27
  static { this.CONVERSATION_ID_LABEL_KEY = "mikan.conversation-id"; }
39
28
  static { this.MOUNT_SIGNATURE_LABEL_KEY = "mikan.mount-signature"; }
40
- static { this.MIGRATE_IMAGE_PREFIX = "mikan-migrate"; }
41
- static { this.MIGRATE_BINDS_LABEL_KEY = "mikan.migrate-binds"; }
42
29
  constructor(image, options = {}) {
43
30
  this.image = image;
44
31
  this.state = new Map();
45
32
  this.inflight = new Map();
46
- this.layoutMigrations = new Map();
47
33
  this.keyQueues = new Map();
48
34
  this.boostedKeys = new Set();
49
35
  this.overrideLimits = new Map();
@@ -51,18 +37,12 @@ export class DockerContainerManager {
51
37
  this.boostLimits = options.boostLimits;
52
38
  this.execFileImpl = options.execFileImpl ?? execFileAsync;
53
39
  }
54
- static sanitizeSegment(value) {
55
- return sanitizeIdentitySegment(value);
56
- }
57
40
  static containerName(containerKey) {
58
41
  return `mikan-sandbox-${containerKey}`;
59
42
  }
60
43
  static networkName(containerKey) {
61
44
  return `mikan-sandbox-net-${containerKey}`;
62
45
  }
63
- static homeVolumeName(containerKey) {
64
- return `mikan-home-${containerKey}`;
65
- }
66
46
  serialize(containerKey, task) {
67
47
  const previous = this.keyQueues.get(containerKey) ?? Promise.resolve();
68
48
  const next = previous.then(task, task);
@@ -87,28 +67,16 @@ export class DockerContainerManager {
87
67
  async provisionInner(containerKey, options) {
88
68
  const containerName = options.containerName ?? DockerContainerManager.containerName(containerKey);
89
69
  const mounts = options.mounts ?? [];
90
- await this.migrateContainerLayout(containerName);
91
70
  const status = await this.inspectStatus(containerName);
92
71
  try {
93
- const binds = status === "missing" ? [] : await this.inspectBindMounts(containerName);
94
- const homeVolume = this.hasHomeVolumeBind(containerKey, binds);
95
72
  const drift = status === "missing"
96
73
  ? undefined
97
- : await this.runtimeDrift(containerKey, containerName, mounts, {
98
- binds,
99
- status,
100
- homeVolume,
101
- });
102
- if (drift && homeVolume) {
74
+ : await this.runtimeDrift(containerKey, containerName, mounts, status);
75
+ if (drift) {
103
76
  log.logInfo(`Container ${containerName} is out of date (${drift}); replacing container`);
104
- await this.replaceHomeVolumeContainer(containerKey, containerName, mounts, options);
77
+ await this.replaceContainer(containerKey, containerName, mounts, options);
105
78
  log.logInfo(`Container ${containerName} replaced from image ${this.image}`);
106
79
  }
107
- else if (drift) {
108
- log.logInfo(`Container ${containerName} configuration changed (${drift}); recreating container`);
109
- await this.recreateContainerPreservingContents(containerKey, containerName, mounts);
110
- log.logInfo(`Container ${containerName} recreated`);
111
- }
112
80
  else if (status === "stopped") {
113
81
  await this.execFileImpl("docker", ["start", containerName]);
114
82
  log.logInfo(`Container ${containerName} started`);
@@ -173,10 +141,10 @@ export class DockerContainerManager {
173
141
  log.logWarning(`Failed to stop container ${containerName}`, errorMessage(err));
174
142
  }
175
143
  }
176
- remove(containerKey, options = {}) {
177
- return this.serialize(containerKey, () => this.removeInner(containerKey, options));
144
+ remove(containerKey) {
145
+ return this.serialize(containerKey, () => this.removeInner(containerKey));
178
146
  }
179
- async removeInner(containerKey, options) {
147
+ async removeInner(containerKey) {
180
148
  const containerName = this.getContainerName(containerKey);
181
149
  const networkName = DockerContainerManager.networkName(containerKey);
182
150
  const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed`, `Failed to remove container ${containerName}`);
@@ -190,9 +158,6 @@ export class DockerContainerManager {
190
158
  catch (err) {
191
159
  log.logWarning(`Failed to remove network ${networkName}`, errorMessage(err));
192
160
  }
193
- await this.removeMigrateSnapshot(containerName);
194
- if (options.purgeHome)
195
- await this.removeHomeVolume(containerKey);
196
161
  this.state.delete(containerKey);
197
162
  this.boostedKeys.delete(containerKey);
198
163
  this.overrideLimits.delete(containerKey);
@@ -207,291 +172,25 @@ export class DockerContainerManager {
207
172
  }
208
173
  await Promise.all(toStop.map((containerKey) => this.stop(containerKey)));
209
174
  }
210
- async removeContainersForConversations(conversationIds) {
211
- if (conversationIds.size === 0)
212
- return;
175
+ async reconcile(workspaceRoot) {
213
176
  const names = await this.listContainerNamesByLabel();
214
- await Promise.all(names.map(async (containerName) => {
215
- const details = await this.inspectContainerDetails(containerName);
216
- if (!details?.conversationId || !conversationIds.has(details.conversationId))
217
- return;
218
- const removed = await this.forceRemoveContainer(containerName, `Removed container ${containerName} after office migration`, `Failed to remove container ${containerName} after office migration`);
219
- const containerKey = this.containerKeyFromContainerName(containerName);
220
- if (removed && containerKey)
221
- await this.removeHomeVolume(containerKey);
222
- }));
223
- }
224
- armContainerLayoutMigration(translator) {
225
- this.bindTranslator = translator;
226
- }
227
- async sweepContainerLayoutMigration(delayMs = 2000) {
228
- if (!this.bindTranslator)
229
- return;
230
- let names;
231
- try {
232
- const containers = await this.listContainerNamesByLabel();
233
- const snapshots = await this.listMigrateSnapshotContainerNames();
234
- names = Array.from(new Set([...containers, ...snapshots]));
235
- }
236
- catch (err) {
237
- log.logWarning("Container layout sweep could not list containers", String(err));
238
- return;
239
- }
240
- for (const containerName of names) {
241
- try {
242
- await this.migrateContainerLayout(containerName);
243
- }
244
- catch (err) {
245
- log.logWarning(`Container layout migration failed for ${containerName}; leaving it in place`, errorMessage(err));
246
- }
247
- await new Promise((resolve) => setTimeout(resolve, delayMs));
248
- }
249
- await this.removeDanglingMigrateImages();
250
- }
251
- migrateContainerLayout(containerName) {
252
- const translator = this.bindTranslator;
253
- if (!translator)
254
- return Promise.resolve();
255
- const existing = this.layoutMigrations.get(containerName);
256
- if (existing)
257
- return existing;
258
- const pending = this.migrateContainerLayoutInner(containerName, translator).finally(() => {
259
- this.layoutMigrations.delete(containerName);
260
- });
261
- this.layoutMigrations.set(containerName, pending);
262
- return pending;
263
- }
264
- async migrateContainerLayoutInner(containerName, translator) {
265
- const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
266
- let originalBinds;
267
- const status = await this.inspectStatus(containerName);
268
- if (status === "missing") {
269
- const labeled = await this.readMigrateImageBinds(snapshotImage);
270
- if (labeled === undefined)
271
- return;
272
- originalBinds = labeled;
273
- }
274
- else {
275
- originalBinds = await this.inspectBindMounts(containerName);
276
- const translated = originalBinds.map(translator);
277
- if (this.sameBinds(translated.toSorted(), originalBinds.slice().toSorted())) {
278
- return;
279
- }
280
- log.logInfo(`Migrating container ${containerName} to the office layout`);
281
- await this.execFileImpl("docker", [
282
- "commit",
283
- "-c",
284
- `LABEL ${DockerContainerManager.MIGRATE_BINDS_LABEL_KEY}=${JSON.stringify(JSON.stringify(originalBinds))}`,
285
- containerName,
286
- snapshotImage,
287
- ]);
288
- await this.execFileImpl("docker", ["rm", "-f", containerName]);
289
- }
290
- await this.createContainerFromSnapshot(containerName, originalBinds.map(translator));
291
- await this.removeMigrateSnapshot(containerName);
292
- log.logInfo(`Container ${containerName} migrated to the office layout`);
293
- }
294
- async createContainerFromSnapshot(containerName, bindSpecs, knownContainerKey) {
295
- const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
296
- const containerKey = knownContainerKey ?? this.containerKeyFromContainerName(containerName) ?? containerName;
297
- const networkName = await this.ensureNetwork(containerKey);
298
- const conversationId = await this.readImageLabel(snapshotImage, DockerContainerManager.CONVERSATION_ID_LABEL_KEY);
299
- const labels = [
300
- "--label",
301
- DockerContainerManager.MANAGED_LABEL,
302
- "--label",
303
- DockerContainerManager.IMAGE_MODE_LABEL,
304
- "--label",
305
- `${DockerContainerManager.VAULT_ID_LABEL_KEY}=${containerKey}`,
306
- ];
307
- if (conversationId) {
308
- labels.push("--label", `${DockerContainerManager.CONVERSATION_ID_LABEL_KEY}=${conversationId}`);
309
- }
310
- labels.push("--label", `${DockerContainerManager.MOUNT_SIGNATURE_LABEL_KEY}=${this.mountSignature(bindSpecs.map(bindSpecToMount))}`);
311
- await this.execFileImpl("docker", [
312
- "create",
313
- "--name",
314
- containerName,
315
- "--network",
316
- networkName,
317
- "--cap-drop",
318
- "ALL",
319
- "--security-opt",
320
- "no-new-privileges",
321
- "--pids-limit",
322
- "1024",
323
- ...labels,
324
- ...this.resourceLimitArgs(this.effectiveLimits(containerKey)),
325
- ...bindSpecs.flatMap((bind) => ["-v", bind]),
326
- snapshotImage,
327
- "sleep",
328
- "infinity",
329
- ]);
330
- }
331
- async removeStaleMountpoints(containerName, bindSpecs) {
332
- const keep = new Set(bindSpecs
333
- .map((bind) => bindSpecToMount(bind).target)
334
- .filter((target) => target.startsWith(`${GUEST_PUBLIC_OFFICES_DIR}/`))
335
- .map((target) => target.slice(GUEST_PUBLIC_OFFICES_DIR.length + 1)));
336
- const script = [
337
- `cd ${GUEST_PUBLIC_OFFICES_DIR} 2>/dev/null || exit 0`,
338
- "for d in *; do",
339
- ' [ -e "$d" ] || continue',
340
- ' case " $KEEP " in *" ${d##*/} "*) continue;; esac',
341
- ' rmdir "$d" 2>/dev/null || true',
342
- "done",
343
- ].join("\n");
344
- try {
345
- await this.execFileImpl("docker", [
346
- "exec",
347
- "-e",
348
- `KEEP=${[...keep].join(" ")}`,
349
- containerName,
350
- "sh",
351
- "-c",
352
- script,
353
- ]);
354
- }
355
- catch (err) {
356
- log.logWarning(`Could not clean stale mountpoints in ${containerName}`, String(err));
357
- }
358
- }
359
- async recreateContainerPreservingContents(containerKey, containerName, mounts) {
360
- const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
361
- const bindSpecs = mounts.map((mount) => this.toBindSpec(mount));
362
- await this.execFileImpl("docker", [
363
- "commit",
364
- "-c",
365
- `LABEL ${DockerContainerManager.MIGRATE_BINDS_LABEL_KEY}=${JSON.stringify(JSON.stringify(bindSpecs))}`,
366
- containerName,
367
- snapshotImage,
368
- ]);
369
- await this.execFileImpl("docker", ["rm", "-f", containerName]);
370
- await this.createContainerFromSnapshot(containerName, bindSpecs, containerKey);
371
- await this.execFileImpl("docker", ["start", containerName]);
372
- await this.removeStaleMountpoints(containerName, bindSpecs);
373
- await this.removeMigrateSnapshot(containerName);
374
- }
375
- async readMigrateImageBinds(snapshotImage) {
376
- const raw = await this.readImageLabel(snapshotImage, DockerContainerManager.MIGRATE_BINDS_LABEL_KEY);
377
- if (raw === undefined)
378
- return undefined;
379
- const parsed = JSON.parse(raw);
380
- if (!Array.isArray(parsed) || parsed.some((bind) => typeof bind !== "string")) {
381
- throw new Error(`Snapshot image ${snapshotImage} carries malformed migrate binds`);
382
- }
383
- return parsed;
384
- }
385
- async readImageLabel(image, key) {
386
- try {
387
- const { stdout } = await this.execFileImpl("docker", [
388
- "inspect",
389
- "-f",
390
- `{{index .Config.Labels "${key}"}}`,
391
- image,
392
- ]);
393
- return this.normalizeDockerValue(stdout.trim());
394
- }
395
- catch {
396
- return undefined;
397
- }
398
- }
399
- async removeMigrateSnapshot(containerName) {
400
- try {
401
- await this.execFileImpl("docker", [
402
- "rmi",
403
- `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`,
404
- ]);
405
- }
406
- catch { }
407
- }
408
- async listMigrateSnapshotContainerNames() {
409
- try {
410
- const { stdout } = await this.execFileImpl("docker", [
411
- "images",
412
- DockerContainerManager.MIGRATE_IMAGE_PREFIX,
413
- "--format",
414
- "{{.Tag}}",
415
- ]);
416
- return this.parseNameLines(stdout);
417
- }
418
- catch {
419
- return [];
420
- }
421
- }
422
- async removeDanglingMigrateImages() {
423
- let stdout;
424
- try {
425
- ({ stdout } = await this.execFileImpl("docker", [
426
- "images",
427
- `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}`,
428
- "--format",
429
- "{{.Repository}}:{{.Tag}}",
430
- ]));
431
- }
432
- catch {
433
- return;
434
- }
435
- for (const image of this.parseNameLines(stdout)) {
436
- const containerName = image.slice(DockerContainerManager.MIGRATE_IMAGE_PREFIX.length + 1);
437
- const status = await this.inspectStatus(containerName);
438
- if (status === "missing")
439
- continue;
440
- if (await this.containerRunsFromImage(containerName, image))
441
- continue;
442
- try {
443
- await this.execFileImpl("docker", ["rmi", image]);
444
- log.logInfo(`Removed layout-migration snapshot image ${image}`);
445
- }
446
- catch (err) {
447
- log.logWarning(`Could not remove snapshot image ${image}`, String(err));
448
- }
449
- }
450
- }
451
- async containerRunsFromImage(containerName, image) {
452
- const { stdout } = await this.execFileImpl("docker", [
453
- "inspect",
454
- "-f",
455
- "{{.Config.Image}}",
456
- containerName,
457
- ]);
458
- return stdout.trim() === image;
459
- }
460
- async reconcile() {
461
- const discovered = new Set();
462
- const labeledNames = await this.listContainerNamesByLabel();
463
- for (const name of labeledNames)
464
- discovered.add(name);
465
- const legacyNames = await this.listContainerNamesByPrefix();
466
- for (const name of legacyNames)
467
- discovered.add(name);
468
177
  this.state.clear();
469
- const inspected = await Promise.all(Array.from(discovered).map(async (containerName) => ({
178
+ const inspected = await Promise.all(names.map(async (containerName) => ({
470
179
  containerName,
471
180
  details: await this.inspectContainerDetails(containerName),
472
181
  })));
473
- const legacyRemovals = [];
474
182
  for (const { containerName, details } of inspected) {
475
- if (!details)
476
- continue;
477
- if (!details.conversationId) {
478
- legacyRemovals.push(this.removeLegacyContainer(containerName));
183
+ if (!details || !mountsWithin(details.bindSources, workspaceRoot))
479
184
  continue;
480
- }
481
185
  const containerKey = this.containerKeyFromContainerName(containerName);
482
186
  if (!containerKey) {
483
187
  log.logWarning(`Skipping unmanaged-style container without container key`, containerName);
484
188
  continue;
485
189
  }
486
- if (containerKey === legacyConversationResourceKey(details.conversationId)) {
487
- legacyRemovals.push(this.forceRemoveContainer(containerName, `Removed container ${containerName} keyed by pre-office resource identity`, `Failed to remove legacy-keyed container ${containerName}`).then(() => undefined));
488
- continue;
489
- }
490
190
  const status = details.running ? "running" : "stopped";
491
191
  const lastUsed = details.startedAtMs ?? Date.now();
492
192
  this.state.set(containerKey, { status, lastUsed, containerName });
493
193
  }
494
- await Promise.all(legacyRemovals);
495
194
  const running = Array.from(this.state.values()).filter((s) => s.status === "running").length;
496
195
  const stopped = this.state.size - running;
497
196
  log.logInfo(`Reconciled ${this.state.size} managed containers (running=${running}, stopped=${stopped})`);
@@ -511,7 +210,6 @@ export class DockerContainerManager {
511
210
  }
512
211
  async runContainer(containerKey, containerName, mounts, options) {
513
212
  const networkName = await this.ensureNetwork(containerKey);
514
- const homeVolume = await this.ensureHomeVolume(containerKey);
515
213
  log.logInfo(`Creating container ${containerName} from image ${this.image}`);
516
214
  const labels = [
517
215
  "--label",
@@ -542,8 +240,6 @@ export class DockerContainerManager {
542
240
  "1024",
543
241
  ...labels,
544
242
  ...this.resourceLimitArgs(this.effectiveLimits(containerKey)),
545
- "-v",
546
- `${homeVolume}:${GUEST_HOME}`,
547
243
  ...this.mountArgs(mounts),
548
244
  this.image,
549
245
  "sleep",
@@ -593,23 +289,19 @@ export class DockerContainerManager {
593
289
  });
594
290
  }
595
291
  }
596
- async runtimeDrift(containerKey, containerName, mounts, current) {
597
- const { binds, status, homeVolume } = current;
598
- if (this.hasBindMountDrift(binds, mounts))
292
+ async runtimeDrift(containerKey, containerName, mounts, status) {
293
+ if (this.hasBindMountDrift(await this.inspectBindMounts(containerName), mounts))
599
294
  return "binds";
600
295
  if (await this.hasMountSignatureDrift(containerName, mounts))
601
296
  return "mount-content";
602
297
  if (await this.hasNetworkModeDrift(containerKey, containerName))
603
298
  return "network";
604
- if (homeVolume && status === "stopped" && (await this.hasImageDrift(containerName))) {
299
+ if (status === "stopped" && (await this.hasImageDrift(containerName)))
605
300
  return "image";
606
- }
607
301
  return undefined;
608
302
  }
609
303
  hasBindMountDrift(binds, mounts) {
610
- const expected = this.expectedBinds(mounts);
611
- const actual = binds.filter((bind) => !this.isHomeVolumeBind(bind));
612
- return !this.sameBinds(expected, actual);
304
+ return !this.sameBinds(this.expectedBinds(mounts), [...binds]);
613
305
  }
614
306
  expectedBinds(mounts) {
615
307
  return mounts
@@ -675,21 +367,6 @@ export class DockerContainerManager {
675
367
  }
676
368
  return [...parsed].toSorted();
677
369
  }
678
- isHomeVolumeBind(bind) {
679
- const separator = bind.indexOf(":");
680
- if (separator === -1)
681
- return false;
682
- const source = bind.slice(0, separator);
683
- const target = bind.slice(separator + 1).replace(/:r[ow]$/, "");
684
- return source.startsWith(DockerContainerManager.homeVolumeName("")) && target === GUEST_HOME;
685
- }
686
- hasHomeVolumeBind(containerKey, binds) {
687
- const expected = `${DockerContainerManager.homeVolumeName(containerKey)}:${GUEST_HOME}`;
688
- return binds.includes(expected);
689
- }
690
- async hasHomeVolume(containerKey, containerName) {
691
- return this.hasHomeVolumeBind(containerKey, await this.inspectBindMounts(containerName));
692
- }
693
370
  async hasImageDrift(containerName) {
694
371
  const desired = await this.localImageId();
695
372
  if (!desired)
@@ -717,107 +394,12 @@ export class DockerContainerManager {
717
394
  return undefined;
718
395
  }
719
396
  }
720
- async ensureHomeVolume(containerKey) {
721
- const volumeName = DockerContainerManager.homeVolumeName(containerKey);
722
- await this.execFileImpl("docker", [
723
- "volume",
724
- "create",
725
- "--label",
726
- DockerContainerManager.MANAGED_LABEL,
727
- "--label",
728
- `${DockerContainerManager.VAULT_ID_LABEL_KEY}=${containerKey}`,
729
- volumeName,
730
- ]);
731
- return volumeName;
732
- }
733
- async removeHomeVolume(containerKey) {
734
- const volumeName = DockerContainerManager.homeVolumeName(containerKey);
735
- try {
736
- await this.execFileImpl("docker", ["volume", "rm", volumeName]);
737
- log.logInfo(`Home volume ${volumeName} removed`);
738
- }
739
- catch (err) {
740
- if (isDockerNotFoundError(err))
741
- return;
742
- log.logWarning(`Failed to remove home volume ${volumeName}`, errorMessage(err));
743
- }
744
- }
745
- async replaceHomeVolumeContainer(containerKey, containerName, mounts, options) {
397
+ async replaceContainer(containerKey, containerName, mounts, options) {
746
398
  const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed for replacement`, `Failed to remove container ${containerName} for replacement`);
747
399
  if (!removed)
748
400
  throw new Error(`Failed to remove container ${containerName} for replacement`);
749
401
  await this.runContainer(containerKey, containerName, mounts, options);
750
402
  }
751
- async inventory() {
752
- const names = await this.listContainerNamesByLabel();
753
- const entries = [];
754
- for (const containerName of names) {
755
- const containerKey = this.containerKeyFromContainerName(containerName);
756
- const status = await this.inspectStatus(containerName);
757
- if (status === "missing")
758
- continue;
759
- const homeVolume = containerKey
760
- ? await this.hasHomeVolume(containerKey, containerName)
761
- : false;
762
- entries.push({
763
- containerName,
764
- containerKey,
765
- running: status === "running",
766
- homeVolume,
767
- imageStale: await this.hasImageDrift(containerName),
768
- });
769
- }
770
- return entries;
771
- }
772
- async systemChanges(containerName) {
773
- const { stdout } = await this.execFileImpl("docker", ["diff", containerName]);
774
- return this.parseNameLines(stdout).filter((line) => {
775
- const path = line.slice(2);
776
- return !this.isUnderGuestPath(path, GUEST_HOME) && !this.isUnderGuestPath(path, "/workspace");
777
- });
778
- }
779
- isUnderGuestPath(path, root) {
780
- return path === root || path.startsWith(`${root}/`);
781
- }
782
- migrateToHomeVolume(containerKey) {
783
- return this.serialize(containerKey, () => this.migrateToHomeVolumeInner(containerKey));
784
- }
785
- async migrateToHomeVolumeInner(containerKey) {
786
- const containerName = DockerContainerManager.containerName(containerKey);
787
- const status = await this.inspectStatus(containerName);
788
- if (status === "missing")
789
- return "missing";
790
- if (await this.hasHomeVolume(containerKey, containerName))
791
- return "already-migrated";
792
- const details = await this.inspectContainerDetails(containerName);
793
- const mounts = (await this.inspectBindMounts(containerName))
794
- .filter((bind) => !this.isHomeVolumeBind(bind))
795
- .map(bindSpecToMount);
796
- const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
797
- log.logInfo(`Migrating container ${containerName} to a home volume`);
798
- await this.execFileImpl("docker", ["commit", containerName, snapshotImage]);
799
- const homeVolume = await this.ensureHomeVolume(containerKey);
800
- await this.execFileImpl("docker", [
801
- "run",
802
- "--rm",
803
- "--network",
804
- "none",
805
- "-v",
806
- `${homeVolume}:${GUEST_HOME}`,
807
- snapshotImage,
808
- "true",
809
- ]);
810
- const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed for home-volume migration`, `Failed to remove container ${containerName} for home-volume migration`);
811
- if (!removed)
812
- throw new Error(`Failed to remove container ${containerName}`);
813
- await this.runContainer(containerKey, containerName, mounts, details?.conversationId ? { conversationId: details.conversationId } : {});
814
- if (status === "stopped")
815
- await this.execFileImpl("docker", ["stop", containerName]);
816
- await this.removeMigrateSnapshot(containerName);
817
- this.setState(containerKey, status === "stopped" ? "stopped" : "running", containerName);
818
- log.logInfo(`Container ${containerName} migrated to home volume ${homeVolume}`);
819
- return "migrated";
820
- }
821
403
  async hasNetworkModeDrift(containerKey, containerName) {
822
404
  const expected = DockerContainerManager.networkName(containerKey);
823
405
  const { stdout } = await this.execFileImpl("docker", [
@@ -888,23 +470,6 @@ export class DockerContainerManager {
888
470
  return [];
889
471
  }
890
472
  }
891
- async listContainerNamesByPrefix() {
892
- try {
893
- const { stdout } = await this.execFileImpl("docker", [
894
- "ps",
895
- "-a",
896
- "--filter",
897
- `name=${DockerContainerManager.containerName("")}`,
898
- "--format",
899
- "{{.Names}}",
900
- ]);
901
- return this.parseNameLines(stdout);
902
- }
903
- catch (err) {
904
- log.logWarning("Failed to list legacy managed containers", errorMessage(err));
905
- return [];
906
- }
907
- }
908
473
  parseNameLines(stdout) {
909
474
  return stdout
910
475
  .split("\n")
@@ -916,14 +481,14 @@ export class DockerContainerManager {
916
481
  const { stdout } = await this.execFileImpl("docker", [
917
482
  "inspect",
918
483
  "-f",
919
- `{{.State.Running}}\t{{.State.StartedAt}}\t{{index .Config.Labels "${DockerContainerManager.VAULT_ID_LABEL_KEY}"}}\t{{index .Config.Labels "${DockerContainerManager.CONVERSATION_ID_LABEL_KEY}"}}`,
484
+ `{{.State.Running}}\t{{.State.StartedAt}}\t{{index .Config.Labels "${DockerContainerManager.VAULT_ID_LABEL_KEY}"}}\t{{index .Config.Labels "${DockerContainerManager.CONVERSATION_ID_LABEL_KEY}"}}\t{{json .HostConfig.Binds}}`,
920
485
  containerName,
921
486
  ]);
922
- const [runningRaw, startedAtRaw, , conversationIdRaw] = stdout.trim().split("\t");
487
+ const [runningRaw, startedAtRaw, , conversationIdRaw, bindsRaw] = stdout.trim().split("\t");
923
488
  const running = runningRaw === "true";
924
489
  const startedAtMs = this.parseDockerTimestamp(startedAtRaw);
925
490
  const conversationId = this.normalizeDockerValue(conversationIdRaw);
926
- return { running, startedAtMs, conversationId };
491
+ return { running, startedAtMs, conversationId, bindSources: parseBindSources(bindsRaw) };
927
492
  }
928
493
  catch (err) {
929
494
  log.logWarning(`Failed to inspect container ${containerName} during reconcile`, errorMessage(err));
@@ -964,8 +529,19 @@ export class DockerContainerManager {
964
529
  return false;
965
530
  }
966
531
  }
967
- async removeLegacyContainer(containerName) {
968
- await this.forceRemoveContainer(containerName, `Removed legacy mikan container ${containerName} (pre-channel-isolation scheme)`, `Failed to remove legacy mikan container ${containerName}`);
969
- }
532
+ }
533
+ function parseBindSources(raw) {
534
+ const parsed = JSON.parse(raw && raw.length > 0 ? raw : "null");
535
+ if (!Array.isArray(parsed))
536
+ return [];
537
+ return parsed
538
+ .filter((bind) => typeof bind === "string")
539
+ .map((bind) => bind.split(":", 1)[0] ?? bind);
540
+ }
541
+ function mountsWithin(sources, root) {
542
+ return sources.some((source) => {
543
+ const path = relative(root, source);
544
+ return path !== "" && !path.startsWith("..") && !isAbsolute(path);
545
+ });
970
546
  }
971
547
  //# sourceMappingURL=provisioner.js.map