@geminixiang/mikan 1.0.0-beta.83 → 1.0.0-beta.85

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 (159) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +2 -2
  3. package/dist/adapters/slack/bot.d.ts +1 -0
  4. package/dist/adapters/slack/bot.d.ts.map +1 -1
  5. package/dist/adapters/slack/bot.js +8 -2
  6. package/dist/adapters/slack/bot.js.map +1 -1
  7. package/dist/adapters/slack/response-lifecycle.js +2 -2
  8. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  9. package/dist/cli/boot.d.ts.map +1 -1
  10. package/dist/cli/boot.js +4 -6
  11. package/dist/cli/boot.js.map +1 -1
  12. package/dist/cli/migrate.d.ts +3 -0
  13. package/dist/cli/migrate.d.ts.map +1 -0
  14. package/dist/cli/migrate.js +75 -0
  15. package/dist/cli/migrate.js.map +1 -0
  16. package/dist/cli/office.d.ts.map +1 -1
  17. package/dist/cli/office.js +8 -129
  18. package/dist/cli/office.js.map +1 -1
  19. package/dist/cli/onboard.d.ts.map +1 -1
  20. package/dist/cli/onboard.js +3 -0
  21. package/dist/cli/onboard.js.map +1 -1
  22. package/dist/cli/types.d.ts +2 -3
  23. package/dist/cli/types.d.ts.map +1 -1
  24. package/dist/cli/types.js.map +1 -1
  25. package/dist/events/index.d.ts +2 -13
  26. package/dist/events/index.d.ts.map +1 -1
  27. package/dist/events/index.js +1 -58
  28. package/dist/events/index.js.map +1 -1
  29. package/dist/harness/budget-stop.d.ts +3 -0
  30. package/dist/harness/budget-stop.d.ts.map +1 -0
  31. package/dist/harness/budget-stop.js +36 -0
  32. package/dist/harness/budget-stop.js.map +1 -0
  33. package/dist/harness/execution-resolver.d.ts.map +1 -1
  34. package/dist/harness/execution-resolver.js +5 -9
  35. package/dist/harness/execution-resolver.js.map +1 -1
  36. package/dist/harness/open-connector.d.ts +0 -9
  37. package/dist/harness/open-connector.d.ts.map +1 -1
  38. package/dist/harness/open-connector.js +1 -62
  39. package/dist/harness/open-connector.js.map +1 -1
  40. package/dist/harness/presenter.d.ts.map +1 -1
  41. package/dist/harness/presenter.js +31 -2
  42. package/dist/harness/presenter.js.map +1 -1
  43. package/dist/harness/prompt.js +1 -1
  44. package/dist/harness/prompt.js.map +1 -1
  45. package/dist/harness/run-events.d.ts.map +1 -1
  46. package/dist/harness/run-events.js +6 -1
  47. package/dist/harness/run-events.js.map +1 -1
  48. package/dist/harness/skills.d.ts.map +1 -1
  49. package/dist/harness/skills.js +19 -14
  50. package/dist/harness/skills.js.map +1 -1
  51. package/dist/harness/tools/jev-browser.d.ts.map +1 -1
  52. package/dist/harness/tools/jev-browser.js +13 -15
  53. package/dist/harness/tools/jev-browser.js.map +1 -1
  54. package/dist/harness/types.d.ts +16 -0
  55. package/dist/harness/types.d.ts.map +1 -1
  56. package/dist/harness/types.js.map +1 -1
  57. package/dist/index.d.ts +0 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +0 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/main.js +8 -50
  62. package/dist/main.js.map +1 -1
  63. package/dist/migrations/actor-vaults.d.ts +3 -0
  64. package/dist/migrations/actor-vaults.d.ts.map +1 -0
  65. package/dist/migrations/actor-vaults.js +43 -0
  66. package/dist/migrations/actor-vaults.js.map +1 -0
  67. package/dist/migrations/conversation-settings.d.ts +3 -0
  68. package/dist/migrations/conversation-settings.d.ts.map +1 -0
  69. package/dist/migrations/conversation-settings.js +31 -0
  70. package/dist/migrations/conversation-settings.js.map +1 -0
  71. package/dist/migrations/index.d.ts +14 -0
  72. package/dist/migrations/index.d.ts.map +1 -0
  73. package/dist/migrations/index.js +97 -0
  74. package/dist/migrations/index.js.map +1 -0
  75. package/dist/migrations/models-json.d.ts +3 -0
  76. package/dist/migrations/models-json.d.ts.map +1 -0
  77. package/dist/migrations/models-json.js +18 -0
  78. package/dist/migrations/models-json.js.map +1 -0
  79. package/dist/migrations/office-layout.d.ts +4 -0
  80. package/dist/migrations/office-layout.d.ts.map +1 -0
  81. package/dist/migrations/office-layout.js +94 -0
  82. package/dist/migrations/office-layout.js.map +1 -0
  83. package/dist/migrations/sandbox-containers.d.ts +3 -0
  84. package/dist/migrations/sandbox-containers.d.ts.map +1 -0
  85. package/dist/migrations/sandbox-containers.js +36 -0
  86. package/dist/migrations/sandbox-containers.js.map +1 -0
  87. package/dist/{sessions/migrate-common.d.ts → migrations/session-files.d.ts} +1 -2
  88. package/dist/migrations/session-files.d.ts.map +1 -0
  89. package/dist/{sessions/migrate-common.js → migrations/session-files.js} +2 -2
  90. package/dist/migrations/session-files.js.map +1 -0
  91. package/dist/{sessions/migrate-v3.d.ts → migrations/sessions-v3.d.ts} +4 -3
  92. package/dist/migrations/sessions-v3.d.ts.map +1 -0
  93. package/dist/{sessions/migrate-v3.js → migrations/sessions-v3.js} +14 -4
  94. package/dist/migrations/sessions-v3.js.map +1 -0
  95. package/dist/migrations/types.d.ts +30 -0
  96. package/dist/migrations/types.d.ts.map +1 -0
  97. package/dist/migrations/types.js +2 -0
  98. package/dist/migrations/types.js.map +1 -0
  99. package/dist/migrations/workspace-events.d.ts +3 -0
  100. package/dist/migrations/workspace-events.d.ts.map +1 -0
  101. package/dist/migrations/workspace-events.js +43 -0
  102. package/dist/migrations/workspace-events.js.map +1 -0
  103. package/dist/office/index.d.ts +2 -23
  104. package/dist/office/index.d.ts.map +1 -1
  105. package/dist/office/index.js +12 -574
  106. package/dist/office/index.js.map +1 -1
  107. package/dist/office/types.d.ts +0 -10
  108. package/dist/office/types.d.ts.map +1 -1
  109. package/dist/office/types.js.map +1 -1
  110. package/dist/sandbox/identity.d.ts +2 -3
  111. package/dist/sandbox/identity.d.ts.map +1 -1
  112. package/dist/sandbox/identity.js +6 -13
  113. package/dist/sandbox/identity.js.map +1 -1
  114. package/dist/sandbox/layout.d.ts +0 -2
  115. package/dist/sandbox/layout.d.ts.map +1 -1
  116. package/dist/sandbox/layout.js +2 -2
  117. package/dist/sandbox/layout.js.map +1 -1
  118. package/dist/sandbox/provisioner.d.ts +3 -37
  119. package/dist/sandbox/provisioner.d.ts.map +1 -1
  120. package/dist/sandbox/provisioner.js +13 -452
  121. package/dist/sandbox/provisioner.js.map +1 -1
  122. package/dist/sessions/session-store.d.ts.map +1 -1
  123. package/dist/sessions/session-store.js +1 -8
  124. package/dist/sessions/session-store.js.map +1 -1
  125. package/dist/sessions/types.d.ts +0 -10
  126. package/dist/sessions/types.d.ts.map +1 -1
  127. package/dist/sessions/types.js.map +1 -1
  128. package/dist/settings/index.d.ts +0 -48
  129. package/dist/settings/index.d.ts.map +1 -1
  130. package/dist/settings/index.js +6 -37
  131. package/dist/settings/index.js.map +1 -1
  132. package/dist/types.d.ts +0 -28
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/types.js.map +1 -1
  135. package/dist/vault/index.d.ts +0 -7
  136. package/dist/vault/index.d.ts.map +1 -1
  137. package/dist/vault/index.js +1 -36
  138. package/dist/vault/index.js.map +1 -1
  139. package/package.json +1 -1
  140. package/dist/cli/sandbox.d.ts +0 -3
  141. package/dist/cli/sandbox.d.ts.map +0 -1
  142. package/dist/cli/sandbox.js +0 -80
  143. package/dist/cli/sandbox.js.map +0 -1
  144. package/dist/cli/sessions.d.ts +0 -2
  145. package/dist/cli/sessions.d.ts.map +0 -1
  146. package/dist/cli/sessions.js +0 -78
  147. package/dist/cli/sessions.js.map +0 -1
  148. package/dist/sessions/migrate-common.d.ts.map +0 -1
  149. package/dist/sessions/migrate-common.js.map +0 -1
  150. package/dist/sessions/migrate-pi-084.d.ts +0 -7
  151. package/dist/sessions/migrate-pi-084.d.ts.map +0 -1
  152. package/dist/sessions/migrate-pi-084.js +0 -293
  153. package/dist/sessions/migrate-pi-084.js.map +0 -1
  154. package/dist/sessions/migrate-v3.d.ts.map +0 -1
  155. package/dist/sessions/migrate-v3.js.map +0 -1
  156. package/dist/settings/migrate.d.ts +0 -16
  157. package/dist/settings/migrate.d.ts.map +0 -1
  158. package/dist/settings/migrate.js +0 -70
  159. package/dist/settings/migrate.js.map +0 -1
@@ -1,6 +1,4 @@
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";
6
4
  import { promisify } from "node:util";
@@ -21,29 +19,16 @@ function isDockerNotFoundError(err) {
21
19
  /error: no such [^\n]+/.test(haystack));
22
20
  }
23
21
  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
22
  export class DockerContainerManager {
35
23
  static { this.MANAGED_LABEL = "mikan.managed=true"; }
36
24
  static { this.IMAGE_MODE_LABEL = "mikan.sandbox=image"; }
37
25
  static { this.VAULT_ID_LABEL_KEY = "mikan.vault-id"; }
38
26
  static { this.CONVERSATION_ID_LABEL_KEY = "mikan.conversation-id"; }
39
27
  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
28
  constructor(image, options = {}) {
43
29
  this.image = image;
44
30
  this.state = new Map();
45
31
  this.inflight = new Map();
46
- this.layoutMigrations = new Map();
47
32
  this.keyQueues = new Map();
48
33
  this.boostedKeys = new Set();
49
34
  this.overrideLimits = new Map();
@@ -51,18 +36,12 @@ export class DockerContainerManager {
51
36
  this.boostLimits = options.boostLimits;
52
37
  this.execFileImpl = options.execFileImpl ?? execFileAsync;
53
38
  }
54
- static sanitizeSegment(value) {
55
- return sanitizeIdentitySegment(value);
56
- }
57
39
  static containerName(containerKey) {
58
40
  return `mikan-sandbox-${containerKey}`;
59
41
  }
60
42
  static networkName(containerKey) {
61
43
  return `mikan-sandbox-net-${containerKey}`;
62
44
  }
63
- static homeVolumeName(containerKey) {
64
- return `mikan-home-${containerKey}`;
65
- }
66
45
  serialize(containerKey, task) {
67
46
  const previous = this.keyQueues.get(containerKey) ?? Promise.resolve();
68
47
  const next = previous.then(task, task);
@@ -87,28 +66,16 @@ export class DockerContainerManager {
87
66
  async provisionInner(containerKey, options) {
88
67
  const containerName = options.containerName ?? DockerContainerManager.containerName(containerKey);
89
68
  const mounts = options.mounts ?? [];
90
- await this.migrateContainerLayout(containerName);
91
69
  const status = await this.inspectStatus(containerName);
92
70
  try {
93
- const binds = status === "missing" ? [] : await this.inspectBindMounts(containerName);
94
- const homeVolume = this.hasHomeVolumeBind(containerKey, binds);
95
71
  const drift = status === "missing"
96
72
  ? undefined
97
- : await this.runtimeDrift(containerKey, containerName, mounts, {
98
- binds,
99
- status,
100
- homeVolume,
101
- });
102
- if (drift && homeVolume) {
73
+ : await this.runtimeDrift(containerKey, containerName, mounts, status);
74
+ if (drift) {
103
75
  log.logInfo(`Container ${containerName} is out of date (${drift}); replacing container`);
104
- await this.replaceHomeVolumeContainer(containerKey, containerName, mounts, options);
76
+ await this.replaceContainer(containerKey, containerName, mounts, options);
105
77
  log.logInfo(`Container ${containerName} replaced from image ${this.image}`);
106
78
  }
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
79
  else if (status === "stopped") {
113
80
  await this.execFileImpl("docker", ["start", containerName]);
114
81
  log.logInfo(`Container ${containerName} started`);
@@ -173,10 +140,10 @@ export class DockerContainerManager {
173
140
  log.logWarning(`Failed to stop container ${containerName}`, errorMessage(err));
174
141
  }
175
142
  }
176
- remove(containerKey, options = {}) {
177
- return this.serialize(containerKey, () => this.removeInner(containerKey, options));
143
+ remove(containerKey) {
144
+ return this.serialize(containerKey, () => this.removeInner(containerKey));
178
145
  }
179
- async removeInner(containerKey, options) {
146
+ async removeInner(containerKey) {
180
147
  const containerName = this.getContainerName(containerKey);
181
148
  const networkName = DockerContainerManager.networkName(containerKey);
182
149
  const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed`, `Failed to remove container ${containerName}`);
@@ -190,9 +157,6 @@ export class DockerContainerManager {
190
157
  catch (err) {
191
158
  log.logWarning(`Failed to remove network ${networkName}`, errorMessage(err));
192
159
  }
193
- await this.removeMigrateSnapshot(containerName);
194
- if (options.purgeHome)
195
- await this.removeHomeVolume(containerKey);
196
160
  this.state.delete(containerKey);
197
161
  this.boostedKeys.delete(containerKey);
198
162
  this.overrideLimits.delete(containerKey);
@@ -207,291 +171,25 @@ export class DockerContainerManager {
207
171
  }
208
172
  await Promise.all(toStop.map((containerKey) => this.stop(containerKey)));
209
173
  }
210
- async removeContainersForConversations(conversationIds) {
211
- if (conversationIds.size === 0)
212
- return;
213
- 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
174
  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);
175
+ const names = await this.listContainerNamesByLabel();
468
176
  this.state.clear();
469
- const inspected = await Promise.all(Array.from(discovered).map(async (containerName) => ({
177
+ const inspected = await Promise.all(names.map(async (containerName) => ({
470
178
  containerName,
471
179
  details: await this.inspectContainerDetails(containerName),
472
180
  })));
473
- const legacyRemovals = [];
474
181
  for (const { containerName, details } of inspected) {
475
182
  if (!details)
476
183
  continue;
477
- if (!details.conversationId) {
478
- legacyRemovals.push(this.removeLegacyContainer(containerName));
479
- continue;
480
- }
481
184
  const containerKey = this.containerKeyFromContainerName(containerName);
482
185
  if (!containerKey) {
483
186
  log.logWarning(`Skipping unmanaged-style container without container key`, containerName);
484
187
  continue;
485
188
  }
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
189
  const status = details.running ? "running" : "stopped";
491
190
  const lastUsed = details.startedAtMs ?? Date.now();
492
191
  this.state.set(containerKey, { status, lastUsed, containerName });
493
192
  }
494
- await Promise.all(legacyRemovals);
495
193
  const running = Array.from(this.state.values()).filter((s) => s.status === "running").length;
496
194
  const stopped = this.state.size - running;
497
195
  log.logInfo(`Reconciled ${this.state.size} managed containers (running=${running}, stopped=${stopped})`);
@@ -511,7 +209,6 @@ export class DockerContainerManager {
511
209
  }
512
210
  async runContainer(containerKey, containerName, mounts, options) {
513
211
  const networkName = await this.ensureNetwork(containerKey);
514
- const homeVolume = await this.ensureHomeVolume(containerKey);
515
212
  log.logInfo(`Creating container ${containerName} from image ${this.image}`);
516
213
  const labels = [
517
214
  "--label",
@@ -542,8 +239,6 @@ export class DockerContainerManager {
542
239
  "1024",
543
240
  ...labels,
544
241
  ...this.resourceLimitArgs(this.effectiveLimits(containerKey)),
545
- "-v",
546
- `${homeVolume}:${GUEST_HOME}`,
547
242
  ...this.mountArgs(mounts),
548
243
  this.image,
549
244
  "sleep",
@@ -593,23 +288,19 @@ export class DockerContainerManager {
593
288
  });
594
289
  }
595
290
  }
596
- async runtimeDrift(containerKey, containerName, mounts, current) {
597
- const { binds, status, homeVolume } = current;
598
- if (this.hasBindMountDrift(binds, mounts))
291
+ async runtimeDrift(containerKey, containerName, mounts, status) {
292
+ if (this.hasBindMountDrift(await this.inspectBindMounts(containerName), mounts))
599
293
  return "binds";
600
294
  if (await this.hasMountSignatureDrift(containerName, mounts))
601
295
  return "mount-content";
602
296
  if (await this.hasNetworkModeDrift(containerKey, containerName))
603
297
  return "network";
604
- if (homeVolume && status === "stopped" && (await this.hasImageDrift(containerName))) {
298
+ if (status === "stopped" && (await this.hasImageDrift(containerName)))
605
299
  return "image";
606
- }
607
300
  return undefined;
608
301
  }
609
302
  hasBindMountDrift(binds, mounts) {
610
- const expected = this.expectedBinds(mounts);
611
- const actual = binds.filter((bind) => !this.isHomeVolumeBind(bind));
612
- return !this.sameBinds(expected, actual);
303
+ return !this.sameBinds(this.expectedBinds(mounts), [...binds]);
613
304
  }
614
305
  expectedBinds(mounts) {
615
306
  return mounts
@@ -675,21 +366,6 @@ export class DockerContainerManager {
675
366
  }
676
367
  return [...parsed].toSorted();
677
368
  }
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
369
  async hasImageDrift(containerName) {
694
370
  const desired = await this.localImageId();
695
371
  if (!desired)
@@ -717,107 +393,12 @@ export class DockerContainerManager {
717
393
  return undefined;
718
394
  }
719
395
  }
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) {
396
+ async replaceContainer(containerKey, containerName, mounts, options) {
746
397
  const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed for replacement`, `Failed to remove container ${containerName} for replacement`);
747
398
  if (!removed)
748
399
  throw new Error(`Failed to remove container ${containerName} for replacement`);
749
400
  await this.runContainer(containerKey, containerName, mounts, options);
750
401
  }
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
402
  async hasNetworkModeDrift(containerKey, containerName) {
822
403
  const expected = DockerContainerManager.networkName(containerKey);
823
404
  const { stdout } = await this.execFileImpl("docker", [
@@ -888,23 +469,6 @@ export class DockerContainerManager {
888
469
  return [];
889
470
  }
890
471
  }
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
472
  parseNameLines(stdout) {
909
473
  return stdout
910
474
  .split("\n")
@@ -964,8 +528,5 @@ export class DockerContainerManager {
964
528
  return false;
965
529
  }
966
530
  }
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
- }
970
531
  }
971
532
  //# sourceMappingURL=provisioner.js.map