@geminixiang/mikan 1.0.0-beta.62 → 1.0.0-beta.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +13 -13
- package/dist/adapters/commands/sandbox.d.ts.map +1 -1
- package/dist/adapters/commands/sandbox.js +30 -53
- package/dist/adapters/commands/sandbox.js.map +1 -1
- package/dist/adapters/commands/types.d.ts +3 -3
- package/dist/adapters/commands/types.d.ts.map +1 -1
- package/dist/adapters/commands/types.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +8 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +32 -2
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/web/admin/portal.d.ts.map +1 -1
- package/dist/adapters/web/admin/portal.js +28 -121
- package/dist/adapters/web/admin/portal.js.map +1 -1
- package/dist/config.d.ts +13 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -50
- package/dist/config.js.map +1 -1
- package/dist/harness/execution-resolver.d.ts.map +1 -1
- package/dist/harness/execution-resolver.js +1 -1
- package/dist/harness/execution-resolver.js.map +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +17 -21
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +2 -2
- package/dist/harness/runner.js.map +1 -1
- package/dist/office/projection.d.ts +19 -2
- package/dist/office/projection.d.ts.map +1 -1
- package/dist/office/projection.js +73 -98
- package/dist/office/projection.js.map +1 -1
- package/dist/office/types.d.ts +13 -8
- package/dist/office/types.d.ts.map +1 -1
- package/dist/office/types.js.map +1 -1
- package/dist/sandbox/index.d.ts +9 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +16 -12
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/provisioner.d.ts +8 -0
- package/dist/sandbox/provisioner.d.ts.map +1 -1
- package/dist/sandbox/provisioner.js +36 -0
- package/dist/sandbox/provisioner.js.map +1 -1
- package/dist/settings-mutation.d.ts +5 -29
- package/dist/settings-mutation.d.ts.map +1 -1
- package/dist/settings-mutation.js +7 -13
- package/dist/settings-mutation.js.map +1 -1
- package/dist/types.d.ts +5 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { atomicWritePrivateFile, ensureDirExists } from "../file-guards.js";
|
|
4
|
-
import {
|
|
4
|
+
import { loadLegacyFullOverride, loadOfficeVisibilityOverride } from "../config.js";
|
|
5
|
+
import { listRegisteredOffices } from "./index.js";
|
|
5
6
|
import * as log from "../log.js";
|
|
6
7
|
const CHANNEL_KIND_FILE = "channel-kind";
|
|
8
|
+
const reportedLegacyFull = new Set();
|
|
7
9
|
const CHANNEL_KINDS = [
|
|
8
10
|
"public_channel",
|
|
9
11
|
"private_channel",
|
|
@@ -35,7 +37,7 @@ export function readPlatformChannelKind(office) {
|
|
|
35
37
|
catch (err) {
|
|
36
38
|
if (err.code === "ENOENT")
|
|
37
39
|
return undefined;
|
|
38
|
-
log.logWarning("Could not read platform channel kind;
|
|
40
|
+
log.logWarning("Could not read platform channel kind; treating the office as private", `${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
39
41
|
return undefined;
|
|
40
42
|
}
|
|
41
43
|
const value = raw.trim();
|
|
@@ -43,119 +45,92 @@ export function readPlatformChannelKind(office) {
|
|
|
43
45
|
? value
|
|
44
46
|
: undefined;
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* One dimension, derived from the platform (ADR 0008): public channels are
|
|
50
|
+
* public; private channels, DMs, group DMs, and externally shared
|
|
51
|
+
* conversations are private; an unknown kind fails closed to private. An
|
|
52
|
+
* operator may narrow a public conversation to private, never the reverse.
|
|
53
|
+
* Legacy door-policy settings are not consulted.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveOfficeVisibility(office) {
|
|
56
|
+
if (loadOfficeVisibilityOverride(office) === "private") {
|
|
57
|
+
return { visibility: "private", source: "override" };
|
|
58
|
+
}
|
|
59
|
+
const kind = readPlatformChannelKind(office);
|
|
60
|
+
if (kind === undefined)
|
|
61
|
+
return { visibility: "private", source: "unknown" };
|
|
62
|
+
return { visibility: kind === "public_channel" ? "public" : "private", source: "platform" };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every public office other than `self`, as read-only mounts under
|
|
66
|
+
* `/workspace/public/<key>`. Bind mounts rather than a symlink directory:
|
|
67
|
+
* symlinks would resolve against the container's own filesystem, where host
|
|
68
|
+
* paths do not exist. A public channel appearing or changing visibility
|
|
69
|
+
* changes the mount signature, so affected containers rebuild on their next
|
|
70
|
+
* message with contents preserved.
|
|
71
|
+
*/
|
|
72
|
+
function publicOfficeMounts(self) {
|
|
73
|
+
const { workspace } = self;
|
|
74
|
+
const mounts = [];
|
|
75
|
+
for (const record of listRegisteredOffices(workspace.stateDir)) {
|
|
76
|
+
const other = workspace.office(record);
|
|
77
|
+
if (other.key === self.key || !exists(other.dir))
|
|
78
|
+
continue;
|
|
79
|
+
if (resolveOfficeVisibility(other).visibility !== "public")
|
|
80
|
+
continue;
|
|
81
|
+
mounts.push({ source: other.dir, target: `/workspace/public/${other.key}`, readOnly: true });
|
|
82
|
+
}
|
|
83
|
+
return mounts.toSorted((a, b) => a.target.localeCompare(b.target));
|
|
84
|
+
}
|
|
46
85
|
/**
|
|
47
86
|
* The single policy seam for a managed office. It both materializes the
|
|
48
87
|
* host-side roots and authorizes the prompt sources that describe them.
|
|
49
|
-
*
|
|
88
|
+
* Every office gets the same shape (ADR 0008): its own directory read-write,
|
|
89
|
+
* every other public office read-only under /workspace/public, and the
|
|
90
|
+
* workspace-global knowledge read-write for public offices or read-only for
|
|
91
|
+
* private ones. No layout mounts the workspace root.
|
|
50
92
|
*/
|
|
51
93
|
export function resolveWorkspaceProjection(office) {
|
|
52
94
|
const { workspace } = office;
|
|
53
|
-
const
|
|
95
|
+
const decision = resolveOfficeVisibility(office);
|
|
96
|
+
const readOnlyKnowledge = decision.visibility === "private";
|
|
54
97
|
assertDirectory(workspace.root, "Host workspace root");
|
|
55
98
|
office.ensure();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
99
|
+
ensureRegularFile(workspace.memoryPath, "Workspace memory");
|
|
100
|
+
ensureDirectoryRoot(workspace.skillsDir, "Workspace skills");
|
|
101
|
+
const ro = { readOnly: true };
|
|
102
|
+
const legacyFull = loadLegacyFullOverride(office);
|
|
103
|
+
if (legacyFull && !reportedLegacyFull.has(office.key)) {
|
|
104
|
+
reportedLegacyFull.add(office.key);
|
|
105
|
+
log.logWarning(`Office ${office.key} still declares the retired "full" door policy`, `visibility is now ${decision.visibility} (${decision.source}); other offices are reachable only through /workspace/public`);
|
|
60
106
|
}
|
|
61
|
-
// Private visibility only changes anything for shared-support: the shared
|
|
62
|
-
// MEMORY.md is the one file that leaks information between offices when
|
|
63
|
-
// writable, so a private office reads it but cannot write it (modeled on
|
|
64
|
-
// Claude Tag's private-channel memory). "full" mounts the whole workspace
|
|
65
|
-
// as one read-write bind with no separate memory file to gate, and
|
|
66
|
-
// "conversation" never sees workspace memory at all.
|
|
67
|
-
//
|
|
68
|
-
// The `readOnly` mount flag below is a kernel-enforced boundary only for
|
|
69
|
-
// backends with managed Workspace projection. Runner and actor execution
|
|
70
|
-
// resolution reject every other backend before the agent can start.
|
|
71
|
-
const globalMemoryReadOnly = shared && effective.visibility === "private";
|
|
72
|
-
const mounts = effective.layout === "full"
|
|
73
|
-
? [{ source: workspace.root, target: "/workspace" }]
|
|
74
|
-
: effective.layout === "shared-support"
|
|
75
|
-
? [
|
|
76
|
-
{
|
|
77
|
-
source: workspace.memoryPath,
|
|
78
|
-
target: "/workspace/MEMORY.md",
|
|
79
|
-
...(globalMemoryReadOnly ? { readOnly: true } : {}),
|
|
80
|
-
},
|
|
81
|
-
{ source: workspace.skillsDir, target: "/workspace/skills" },
|
|
82
|
-
{ source: office.dir, target: `/workspace/${office.key}` },
|
|
83
|
-
]
|
|
84
|
-
: [{ source: office.dir, target: `/workspace/${office.key}` }];
|
|
85
107
|
return {
|
|
86
|
-
...
|
|
87
|
-
|
|
108
|
+
...decision,
|
|
109
|
+
legacyFull,
|
|
110
|
+
mounts: [
|
|
111
|
+
{ source: office.dir, target: `/workspace/${office.key}` },
|
|
112
|
+
{
|
|
113
|
+
source: workspace.memoryPath,
|
|
114
|
+
target: "/workspace/MEMORY.md",
|
|
115
|
+
...(readOnlyKnowledge ? ro : {}),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
source: workspace.skillsDir,
|
|
119
|
+
target: "/workspace/skills",
|
|
120
|
+
...(readOnlyKnowledge ? ro : {}),
|
|
121
|
+
},
|
|
122
|
+
...publicOfficeMounts(office),
|
|
123
|
+
],
|
|
88
124
|
promptSources: {
|
|
89
125
|
conversationDir: office.dir,
|
|
90
126
|
conversationMemoryPath: office.memoryPath,
|
|
91
127
|
conversationSkillsDir: office.skillsDir,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
globalSkillsDir: workspace.skillsDir,
|
|
96
|
-
...(globalMemoryReadOnly ? { globalMemoryReadOnly: true } : {}),
|
|
97
|
-
}
|
|
98
|
-
: {}),
|
|
128
|
+
globalMemoryPath: workspace.memoryPath,
|
|
129
|
+
globalSkillsDir: workspace.skillsDir,
|
|
130
|
+
...(readOnlyKnowledge ? { globalKnowledgeReadOnly: true } : {}),
|
|
99
131
|
},
|
|
100
132
|
};
|
|
101
133
|
}
|
|
102
|
-
function resolveEffectiveWorkspace(office) {
|
|
103
|
-
try {
|
|
104
|
-
const settings = resolveConversationSettings(office).sandbox;
|
|
105
|
-
if (settings?.workspace) {
|
|
106
|
-
return normalizeWorkspace(settings.workspace.doorPolicy, settings.workspace.layout, settings.workspace.visibility);
|
|
107
|
-
}
|
|
108
|
-
if (settings?.image?.workspaceMount) {
|
|
109
|
-
return legacyWorkspace(settings.image.workspaceMount);
|
|
110
|
-
}
|
|
111
|
-
return platformDerivedWorkspace(office);
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
// Settings are host-authoritative. A malformed file must not fall back to
|
|
115
|
-
// an unvalidated value from a mounted legacy location.
|
|
116
|
-
log.logWarning("Refusing to resolve workspace projection from malformed settings", err instanceof Error ? err.message : String(err));
|
|
117
|
-
throw new Error("Cannot resolve workspace projection: settings are malformed", { cause: err });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* No explicit workspace setting anywhere: follow the platform's own channel
|
|
122
|
-
* vocabulary (modeled on Claude Tag). A public channel's knowledge is shared
|
|
123
|
-
* workspace-wide read-write; a private channel reads the shared pool but
|
|
124
|
-
* writes only its own conversation memory; DMs, externally shared channels,
|
|
125
|
-
* and conversations whose kind was never observed stay fully isolated —
|
|
126
|
-
* platform-public is a necessary condition for sharing, never assumed.
|
|
127
|
-
* An admin's explicit setting (conversation or global) always wins over this.
|
|
128
|
-
*/
|
|
129
|
-
function platformDerivedWorkspace(office) {
|
|
130
|
-
switch (readPlatformChannelKind(office)) {
|
|
131
|
-
case "public_channel":
|
|
132
|
-
return { doorPolicy: "trusted", layout: "shared-support", visibility: "public" };
|
|
133
|
-
case "private_channel":
|
|
134
|
-
return { doorPolicy: "trusted", layout: "shared-support", visibility: "private" };
|
|
135
|
-
default:
|
|
136
|
-
return { doorPolicy: "isolated", layout: "conversation", visibility: "public" };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function normalizeWorkspace(doorPolicy, layout, visibility) {
|
|
140
|
-
const policy = doorPolicy ?? "isolated";
|
|
141
|
-
if (policy === "isolated") {
|
|
142
|
-
return { doorPolicy: policy, layout: "conversation", visibility: "public" };
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
doorPolicy: policy,
|
|
146
|
-
layout: layout === "full" ? "full" : "shared-support",
|
|
147
|
-
// Missing visibility defaults to "public": every office that predates this
|
|
148
|
-
// setting keeps today's read-write shared MEMORY.md behavior unchanged.
|
|
149
|
-
visibility: visibility ?? "public",
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function legacyWorkspace(mode) {
|
|
153
|
-
return mode === "full"
|
|
154
|
-
? { doorPolicy: "trusted", layout: "full", visibility: "public" }
|
|
155
|
-
: mode === "private"
|
|
156
|
-
? { doorPolicy: "trusted", layout: "shared-support", visibility: "public" }
|
|
157
|
-
: { doorPolicy: "isolated", layout: "conversation", visibility: "public" };
|
|
158
|
-
}
|
|
159
134
|
function ensureDirectoryRoot(path, label) {
|
|
160
135
|
if (!exists(path)) {
|
|
161
136
|
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/office/projection.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport { lstatSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { atomicWritePrivateFile, ensureDirExists } from \"../file-guards.js\";\nimport { resolveConversationSettings } from \"../config.js\";\nimport type { Office } from \"./index.js\";\nimport * as log from \"../log.js\";\nimport type {\n ImageWorkspaceMountMode,\n WorkspaceDoorPolicy,\n WorkspaceLayout,\n WorkspaceVisibility,\n} from \"../types.js\";\nimport type { PlatformChannelKind, WorkspaceProjection } from \"./types.js\";\n\nexport type { PlatformChannelKind, WorkspaceProjection } from \"./types.js\";\n\nconst CHANNEL_KIND_FILE = \"channel-kind\";\nconst CHANNEL_KINDS: readonly PlatformChannelKind[] = [\n \"public_channel\",\n \"private_channel\",\n \"im\",\n \"external\",\n];\n\n/**\n * Record the platform's own channel kind for this conversation, as observed\n * by the adapter at message intake. Stored under the host-only office state\n * dir (never the sandbox-mounted workspace), so sandboxed code cannot\n * promote its own conversation into the shared pool. Written only on change;\n * the value is a snapshot as of the last message, which is exactly the\n * freshness the projection needs — a conversation that never speaks again\n * never needs a fresher value.\n */\nexport function recordPlatformChannelKind(office: Office, kind: PlatformChannelKind): void {\n if (readPlatformChannelKind(office) === kind) return;\n ensureDirExists(office.stateDir);\n atomicWritePrivateFile(join(office.stateDir, CHANNEL_KIND_FILE), kind + \"\\n\");\n}\n\n/** The recorded platform channel kind, or undefined when never observed. */\nexport function readPlatformChannelKind(office: Office): PlatformChannelKind | undefined {\n const path = join(office.stateDir, CHANNEL_KIND_FILE);\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n log.logWarning(\n \"Could not read platform channel kind; falling back to isolated workspace\",\n `${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return undefined;\n }\n const value = raw.trim();\n return (CHANNEL_KINDS as readonly string[]).includes(value)\n ? (value as PlatformChannelKind)\n : undefined;\n}\n\n/**\n * The single policy seam for a managed office. It both materializes the\n * host-side roots and authorizes the prompt sources that describe them.\n * Legacy image settings are deliberately translated here, never at callers.\n */\nexport function resolveWorkspaceProjection(office: Office): WorkspaceProjection {\n const { workspace } = office;\n const effective = resolveEffectiveWorkspace(office);\n\n assertDirectory(workspace.root, \"Host workspace root\");\n office.ensure();\n const shared = effective.layout === \"shared-support\";\n if (shared) {\n ensureRegularFile(workspace.memoryPath, \"Workspace memory\");\n ensureDirectoryRoot(workspace.skillsDir, \"Workspace skills\");\n }\n\n // Private visibility only changes anything for shared-support: the shared\n // MEMORY.md is the one file that leaks information between offices when\n // writable, so a private office reads it but cannot write it (modeled on\n // Claude Tag's private-channel memory). \"full\" mounts the whole workspace\n // as one read-write bind with no separate memory file to gate, and\n // \"conversation\" never sees workspace memory at all.\n //\n // The `readOnly` mount flag below is a kernel-enforced boundary only for\n // backends with managed Workspace projection. Runner and actor execution\n // resolution reject every other backend before the agent can start.\n const globalMemoryReadOnly = shared && effective.visibility === \"private\";\n const mounts =\n effective.layout === \"full\"\n ? [{ source: workspace.root, target: \"/workspace\" }]\n : effective.layout === \"shared-support\"\n ? [\n {\n source: workspace.memoryPath,\n target: \"/workspace/MEMORY.md\",\n ...(globalMemoryReadOnly ? { readOnly: true as const } : {}),\n },\n { source: workspace.skillsDir, target: \"/workspace/skills\" },\n { source: office.dir, target: `/workspace/${office.key}` },\n ]\n : [{ source: office.dir, target: `/workspace/${office.key}` }];\n\n return {\n ...effective,\n mounts,\n promptSources: {\n conversationDir: office.dir,\n conversationMemoryPath: office.memoryPath,\n conversationSkillsDir: office.skillsDir,\n ...(effective.layout === \"shared-support\" || effective.layout === \"full\"\n ? {\n globalMemoryPath: workspace.memoryPath,\n globalSkillsDir: workspace.skillsDir,\n ...(globalMemoryReadOnly ? { globalMemoryReadOnly: true } : {}),\n }\n : {}),\n },\n };\n}\n\nfunction resolveEffectiveWorkspace(\n office: Office,\n): Pick<WorkspaceProjection, \"doorPolicy\" | \"layout\" | \"visibility\"> {\n try {\n const settings = resolveConversationSettings(office).sandbox;\n if (settings?.workspace) {\n return normalizeWorkspace(\n settings.workspace.doorPolicy,\n settings.workspace.layout,\n settings.workspace.visibility,\n );\n }\n if (settings?.image?.workspaceMount) {\n return legacyWorkspace(settings.image.workspaceMount);\n }\n return platformDerivedWorkspace(office);\n } catch (err) {\n // Settings are host-authoritative. A malformed file must not fall back to\n // an unvalidated value from a mounted legacy location.\n log.logWarning(\n \"Refusing to resolve workspace projection from malformed settings\",\n err instanceof Error ? err.message : String(err),\n );\n throw new Error(\"Cannot resolve workspace projection: settings are malformed\", { cause: err });\n }\n}\n\n/**\n * No explicit workspace setting anywhere: follow the platform's own channel\n * vocabulary (modeled on Claude Tag). A public channel's knowledge is shared\n * workspace-wide read-write; a private channel reads the shared pool but\n * writes only its own conversation memory; DMs, externally shared channels,\n * and conversations whose kind was never observed stay fully isolated —\n * platform-public is a necessary condition for sharing, never assumed.\n * An admin's explicit setting (conversation or global) always wins over this.\n */\nfunction platformDerivedWorkspace(\n office: Office,\n): Pick<WorkspaceProjection, \"doorPolicy\" | \"layout\" | \"visibility\"> {\n switch (readPlatformChannelKind(office)) {\n case \"public_channel\":\n return { doorPolicy: \"trusted\", layout: \"shared-support\", visibility: \"public\" };\n case \"private_channel\":\n return { doorPolicy: \"trusted\", layout: \"shared-support\", visibility: \"private\" };\n default:\n return { doorPolicy: \"isolated\", layout: \"conversation\", visibility: \"public\" };\n }\n}\n\nfunction normalizeWorkspace(\n doorPolicy: WorkspaceDoorPolicy | undefined,\n layout: WorkspaceLayout | undefined,\n visibility: WorkspaceVisibility | undefined,\n): Pick<WorkspaceProjection, \"doorPolicy\" | \"layout\" | \"visibility\"> {\n const policy = doorPolicy ?? \"isolated\";\n if (policy === \"isolated\") {\n return { doorPolicy: policy, layout: \"conversation\", visibility: \"public\" };\n }\n return {\n doorPolicy: policy,\n layout: layout === \"full\" ? \"full\" : \"shared-support\",\n // Missing visibility defaults to \"public\": every office that predates this\n // setting keeps today's read-write shared MEMORY.md behavior unchanged.\n visibility: visibility ?? \"public\",\n };\n}\n\nfunction legacyWorkspace(\n mode: ImageWorkspaceMountMode | undefined,\n): Pick<WorkspaceProjection, \"doorPolicy\" | \"layout\" | \"visibility\"> {\n return mode === \"full\"\n ? { doorPolicy: \"trusted\", layout: \"full\", visibility: \"public\" }\n : mode === \"private\"\n ? { doorPolicy: \"trusted\", layout: \"shared-support\", visibility: \"public\" }\n : { doorPolicy: \"isolated\", layout: \"conversation\", visibility: \"public\" };\n}\n\nfunction ensureDirectoryRoot(path: string, label: string): void {\n if (!exists(path)) {\n mkdirSync(path, { recursive: true, mode: 0o700 });\n return;\n }\n assertDirectory(path, label);\n}\n\nfunction ensureRegularFile(path: string, label: string): void {\n if (!exists(path)) {\n ensureDirExists(dirname(path));\n writeFileSync(path, \"\", { mode: 0o600, flag: \"wx\" });\n return;\n }\n let stats;\n try {\n stats = lstatSync(path);\n } catch (err) {\n throw new Error(`${label} cannot be inspected: ${path}`, { cause: err });\n }\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new Error(`${label} must be a regular non-symlink file: ${path}`);\n }\n}\n\nfunction assertDirectory(path: string, label: string): void {\n let stats;\n try {\n stats = lstatSync(path);\n } catch (err) {\n throw new Error(`${label} cannot be inspected: ${path}`, { cause: err });\n }\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new Error(`${label} must be a regular non-symlink directory: ${path}`);\n }\n}\n\nfunction exists(path: string): boolean {\n try {\n lstatSync(path);\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw err;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/office/projection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAe,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,GAAG,MAAM,WAAW,CAAC;AAMjC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AACzC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAU,CAAC;AAC7C,MAAM,aAAa,GAAmC;IACpD,gBAAgB;IAChB,iBAAiB;IACjB,IAAI;IACJ,UAAU;CACX,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAc,EAAE,IAAyB;IACjF,IAAI,uBAAuB,CAAC,MAAM,CAAC,KAAK,IAAI;QAAE,OAAO;IACrD,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;AAChF,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IACtD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACvE,GAAG,CAAC,UAAU,CACZ,sEAAsE,EACtE,GAAG,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/D,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,OAAQ,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC;QACzD,CAAC,CAAE,KAA6B;QAChC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,IAAI,4BAA4B,CAAC,MAAM,CAAC,KAAK,SAAS,EAAE,CAAC;QACvD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,IAAI,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC5E,OAAO,EAAE,UAAU,EAAE,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAC9F,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3B,MAAM,MAAM,GAAqB,EAAE,CAAC;IACpC,KAAK,MAAM,MAAM,IAAI,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;YAAE,SAAS;QAC3D,IAAI,uBAAuB,CAAC,KAAK,CAAC,CAAC,UAAU,KAAK,QAAQ;YAAE,SAAS;QACrE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,qBAAqB,KAAK,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAc;IACvD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAC7B,MAAM,QAAQ,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC;IAE5D,eAAe,CAAC,SAAS,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IACvD,MAAM,CAAC,MAAM,EAAE,CAAC;IAChB,iBAAiB,CAAC,SAAS,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;IAC5D,mBAAmB,CAAC,SAAS,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAa,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,UAAU,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACtD,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnC,GAAG,CAAC,UAAU,CACZ,UAAU,MAAM,CAAC,GAAG,gDAAgD,EACpE,qBAAqB,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,MAAM,+DAA+D,CAC5H,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,QAAQ;QACX,UAAU;QACV,MAAM,EAAE;YACN,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,MAAM,CAAC,GAAG,EAAE,EAAE;YAC1D;gBACE,MAAM,EAAE,SAAS,CAAC,UAAU;gBAC5B,MAAM,EAAE,sBAAsB;gBAC9B,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjC;YACD;gBACE,MAAM,EAAE,SAAS,CAAC,SAAS;gBAC3B,MAAM,EAAE,mBAAmB;gBAC3B,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjC;YACD,GAAG,kBAAkB,CAAC,MAAM,CAAC;SAC9B;QACD,aAAa,EAAE;YACb,eAAe,EAAE,MAAM,CAAC,GAAG;YAC3B,sBAAsB,EAAE,MAAM,CAAC,UAAU;YACzC,qBAAqB,EAAE,MAAM,CAAC,SAAS;YACvC,gBAAgB,EAAE,SAAS,CAAC,UAAU;YACtC,eAAe,EAAE,SAAS,CAAC,SAAS;YACpC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,uBAAuB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChE;KACF,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY,EAAE,KAAa;IACtD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,OAAO;IACT,CAAC;IACD,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,KAAa;IACpD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/B,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,OAAO;IACT,CAAC;IACD,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,yBAAyB,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,wCAAwC,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,KAAa;IAClD,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,yBAAyB,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,6CAA6C,IAAI,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACnE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC","sourcesContent":["import { dirname, join } from \"node:path\";\nimport { lstatSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { atomicWritePrivateFile, ensureDirExists } from \"../file-guards.js\";\nimport { loadLegacyFullOverride, loadOfficeVisibilityOverride } from \"../config.js\";\nimport { listRegisteredOffices, type Office } from \"./index.js\";\nimport * as log from \"../log.js\";\nimport type { ContainerMount, WorkspaceVisibility } from \"../types.js\";\nimport type { PlatformChannelKind, WorkspaceProjection } from \"./types.js\";\n\nexport type { PlatformChannelKind, WorkspaceProjection } from \"./types.js\";\n\nconst CHANNEL_KIND_FILE = \"channel-kind\";\nconst reportedLegacyFull = new Set<string>();\nconst CHANNEL_KINDS: readonly PlatformChannelKind[] = [\n \"public_channel\",\n \"private_channel\",\n \"im\",\n \"external\",\n];\n\n/**\n * Record the platform's own channel kind for this conversation, as observed\n * by the adapter at message intake. Stored under the host-only office state\n * dir (never the sandbox-mounted workspace), so sandboxed code cannot\n * promote its own conversation into the shared pool. Written only on change;\n * the value is a snapshot as of the last message, which is exactly the\n * freshness the projection needs — a conversation that never speaks again\n * never needs a fresher value.\n */\nexport function recordPlatformChannelKind(office: Office, kind: PlatformChannelKind): void {\n if (readPlatformChannelKind(office) === kind) return;\n ensureDirExists(office.stateDir);\n atomicWritePrivateFile(join(office.stateDir, CHANNEL_KIND_FILE), kind + \"\\n\");\n}\n\n/** The recorded platform channel kind, or undefined when never observed. */\nexport function readPlatformChannelKind(office: Office): PlatformChannelKind | undefined {\n const path = join(office.stateDir, CHANNEL_KIND_FILE);\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n log.logWarning(\n \"Could not read platform channel kind; treating the office as private\",\n `${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return undefined;\n }\n const value = raw.trim();\n return (CHANNEL_KINDS as readonly string[]).includes(value)\n ? (value as PlatformChannelKind)\n : undefined;\n}\n\n/** Where an office's visibility came from; shown to operators instead of raw values. */\nexport interface OfficeVisibilityDecision {\n visibility: WorkspaceVisibility;\n source: \"platform\" | \"override\" | \"unknown\";\n}\n\n/**\n * One dimension, derived from the platform (ADR 0008): public channels are\n * public; private channels, DMs, group DMs, and externally shared\n * conversations are private; an unknown kind fails closed to private. An\n * operator may narrow a public conversation to private, never the reverse.\n * Legacy door-policy settings are not consulted.\n */\nexport function resolveOfficeVisibility(office: Office): OfficeVisibilityDecision {\n if (loadOfficeVisibilityOverride(office) === \"private\") {\n return { visibility: \"private\", source: \"override\" };\n }\n const kind = readPlatformChannelKind(office);\n if (kind === undefined) return { visibility: \"private\", source: \"unknown\" };\n return { visibility: kind === \"public_channel\" ? \"public\" : \"private\", source: \"platform\" };\n}\n\n/**\n * Every public office other than `self`, as read-only mounts under\n * `/workspace/public/<key>`. Bind mounts rather than a symlink directory:\n * symlinks would resolve against the container's own filesystem, where host\n * paths do not exist. A public channel appearing or changing visibility\n * changes the mount signature, so affected containers rebuild on their next\n * message with contents preserved.\n */\nfunction publicOfficeMounts(self: Office): ContainerMount[] {\n const { workspace } = self;\n const mounts: ContainerMount[] = [];\n for (const record of listRegisteredOffices(workspace.stateDir)) {\n const other = workspace.office(record);\n if (other.key === self.key || !exists(other.dir)) continue;\n if (resolveOfficeVisibility(other).visibility !== \"public\") continue;\n mounts.push({ source: other.dir, target: `/workspace/public/${other.key}`, readOnly: true });\n }\n return mounts.toSorted((a, b) => a.target.localeCompare(b.target));\n}\n\n/**\n * The single policy seam for a managed office. It both materializes the\n * host-side roots and authorizes the prompt sources that describe them.\n * Every office gets the same shape (ADR 0008): its own directory read-write,\n * every other public office read-only under /workspace/public, and the\n * workspace-global knowledge read-write for public offices or read-only for\n * private ones. No layout mounts the workspace root.\n */\nexport function resolveWorkspaceProjection(office: Office): WorkspaceProjection {\n const { workspace } = office;\n const decision = resolveOfficeVisibility(office);\n const readOnlyKnowledge = decision.visibility === \"private\";\n\n assertDirectory(workspace.root, \"Host workspace root\");\n office.ensure();\n ensureRegularFile(workspace.memoryPath, \"Workspace memory\");\n ensureDirectoryRoot(workspace.skillsDir, \"Workspace skills\");\n const ro = { readOnly: true as const };\n const legacyFull = loadLegacyFullOverride(office);\n if (legacyFull && !reportedLegacyFull.has(office.key)) {\n reportedLegacyFull.add(office.key);\n log.logWarning(\n `Office ${office.key} still declares the retired \"full\" door policy`,\n `visibility is now ${decision.visibility} (${decision.source}); other offices are reachable only through /workspace/public`,\n );\n }\n\n return {\n ...decision,\n legacyFull,\n mounts: [\n { source: office.dir, target: `/workspace/${office.key}` },\n {\n source: workspace.memoryPath,\n target: \"/workspace/MEMORY.md\",\n ...(readOnlyKnowledge ? ro : {}),\n },\n {\n source: workspace.skillsDir,\n target: \"/workspace/skills\",\n ...(readOnlyKnowledge ? ro : {}),\n },\n ...publicOfficeMounts(office),\n ],\n promptSources: {\n conversationDir: office.dir,\n conversationMemoryPath: office.memoryPath,\n conversationSkillsDir: office.skillsDir,\n globalMemoryPath: workspace.memoryPath,\n globalSkillsDir: workspace.skillsDir,\n ...(readOnlyKnowledge ? { globalKnowledgeReadOnly: true } : {}),\n },\n };\n}\n\nfunction ensureDirectoryRoot(path: string, label: string): void {\n if (!exists(path)) {\n mkdirSync(path, { recursive: true, mode: 0o700 });\n return;\n }\n assertDirectory(path, label);\n}\n\nfunction ensureRegularFile(path: string, label: string): void {\n if (!exists(path)) {\n ensureDirExists(dirname(path));\n writeFileSync(path, \"\", { mode: 0o600, flag: \"wx\" });\n return;\n }\n let stats;\n try {\n stats = lstatSync(path);\n } catch (err) {\n throw new Error(`${label} cannot be inspected: ${path}`, { cause: err });\n }\n if (stats.isSymbolicLink() || !stats.isFile()) {\n throw new Error(`${label} must be a regular non-symlink file: ${path}`);\n }\n}\n\nfunction assertDirectory(path: string, label: string): void {\n let stats;\n try {\n stats = lstatSync(path);\n } catch (err) {\n throw new Error(`${label} cannot be inspected: ${path}`, { cause: err });\n }\n if (stats.isSymbolicLink() || !stats.isDirectory()) {\n throw new Error(`${label} must be a regular non-symlink directory: ${path}`);\n }\n}\n\nfunction exists(path: string): boolean {\n try {\n lstatSync(path);\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw err;\n }\n}\n"]}
|
package/dist/office/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContainerMount, OfficeAddress, OfficeKey,
|
|
1
|
+
import type { ContainerMount, OfficeAddress, OfficeKey, WorkspaceVisibility } from "../types.js";
|
|
2
2
|
export interface OfficeMigrationRunSummary {
|
|
3
3
|
/** Raw ids whose directories moved to the office-key layout this run. */
|
|
4
4
|
migrated: string[];
|
|
@@ -97,16 +97,21 @@ interface WorkspacePromptSources {
|
|
|
97
97
|
conversationDir: string;
|
|
98
98
|
conversationMemoryPath: string;
|
|
99
99
|
conversationSkillsDir: string;
|
|
100
|
-
globalMemoryPath
|
|
101
|
-
globalSkillsDir
|
|
102
|
-
/** True
|
|
103
|
-
|
|
100
|
+
globalMemoryPath: string;
|
|
101
|
+
globalSkillsDir: string;
|
|
102
|
+
/** True for private offices: global MEMORY.md and skills are mounted read-only. */
|
|
103
|
+
globalKnowledgeReadOnly?: boolean;
|
|
104
104
|
}
|
|
105
105
|
export interface WorkspaceProjection {
|
|
106
|
-
doorPolicy: WorkspaceDoorPolicy;
|
|
107
|
-
layout: WorkspaceLayout;
|
|
108
|
-
/** Only meaningful for shared-support layout; "public" is the default and preserves prior read-write behavior. */
|
|
109
106
|
visibility: WorkspaceVisibility;
|
|
107
|
+
/** Where the visibility came from, for operator display. */
|
|
108
|
+
source: "platform" | "override" | "unknown";
|
|
109
|
+
/**
|
|
110
|
+
* The office still declares the retired `full` door policy. Observation
|
|
111
|
+
* only: it no longer widens mounts, but cross-office access is logged so
|
|
112
|
+
* the migration can be verified before the setting is dropped (ADR 0008).
|
|
113
|
+
*/
|
|
114
|
+
legacyFull: boolean;
|
|
110
115
|
mounts: ContainerMount[];
|
|
111
116
|
promptSources: WorkspacePromptSources;
|
|
112
117
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/office/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/office/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEjG,MAAM,WAAW,yBAAyB;IACxC,yEAAyE;IACzE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,uEAAuE;IACvE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iEAAiE;IACjE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,yEAAyE;IACzE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,gFAAgF;IAChF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,gFAAgF;IAChF,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAAC;CACxC;AAED;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B;;;;;OAKG;IACH,MAAM,IAAI,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,IAAI,GAAG,UAAU,CAAC;AAE3F,UAAU,sBAAsB;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,mBAAmB,CAAC;IAChC,4DAA4D;IAC5D,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,aAAa,EAAE,sBAAsB,CAAC;CACvC"}
|
package/dist/office/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/office/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/office/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ContainerMount, OfficeAddress, OfficeKey, WorkspaceVisibility } from \"../types.js\";\n\nexport interface OfficeMigrationRunSummary {\n /** Raw ids whose directories moved to the office-key layout this run. */\n migrated: string[];\n /** Raw ids whose interrupted moves were completed from the journal. */\n recovered: string[];\n /** Raw ids that still need an explicit owner (`mikan office claim`). */\n unowned: string[];\n /** Raw ids whose records are failed and need operator repair. */\n failed: string[];\n /** Raw ids whose conversation vault directories moved to office keys. */\n vaultKeysMigrated: string[];\n /** Raw ids whose legacy and office-key vault dirs both exist (manual merge). */\n vaultConflicts: string[];\n /** Raw ids whose host state directories moved. */\n stateDirsMigrated: string[];\n /** Raw ids whose legacy and office-key state dirs both exist (manual merge). */\n stateDirConflicts: string[];\n}\n\n/**\n * One mikan deployment's agent world on the host: the workspace root, the\n * workspace-global surfaces, and the factory for per-conversation offices.\n * Constructed once per process (`createWorkspace`); everything downstream\n * receives the value instead of re-deriving paths from root strings.\n */\nexport interface Workspace {\n /** Host workspace root directory. */\n readonly root: string;\n /**\n * Host-only state root (settings, credentials, and office records).\n * Not part of the Workspace projection; carried here so offices can\n * derive their state dirs and the registry knows where its journal lives.\n */\n readonly stateDir: string;\n /** Workspace-global memory file: `<root>/MEMORY.md`. */\n readonly memoryPath: string;\n /** Workspace-global skills directory: `<root>/skills`. */\n readonly skillsDir: string;\n /** Subagent profile patches: `<root>/agents`. */\n readonly agentsDir: string;\n /** Workspace-root entries that are shared infrastructure, never office dirs. */\n readonly reservedNames: ReadonlySet<string>;\n /**\n * The Conversation office for an address. Values are memoized per address,\n * so repeated calls on hot paths cost one map lookup, not a hash.\n */\n office(address: OfficeAddress): Office;\n}\n\n/**\n * One Conversation office: canonical identity plus the office's host-side\n * directory layout. A frozen value — every field is precomputed at\n * construction. Fields exist only for paths that more than one module needs;\n * single-consumer subpaths stay `join(office.dir, …)` at their call site.\n */\nexport interface Office {\n /** Canonical identity: platform plus raw platform conversation id. */\n readonly address: OfficeAddress;\n /**\n * The office key — the collision-resistant directory segment, identical on\n * the host and inside sandbox runtimes, and the conversation vault key.\n */\n readonly key: OfficeKey;\n /** The office working directory: `<workspace root>/<key>`. */\n readonly dir: string;\n /** Conversation memory file: `<dir>/MEMORY.md`. */\n readonly memoryPath: string;\n /** Conversation skills directory: `<dir>/skills`. */\n readonly skillsDir: string;\n /** Persisted chat-session files: `<dir>/sessions`. */\n readonly sessionsDir: string;\n /** Downloaded message attachments: `<dir>/attachments`. */\n readonly attachmentsDir: string;\n /** Human-readable message history: `<dir>/log.jsonl`. */\n readonly logPath: string;\n /** Host-only per-office state: `<state dir>/conversations/<key>`. */\n readonly stateDir: string;\n /** The workspace this office belongs to. */\n readonly workspace: Workspace;\n /**\n * Materialize the office working directory: record the office in the\n * registry, then create the directory. Idempotent and safe on hot\n * per-message paths — recording is cached, the mkdir is a guarded check.\n * Returns `dir`.\n */\n ensure(): string;\n}\n\n/**\n * The conversation's platform-native channel kind, using the platform's own\n * vocabulary (Slack conversation types), not a mikan-invented one. Recorded\n * at message intake and used to derive the workspace projection when no\n * explicit workspace setting exists: public channels share workspace memory\n * read-write, private channels read it without writing back, DMs and\n * externally shared channels stay isolated.\n */\nexport type PlatformChannelKind = \"public_channel\" | \"private_channel\" | \"im\" | \"external\";\n\ninterface WorkspacePromptSources {\n conversationDir: string;\n conversationMemoryPath: string;\n conversationSkillsDir: string;\n globalMemoryPath: string;\n globalSkillsDir: string;\n /** True for private offices: global MEMORY.md and skills are mounted read-only. */\n globalKnowledgeReadOnly?: boolean;\n}\n\nexport interface WorkspaceProjection {\n visibility: WorkspaceVisibility;\n /** Where the visibility came from, for operator display. */\n source: \"platform\" | \"override\" | \"unknown\";\n /**\n * The office still declares the retired `full` door policy. Observation\n * only: it no longer widens mounts, but cross-office access is logged so\n * the migration can be verified before the setting is dropped (ADR 0008).\n */\n legacyFull: boolean;\n mounts: ContainerMount[];\n promptSources: WorkspacePromptSources;\n}\n"]}
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -9,7 +9,15 @@ declare const sandboxAdapters: readonly [SandboxAdapter<import("./types.js").Hos
|
|
|
9
9
|
export declare function getSandboxAdapters(): readonly [...typeof sandboxAdapters];
|
|
10
10
|
export declare function getSandboxCredentialCapabilities(type: SandboxConfig["type"]): SandboxAdapter["credentials"];
|
|
11
11
|
export declare function getSandboxWorkspaceCapabilities(type: SandboxConfig["type"]): SandboxAdapter["workspace"];
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Only backends with a managed Workspace projection enforce office
|
|
14
|
+
* visibility (read-only global knowledge, no access to other private
|
|
15
|
+
* offices). Host, shared-container, and remote backends run every office in
|
|
16
|
+
* one filesystem and are documented as trusted deployments (ADR 0003), so a
|
|
17
|
+
* private office there is served with a one-time warning rather than
|
|
18
|
+
* refused: refusing would take every DM offline on those backends.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertSandboxSupportsWorkspacePolicy(sandboxConfig: SandboxConfig, visibility: "public" | "private", officeKey: string): void;
|
|
13
21
|
export declare function parseSandboxArg(value: string): SandboxConfig;
|
|
14
22
|
export declare function validateSandbox(config: SandboxConfig): Promise<void>;
|
|
15
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA2B,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAA4B,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAsB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA2B,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAA4B,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAsB,MAAM,WAAW,CAAC;AAG7D,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,uBAAuB,EACvB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA+B1C,QAAA,MAAM,eAAe,kOAKX,CAAC;AAKX,wBAAgB,kBAAkB,IAAI,SAAS,CAAC,GAAG,OAAO,eAAe,CAAC,CAEzE;AAQD,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,cAAc,CAAC,aAAa,CAAC,CAE/B;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,cAAc,CAAC,WAAW,CAAC,CAE7B;AAID;;;;;;;GAOG;AACH,wBAAgB,oCAAoC,CAClD,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,QAAQ,GAAG,SAAS,EAChC,SAAS,EAAE,MAAM,GAChB,IAAI,CASN;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAiB5D;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAChC,QAAQ,CAMV;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,aAAa,EAC5B,iBAAiB,EAAE,MAAM,GACxB,kBAAkB,CAMpB"}
|
package/dist/sandbox/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { ContainerExecutor, containerSandboxAdapter } from "./container.js";
|
|
|
2
2
|
import { CloudflareSandboxExecutor, cloudflareSandboxAdapter } from "./cloudflare.js";
|
|
3
3
|
import { HostExecutor, hostSandboxAdapter } from "./host.js";
|
|
4
4
|
import { createMountedRuntimePathContext, execSimple, SandboxError } from "./utils.js";
|
|
5
|
+
import * as log from "../log.js";
|
|
5
6
|
export { CloudflareSandboxExecutor, ContainerExecutor, HostExecutor };
|
|
6
7
|
export { SandboxError } from "./utils.js";
|
|
7
8
|
function parseImageSandboxArg(value) {
|
|
@@ -52,20 +53,23 @@ export function getSandboxCredentialCapabilities(type) {
|
|
|
52
53
|
export function getSandboxWorkspaceCapabilities(type) {
|
|
53
54
|
return requireSandboxAdapter(type).workspace;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
const warnedUnenforcedPrivacy = new Set();
|
|
57
|
+
/**
|
|
58
|
+
* Only backends with a managed Workspace projection enforce office
|
|
59
|
+
* visibility (read-only global knowledge, no access to other private
|
|
60
|
+
* offices). Host, shared-container, and remote backends run every office in
|
|
61
|
+
* one filesystem and are documented as trusted deployments (ADR 0003), so a
|
|
62
|
+
* private office there is served with a one-time warning rather than
|
|
63
|
+
* refused: refusing would take every DM offline on those backends.
|
|
64
|
+
*/
|
|
65
|
+
export function assertSandboxSupportsWorkspacePolicy(sandboxConfig, visibility, officeKey) {
|
|
60
66
|
const capabilities = getSandboxWorkspaceCapabilities(sandboxConfig.type);
|
|
61
|
-
if (capabilities.managedProjection)
|
|
67
|
+
if (capabilities.managedProjection || visibility === "public")
|
|
62
68
|
return;
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new SandboxError(`Sandbox '${sandboxConfig.type}' cannot enforce read-only shared workspace memory; use image:*, or explicitly choose isolated or trusted read-write workspace policy`);
|
|
68
|
-
}
|
|
69
|
+
if (warnedUnenforcedPrivacy.has(officeKey))
|
|
70
|
+
return;
|
|
71
|
+
warnedUnenforcedPrivacy.add(officeKey);
|
|
72
|
+
log.logWarning(`Sandbox '${sandboxConfig.type}' cannot enforce private office visibility for ${officeKey}`, "host, container, and cloudflare backends share one filesystem; use image:* to isolate private channels and DMs");
|
|
69
73
|
}
|
|
70
74
|
export function parseSandboxArg(value) {
|
|
71
75
|
for (const adapter of sandboxAdapters) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,+BAA+B,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContainerExecutor, containerSandboxAdapter } from \"./container.js\";\nimport { CloudflareSandboxExecutor, cloudflareSandboxAdapter } from \"./cloudflare.js\";\nimport { HostExecutor, hostSandboxAdapter } from \"./host.js\";\nimport { createMountedRuntimePathContext, execSimple, SandboxError } from \"./utils.js\";\nimport * as log from \"../log.js\";\nimport type {\n Executor,\n ImageSandboxConfig,\n RuntimePathContext,\n SandboxAdapter,\n SandboxConfig,\n} from \"./types.js\";\n\nexport type {\n CloudflareSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n SandboxConfig,\n} from \"./types.js\";\nexport { CloudflareSandboxExecutor, ContainerExecutor, HostExecutor };\nexport { SandboxError } from \"./utils.js\";\n\nfunction parseImageSandboxArg(value: string): ImageSandboxConfig | undefined {\n if (!value.startsWith(\"image:\")) {\n return undefined;\n }\n\n const image = value.slice(\"image:\".length);\n if (!image) {\n throw new SandboxError(\"Error: image sandbox requires image name (e.g., image:ubuntu:24.04)\");\n }\n return { type: \"image\", image };\n}\n\nasync function validateImageSandbox(config: ImageSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n console.log(` Image auto-provisioning enabled. Image: ${config.image}`);\n}\n\nconst imageSandboxAdapter: SandboxAdapter<ImageSandboxConfig> = {\n type: \"image\",\n credentials: { env: true, fileMounts: true },\n workspace: { managedProjection: true },\n parse: parseImageSandboxArg,\n validate: validateImageSandbox,\n};\n\nconst sandboxAdapters = [\n hostSandboxAdapter,\n containerSandboxAdapter,\n imageSandboxAdapter,\n cloudflareSandboxAdapter,\n] as const;\nconst sandboxAdapterByType = new Map(\n sandboxAdapters.map((adapter) => [adapter.type, adapter]),\n) as Map<SandboxConfig[\"type\"], SandboxAdapter>;\n\nexport function getSandboxAdapters(): readonly [...typeof sandboxAdapters] {\n return sandboxAdapters;\n}\n\nfunction requireSandboxAdapter(type: SandboxConfig[\"type\"]): SandboxAdapter {\n const adapter = sandboxAdapterByType.get(type);\n if (!adapter) throw new SandboxError(`Error: Unsupported sandbox type '${type}'`);\n return adapter;\n}\n\nexport function getSandboxCredentialCapabilities(\n type: SandboxConfig[\"type\"],\n): SandboxAdapter[\"credentials\"] {\n return requireSandboxAdapter(type).credentials;\n}\n\nexport function getSandboxWorkspaceCapabilities(\n type: SandboxConfig[\"type\"],\n): SandboxAdapter[\"workspace\"] {\n return requireSandboxAdapter(type).workspace;\n}\n\nconst warnedUnenforcedPrivacy = new Set<string>();\n\n/**\n * Only backends with a managed Workspace projection enforce office\n * visibility (read-only global knowledge, no access to other private\n * offices). Host, shared-container, and remote backends run every office in\n * one filesystem and are documented as trusted deployments (ADR 0003), so a\n * private office there is served with a one-time warning rather than\n * refused: refusing would take every DM offline on those backends.\n */\nexport function assertSandboxSupportsWorkspacePolicy(\n sandboxConfig: SandboxConfig,\n visibility: \"public\" | \"private\",\n officeKey: string,\n): void {\n const capabilities = getSandboxWorkspaceCapabilities(sandboxConfig.type);\n if (capabilities.managedProjection || visibility === \"public\") return;\n if (warnedUnenforcedPrivacy.has(officeKey)) return;\n warnedUnenforcedPrivacy.add(officeKey);\n log.logWarning(\n `Sandbox '${sandboxConfig.type}' cannot enforce private office visibility for ${officeKey}`,\n \"host, container, and cloudflare backends share one filesystem; use image:* to isolate private channels and DMs\",\n );\n}\n\nexport function parseSandboxArg(value: string): SandboxConfig {\n for (const adapter of sandboxAdapters) {\n const config = adapter.parse(value);\n if (config) {\n return config;\n }\n }\n\n if (value.startsWith(\"docker:\")) {\n throw new SandboxError(\n `Error: '${value}' is not supported. Use 'container:<container-name>' for the shared-container mode or 'image:<image-name>' for mikan-managed per-user containers.`,\n );\n }\n\n throw new SandboxError(\n `Error: Invalid sandbox type '${value}'. Use 'host', 'container:<container-name>', 'image:<image-name>', or 'cloudflare:<sandbox-id>'`,\n );\n}\n\nexport async function validateSandbox(config: SandboxConfig): Promise<void> {\n await requireSandboxAdapter(config.type).validate?.(config);\n}\n\n/**\n * Create an executor that runs commands on host, in Docker, or through a\n * Cloudflare sandbox bridge.\n */\nexport function createExecutor(\n config: SandboxConfig,\n env?: Record<string, string>,\n ensureReady?: () => Promise<void>,\n): Executor {\n const adapter = requireSandboxAdapter(config.type);\n if (!adapter.createExecutor) {\n throw new SandboxError(\"Error: image sandbox must resolve to a concrete container executor\");\n }\n return adapter.createExecutor(config, env, ensureReady);\n}\n\n/**\n * The runtime path context for a sandbox config before an actor-specific\n * executor is resolved. `image` configs are unresolved by nature (the actor\n * decides the concrete container), but their mount layout is fixed, so path\n * mapping is known without resolving.\n */\nexport function getUnresolvedSandboxPathContext(\n sandboxConfig: SandboxConfig,\n hostWorkspaceRoot: string,\n): RuntimePathContext {\n if (sandboxConfig.type === \"image\") {\n return createMountedRuntimePathContext(hostWorkspaceRoot, \"/workspace\");\n }\n\n return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);\n}\n"]}
|
|
@@ -65,6 +65,14 @@ export declare class DockerContainerManager {
|
|
|
65
65
|
* callers that need the container running start it themselves.
|
|
66
66
|
*/
|
|
67
67
|
private createContainerFromSnapshot;
|
|
68
|
+
/**
|
|
69
|
+
* `docker commit` captures the empty directories Docker created as bind
|
|
70
|
+
* targets, so a public office that stopped being mounted would linger in
|
|
71
|
+
* the snapshot as an empty `/workspace/public/<key>`. Remove any entry
|
|
72
|
+
* there that no current bind targets; only empty directories are removed,
|
|
73
|
+
* so a real file could never be deleted by this pass.
|
|
74
|
+
*/
|
|
75
|
+
private removeStaleMountpoints;
|
|
68
76
|
/**
|
|
69
77
|
* Recreate a drifted container with the desired mounts while keeping its
|
|
70
78
|
* writable layer: commit — the new binds ride the snapshot label, so a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provisioner.d.ts","sourceRoot":"","sources":["../../src/sandbox/provisioner.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"provisioner.d.ts","sourceRoot":"","sources":["../../src/sandbox/provisioner.ts"],"names":[],"mappings":"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"}
|
|
@@ -332,6 +332,41 @@ export class DockerContainerManager {
|
|
|
332
332
|
"infinity",
|
|
333
333
|
]);
|
|
334
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* `docker commit` captures the empty directories Docker created as bind
|
|
337
|
+
* targets, so a public office that stopped being mounted would linger in
|
|
338
|
+
* the snapshot as an empty `/workspace/public/<key>`. Remove any entry
|
|
339
|
+
* there that no current bind targets; only empty directories are removed,
|
|
340
|
+
* so a real file could never be deleted by this pass.
|
|
341
|
+
*/
|
|
342
|
+
async removeStaleMountpoints(containerName, bindSpecs) {
|
|
343
|
+
const keep = new Set(bindSpecs
|
|
344
|
+
.map((bind) => bindSpecToMount(bind).target)
|
|
345
|
+
.filter((target) => target.startsWith("/workspace/public/"))
|
|
346
|
+
.map((target) => target.slice("/workspace/public/".length)));
|
|
347
|
+
const script = [
|
|
348
|
+
"[ -d /workspace/public ] || exit 0",
|
|
349
|
+
"for d in /workspace/public/*; do",
|
|
350
|
+
' [ -e "$d" ] || continue',
|
|
351
|
+
' case " $KEEP " in *" ${d##*/} "*) continue;; esac',
|
|
352
|
+
' rmdir "$d" 2>/dev/null || true',
|
|
353
|
+
"done",
|
|
354
|
+
].join("\n");
|
|
355
|
+
try {
|
|
356
|
+
await this.execFileImpl("docker", [
|
|
357
|
+
"exec",
|
|
358
|
+
"-e",
|
|
359
|
+
`KEEP=${[...keep].join(" ")}`,
|
|
360
|
+
containerName,
|
|
361
|
+
"sh",
|
|
362
|
+
"-c",
|
|
363
|
+
script,
|
|
364
|
+
]);
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
log.logWarning(`Could not clean stale mountpoints in ${containerName}`, String(err));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
335
370
|
/**
|
|
336
371
|
* Recreate a drifted container with the desired mounts while keeping its
|
|
337
372
|
* writable layer: commit — the new binds ride the snapshot label, so a
|
|
@@ -354,6 +389,7 @@ export class DockerContainerManager {
|
|
|
354
389
|
await this.execFileImpl("docker", ["rm", "-f", containerName]);
|
|
355
390
|
await this.createContainerFromSnapshot(containerName, bindSpecs, containerKey);
|
|
356
391
|
await this.execFileImpl("docker", ["start", containerName]);
|
|
392
|
+
await this.removeStaleMountpoints(containerName, bindSpecs);
|
|
357
393
|
const currentImageId = await this.readImageId(snapshotImage);
|
|
358
394
|
if (previousImageId && previousImageId !== currentImageId) {
|
|
359
395
|
try {
|