@love-moon/ai-sdk 0.10.0 → 0.11.0
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
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @love-moon/ai-sdk
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 30afccc: Make the claude backend usable when conductor runs as root.
|
|
8
|
+
|
|
9
|
+
Claude Code refuses `bypassPermissions` / `--dangerously-skip-permissions` under
|
|
10
|
+
root unless `IS_SANDBOX=1` or `CLAUDE_CODE_BUBBLEWRAP` is set, so every claude
|
|
11
|
+
turn died immediately on root installs (docker, CI, bare VPS). Both routes a
|
|
12
|
+
claude command takes are now root-aware, sharing one check
|
|
13
|
+
(`isClaudeRootPermissionRestricted`, which mirrors claude's own gate):
|
|
14
|
+
|
|
15
|
+
- The agent-SDK session downgrades the permission mode to `acceptEdits` (auto
|
|
16
|
+
mode) and stops passing `allowDangerouslySkipPermissions`.
|
|
17
|
+
- The daemon's tool-preset PTY path rewrites the configured `allow_cli_list`
|
|
18
|
+
command via `resolveClaudeCommandForRoot`, using the same env the spawned
|
|
19
|
+
child receives, so a per-task `IS_SANDBOX=1` opt-out is honored.
|
|
20
|
+
- `conductor config` no longer writes `--dangerously-skip-permissions` into a
|
|
21
|
+
config generated on a root machine.
|
|
22
|
+
|
|
23
|
+
The mode is also configurable now: `claude --permission-mode acceptEdits` in
|
|
24
|
+
`allow_cli_list` is lifted out of the command string. An unrecognized mode still
|
|
25
|
+
falls back to the default instead of failing, but logs a warning at session boot.
|
|
26
|
+
|
|
27
|
+
ai-sdk gains three exports: `isClaudeRootPermissionRestricted`,
|
|
28
|
+
`resolveClaudeCommandForRoot`, `resolveClaudePermissionPolicy`.
|
|
29
|
+
|
|
3
30
|
## 0.10.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { BUILT_IN_BACKENDS } from "./built-in-backends.js";
|
|
2
2
|
export { PROVIDER_MEDIA_CAPABILITIES } from "./media-adapters.js";
|
|
3
3
|
export { createAiSession, RemoteAiSession } from "./client.js";
|
|
4
|
+
export { isClaudeRootPermissionRestricted, resolveClaudeCommandForRoot, resolveClaudePermissionPolicy } from "./providers/claude-agent-sdk-session.js";
|
|
4
5
|
export { appendContextFilesToPrompt, normalizeContextFiles } from "./context-files.js";
|
|
5
6
|
export { assertMediaCapabilities, fileMediaToBase64, fileMediaToDataUri, normalizeMediaInputs, resolveTurnMedia } from "./media-input.js";
|
|
6
7
|
export { GOAL_STATUSES, TERMINAL_GOAL_STATUSES, isGoalStatus, isTerminalGoalStatus } from "./shared.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createAiSession, RemoteAiSession } from "./client.js";
|
|
2
2
|
export { BUILT_IN_BACKENDS } from "./built-in-backends.js";
|
|
3
3
|
export { PROVIDER_MEDIA_CAPABILITIES } from "./media-adapters.js";
|
|
4
|
+
export { isClaudeRootPermissionRestricted, resolveClaudeCommandForRoot, resolveClaudePermissionPolicy, } from "./providers/claude-agent-sdk-session.js";
|
|
4
5
|
export { appendContextFilesToPrompt, normalizeContextFiles } from "./context-files.js";
|
|
5
6
|
export { assertMediaCapabilities, fileMediaToBase64, fileMediaToDataUri, normalizeMediaInputs, resolveTurnMedia, } from "./media-input.js";
|
|
6
7
|
export { GOAL_STATUSES, TERMINAL_GOAL_STATUSES, isGoalStatus, isTerminalGoalStatus, } from "./shared.js";
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export function isClaudeRootPermissionRestricted(env?: NodeJS.ProcessEnv): boolean;
|
|
2
|
+
export function resolveClaudePermissionPolicy(options?: {}, env?: NodeJS.ProcessEnv): {
|
|
3
|
+
permissionMode: string;
|
|
4
|
+
allowDangerouslySkipPermissions: boolean;
|
|
5
|
+
downgradedFrom: string;
|
|
6
|
+
invalidMode: any;
|
|
7
|
+
};
|
|
8
|
+
export function resolveClaudeCommandForRoot(commandLine: any, env?: NodeJS.ProcessEnv): string;
|
|
1
9
|
export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
2
10
|
static capabilities: Readonly<{
|
|
3
11
|
goal: true;
|
|
@@ -49,6 +57,12 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
49
57
|
turnDeadlineMs: any;
|
|
50
58
|
sdkModulePromise: Promise<any> | Promise<typeof import("@anthropic-ai/claude-agent-sdk")> | null;
|
|
51
59
|
env: any;
|
|
60
|
+
permissionPolicy: {
|
|
61
|
+
permissionMode: string;
|
|
62
|
+
allowDangerouslySkipPermissions: boolean;
|
|
63
|
+
downgradedFrom: string;
|
|
64
|
+
invalidMode: any;
|
|
65
|
+
};
|
|
52
66
|
writeLog(message: any): void;
|
|
53
67
|
trace(message: any): void;
|
|
54
68
|
get threadId(): any;
|
|
@@ -131,7 +145,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
131
145
|
abortController: any;
|
|
132
146
|
cwd: any;
|
|
133
147
|
env: any;
|
|
134
|
-
permissionMode:
|
|
148
|
+
permissionMode: string;
|
|
135
149
|
settingSources: string[];
|
|
136
150
|
persistSession: boolean;
|
|
137
151
|
};
|
|
@@ -8,6 +8,10 @@ const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
|
8
8
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
9
9
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
10
10
|
const DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
|
|
11
|
+
const PERMISSION_MODES = new Set(["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk"]);
|
|
12
|
+
const DEFAULT_PERMISSION_MODE = "bypassPermissions";
|
|
13
|
+
// Auto ("accept edits") mode: the strongest mode claude allows as root.
|
|
14
|
+
const ROOT_PERMISSION_MODE = "acceptEdits";
|
|
11
15
|
function waitForever() {
|
|
12
16
|
return new Promise(() => { });
|
|
13
17
|
}
|
|
@@ -50,16 +54,83 @@ function normalizeSettingSources(value) {
|
|
|
50
54
|
}
|
|
51
55
|
function normalizePermissionMode(value) {
|
|
52
56
|
const normalized = typeof value === "string" ? value.trim() : "";
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
return PERMISSION_MODES.has(normalized) ? normalized : DEFAULT_PERMISSION_MODE;
|
|
58
|
+
}
|
|
59
|
+
// Mirrors claude's own root gate byte for byte:
|
|
60
|
+
//
|
|
61
|
+
// permissionMode === "bypassPermissions" || dangerouslySkipPermissions
|
|
62
|
+
// ? getuid() === 0 && process.env.IS_SANDBOX !== "1" && !CLAUDE_CODE_BUBBLEWRAP
|
|
63
|
+
// -> "--dangerously-skip-permissions cannot be used with root/sudo
|
|
64
|
+
// privileges for security reasons" + exit(1)
|
|
65
|
+
//
|
|
66
|
+
// Two different env idioms in that one line, and both are load-bearing:
|
|
67
|
+
// IS_SANDBOX is a *strict* `=== "1"` compare, while CLAUDE_CODE_BUBBLEWRAP goes
|
|
68
|
+
// through claude's loose truthy parser (`1|true|yes|on`, trimmed+lowercased).
|
|
69
|
+
// Do not "helpfully" accept IS_SANDBOX=true here — being more permissive than
|
|
70
|
+
// the gate keeps bypassPermissions and just moves the failure into claude,
|
|
71
|
+
// which is the exact bug we are fixing.
|
|
72
|
+
export function isClaudeRootPermissionRestricted(env = process.env) {
|
|
73
|
+
if (typeof process.getuid !== "function" || process.getuid() !== 0) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (env?.IS_SANDBOX === "1") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const bubblewrap = String(env?.CLAUDE_CODE_BUBBLEWRAP ?? "").trim().toLowerCase();
|
|
80
|
+
return !["1", "true", "yes", "on"].includes(bubblewrap);
|
|
81
|
+
}
|
|
82
|
+
// Root installs (docker, CI, bare VPS) would otherwise fail every turn, so fall
|
|
83
|
+
// back to the closest usable mode instead of dying.
|
|
84
|
+
export function resolveClaudePermissionPolicy(options = {}, env = process.env) {
|
|
85
|
+
const requested = normalizePermissionMode(options.permissionMode);
|
|
86
|
+
// A configured value we don't recognize still falls back to the default (a
|
|
87
|
+
// typo must never take the session down), but the caller reports it so the
|
|
88
|
+
// user finds out their config line is doing nothing.
|
|
89
|
+
const rawMode = typeof options.permissionMode === "string" ? options.permissionMode.trim() : "";
|
|
90
|
+
const invalidMode = rawMode && !PERMISSION_MODES.has(rawMode) ? rawMode : "";
|
|
91
|
+
if (!isClaudeRootPermissionRestricted(env)) {
|
|
92
|
+
return {
|
|
93
|
+
permissionMode: requested,
|
|
94
|
+
allowDangerouslySkipPermissions: requested === DEFAULT_PERMISSION_MODE || options.allowDangerouslySkipPermissions === true,
|
|
95
|
+
downgradedFrom: "",
|
|
96
|
+
invalidMode,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const permissionMode = requested === DEFAULT_PERMISSION_MODE ? ROOT_PERMISSION_MODE : requested;
|
|
100
|
+
return {
|
|
101
|
+
permissionMode,
|
|
102
|
+
allowDangerouslySkipPermissions: false,
|
|
103
|
+
downgradedFrom: permissionMode === requested ? "" : requested,
|
|
104
|
+
invalidMode,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// The PTY / tool-preset path in the daemon hands a configured allow_cli_list
|
|
108
|
+
// command straight to a shell, so it never reaches the session class above and
|
|
109
|
+
// never sees resolveClaudePermissionPolicy. Rewrite the command string there
|
|
110
|
+
// too, or a root box keeps failing in terminal tasks after the SDK path is fixed.
|
|
111
|
+
export function resolveClaudeCommandForRoot(commandLine, env = process.env) {
|
|
112
|
+
const command = typeof commandLine === "string" ? commandLine : "";
|
|
113
|
+
if (!command.trim() || !isClaudeRootPermissionRestricted(env)) {
|
|
114
|
+
return command;
|
|
115
|
+
}
|
|
116
|
+
// Also drop a valueless `--permission-mode` (end of string, or immediately
|
|
117
|
+
// followed by another flag). Without this the append branch below would emit
|
|
118
|
+
// `--permission-mode --permission-mode acceptEdits`, and claude reads the
|
|
119
|
+
// flag name as the mode and `acceptEdits` as a positional prompt.
|
|
120
|
+
const stripped = command
|
|
121
|
+
.replace(/\s*--dangerously-skip-permissions(?=\s|$)/g, "")
|
|
122
|
+
.replace(/\s*--permission-mode=(?=\s|$)/g, "")
|
|
123
|
+
.replace(/\s*--permission-mode(?=\s+-|\s*$)/g, "")
|
|
124
|
+
.trim();
|
|
125
|
+
const configuredMode = extractLongFlagFromCommandLine(stripped, "permission-mode");
|
|
126
|
+
if (!configuredMode) {
|
|
127
|
+
return `${stripped} --permission-mode ${ROOT_PERMISSION_MODE}`;
|
|
128
|
+
}
|
|
129
|
+
if (configuredMode === DEFAULT_PERMISSION_MODE) {
|
|
130
|
+
return stripped.replace(/(--permission-mode[=\s]+)bypassPermissions(?=\s|$)/, `$1${ROOT_PERMISSION_MODE}`);
|
|
62
131
|
}
|
|
132
|
+
// Any other configured mode is already root-safe; leave it verbatim.
|
|
133
|
+
return stripped;
|
|
63
134
|
}
|
|
64
135
|
function normalizeText(value) {
|
|
65
136
|
return typeof value === "string" ? value : "";
|
|
@@ -163,6 +234,14 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
163
234
|
options = { ...options, effort: effortFromCommandLine };
|
|
164
235
|
}
|
|
165
236
|
}
|
|
237
|
+
// Same for `--permission-mode`, so `claude --permission-mode acceptEdits`
|
|
238
|
+
// in allow_cli_list picks the mode without new plumbing.
|
|
239
|
+
if (options.permissionMode === undefined && typeof options.commandLine === "string") {
|
|
240
|
+
const modeFromCommandLine = extractLongFlagFromCommandLine(options.commandLine, "permission-mode");
|
|
241
|
+
if (modeFromCommandLine) {
|
|
242
|
+
options = { ...options, permissionMode: modeFromCommandLine };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
166
245
|
this.options = options;
|
|
167
246
|
this.logger = normalizeLogger(options.logger);
|
|
168
247
|
this.cwd =
|
|
@@ -205,6 +284,18 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
205
284
|
if (!this.env.CLAUDE_AGENT_SDK_CLIENT_APP) {
|
|
206
285
|
this.env.CLAUDE_AGENT_SDK_CLIENT_APP = "conductor-ai-sdk/0.0.0";
|
|
207
286
|
}
|
|
287
|
+
// Resolved once at boot (options and env are both fixed by now) so the
|
|
288
|
+
// config warning lands at session start instead of once per turn.
|
|
289
|
+
this.permissionPolicy = resolveClaudePermissionPolicy(this.options, { ...process.env, ...this.env });
|
|
290
|
+
if (this.permissionPolicy.invalidMode) {
|
|
291
|
+
this.trace(`WARN unknown permission mode ${JSON.stringify(this.permissionPolicy.invalidMode)} in config; `
|
|
292
|
+
+ `using ${this.permissionPolicy.permissionMode} instead `
|
|
293
|
+
+ `(valid: ${[...PERMISSION_MODES].join(", ")})`);
|
|
294
|
+
}
|
|
295
|
+
if (this.permissionPolicy.downgradedFrom) {
|
|
296
|
+
this.trace(`permission mode ${this.permissionPolicy.downgradedFrom} -> ${this.permissionPolicy.permissionMode} `
|
|
297
|
+
+ `(claude rejects ${this.permissionPolicy.downgradedFrom} as root; set IS_SANDBOX=1 to keep it)`);
|
|
298
|
+
}
|
|
208
299
|
}
|
|
209
300
|
writeLog(message) {
|
|
210
301
|
emitLog(this.logger, message);
|
|
@@ -537,7 +628,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
537
628
|
});
|
|
538
629
|
}
|
|
539
630
|
buildSdkOptions(abortController) {
|
|
540
|
-
const permissionMode =
|
|
631
|
+
const { permissionMode } = this.permissionPolicy;
|
|
541
632
|
const options = {
|
|
542
633
|
abortController,
|
|
543
634
|
cwd: this.cwd,
|
|
@@ -549,7 +640,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
549
640
|
settingSources: normalizeSettingSources(this.options.settingSources),
|
|
550
641
|
persistSession: this.options.persistSession !== false,
|
|
551
642
|
};
|
|
552
|
-
if (
|
|
643
|
+
if (this.permissionPolicy.allowDangerouslySkipPermissions) {
|
|
553
644
|
options.allowDangerouslySkipPermissions = true;
|
|
554
645
|
}
|
|
555
646
|
if (this.sessionId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"zod": "^4.1.5"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@love-moon/chat-web": "^0.
|
|
56
|
+
"@love-moon/chat-web": "^0.11.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.2",
|
|
60
60
|
"tsx": "^4.20.6",
|
|
61
61
|
"typescript": "^5.6.3"
|
|
62
62
|
},
|
|
63
|
-
"gitCommitId": "
|
|
63
|
+
"gitCommitId": "7d57852"
|
|
64
64
|
}
|