Merge remote-tracking branch 'origin/main' into skill/apple-container

# Conflicts:
#	src/container-runner.ts
This commit is contained in:
gavrielc
2026-03-09 23:20:34 +02:00
16 changed files with 481 additions and 84 deletions

View File

@@ -51,7 +51,8 @@ RUN npm run build
RUN mkdir -p /workspace/group /workspace/global /workspace/extra /workspace/ipc/messages /workspace/ipc/tasks /workspace/ipc/input
# Create entrypoint script
# Secrets are passed via stdin JSON — temp file is deleted immediately after Node reads it
# Container input (prompt, group info) is passed via stdin JSON.
# Credentials are injected by the host's credential proxy — never passed here.
# Follow-up messages arrive via IPC files in /workspace/ipc/input/
# Apple Container only supports directory mounts (VirtioFS), so .env cannot be
# shadowed with a host-side /dev/null file mount. Instead the entrypoint starts

View File

@@ -16,7 +16,7 @@
import fs from 'fs';
import path from 'path';
import { query, HookCallback, PreCompactHookInput, PreToolUseHookInput } from '@anthropic-ai/claude-agent-sdk';
import { query, HookCallback, PreCompactHookInput } from '@anthropic-ai/claude-agent-sdk';
import { fileURLToPath } from 'url';
interface ContainerInput {
@@ -27,7 +27,6 @@ interface ContainerInput {
isMain: boolean;
isScheduledTask?: boolean;
assistantName?: string;
secrets?: Record<string, string>;
}
interface ContainerOutput {
@@ -185,30 +184,6 @@ function createPreCompactHook(assistantName?: string): HookCallback {
};
}
// Secrets to strip from Bash tool subprocess environments.
// These are needed by claude-code for API auth but should never
// be visible to commands Kit runs.
const SECRET_ENV_VARS = ['ANTHROPIC_API_KEY', 'CLAUDE_CODE_OAUTH_TOKEN'];
function createSanitizeBashHook(): HookCallback {
return async (input, _toolUseId, _context) => {
const preInput = input as PreToolUseHookInput;
const command = (preInput.tool_input as { command?: string })?.command;
if (!command) return {};
const unsetPrefix = `unset ${SECRET_ENV_VARS.join(' ')} 2>/dev/null; `;
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
updatedInput: {
...(preInput.tool_input as Record<string, unknown>),
command: unsetPrefix + command,
},
},
};
};
}
function sanitizeFilename(summary: string): string {
return summary
.toLowerCase()
@@ -451,7 +426,6 @@ async function runQuery(
},
hooks: {
PreCompact: [{ hooks: [createPreCompactHook(containerInput.assistantName)] }],
PreToolUse: [{ matcher: 'Bash', hooks: [createSanitizeBashHook()] }],
},
}
})) {
@@ -496,7 +470,6 @@ async function main(): Promise<void> {
try {
const stdinData = await readStdin();
containerInput = JSON.parse(stdinData);
// Delete the temp file the entrypoint wrote — it contains secrets
try { fs.unlinkSync('/tmp/input.json'); } catch { /* may not exist */ }
log(`Received input for group: ${containerInput.groupFolder}`);
} catch (err) {
@@ -508,12 +481,9 @@ async function main(): Promise<void> {
process.exit(1);
}
// Build SDK env: merge secrets into process.env for the SDK only.
// Secrets never touch process.env itself, so Bash subprocesses can't see them.
// Credentials are injected by the host's credential proxy via ANTHROPIC_BASE_URL.
// No real secrets exist in the container environment.
const sdkEnv: Record<string, string | undefined> = { ...process.env };
for (const [key, value] of Object.entries(containerInput.secrets || {})) {
sdkEnv[key] = value;
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mcpServerPath = path.join(__dirname, 'ipc-mcp-stdio.js');