@vellumai/assistant 0.11.2-dev.202608051933.deffc0f → 0.11.2-dev.202608052033.7272f57
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/package.json +1 -1
- package/src/__tests__/top-level-renderer.test.ts +54 -1
- package/src/oauth/seed-providers.ts +9 -1
- package/src/tools/host-filesystem/edit.ts +6 -1
- package/src/tools/host-filesystem/read.ts +6 -1
- package/src/tools/host-filesystem/write.ts +6 -1
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/workspace/top-level-renderer.ts +15 -2
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { homedir, userInfo } from "node:os";
|
|
2
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
3
3
|
|
|
4
4
|
import { renderWorkspaceTopLevelContext } from "../workspace/top-level-renderer.js";
|
|
5
5
|
import type { TopLevelSnapshot } from "../workspace/top-level-scanner.js";
|
|
@@ -214,4 +214,57 @@ describe("renderWorkspaceTopLevelContext", () => {
|
|
|
214
214
|
expect(result).toContain("Host home directory: /Users/alice");
|
|
215
215
|
expect(result).toContain(`Host username: ${userInfo().username}`);
|
|
216
216
|
});
|
|
217
|
+
|
|
218
|
+
describe("containerized daemon", () => {
|
|
219
|
+
// In a container the daemon's own homedir/username describe the
|
|
220
|
+
// container, not the user's machine. Reporting them as the HOST values
|
|
221
|
+
// is what leads a host_bash command to write to a path that exists
|
|
222
|
+
// nowhere on the user's machine.
|
|
223
|
+
const priorIsContainerized = process.env.IS_CONTAINERIZED;
|
|
224
|
+
|
|
225
|
+
afterEach(() => {
|
|
226
|
+
if (priorIsContainerized === undefined) {
|
|
227
|
+
delete process.env.IS_CONTAINERIZED;
|
|
228
|
+
} else {
|
|
229
|
+
process.env.IS_CONTAINERIZED = priorIsContainerized;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const snapshot: TopLevelSnapshot = {
|
|
234
|
+
rootPath: "/workspace",
|
|
235
|
+
directories: ["src"],
|
|
236
|
+
files: ["package.json"],
|
|
237
|
+
truncated: false,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
test("does not pass off the container's own home dir as the host's", () => {
|
|
241
|
+
process.env.IS_CONTAINERIZED = "true";
|
|
242
|
+
|
|
243
|
+
const result = renderWorkspaceTopLevelContext(snapshot, {});
|
|
244
|
+
|
|
245
|
+
expect(result).not.toContain(`Host home directory: ${homedir()}`);
|
|
246
|
+
// Name the way to find the real value rather than leaving a dead end.
|
|
247
|
+
expect(result.split("\n")).toEqual([
|
|
248
|
+
"<workspace>",
|
|
249
|
+
"Root: /workspace",
|
|
250
|
+
"Directories: src",
|
|
251
|
+
"Files: package.json",
|
|
252
|
+
"Host home directory: unknown (ask the host shell: `host_bash` with `echo $HOME`)",
|
|
253
|
+
"Host username: unknown",
|
|
254
|
+
"</workspace>",
|
|
255
|
+
]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("still reports the client-supplied host env when it is known", () => {
|
|
259
|
+
process.env.IS_CONTAINERIZED = "true";
|
|
260
|
+
|
|
261
|
+
const result = renderWorkspaceTopLevelContext(snapshot, {
|
|
262
|
+
hostHomeDir: "/Users/alice",
|
|
263
|
+
hostUsername: "alice",
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(result).toContain("Host home directory: /Users/alice");
|
|
267
|
+
expect(result).toContain("Host username: alice");
|
|
268
|
+
});
|
|
269
|
+
});
|
|
217
270
|
});
|
|
@@ -726,7 +726,15 @@ export const PROVIDER_SEED_DATA: Record<
|
|
|
726
726
|
],
|
|
727
727
|
availableScopes:
|
|
728
728
|
"https://learn.microsoft.com/en-us/graph/permissions-reference",
|
|
729
|
-
|
|
729
|
+
// `select_account`, not `consent`: the Microsoft identity platform accepts
|
|
730
|
+
// a single prompt value, and `consent` honours an existing session cookie —
|
|
731
|
+
// it re-asks for consent but never for *which* account, so a user who
|
|
732
|
+
// already signed in cannot connect a second mailbox. `select_account`
|
|
733
|
+
// always shows the account picker with its "Use another account" option.
|
|
734
|
+
// Consent is still collected for an account that has not granted it, and
|
|
735
|
+
// refresh tokens come from the `offline_access` scope above, so nothing is
|
|
736
|
+
// lost by dropping `consent`.
|
|
737
|
+
authorizeParams: { prompt: "select_account" },
|
|
730
738
|
tokenEndpointAuthMethod: "client_secret_post",
|
|
731
739
|
loopbackPort: 17334,
|
|
732
740
|
managedServiceConfigKey: "outlook-oauth",
|
|
@@ -25,7 +25,12 @@ import type {
|
|
|
25
25
|
* a non-string (or empty) value means "untargeted".
|
|
26
26
|
*/
|
|
27
27
|
export const hostFileEditInputSchema = z.looseObject({
|
|
28
|
-
path: z
|
|
28
|
+
path: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.describe(
|
|
32
|
+
"Absolute path on the guardian's device, which is a separate filesystem from your workspace, to edit.",
|
|
33
|
+
),
|
|
29
34
|
old_string: z
|
|
30
35
|
.string()
|
|
31
36
|
.min(1, { message: "old_string must not be empty" })
|
|
@@ -38,7 +38,12 @@ import type {
|
|
|
38
38
|
* "untargeted".
|
|
39
39
|
*/
|
|
40
40
|
export const hostFileReadInputSchema = z.looseObject({
|
|
41
|
-
path: z
|
|
41
|
+
path: z
|
|
42
|
+
.string()
|
|
43
|
+
.min(1)
|
|
44
|
+
.describe(
|
|
45
|
+
"Absolute path on the guardian's device, which is a separate filesystem from your workspace, to read.",
|
|
46
|
+
),
|
|
42
47
|
offset: z
|
|
43
48
|
.number()
|
|
44
49
|
.describe("Line number to start reading from (1-indexed)")
|
|
@@ -24,7 +24,12 @@ import type {
|
|
|
24
24
|
* non-string (or empty) value means "untargeted".
|
|
25
25
|
*/
|
|
26
26
|
export const hostFileWriteInputSchema = z.looseObject({
|
|
27
|
-
path: z
|
|
27
|
+
path: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.describe(
|
|
31
|
+
"Absolute path on the guardian's device, which is a separate filesystem from your workspace, to write.",
|
|
32
|
+
),
|
|
28
33
|
content: z.string().describe("The content to write to the file"),
|
|
29
34
|
target_client_id: z
|
|
30
35
|
.string()
|
|
@@ -121,7 +121,7 @@ export const hostShellInputSchema = z.looseObject({
|
|
|
121
121
|
z
|
|
122
122
|
.string()
|
|
123
123
|
.describe(
|
|
124
|
-
"Optional absolute host
|
|
124
|
+
"Optional absolute path on the host's filesystem, which is separate from your workspace (defaults to the host user's home).",
|
|
125
125
|
),
|
|
126
126
|
),
|
|
127
127
|
timeout_seconds: z
|
|
@@ -150,7 +150,7 @@ export const hostShellInputSchema = z.looseObject({
|
|
|
150
150
|
export const hostShellTool = {
|
|
151
151
|
name: "host_bash",
|
|
152
152
|
description:
|
|
153
|
-
"LAST RESORT — Execute a shell command directly on the host machine. You MUST strongly prefer the regular `bash` tool for all commands. Only use `host_bash` when you are absolutely certain the command MUST run on the host machine and CANNOT run in the workspace (e.g., managing host-level system services, accessing host-only peripherals, or interacting with host paths outside the workspace). If in doubt, use `bash` instead. Approval-gated: each invocation must be explicitly approved. Do not use for commands that require injected credentials or secrets.",
|
|
153
|
+
"LAST RESORT — Execute a shell command directly on the host machine. You MUST strongly prefer the regular `bash` tool for all commands. Only use `host_bash` when you are absolutely certain the command MUST run on the host machine and CANNOT run in the workspace (e.g., managing host-level system services, accessing host-only peripherals, or interacting with host paths outside the workspace). If in doubt, use `bash` instead. Paths resolve on the host's filesystem, which is separate from your workspace; write output somewhere on the host and read it back with `host_file_read`. Approval-gated: each invocation must be explicitly approved. Do not use for commands that require injected credentials or secrets.",
|
|
154
154
|
category: "host-terminal",
|
|
155
155
|
executionTarget: "host",
|
|
156
156
|
defaultRiskLevel: RiskLevel.Medium,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir, userInfo } from "node:os";
|
|
2
2
|
|
|
3
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
3
4
|
import type { TopLevelSnapshot } from "./top-level-scanner.js";
|
|
4
5
|
|
|
5
6
|
// `os.userInfo()` throws `SystemError` when the current UID has no passwd
|
|
@@ -52,10 +53,22 @@ export function renderWorkspaceTopLevelContext(
|
|
|
52
53
|
if (snapshot.truncated) {
|
|
53
54
|
lines.push("(list truncated — more entries exist)");
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
+
// The daemon's own `homedir()` / `userInfo()` describe the HOST only when
|
|
57
|
+
// the daemon runs on it. In a container they describe the container, and
|
|
58
|
+
// labelling them "Host …" invites host-side tools (`host_bash`,
|
|
59
|
+
// `host_file_*`, computer use) to be handed a path that exists nowhere on
|
|
60
|
+
// the user's machine. Fall back only when the two are the same machine;
|
|
61
|
+
// otherwise say the value is unknown and name the way to find it, which is
|
|
62
|
+
// cheaper than a failed write on the user's Mac.
|
|
63
|
+
const hostHomeDir =
|
|
64
|
+
options.hostHomeDir ?? (getIsContainerized() ? undefined : homedir());
|
|
65
|
+
const hostUsername =
|
|
66
|
+
options.hostUsername ??
|
|
67
|
+
(getIsContainerized() ? undefined : safeUserInfoUsername());
|
|
56
68
|
lines.push(
|
|
57
|
-
`Host
|
|
69
|
+
`Host home directory: ${hostHomeDir ?? "unknown (ask the host shell: `host_bash` with `echo $HOME`)"}`,
|
|
58
70
|
);
|
|
71
|
+
lines.push(`Host username: ${hostUsername ?? "unknown"}`);
|
|
59
72
|
lines.push("</workspace>");
|
|
60
73
|
return lines.join("\n");
|
|
61
74
|
}
|