{"repo": "blocksecteam/web3-companion", "files": {"tests/companion/manager_test.go": {"dcde338abd56b453a3082f932e8428e8d42ca754": {"t": 1785746067.4070256, "n": 35957, "h": "c10dfd144a803359", "c": "// Copyright (c) 2026 BlockSec (https://blocksec.com).\n// Licensed under the MIT License.\n\npackage companion_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/blocksecteam/web3-companion/internal/companion\"\n)\n\nfunc TestMain(m *testing.M) {\n\tswitch os.Getenv(\"COMPANION_TEST_HELPER\") {\n\tcase \"delayed-ready\":\n\t\trunCompanionHelper(parseHelperDelay())\n\t\treturn\n\tcase \"mock-health\":\n\t\trunMockHealthHelper()\n\t\treturn\n\tcase \"term-aware\":\n\t\trunCompanionHelper(0)\n\t\treturn\n\tdefault:\n\t\tos.Exit(m.Run())\n\t}\n}\n\nfunc TestUserHomeAndSQLiteDBPathArePerUser(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\n\thome := cfg.UserHome(\"user/with odd spaces\")\n\tif !strings.HasPrefix(home, cfg.HomeRoot) {\n\t\tt.Fatalf(\"home %q must be under root %q\", home, cfg.HomeRoot)\n\t}\n\tif strings.Contains(home, \"/with odd spaces\") || strings.Contains(home, \" \") {\n\t\tt.Fatalf(\"home path should use sanitized user id, got %q\", home)\n\t}\n\tif got, want := cfg.SQLiteDBPath(\"user/with odd spaces\"), filepath.Join(home, \"sessions.sqlite\"); got != want {\n\t\tt.Fatalf(\"SQLiteDBPath() = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestWriteContainerCredentialUsesFixedFileAndDoesNotExposeSecretInEnv(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: \"/usr/bin/bun\",\n\t\tScriptPath: \"/srv/companion-server.js\",\n\t\tBackendRoot: \"/srv/backend\",\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t}\n\n\tcredential := companion.ContainerCredential{\n\t\tVersion: 1,\n\t\tClient: \"container\",\n\t\tCredentialID: \"cc_1\",\n\t\tSecret: \"secret-container-credential\",\n\t\tAPIURL: \"http://localhost:8080\",\n\t}\n\thome, err := cfg.PrepareUserHome(\"user_1\", companion.AuthHandoff{ContainerCredential: credential}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".local\", \"web3\", \"container-credential.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read container credential: %v\", err)\n\t}\n\tif !strings.Contains(string(data), `\"credential_id\":\"cc_1\"`) ||\n\t\t!strings.Contains(string(data), `\"secret\":\"secret-container-credential\"`) {\n\t\tt.Fatalf(\"container credential data = %q\", string(data))\n\t}\n\tcmd := cfg.BuildCommand(\"user_1\", 19001)\n\tfor _, item := range cmd.Env {\n\t\tif strings.Contains(item, \"secret-container-credential\") {\n\t\t\tt.Fatalf(\"command env must not expose auth tokens: %#v\", cmd.Env)\n\t\t}\n\t}\n\tif envContainsAny(cmd.Env, \"ANTHROPIC_AUTH_TOKEN\", \"ANTHROPIC_API_KEY\", \"ANTHROPIC_MODEL\", \"ANTHROPIC_BASE_URL\", \"CLAUDE_CODE_EFFORT_LEVEL\") {\n\t\tt.Fatalf(\"model/provider secrets and defaults should live in settings.json, not env: %#v\", cmd.Env)\n\t}\n}\n\nfunc TestBuildCommandPassesSecurityAgentConfigFromToml(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: \"/usr/bin/bun\",\n\t\tScriptPath: \"/srv/companion-server.js\",\n\t\tBackendRoot: \"/srv/backend\",\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t\tSecurityAgent: companion.SecurityAgentConfig{\n\t\t\tReviewMode: \"warn\",\n\t\t\tAllowReviewClear: false,\n\t\t},\n\t}\n\n\tcmd := cfg.BuildCommand(\"user_security_agent\", 19002)\n\n\tfor _, want := range []string{\n\t\t\"WEB3_SECURITY_AGENT_CONFIG={\\\"review_mode\\\":\\\"warn\\\",\\\"allow_review_clear\\\":false}\",\n\t} {\n\t\tif !envHas(cmd.Env, want) {\n\t\t\tt.Fatalf(\"command env missing %q: %#v\", want, cmd.Env)\n\t\t}\n\t}\n}\n\nfunc TestBuildCommandDefaultsSecurityAgentConservatively(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: \"/usr/bin/bun\",\n\t\tScriptPath: \"/srv/companion-server.js\",\n\t\tBackendRoot: \"/srv/backend\",\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t}\n\n\tcmd := cfg.BuildCommand(\"user_security_agent_defaults\", 19003)\n\n\tif !envHas(cmd.Env, \"WEB3_SECURITY_AGENT_CONFIG={\\\"review_mode\\\":\\\"always\\\",\\\"allow_review_clear\\\":false}\") {\n\t\tt.Fatalf(\"command env missing conservative security agent defaults: %#v\", cmd.Env)\n\t}\n}\n\nfunc TestPrepareUserHomeWritesClaudeSettings(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome, err := cfg.PrepareUserHome(\"user_settings\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tAPIKey: \"settings-token\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \"settings.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read claude settings: %v\", err)\n\t}\n\tsettings := string(data)\n\tfor _, needle := range []string{\n\t\t`\"model\": \"deepseek/deepseek-v4-pro\"`,\n\t\t`\"ANTHROPIC_BASE_URL\": \"https://openrouter.ai/api\"`,\n\t\t`\"ANTHROPIC_AUTH_TOKEN\": \"settings-token\"`,\n\t\t`\"CLAUDE_CODE_SUBAGENT_MODEL\": \"deepseek/deepseek-v4-pro\"`,\n\t\t`\"Bash(web3 *)\"`,\n\t} {\n\t\tif !strings.Contains(settings, needle) {\n\t\t\tt.Fatalf(\"settings.json missing %q: %s\", needle, settings)\n\t\t}\n\t}\n\tif strings.Contains(settings, \"dist/web3\") {\n\t\tt.Fatalf(\"settings.json should only allow web3 from PATH: %s\", settings)\n\t}\n\n\tdata, err = os.ReadFile(filepath.Join(home, \".claude\", \".claude.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read claude home config: %v\", err)\n\t}\n\thomeConfig := string(data)\n\tfor _, needle := range []string{\n\t\t`\"hasCompletedOnboarding\": true`,\n\t\tfilepath.Join(home, \"workdir\"),\n\t\t`\"Bash(web3 *)\"`,\n\t} {\n\t\tif !strings.Contains(homeConfig, needle) {\n\t\t\tt.Fatalf(\".claude.json missing %q: %s\", needle, homeConfig)\n\t\t}\n\t}\n\tif strings.Contains(homeConfig, \"dist/web3\") {\n\t\tt.Fatalf(\".claude.json should only allow web3 from PATH: %s\", homeConfig)\n\t}\n}\n\nfunc TestPrepareUserHomePreservesExistingClaudeSettingsAuth(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome := cfg.UserHome(\"user_existing_settings\")\n\twriteFileForTest(t, filepath.Join(home, \".claude\", \"settings.json\"), `{\"env\":{\"ANTHROPIC_AUTH_TOKEN\":\"kept-token\"}}`+\"\\n\", 0o600)\n\n\tif _, err := cfg.PrepareUserHome(\"user_existing_settings\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t}); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \"settings.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read settings: %v\", err)\n\t}\n\tsettings := string(data)\n\tfor _, needle := range []string{\n\t\t`\"ANTHROPIC_AUTH_TOKEN\": \"kept-token\"`,\n\t\t`\"ANTHROPIC_BASE_URL\": \"https://openrouter.ai/api\"`,\n\t} {\n\t\tif !strings.Contains(settings, needle) {\n\t\t\tt.Fatalf(\"settings.json missing %q: %s\", needle, settings)\n\t\t}\n\t}\n}\n\nfunc TestPrepareUserHomeBackfillsAuthWhenExistingClaudeSettingsHaveProviderOnly(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome := cfg.UserHome(\"user_provider_without_auth\")\n\twriteFileForTest(t, filepath.Join(home, \".claude\", \"settings.json\"), `{\n \"model\": \"deepseek-v4-pro\",\n \"env\": {\n \"ANTHROPIC_BASE_URL\": \"https://api.deepseek.com/anthropic\",\n \"ANTHROPIC_MODEL\": \"deepseek-v4-pro\"\n }\n}`+\"\\n\", 0o600)\n\n\tif _, err := cfg.PrepareUserHome(\"user_provider_without_auth\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tAPIKey: \"backfilled-token\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t}); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tsettings, err := cfg.ReadUserClaudeSettings(\"user_provider_without_auth\")\n\tif err != nil {\n\t\tt.Fatalf(\"ReadUserClaudeSettings: %v\", err)\n\t}\n\tif settings.Env[\"ANTHROPIC_AUTH_TOKEN\"] != \"backfilled-token\" {\n\t\tt.Fatalf(\"ANTHROPIC_AUTH_TOKEN = %q, want backfilled token; env=%#v\", settings.Env[\"ANTHROPIC_AUTH_TOKEN\"], settings.Env)\n\t}\n}\n\nfunc TestPrepareUserHomeOverwritesExistingProviderSettingsWithResolvedRuntimeProvider(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\tif err := cfg.WriteUserClaudeSettings(\"user_existing_provider\", companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tAPIKey: \"openrouter-token\",\n\t}); err != nil {\n\t\tt.Fatalf(\"WriteUserClaudeSettings: %v\", err)\n\t}\n\n\thome, err := cfg.PrepareUserHome(\"user_existing_provider\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"deepseek\",\n\t\tAPIKey: \"deepseek-token\",\n\t\tModelName: \"deepseek-v4-pro\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \"settings.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read settings: %v\", err)\n\t}\n\tsettings := string(data)\n\tfor _, needle := range []string{\n\t\t`\"ANTHROPIC_BASE_URL\": \"https://api.deepseek.com/anthropic\"`,\n\t\t`\"ANTHROPIC_AUTH_TOKEN\": \"deepseek-token\"`,\n\t\t`\"ANTHROPIC_MODEL\": \"deepseek-v4-pro\"`,\n\t} {\n\t\tif !strings.Contains(settings, needle) {\n\t\t\tt.Fatalf(\"settings.json missing %q: %s\", needle, settings)\n\t\t}\n\t}\n\tif strings.Contains(settings, \"openrouter-token\") {\n\t\tt.Fatalf(\"PrepareUserHome kept stale provider auth: %s\", settings)\n\t}\n}\n\nfunc TestPrepareUserHomeDoesNotOverwriteClaudeHomeConfig(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome := cfg.UserHome(\"user_existing_claude\")\n\tcustom := `{\"projects\":{},\"hasCompletedOnboarding\":false}` + \"\\n\"\n\twriteFileForTest(t, filepath.Join(home, \".claude\", \".claude.json\"), custom, 0o600)\n\n\tif _, err := cfg.PrepareUserHome(\"user_existing_claude\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \".claude.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read claude home config: %v\", err)\n\t}\n\tif string(data) != custom {\n\t\tt.Fatalf(\"PrepareUserHome overwrote existing .claude.json: %s\", data)\n\t}\n}\n\nfunc TestBuildCommandUsesDirectContainerHomeAndWorkdir(t *testing.T) {\n\thomeRoot := t.TempDir()\n\tstageRoot := t.TempDir()\n\tcfg := companion.Config{\n\t\tHomeRoot: homeRoot,\n\t\tRuntimePath: filepath.Join(stageRoot, \"bin\", \"bun\"),\n\t\tScriptPath: filepath.Join(stageRoot, \"bin\", \"companion-server.js\"),\n\t\tClaudeExecutablePath: filepath.Join(stageRoot, \"bin\", \"claude\"),\n\t\tBackendRoot: filepath.Join(stageRoot, \"backend\"),\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t}\n\tif err := os.MkdirAll(filepath.Join(stageRoot, \"bin\"), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.MkdirAll(cfg.BackendRoot, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := cfg.PrepareUserHome(\"user_2\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\n\tcmd := cfg.BuildCommand(\"user_2\", 19002)\n\tif cmd.Name != cfg.RuntimePath {\n\t\tt.Fatalf(\"command name = %q, want direct runtime\", cmd.Name)\n\t}\n\tif len(cmd.Args) != 1 || cmd.Args[0] != cfg.ScriptPath {\n\t\tt.Fatalf(\"command args = %#v, want direct companion script\", cmd.Args)\n\t}\n\tif cmd.Dir != filepath.Join(cfg.UserHome(\"user_2\"), \"workdir\") {\n\t\tt.Fatalf(\"command dir = %q, want per-user workdir\", cmd.Dir)\n\t}\n\tfor _, want := range []string{\n\t\t\"HOME=\" + cfg.UserHome(\"user_2\"),\n\t\t\"TMPDIR=\" + filepath.Join(cfg.UserHome(\"user_2\"), \"tmp\"),\n\t\t\"COMPANION_SESSION_STORE=sqlite:file=\" + cfg.SQLiteDBPath(\"user_2\"),\n\t\t\"WEB3_BACKEND_ROOT=\" + cfg.BackendRoot,\n\t\t\"WEB3_REPO_SKILLS_DIR=\" + filepath.Join(cfg.BackendRoot, \"skills\"),\n\t\t\"WEB3_AGENT_WORKDIR=\" + filepath.Join(cfg.UserHome(\"user_2\"), \"workdir\"),\n\t\t\"CLAUDE_CONFIG_DIR=\" + filepath.Join(cfg.UserHome(\"user_2\"), \".claude\"),\n\t\t\"CLAUDE_CODE_EXECUTABLE=\" + cfg.ClaudeExecutablePath,\n\t} {\n\t\tif !envHas(cmd.Env, want) {\n\t\t\tt.Fatalf(\"command env missing %q: %#v\", want, cmd.Env)\n\t\t}\n\t}\n\tif envContainsAny(cmd.Env, \"ANTHROPIC_AUTH_TOKEN\", \"ANTHROPIC_API_KEY\", \"WEB3_SSM_KEY\", \"AWS_SECRET_ACCESS_KEY\", \"WEB3_JWT_SECRET\", \"WEB3_RPC_ETHEREUM_URL\") {\n\t\tt.Fatalf(\"command env leaks forbidden secrets: %#v\", cmd.Env)\n\t}\n}\n\nfunc TestEnsureEmbeddedScriptWritesCompanionServerBundle(t *testing.T) {\n\tworkdir := t.TempDir()\n\tpath, err := companion.EnsureEmbeddedScript(workdir)\n\tif err != nil {\n\t\tt.Fatalf(\"EnsureEmbeddedScript: %v\", err)\n\t}\n\tif path != filepath.Join(workdir, \"companion\", \"server\", \"companion-server.js\") {\n\t\tt.Fatalf(\"embedded script path = %q\", path)\n\t}\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tt.Fatalf(\"stat embedded script: %v\", err)\n\t}\n\tif info.Size() == 0 {\n\t\tt.Fatal(\"embedded script should not be empty\")\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read embedded script: %v\", err)\n\t}\n\tif !strings.Contains(string(data), \"Web3 Companion server listening\") {\n\t\tt.Fatalf(\"embedded script does not look like companion bundle\")\n\t}\n}\n\nfunc TestDirectStartUsesPersistentHomeForSessionStore(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: writeRuntimeShim(t, \"term-aware\", nil),\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tpersistentHome := cfg.UserHome(\"user_sessions\")\n\twriteFileForTest(t, filepath.Join(persistentHome, \"sessions.sqlite\"), \"history\\n\", 0o600)\n\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tif _, err := manager.Ensure(t.Context(), \"user_sessions\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\n\tlogPath := filepath.Join(persistentHome, \"companion.log\")\n\tdata, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read companion log: %v\", err)\n\t}\n\tif !strings.Contains(string(data), \"helper session store sqlite:file=\"+filepath.Join(persistentHome, \"sessions.sqlite\")) {\n\t\tt.Fatalf(\"companion did not receive persistent session store; log=%s\", data)\n\t}\n}\n\nfunc TestCompanionPreflightReportsMissingScript(t *testing.T) {\n\truntimePath := filepath.Join(t.TempDir(), \"bun\")\n\tif err := os.WriteFile(runtimePath, []byte(\"#!/bin/sh\\n\"), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: filepath.Join(t.TempDir(), \"missing-companion-server.js\"),\n\t\tBackendRoot: t.TempDir(),\n\t}\n\tmanager := companion.NewManager(cfg)\n\n\t_, err := manager.Ensure(t.Context(), \"user_3\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err == nil {\n\t\tt.Fatal(\"Ensure returned nil error\")\n\t}\n\tif !strings.Contains(err.Error(), \"companion script unavailable\") {\n\t\tt.Fatalf(\"error = %q, want missing script detail\", err)\n\t}\n}\n\nfunc TestEnsureWaitsForReadyAndCapturesChildOutput(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"250ms\",\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tstartedAt := time.Now()\n\tproc, err := manager.Ensure(t.Context(), \"user_ready\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tif elapsed := time.Since(startedAt); elapsed < 200*time.Millisecond {\n\t\tt.Fatalf(\"Ensure returned before companion was ready: elapsed=%s\", elapsed)\n\t}\n\tconn, err := net.DialTimeout(\"tcp\", proc.BaseURL.Host, 200*time.Millisecond)\n\tif err != nil {\n\t\tt.Fatalf(\"companion was not reachable after Ensure returned: %v\", err)\n\t}\n\t_ = conn.Close()\n\n\tlogPath := filepath.Join(cfg.UserHome(\"user_ready\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper listening\") {\n\t\tt.Fatalf(\"companion log %s did not contain helper output\", logPath)\n\t}\n}\n\nfunc TestEnsureRejectsMockHealthRuntime(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"mock-health\", nil)\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 500 * time.Millisecond,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { _ = manager.Shutdown(context.Background()) })\n\n\t_, err := manager.Ensure(t.Context(), \"user_mock_health\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err == nil {\n\t\tt.Fatal(\"Ensure accepted mock health runtime\")\n\t}\n\tif !strings.Contains(err.Error(), \"companion readiness\") && !strings.Contains(err.Error(), \"sdk\") {\n\t\tt.Fatalf(\"Ensure error = %q, want readiness/sdk detail\", err)\n\t}\n}\n\nfunc TestStartProcessUsesPortZeroAndReadyFileNonce(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"term-aware\", nil)\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tproc, err := manager.Ensure(t.Context(), \"user_port_zero\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tif proc.Port == 0 {\n\t\tt.Fatal(\"process should expose the actual bound port after ready file is read\")\n\t}\n\tlogPath := filepath.Join(cfg.UserHome(\"user_port_zero\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper requested port 0\") {\n\t\tt.Fatalf(\"companion log %s did not show child received COMPANION_PORT=0\", logPath)\n\t}\n\tif !waitForFileContains(t, logPath, \"helper ready nonce\") {\n\t\tt.Fatalf(\"companion log %s did not show child received startup nonce\", logPath)\n\t}\n}\n\nfunc TestEnsureWritesChildOutputOnlyToCompanionLog(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"250ms\",\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tstdout, stderr := captureProcessStreams(t)\n\tif _, err := manager.Ensure(t.Context(), \"user_quiet_logs\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tshutdownManager(t, manager)\n\n\tlogPath := filepath.Join(cfg.UserHome(\"user_quiet_logs\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper listening\") {\n\t\tt.Fatalf(\"companion log %s did not contain helper output\", logPath)\n\t}\n\toutput := stdout() + stderr()\n\tif strings.Contains(output, \"helper starting\") || strings.Contains(output, \"helper listening\") {\n\t\tt.Fatalf(\"server streams should not include companion helper output, got %q\", output)\n\t}\n}\n\nfunc TestEnsureRefreshesContainerCredentialWhenReusingProcess(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"term-aware\", nil)\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tfirst, err := manager.Ensure(t.Context(), \"user_refresh\", companion.AuthHandoff{\n\t\tContainerCredential: companion.ContainerCredential{\n\t\t\tVersion: 1,\n\t\t\tClient: \"container\",\n\t\t\tCredentialID: \"cc_old\",\n\t\t\tSecret: \"old-secret\",\n\t\t\tAPIURL: \"http://localhost:8080\",\n\t\t},\n\t}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"initial Ensure: %v\", err)\n\t}\n\tsecond, err := manager.Ensure(t.Context(), \"user_refresh\", companion.AuthHandoff{\n\t\tContainerCredential: companion.ContainerCredential{\n\t\t\tVersion: 1,\n\t\t\tClient: \"container\",\n\t\t\tCredentialID: \"cc_new\",\n\t\t\tSecret: \"new-secret\",\n\t\t\tAPIURL: \"http://localhost:8080\",\n\t\t},\n\t}, companion.LLMProviderConfig{Provider: \"openrouter\", APIKey: \"runtime-token-new\", ModelName: \"deepseek/deepseek-v4-pro\"})\n\tif err != nil {\n\t\tt.Fatalf(\"second Ensure: %v\", err)\n\t}\n\tif second != first {\n\t\tt.Fatalf(\"Ensure should reuse the running process, got %p then %p\", first, second)\n\t}\n\n\tcredentialPath := filepath.Join(cfg.UserHome(\"user_refresh\"), \".local\", \"web3\", \"container-credential.json\")\n\tdata, err := os.ReadFile(credentialPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read refreshed container credential: %v\", err)\n\t}\n\tcredential := string(data)\n\tfor _, needle := range []string{\n\t\t`\"credential_id\":\"cc_new\"`,\n\t\t`\"secret\":\"new-secret\"`,\n\t} {\n\t\tif !strings.Contains(credential, needle) {\n\t\t\tt.Fatalf(\"container credential was not refreshed with %q: %s\", needle, credential)\n\t\t}\n\t}\n\tif strings.Contains(credential, \"old-secret\") {\n\t\tt.Fatalf(\"container credential still contains old secret: %s\", credential)\n\t}\n\tsettings, err := cfg.ReadUserClaudeSettings(\"user_refresh\")\n\tif err != nil {\n\t\tt.Fatalf(\"read refreshed provider settings: %v\", err)\n\t}\n\tif settings.Env[\"ANTHROPIC_AUTH_TOKEN\"] != \"runtime-token-new\" {\n\t\tt.Fatalf(\"reused process did not refresh runtime provider settings: %#v\", settings.Env)\n\t}\n}\n\nfunc TestStopUserTerminatesCompanionProcess(t *testing.T) {\n\tmarkerPath := filepath.Join(t.TempDir(), \"stop.marker\")\n\truntimePath := writeRuntimeShim(t, \"term-aware\", map[string]string{\n\t\t\"COMPANION_TEST_TERM_MARKER\": markerPath,\n\t})\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { _ = manager.Shutdown(context.Background()) })\n\n\tif _, err := manager.Ensure(t.Context(), \"user_stop\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tif err := manager.StopUser(t.Context(), \"user_stop\"); err != nil {\n\t\tt.Fatalf(\"StopUser: %v\", err)\n\t}\n\tdata, err := os.ReadFile(markerPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read stop marker: %v\", err)\n\t}\n\tif string(data) != \"terminated\\n\" {\n\t\tt.Fatalf(\"stop marker = %q, want terminated\", data)\n\t}\n}\n\nfunc TestMaxProcessesEvictsLeastRecentlyUsedIdleProcess(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: writeRuntimeShim(t, \"term-aware\", nil),\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t\tMaxProcesses: 1,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { _ = manager.Shutdown(context.Background()) })\n\n\tfirst, err := manager.Ensure(t.Context(), \"user_lru_old\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"first Ensure: %v\", err)\n\t}\n\tsecond, err := manager.Ensure(t.Context(), \"user_lru_new\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"second Ensure: %v\", err)\n\t}\n\tif first == second {\n\t\tt.Fatal(\"expected a distinct process for the second user\")\n\t}\n\tselect {\n\tcase <-first.DoneForTest():\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"least recently used process was not evicted\")\n\t}\n\tif second.IsDoneForTest() {\n\t\tt.Fatal(\"new process was unexpectedly terminated\")\n\t}\n}\n\nfunc TestConcurrentEnsureWaitsForSameReadyProcess(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"250ms\",\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\ttype result struct {\n\t\tproc *companion.Process\n\t\terr error\n\t\telapsed time.Duration\n\t}\n\tresults := make(chan result, 2)\n\tvar wg sync.WaitGroup\n\tstart := make(chan struct{})\n\tfor range 2 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\t<-start\n\t\t\tstartedAt := time.Now()\n\t\t\tproc, err := manager.Ensure(t.Context(), \"user_concurrent\", companion.AuthHandoff{}, testRuntimeProvider())\n\t\t\tresults <- result{proc: proc, err: err, elapsed: time.Since(startedAt)}\n\t\t}()\n\t}\n\tclose(start)\n\twg.Wait()\n\tclose(results)\n\n\tvar got []result\n\tfor item := range results {\n\t\tgot = append(got, item)\n\t}\n\tif len(got) != 2 {\n\t\tt.Fatalf(\"got %d Ensure results, want 2\", len(got))\n\t}\n\tfor _, item := range got {\n\t\tif item.err != nil {\n\t\t\tt.Fatalf(\"Ensure: %v\", item.err)\n\t\t}\n\t\tif item.elapsed < 200*time.Millisecond {\n\t\t\tt.Fatalf(\"concurrent Ensure returned before readiness: elapsed=%s\", item.elapsed)\n\t\t}\n\t}\n\tif got[0].proc != got[1].proc {\n\t\tt.Fatalf(\"concurrent Ensure should reuse one process, got %p and %p\", got[0].proc, got[1].proc)\n\t}\n\n\tlogPath := filepath.Join(cfg.UserHome(\"user_concurrent\"), \"companion.log\")\n\tdata, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read companion log: %v\", err)\n\t}\n\tif count := strings.Count(string(data), \"helper listening\"); count != 1 {\n\t\tt.Fatalf(\"started %d helper processes, want 1; log=%q\", count, string(data))\n\t}\n}\n\nfunc TestShutdownTerminatesCompanionProcessesGracefully(t *testing.T) {\n\tmarkerPath := filepath.Join(t.TempDir(), \"term.marker\")\n\truntimePath := writeRuntimeShim(t, \"term-aware\", map[string]string{\n\t\t\"COMPANION_TEST_TERM_MARKER\": markerPath,\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tif _, err := manager.Ensure(t.Context(), \"user_term\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tif err := manager.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Shutdown: %v\", err)\n\t}\n\tdata, err := os.ReadFile(markerPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read SIGTERM marker: %v\", err)\n\t}\n\tif string(data) != \"terminated\\n\" {\n\t\tt.Fatalf(\"SIGTERM marker = %q, want terminated\", string(data))\n\t}\n}\n\nfunc TestShutdownPreventsNewCompanionStarts(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: writeRuntimeShim(t, \"term-aware\", nil),\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t}\n\tmanager := companion.NewManager(cfg)\n\tif err := manager.Shutdown(context.Background()); err != nil {\n\t\tt.Fatalf(\"Shutdown: %v\", err)\n\t}\n\n\t_, err := manager.Ensure(t.Context(), \"user_after_shutdown\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err == nil {\n\t\tt.Fatal(\"Ensure after Shutdown returned nil error\")\n\t}\n\tif !strings.Contains(err.Error(), \"shutting down\") {\n\t\tt.Fatalf(\"Ensure error = %q, want shutting down\", err)\n\t}\n}\n\nfunc TestShutdownStopsProcessThatIsStillStarting(t *testing.T) {\n\tmarkerPath := filepath.Join(t.TempDir(), \"starting-term.marker\")\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"5s\",\n\t\t\"COMPANION_TEST_TERM_MARKER\": markerPath,\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 10 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\t_, err := manager.Ensure(context.Background(), \"user_starting_shutdown\", companion.AuthHandoff{}, testRuntimeProvider())\n\t\terrCh <- err\n\t}()\n\tlogPath := filepath.Join(cfg.UserHome(\"user_starting_shutdown\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper starting\") {\n\t\tt.Fatalf(\"companion log %s did not show helper startup\", logPath)\n\t}\n\n\tstartedAt := time.Now()\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tif err := manager.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Shutdown: %v\", err)\n\t}\n\tif elapsed := time.Since(startedAt); elapsed > time.Second {\n\t\tt.Fatalf(\"Shutdown waited for startup timeout instead of terminating starting process: elapsed=%s\", elapsed)\n\t}\n\tdata, err := os.ReadFile(markerPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read SIGTERM marker: %v\", err)\n\t}\n\tif string(data) != \"terminated\\n\" {\n\t\tt.Fatalf(\"SIGTERM marker = %q, want terminated\", string(data))\n\t}\n\tselect {\n\tcase err := <-errCh:\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Ensure returned nil error after Shutdown stopped starting process\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Ensure did not return after Shutdown stopped starting process\")\n\t}\n}\n\nfunc containsSequence(items []string, sequence []string) bool {\n\treturn indexSequence(items, sequence) >= 0\n}\n\nfunc envContainsAny(env []string, needles ...string) bool {\n\tfor _, item := range env {\n\t\tfor _, needle := range needles {\n\t\t\tif needle != \"\" && strings.Contains(item, needle) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc envHas(env []string, value string) bool {\n\tfor _, item := range env {\n\t\tif item == value {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc indexSequence(items []string, sequence []string) int {\n\tfor i := 0; i+len(sequence) <= len(items); i++ {\n\t\tok := true\n\t\tfor j := range sequence {\n\t\t\tif items[i+j] != sequence[j] {\n\t\t\t\tok = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ok {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc testRuntimeProvider() companion.LLMProviderConfig {\n\treturn companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tAPIKey: \"runtime-token\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t}\n}\n\nfunc writeRuntimeShim(t *testing.T, helperMode string, env map[string]string) string {\n\tt.Helper()\n\tpath := filepath.Join(t.TempDir(), \"runtime-shim\")\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"#!/bin/sh\\n\")\n\tbuilder.WriteString(\"export COMPANION_TEST_HELPER=\" + shellQuote(helperMode) + \"\\n\")\n\tfor key, value := range env {\n\t\tbuilder.WriteString(\"export \" + key + \"=\" + shellQuote(value) + \"\\n\")\n\t}\n\tbuilder.WriteString(\"exec \" + shellQuote(os.Args[0]) + \" \\\"$@\\\"\\n\")\n\tif err := os.WriteFile(path, []byte(builder.String()), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn path\n}\n\nfunc writePlaceholderScript(t *testing.T) string {\n\tt.Helper()\n\tpath := filepath.Join(t.TempDir(), \"companion-server.js\")\n\twriteFileForTest(t, path, \"// test companion script\\n\", 0o644)\n\treturn path\n}\n\nfunc writeFileForTest(t *testing.T, path, content string, perm os.FileMode) {\n\tt.Helper()\n\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.WriteFile(path, []byte(content), perm); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc shellQuote(value string) string {\n\treturn \"'\" + strings.ReplaceAll(value, \"'\", \"'\\\\''\") + \"'\"\n}\n\nfunc shutdownManager(t *testing.T, manager *companion.Manager) {\n\tt.Helper()\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tif err := manager.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Shutdown cleanup: %v\", err)\n\t}\n}\n\nfunc waitForFileContains(t *testing.T, path, needle string) bool {\n\tt.Helper()\n\tdeadline := time.Now().Add(2 * time.Second)\n\tfor time.Now().Before(deadline) {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err == nil && strings.Contains(string(data), needle) {\n\t\t\treturn true\n\t\t}\n\t\ttime.Sleep(20 * time.Millisecond)\n\t}\n\treturn false\n}\n\nfunc captureProcessStreams(t *testing.T) (func() string, func() string) {\n\tt.Helper()\n\torigStdout := os.Stdout\n\torigStderr := os.Stderr\n\tstdoutReader, stdoutWriter, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstderrReader, stderrWriter, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tos.Stdout = stdoutWriter\n\tos.Stderr = stderrWriter\n\tt.Cleanup(func() {\n\t\tos.Stdout = origStdout\n\t\tos.Stderr = origStderr\n\t})\n\n\treadPipe := func(reader *os.File, writer *os.File) func() string {\n\t\tvar once sync.Once\n\t\tvar output string\n\t\treturn func() string {\n\t\t\tonce.Do(func() {\n\t\t\t\t_ = writer.Close()\n\t\t\t\tdata, err := io.ReadAll(reader)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"read captured stream: %v\", err)\n\t\t\t\t}\n\t\t\t\toutput = string(data)\n\t\t\t\t_ = reader.Close()\n\t\t\t})\n\t\t\treturn output\n\t\t}\n\t}\n\n\treturn readPipe(stdoutReader, stdoutWriter), readPipe(stderrReader, stderrWriter)\n}\n\nfunc parseHelperDelay() time.Duration {\n\tdelay, err := time.ParseDuration(os.Getenv(\"COMPANION_TEST_READY_DELAY\"))\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn delay\n}\n\nfunc runCompanionHelper(delay time.Duration) {\n\tif delay > 0 {\n\t\tfmt.Println(\"helper starting\")\n\t\tsetupSignalMarker(nil)\n\t\ttime.Sleep(delay)\n\t}\n\thost := os.Getenv(\"COMPANION_HOST\")\n\tif host == \"\" {\n\t\thost = \"127.0.0.1\"\n\t}\n\tln, err := net.Listen(\"tcp\", net.JoinHostPort(host, os.Getenv(\"COMPANION_PORT\")))\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"helper listen failed: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n\tif os.Getenv(\"COMPANION_PORT\") == \"0\" {\n\t\tfmt.Println(\"helper requested port 0\")\n\t}\n\tif nonce := os.Getenv(\"COMPANION_STARTUP_NONCE\"); nonce != \"\" {\n\t\tfmt.Printf(\"helper ready nonce %s\\n\", nonce)\n\t}\n\tif sessionStore := os.Getenv(\"COMPANION_SESSION_STORE\"); sessionStore != \"\" {\n\t\tfmt.Printf(\"helper session store %s\\n\", sessionStore)\n\t}\n\tif agentWorkdir := os.Getenv(\"WEB3_AGENT_WORKDIR\"); agentWorkdir != \"\" {\n\t\tfmt.Printf(\"helper agent workdir %s\\n\", agentWorkdir)\n\t}\n\tif readyFile := os.Getenv(\"COMPANION_READY_FILE\"); readyFile != \"\" {\n\t\tport := ln.Addr().(*net.TCPAddr).Port\n\t\t_ = os.MkdirAll(filepath.Dir(readyFile), 0o700)\n\t\t_ = os.WriteFile(readyFile, []byte(fmt.Sprintf(`{\"host\":%q,\"port\":%d,\"pid\":%d,\"nonce\":%q,\"sdk\":\"claude-agent-sdk\"}`+\"\\n\", host, port, os.Getpid(), os.Getenv(\"COMPANION_STARTUP_NONCE\"))), 0o600)\n\t}\n\tfmt.Println(\"helper listening\")\n\n\tsetupSignalMarker(ln)\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tbuf := make([]byte, 1024)\n\t\tn, _ := conn.Read(buf)\n\t\tif strings.Contains(string(buf[:n]), \"GET /api/health \") {\n\t\t\tbody := fmt.Sprintf(`{\"ok\":true,\"sdk\":\"claude-agent-sdk\",\"nonce\":%q}`, os.Getenv(\"COMPANION_STARTUP_NONCE\"))\n\t\t\t_, _ = conn.Write([]byte(fmt.Sprintf(\"HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\n\\r\\n%s\", len(body), body)))\n\t\t} else {\n\t\t\t_, _ = conn.Write([]byte(\"HTTP/1.1 404 Not Found\\r\\nContent-Length: 0\\r\\n\\r\\n\"))\n\t\t}\n\t\t_ = conn.Close()\n\t}\n}\n\nfunc runMockHealthHelper() {\n\thost := os.Getenv(\"COMPANION_HOST\")\n\tif host == \"\" {\n\t\thost = \"127.0.0.1\"\n\t}\n\tln, err := net.Listen(\"tcp\", net.JoinHostPort(host, os.Getenv(\"COMPANION_PORT\")))\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"mock helper listen failed: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n\tif readyFile := os.Getenv(\"COMPANION_READY_FILE\"); readyFile != \"\" {\n\t\tport := ln.Addr().(*net.TCPAddr).Port\n\t\t_ = os.MkdirAll(filepath.Dir(readyFile), 0o700)\n\t\t_ = os.WriteFile(readyFile, []byte(fmt.Sprintf(`{\"host\":%q,\"port\":%d,\"pid\":%d,\"nonce\":%q,\"sdk\":\"mock\"}`+\"\\n\", host, port, os.Getpid(), os.Getenv(\"COMPANION_STARTUP_NONCE\"))), 0o600)\n\t}\n\tsetupSignalMarker(ln)\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tbuf := make([]byte, 1024)\n\t\tn, _ := conn.Read(buf)\n\t\tif strings.Contains(string(buf[:n]), \"GET /api/health \") {\n\t\t\tbody := fmt.Sprintf(`{\"ok\":true,\"sdk\":\"mock\",\"nonce\":%q}`, os.Getenv(\"COMPANION_STARTUP_NONCE\"))\n\t\t\t_, _ = conn.Write([]byte(fmt.Sprintf(\"HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\n\\r\\n%s\", len(body), body)))\n\t\t} else {\n\t\t\t_, _ = conn.Write([]byte(\"HTTP/1.1 404 Not Found\\r\\nContent-Length: 0\\r\\n\\r\\n\"))\n\t\t}\n\t\t_ = conn.Close()\n\t}\n}\n\nfunc setupSignalMarker(listener net.Listener) {\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)\n\tgo func() {\n\t\t<-signals\n\t\tif marker := os.Getenv(\"COMPANION_TEST_TERM_MARKER\"); marker != \"\" {\n\t\t\t_ = os.WriteFile(marker, []byte(\"terminated\\n\"), 0o600)\n\t\t}\n\t\tif listener != nil {\n\t\t\t_ = listener.Close()\n\t\t}\n\t\tos.Exit(0)\n\t}()\n}\n"}, "a79f7e6c456cf3bb82353967ee22b8aa881f3bae": {"t": 1785759442.8928463, "n": 35957, "h": "c10dfd144a803359", "c": "// Copyright (c) 2026 BlockSec (https://blocksec.com).\n// Licensed under the MIT License.\n\npackage companion_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/blocksecteam/web3-companion/internal/companion\"\n)\n\nfunc TestMain(m *testing.M) {\n\tswitch os.Getenv(\"COMPANION_TEST_HELPER\") {\n\tcase \"delayed-ready\":\n\t\trunCompanionHelper(parseHelperDelay())\n\t\treturn\n\tcase \"mock-health\":\n\t\trunMockHealthHelper()\n\t\treturn\n\tcase \"term-aware\":\n\t\trunCompanionHelper(0)\n\t\treturn\n\tdefault:\n\t\tos.Exit(m.Run())\n\t}\n}\n\nfunc TestUserHomeAndSQLiteDBPathArePerUser(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\n\thome := cfg.UserHome(\"user/with odd spaces\")\n\tif !strings.HasPrefix(home, cfg.HomeRoot) {\n\t\tt.Fatalf(\"home %q must be under root %q\", home, cfg.HomeRoot)\n\t}\n\tif strings.Contains(home, \"/with odd spaces\") || strings.Contains(home, \" \") {\n\t\tt.Fatalf(\"home path should use sanitized user id, got %q\", home)\n\t}\n\tif got, want := cfg.SQLiteDBPath(\"user/with odd spaces\"), filepath.Join(home, \"sessions.sqlite\"); got != want {\n\t\tt.Fatalf(\"SQLiteDBPath() = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestWriteContainerCredentialUsesFixedFileAndDoesNotExposeSecretInEnv(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: \"/usr/bin/bun\",\n\t\tScriptPath: \"/srv/companion-server.js\",\n\t\tBackendRoot: \"/srv/backend\",\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t}\n\n\tcredential := companion.ContainerCredential{\n\t\tVersion: 1,\n\t\tClient: \"container\",\n\t\tCredentialID: \"cc_1\",\n\t\tSecret: \"secret-container-credential\",\n\t\tAPIURL: \"http://localhost:8080\",\n\t}\n\thome, err := cfg.PrepareUserHome(\"user_1\", companion.AuthHandoff{ContainerCredential: credential}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".local\", \"web3\", \"container-credential.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read container credential: %v\", err)\n\t}\n\tif !strings.Contains(string(data), `\"credential_id\":\"cc_1\"`) ||\n\t\t!strings.Contains(string(data), `\"secret\":\"secret-container-credential\"`) {\n\t\tt.Fatalf(\"container credential data = %q\", string(data))\n\t}\n\tcmd := cfg.BuildCommand(\"user_1\", 19001)\n\tfor _, item := range cmd.Env {\n\t\tif strings.Contains(item, \"secret-container-credential\") {\n\t\t\tt.Fatalf(\"command env must not expose auth tokens: %#v\", cmd.Env)\n\t\t}\n\t}\n\tif envContainsAny(cmd.Env, \"ANTHROPIC_AUTH_TOKEN\", \"ANTHROPIC_API_KEY\", \"ANTHROPIC_MODEL\", \"ANTHROPIC_BASE_URL\", \"CLAUDE_CODE_EFFORT_LEVEL\") {\n\t\tt.Fatalf(\"model/provider secrets and defaults should live in settings.json, not env: %#v\", cmd.Env)\n\t}\n}\n\nfunc TestBuildCommandPassesSecurityAgentConfigFromToml(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: \"/usr/bin/bun\",\n\t\tScriptPath: \"/srv/companion-server.js\",\n\t\tBackendRoot: \"/srv/backend\",\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t\tSecurityAgent: companion.SecurityAgentConfig{\n\t\t\tReviewMode: \"warn\",\n\t\t\tAllowReviewClear: false,\n\t\t},\n\t}\n\n\tcmd := cfg.BuildCommand(\"user_security_agent\", 19002)\n\n\tfor _, want := range []string{\n\t\t\"WEB3_SECURITY_AGENT_CONFIG={\\\"review_mode\\\":\\\"warn\\\",\\\"allow_review_clear\\\":false}\",\n\t} {\n\t\tif !envHas(cmd.Env, want) {\n\t\t\tt.Fatalf(\"command env missing %q: %#v\", want, cmd.Env)\n\t\t}\n\t}\n}\n\nfunc TestBuildCommandDefaultsSecurityAgentConservatively(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: \"/usr/bin/bun\",\n\t\tScriptPath: \"/srv/companion-server.js\",\n\t\tBackendRoot: \"/srv/backend\",\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t}\n\n\tcmd := cfg.BuildCommand(\"user_security_agent_defaults\", 19003)\n\n\tif !envHas(cmd.Env, \"WEB3_SECURITY_AGENT_CONFIG={\\\"review_mode\\\":\\\"always\\\",\\\"allow_review_clear\\\":false}\") {\n\t\tt.Fatalf(\"command env missing conservative security agent defaults: %#v\", cmd.Env)\n\t}\n}\n\nfunc TestPrepareUserHomeWritesClaudeSettings(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome, err := cfg.PrepareUserHome(\"user_settings\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tAPIKey: \"settings-token\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \"settings.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read claude settings: %v\", err)\n\t}\n\tsettings := string(data)\n\tfor _, needle := range []string{\n\t\t`\"model\": \"deepseek/deepseek-v4-pro\"`,\n\t\t`\"ANTHROPIC_BASE_URL\": \"https://openrouter.ai/api\"`,\n\t\t`\"ANTHROPIC_AUTH_TOKEN\": \"settings-token\"`,\n\t\t`\"CLAUDE_CODE_SUBAGENT_MODEL\": \"deepseek/deepseek-v4-pro\"`,\n\t\t`\"Bash(web3 *)\"`,\n\t} {\n\t\tif !strings.Contains(settings, needle) {\n\t\t\tt.Fatalf(\"settings.json missing %q: %s\", needle, settings)\n\t\t}\n\t}\n\tif strings.Contains(settings, \"dist/web3\") {\n\t\tt.Fatalf(\"settings.json should only allow web3 from PATH: %s\", settings)\n\t}\n\n\tdata, err = os.ReadFile(filepath.Join(home, \".claude\", \".claude.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read claude home config: %v\", err)\n\t}\n\thomeConfig := string(data)\n\tfor _, needle := range []string{\n\t\t`\"hasCompletedOnboarding\": true`,\n\t\tfilepath.Join(home, \"workdir\"),\n\t\t`\"Bash(web3 *)\"`,\n\t} {\n\t\tif !strings.Contains(homeConfig, needle) {\n\t\t\tt.Fatalf(\".claude.json missing %q: %s\", needle, homeConfig)\n\t\t}\n\t}\n\tif strings.Contains(homeConfig, \"dist/web3\") {\n\t\tt.Fatalf(\".claude.json should only allow web3 from PATH: %s\", homeConfig)\n\t}\n}\n\nfunc TestPrepareUserHomePreservesExistingClaudeSettingsAuth(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome := cfg.UserHome(\"user_existing_settings\")\n\twriteFileForTest(t, filepath.Join(home, \".claude\", \"settings.json\"), `{\"env\":{\"ANTHROPIC_AUTH_TOKEN\":\"kept-token\"}}`+\"\\n\", 0o600)\n\n\tif _, err := cfg.PrepareUserHome(\"user_existing_settings\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t}); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \"settings.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read settings: %v\", err)\n\t}\n\tsettings := string(data)\n\tfor _, needle := range []string{\n\t\t`\"ANTHROPIC_AUTH_TOKEN\": \"kept-token\"`,\n\t\t`\"ANTHROPIC_BASE_URL\": \"https://openrouter.ai/api\"`,\n\t} {\n\t\tif !strings.Contains(settings, needle) {\n\t\t\tt.Fatalf(\"settings.json missing %q: %s\", needle, settings)\n\t\t}\n\t}\n}\n\nfunc TestPrepareUserHomeBackfillsAuthWhenExistingClaudeSettingsHaveProviderOnly(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome := cfg.UserHome(\"user_provider_without_auth\")\n\twriteFileForTest(t, filepath.Join(home, \".claude\", \"settings.json\"), `{\n \"model\": \"deepseek-v4-pro\",\n \"env\": {\n \"ANTHROPIC_BASE_URL\": \"https://api.deepseek.com/anthropic\",\n \"ANTHROPIC_MODEL\": \"deepseek-v4-pro\"\n }\n}`+\"\\n\", 0o600)\n\n\tif _, err := cfg.PrepareUserHome(\"user_provider_without_auth\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tAPIKey: \"backfilled-token\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t}); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tsettings, err := cfg.ReadUserClaudeSettings(\"user_provider_without_auth\")\n\tif err != nil {\n\t\tt.Fatalf(\"ReadUserClaudeSettings: %v\", err)\n\t}\n\tif settings.Env[\"ANTHROPIC_AUTH_TOKEN\"] != \"backfilled-token\" {\n\t\tt.Fatalf(\"ANTHROPIC_AUTH_TOKEN = %q, want backfilled token; env=%#v\", settings.Env[\"ANTHROPIC_AUTH_TOKEN\"], settings.Env)\n\t}\n}\n\nfunc TestPrepareUserHomeOverwritesExistingProviderSettingsWithResolvedRuntimeProvider(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\tif err := cfg.WriteUserClaudeSettings(\"user_existing_provider\", companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tBaseURL: \"https://openrouter.ai/api\",\n\t\tAPIKey: \"openrouter-token\",\n\t}); err != nil {\n\t\tt.Fatalf(\"WriteUserClaudeSettings: %v\", err)\n\t}\n\n\thome, err := cfg.PrepareUserHome(\"user_existing_provider\", companion.AuthHandoff{}, companion.LLMProviderConfig{\n\t\tProvider: \"deepseek\",\n\t\tAPIKey: \"deepseek-token\",\n\t\tModelName: \"deepseek-v4-pro\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \"settings.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read settings: %v\", err)\n\t}\n\tsettings := string(data)\n\tfor _, needle := range []string{\n\t\t`\"ANTHROPIC_BASE_URL\": \"https://api.deepseek.com/anthropic\"`,\n\t\t`\"ANTHROPIC_AUTH_TOKEN\": \"deepseek-token\"`,\n\t\t`\"ANTHROPIC_MODEL\": \"deepseek-v4-pro\"`,\n\t} {\n\t\tif !strings.Contains(settings, needle) {\n\t\t\tt.Fatalf(\"settings.json missing %q: %s\", needle, settings)\n\t\t}\n\t}\n\tif strings.Contains(settings, \"openrouter-token\") {\n\t\tt.Fatalf(\"PrepareUserHome kept stale provider auth: %s\", settings)\n\t}\n}\n\nfunc TestPrepareUserHomeDoesNotOverwriteClaudeHomeConfig(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t}\n\thome := cfg.UserHome(\"user_existing_claude\")\n\tcustom := `{\"projects\":{},\"hasCompletedOnboarding\":false}` + \"\\n\"\n\twriteFileForTest(t, filepath.Join(home, \".claude\", \".claude.json\"), custom, 0o600)\n\n\tif _, err := cfg.PrepareUserHome(\"user_existing_claude\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(home, \".claude\", \".claude.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read claude home config: %v\", err)\n\t}\n\tif string(data) != custom {\n\t\tt.Fatalf(\"PrepareUserHome overwrote existing .claude.json: %s\", data)\n\t}\n}\n\nfunc TestBuildCommandUsesDirectContainerHomeAndWorkdir(t *testing.T) {\n\thomeRoot := t.TempDir()\n\tstageRoot := t.TempDir()\n\tcfg := companion.Config{\n\t\tHomeRoot: homeRoot,\n\t\tRuntimePath: filepath.Join(stageRoot, \"bin\", \"bun\"),\n\t\tScriptPath: filepath.Join(stageRoot, \"bin\", \"companion-server.js\"),\n\t\tClaudeExecutablePath: filepath.Join(stageRoot, \"bin\", \"claude\"),\n\t\tBackendRoot: filepath.Join(stageRoot, \"backend\"),\n\t\tListenHost: \"127.0.0.1\",\n\t\tBackendAPIURL: \"http://localhost:8080\",\n\t}\n\tif err := os.MkdirAll(filepath.Join(stageRoot, \"bin\"), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.MkdirAll(cfg.BackendRoot, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := cfg.PrepareUserHome(\"user_2\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"PrepareUserHome: %v\", err)\n\t}\n\n\tcmd := cfg.BuildCommand(\"user_2\", 19002)\n\tif cmd.Name != cfg.RuntimePath {\n\t\tt.Fatalf(\"command name = %q, want direct runtime\", cmd.Name)\n\t}\n\tif len(cmd.Args) != 1 || cmd.Args[0] != cfg.ScriptPath {\n\t\tt.Fatalf(\"command args = %#v, want direct companion script\", cmd.Args)\n\t}\n\tif cmd.Dir != filepath.Join(cfg.UserHome(\"user_2\"), \"workdir\") {\n\t\tt.Fatalf(\"command dir = %q, want per-user workdir\", cmd.Dir)\n\t}\n\tfor _, want := range []string{\n\t\t\"HOME=\" + cfg.UserHome(\"user_2\"),\n\t\t\"TMPDIR=\" + filepath.Join(cfg.UserHome(\"user_2\"), \"tmp\"),\n\t\t\"COMPANION_SESSION_STORE=sqlite:file=\" + cfg.SQLiteDBPath(\"user_2\"),\n\t\t\"WEB3_BACKEND_ROOT=\" + cfg.BackendRoot,\n\t\t\"WEB3_REPO_SKILLS_DIR=\" + filepath.Join(cfg.BackendRoot, \"skills\"),\n\t\t\"WEB3_AGENT_WORKDIR=\" + filepath.Join(cfg.UserHome(\"user_2\"), \"workdir\"),\n\t\t\"CLAUDE_CONFIG_DIR=\" + filepath.Join(cfg.UserHome(\"user_2\"), \".claude\"),\n\t\t\"CLAUDE_CODE_EXECUTABLE=\" + cfg.ClaudeExecutablePath,\n\t} {\n\t\tif !envHas(cmd.Env, want) {\n\t\t\tt.Fatalf(\"command env missing %q: %#v\", want, cmd.Env)\n\t\t}\n\t}\n\tif envContainsAny(cmd.Env, \"ANTHROPIC_AUTH_TOKEN\", \"ANTHROPIC_API_KEY\", \"WEB3_SSM_KEY\", \"AWS_SECRET_ACCESS_KEY\", \"WEB3_JWT_SECRET\", \"WEB3_RPC_ETHEREUM_URL\") {\n\t\tt.Fatalf(\"command env leaks forbidden secrets: %#v\", cmd.Env)\n\t}\n}\n\nfunc TestEnsureEmbeddedScriptWritesCompanionServerBundle(t *testing.T) {\n\tworkdir := t.TempDir()\n\tpath, err := companion.EnsureEmbeddedScript(workdir)\n\tif err != nil {\n\t\tt.Fatalf(\"EnsureEmbeddedScript: %v\", err)\n\t}\n\tif path != filepath.Join(workdir, \"companion\", \"server\", \"companion-server.js\") {\n\t\tt.Fatalf(\"embedded script path = %q\", path)\n\t}\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tt.Fatalf(\"stat embedded script: %v\", err)\n\t}\n\tif info.Size() == 0 {\n\t\tt.Fatal(\"embedded script should not be empty\")\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read embedded script: %v\", err)\n\t}\n\tif !strings.Contains(string(data), \"Web3 Companion server listening\") {\n\t\tt.Fatalf(\"embedded script does not look like companion bundle\")\n\t}\n}\n\nfunc TestDirectStartUsesPersistentHomeForSessionStore(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: writeRuntimeShim(t, \"term-aware\", nil),\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tpersistentHome := cfg.UserHome(\"user_sessions\")\n\twriteFileForTest(t, filepath.Join(persistentHome, \"sessions.sqlite\"), \"history\\n\", 0o600)\n\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tif _, err := manager.Ensure(t.Context(), \"user_sessions\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\n\tlogPath := filepath.Join(persistentHome, \"companion.log\")\n\tdata, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read companion log: %v\", err)\n\t}\n\tif !strings.Contains(string(data), \"helper session store sqlite:file=\"+filepath.Join(persistentHome, \"sessions.sqlite\")) {\n\t\tt.Fatalf(\"companion did not receive persistent session store; log=%s\", data)\n\t}\n}\n\nfunc TestCompanionPreflightReportsMissingScript(t *testing.T) {\n\truntimePath := filepath.Join(t.TempDir(), \"bun\")\n\tif err := os.WriteFile(runtimePath, []byte(\"#!/bin/sh\\n\"), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: filepath.Join(t.TempDir(), \"missing-companion-server.js\"),\n\t\tBackendRoot: t.TempDir(),\n\t}\n\tmanager := companion.NewManager(cfg)\n\n\t_, err := manager.Ensure(t.Context(), \"user_3\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err == nil {\n\t\tt.Fatal(\"Ensure returned nil error\")\n\t}\n\tif !strings.Contains(err.Error(), \"companion script unavailable\") {\n\t\tt.Fatalf(\"error = %q, want missing script detail\", err)\n\t}\n}\n\nfunc TestEnsureWaitsForReadyAndCapturesChildOutput(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"250ms\",\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tstartedAt := time.Now()\n\tproc, err := manager.Ensure(t.Context(), \"user_ready\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tif elapsed := time.Since(startedAt); elapsed < 200*time.Millisecond {\n\t\tt.Fatalf(\"Ensure returned before companion was ready: elapsed=%s\", elapsed)\n\t}\n\tconn, err := net.DialTimeout(\"tcp\", proc.BaseURL.Host, 200*time.Millisecond)\n\tif err != nil {\n\t\tt.Fatalf(\"companion was not reachable after Ensure returned: %v\", err)\n\t}\n\t_ = conn.Close()\n\n\tlogPath := filepath.Join(cfg.UserHome(\"user_ready\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper listening\") {\n\t\tt.Fatalf(\"companion log %s did not contain helper output\", logPath)\n\t}\n}\n\nfunc TestEnsureRejectsMockHealthRuntime(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"mock-health\", nil)\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 500 * time.Millisecond,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { _ = manager.Shutdown(context.Background()) })\n\n\t_, err := manager.Ensure(t.Context(), \"user_mock_health\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err == nil {\n\t\tt.Fatal(\"Ensure accepted mock health runtime\")\n\t}\n\tif !strings.Contains(err.Error(), \"companion readiness\") && !strings.Contains(err.Error(), \"sdk\") {\n\t\tt.Fatalf(\"Ensure error = %q, want readiness/sdk detail\", err)\n\t}\n}\n\nfunc TestStartProcessUsesPortZeroAndReadyFileNonce(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"term-aware\", nil)\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tproc, err := manager.Ensure(t.Context(), \"user_port_zero\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tif proc.Port == 0 {\n\t\tt.Fatal(\"process should expose the actual bound port after ready file is read\")\n\t}\n\tlogPath := filepath.Join(cfg.UserHome(\"user_port_zero\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper requested port 0\") {\n\t\tt.Fatalf(\"companion log %s did not show child received COMPANION_PORT=0\", logPath)\n\t}\n\tif !waitForFileContains(t, logPath, \"helper ready nonce\") {\n\t\tt.Fatalf(\"companion log %s did not show child received startup nonce\", logPath)\n\t}\n}\n\nfunc TestEnsureWritesChildOutputOnlyToCompanionLog(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"250ms\",\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tstdout, stderr := captureProcessStreams(t)\n\tif _, err := manager.Ensure(t.Context(), \"user_quiet_logs\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tshutdownManager(t, manager)\n\n\tlogPath := filepath.Join(cfg.UserHome(\"user_quiet_logs\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper listening\") {\n\t\tt.Fatalf(\"companion log %s did not contain helper output\", logPath)\n\t}\n\toutput := stdout() + stderr()\n\tif strings.Contains(output, \"helper starting\") || strings.Contains(output, \"helper listening\") {\n\t\tt.Fatalf(\"server streams should not include companion helper output, got %q\", output)\n\t}\n}\n\nfunc TestEnsureRefreshesContainerCredentialWhenReusingProcess(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"term-aware\", nil)\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\tfirst, err := manager.Ensure(t.Context(), \"user_refresh\", companion.AuthHandoff{\n\t\tContainerCredential: companion.ContainerCredential{\n\t\t\tVersion: 1,\n\t\t\tClient: \"container\",\n\t\t\tCredentialID: \"cc_old\",\n\t\t\tSecret: \"old-secret\",\n\t\t\tAPIURL: \"http://localhost:8080\",\n\t\t},\n\t}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"initial Ensure: %v\", err)\n\t}\n\tsecond, err := manager.Ensure(t.Context(), \"user_refresh\", companion.AuthHandoff{\n\t\tContainerCredential: companion.ContainerCredential{\n\t\t\tVersion: 1,\n\t\t\tClient: \"container\",\n\t\t\tCredentialID: \"cc_new\",\n\t\t\tSecret: \"new-secret\",\n\t\t\tAPIURL: \"http://localhost:8080\",\n\t\t},\n\t}, companion.LLMProviderConfig{Provider: \"openrouter\", APIKey: \"runtime-token-new\", ModelName: \"deepseek/deepseek-v4-pro\"})\n\tif err != nil {\n\t\tt.Fatalf(\"second Ensure: %v\", err)\n\t}\n\tif second != first {\n\t\tt.Fatalf(\"Ensure should reuse the running process, got %p then %p\", first, second)\n\t}\n\n\tcredentialPath := filepath.Join(cfg.UserHome(\"user_refresh\"), \".local\", \"web3\", \"container-credential.json\")\n\tdata, err := os.ReadFile(credentialPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read refreshed container credential: %v\", err)\n\t}\n\tcredential := string(data)\n\tfor _, needle := range []string{\n\t\t`\"credential_id\":\"cc_new\"`,\n\t\t`\"secret\":\"new-secret\"`,\n\t} {\n\t\tif !strings.Contains(credential, needle) {\n\t\t\tt.Fatalf(\"container credential was not refreshed with %q: %s\", needle, credential)\n\t\t}\n\t}\n\tif strings.Contains(credential, \"old-secret\") {\n\t\tt.Fatalf(\"container credential still contains old secret: %s\", credential)\n\t}\n\tsettings, err := cfg.ReadUserClaudeSettings(\"user_refresh\")\n\tif err != nil {\n\t\tt.Fatalf(\"read refreshed provider settings: %v\", err)\n\t}\n\tif settings.Env[\"ANTHROPIC_AUTH_TOKEN\"] != \"runtime-token-new\" {\n\t\tt.Fatalf(\"reused process did not refresh runtime provider settings: %#v\", settings.Env)\n\t}\n}\n\nfunc TestStopUserTerminatesCompanionProcess(t *testing.T) {\n\tmarkerPath := filepath.Join(t.TempDir(), \"stop.marker\")\n\truntimePath := writeRuntimeShim(t, \"term-aware\", map[string]string{\n\t\t\"COMPANION_TEST_TERM_MARKER\": markerPath,\n\t})\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { _ = manager.Shutdown(context.Background()) })\n\n\tif _, err := manager.Ensure(t.Context(), \"user_stop\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\tif err := manager.StopUser(t.Context(), \"user_stop\"); err != nil {\n\t\tt.Fatalf(\"StopUser: %v\", err)\n\t}\n\tdata, err := os.ReadFile(markerPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read stop marker: %v\", err)\n\t}\n\tif string(data) != \"terminated\\n\" {\n\t\tt.Fatalf(\"stop marker = %q, want terminated\", data)\n\t}\n}\n\nfunc TestMaxProcessesEvictsLeastRecentlyUsedIdleProcess(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: writeRuntimeShim(t, \"term-aware\", nil),\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t\tMaxProcesses: 1,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { _ = manager.Shutdown(context.Background()) })\n\n\tfirst, err := manager.Ensure(t.Context(), \"user_lru_old\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"first Ensure: %v\", err)\n\t}\n\tsecond, err := manager.Ensure(t.Context(), \"user_lru_new\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err != nil {\n\t\tt.Fatalf(\"second Ensure: %v\", err)\n\t}\n\tif first == second {\n\t\tt.Fatal(\"expected a distinct process for the second user\")\n\t}\n\tselect {\n\tcase <-first.DoneForTest():\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"least recently used process was not evicted\")\n\t}\n\tif second.IsDoneForTest() {\n\t\tt.Fatal(\"new process was unexpectedly terminated\")\n\t}\n}\n\nfunc TestConcurrentEnsureWaitsForSameReadyProcess(t *testing.T) {\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"250ms\",\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tt.Cleanup(func() { shutdownManager(t, manager) })\n\n\ttype result struct {\n\t\tproc *companion.Process\n\t\terr error\n\t\telapsed time.Duration\n\t}\n\tresults := make(chan result, 2)\n\tvar wg sync.WaitGroup\n\tstart := make(chan struct{})\n\tfor range 2 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\t<-start\n\t\t\tstartedAt := time.Now()\n\t\t\tproc, err := manager.Ensure(t.Context(), \"user_concurrent\", companion.AuthHandoff{}, testRuntimeProvider())\n\t\t\tresults <- result{proc: proc, err: err, elapsed: time.Since(startedAt)}\n\t\t}()\n\t}\n\tclose(start)\n\twg.Wait()\n\tclose(results)\n\n\tvar got []result\n\tfor item := range results {\n\t\tgot = append(got, item)\n\t}\n\tif len(got) != 2 {\n\t\tt.Fatalf(\"got %d Ensure results, want 2\", len(got))\n\t}\n\tfor _, item := range got {\n\t\tif item.err != nil {\n\t\t\tt.Fatalf(\"Ensure: %v\", item.err)\n\t\t}\n\t\tif item.elapsed < 200*time.Millisecond {\n\t\t\tt.Fatalf(\"concurrent Ensure returned before readiness: elapsed=%s\", item.elapsed)\n\t\t}\n\t}\n\tif got[0].proc != got[1].proc {\n\t\tt.Fatalf(\"concurrent Ensure should reuse one process, got %p and %p\", got[0].proc, got[1].proc)\n\t}\n\n\tlogPath := filepath.Join(cfg.UserHome(\"user_concurrent\"), \"companion.log\")\n\tdata, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read companion log: %v\", err)\n\t}\n\tif count := strings.Count(string(data), \"helper listening\"); count != 1 {\n\t\tt.Fatalf(\"started %d helper processes, want 1; log=%q\", count, string(data))\n\t}\n}\n\nfunc TestShutdownTerminatesCompanionProcessesGracefully(t *testing.T) {\n\tmarkerPath := filepath.Join(t.TempDir(), \"term.marker\")\n\truntimePath := writeRuntimeShim(t, \"term-aware\", map[string]string{\n\t\t\"COMPANION_TEST_TERM_MARKER\": markerPath,\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 2 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\tif _, err := manager.Ensure(t.Context(), \"user_term\", companion.AuthHandoff{}, testRuntimeProvider()); err != nil {\n\t\tt.Fatalf(\"Ensure: %v\", err)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tif err := manager.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Shutdown: %v\", err)\n\t}\n\tdata, err := os.ReadFile(markerPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read SIGTERM marker: %v\", err)\n\t}\n\tif string(data) != \"terminated\\n\" {\n\t\tt.Fatalf(\"SIGTERM marker = %q, want terminated\", string(data))\n\t}\n}\n\nfunc TestShutdownPreventsNewCompanionStarts(t *testing.T) {\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: writeRuntimeShim(t, \"term-aware\", nil),\n\t\tScriptPath: writePlaceholderScript(t),\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t}\n\tmanager := companion.NewManager(cfg)\n\tif err := manager.Shutdown(context.Background()); err != nil {\n\t\tt.Fatalf(\"Shutdown: %v\", err)\n\t}\n\n\t_, err := manager.Ensure(t.Context(), \"user_after_shutdown\", companion.AuthHandoff{}, testRuntimeProvider())\n\tif err == nil {\n\t\tt.Fatal(\"Ensure after Shutdown returned nil error\")\n\t}\n\tif !strings.Contains(err.Error(), \"shutting down\") {\n\t\tt.Fatalf(\"Ensure error = %q, want shutting down\", err)\n\t}\n}\n\nfunc TestShutdownStopsProcessThatIsStillStarting(t *testing.T) {\n\tmarkerPath := filepath.Join(t.TempDir(), \"starting-term.marker\")\n\truntimePath := writeRuntimeShim(t, \"delayed-ready\", map[string]string{\n\t\t\"COMPANION_TEST_READY_DELAY\": \"5s\",\n\t\t\"COMPANION_TEST_TERM_MARKER\": markerPath,\n\t})\n\tscriptPath := writePlaceholderScript(t)\n\tcfg := companion.Config{\n\t\tHomeRoot: t.TempDir(),\n\t\tRuntimePath: runtimePath,\n\t\tScriptPath: scriptPath,\n\t\tBackendRoot: t.TempDir(),\n\t\tListenHost: \"127.0.0.1\",\n\t\tStartupTimeout: 10 * time.Second,\n\t}\n\tmanager := companion.NewManager(cfg)\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\t_, err := manager.Ensure(context.Background(), \"user_starting_shutdown\", companion.AuthHandoff{}, testRuntimeProvider())\n\t\terrCh <- err\n\t}()\n\tlogPath := filepath.Join(cfg.UserHome(\"user_starting_shutdown\"), \"companion.log\")\n\tif !waitForFileContains(t, logPath, \"helper starting\") {\n\t\tt.Fatalf(\"companion log %s did not show helper startup\", logPath)\n\t}\n\n\tstartedAt := time.Now()\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tif err := manager.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Shutdown: %v\", err)\n\t}\n\tif elapsed := time.Since(startedAt); elapsed > time.Second {\n\t\tt.Fatalf(\"Shutdown waited for startup timeout instead of terminating starting process: elapsed=%s\", elapsed)\n\t}\n\tdata, err := os.ReadFile(markerPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read SIGTERM marker: %v\", err)\n\t}\n\tif string(data) != \"terminated\\n\" {\n\t\tt.Fatalf(\"SIGTERM marker = %q, want terminated\", string(data))\n\t}\n\tselect {\n\tcase err := <-errCh:\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Ensure returned nil error after Shutdown stopped starting process\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Ensure did not return after Shutdown stopped starting process\")\n\t}\n}\n\nfunc containsSequence(items []string, sequence []string) bool {\n\treturn indexSequence(items, sequence) >= 0\n}\n\nfunc envContainsAny(env []string, needles ...string) bool {\n\tfor _, item := range env {\n\t\tfor _, needle := range needles {\n\t\t\tif needle != \"\" && strings.Contains(item, needle) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc envHas(env []string, value string) bool {\n\tfor _, item := range env {\n\t\tif item == value {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc indexSequence(items []string, sequence []string) int {\n\tfor i := 0; i+len(sequence) <= len(items); i++ {\n\t\tok := true\n\t\tfor j := range sequence {\n\t\t\tif items[i+j] != sequence[j] {\n\t\t\t\tok = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ok {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc testRuntimeProvider() companion.LLMProviderConfig {\n\treturn companion.LLMProviderConfig{\n\t\tProvider: \"openrouter\",\n\t\tAPIKey: \"runtime-token\",\n\t\tModelName: \"deepseek/deepseek-v4-pro\",\n\t}\n}\n\nfunc writeRuntimeShim(t *testing.T, helperMode string, env map[string]string) string {\n\tt.Helper()\n\tpath := filepath.Join(t.TempDir(), \"runtime-shim\")\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"#!/bin/sh\\n\")\n\tbuilder.WriteString(\"export COMPANION_TEST_HELPER=\" + shellQuote(helperMode) + \"\\n\")\n\tfor key, value := range env {\n\t\tbuilder.WriteString(\"export \" + key + \"=\" + shellQuote(value) + \"\\n\")\n\t}\n\tbuilder.WriteString(\"exec \" + shellQuote(os.Args[0]) + \" \\\"$@\\\"\\n\")\n\tif err := os.WriteFile(path, []byte(builder.String()), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn path\n}\n\nfunc writePlaceholderScript(t *testing.T) string {\n\tt.Helper()\n\tpath := filepath.Join(t.TempDir(), \"companion-server.js\")\n\twriteFileForTest(t, path, \"// test companion script\\n\", 0o644)\n\treturn path\n}\n\nfunc writeFileForTest(t *testing.T, path, content string, perm os.FileMode) {\n\tt.Helper()\n\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.WriteFile(path, []byte(content), perm); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc shellQuote(value string) string {\n\treturn \"'\" + strings.ReplaceAll(value, \"'\", \"'\\\\''\") + \"'\"\n}\n\nfunc shutdownManager(t *testing.T, manager *companion.Manager) {\n\tt.Helper()\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tif err := manager.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Shutdown cleanup: %v\", err)\n\t}\n}\n\nfunc waitForFileContains(t *testing.T, path, needle string) bool {\n\tt.Helper()\n\tdeadline := time.Now().Add(2 * time.Second)\n\tfor time.Now().Before(deadline) {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err == nil && strings.Contains(string(data), needle) {\n\t\t\treturn true\n\t\t}\n\t\ttime.Sleep(20 * time.Millisecond)\n\t}\n\treturn false\n}\n\nfunc captureProcessStreams(t *testing.T) (func() string, func() string) {\n\tt.Helper()\n\torigStdout := os.Stdout\n\torigStderr := os.Stderr\n\tstdoutReader, stdoutWriter, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstderrReader, stderrWriter, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tos.Stdout = stdoutWriter\n\tos.Stderr = stderrWriter\n\tt.Cleanup(func() {\n\t\tos.Stdout = origStdout\n\t\tos.Stderr = origStderr\n\t})\n\n\treadPipe := func(reader *os.File, writer *os.File) func() string {\n\t\tvar once sync.Once\n\t\tvar output string\n\t\treturn func() string {\n\t\t\tonce.Do(func() {\n\t\t\t\t_ = writer.Close()\n\t\t\t\tdata, err := io.ReadAll(reader)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"read captured stream: %v\", err)\n\t\t\t\t}\n\t\t\t\toutput = string(data)\n\t\t\t\t_ = reader.Close()\n\t\t\t})\n\t\t\treturn output\n\t\t}\n\t}\n\n\treturn readPipe(stdoutReader, stdoutWriter), readPipe(stderrReader, stderrWriter)\n}\n\nfunc parseHelperDelay() time.Duration {\n\tdelay, err := time.ParseDuration(os.Getenv(\"COMPANION_TEST_READY_DELAY\"))\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn delay\n}\n\nfunc runCompanionHelper(delay time.Duration) {\n\tif delay > 0 {\n\t\tfmt.Println(\"helper starting\")\n\t\tsetupSignalMarker(nil)\n\t\ttime.Sleep(delay)\n\t}\n\thost := os.Getenv(\"COMPANION_HOST\")\n\tif host == \"\" {\n\t\thost = \"127.0.0.1\"\n\t}\n\tln, err := net.Listen(\"tcp\", net.JoinHostPort(host, os.Getenv(\"COMPANION_PORT\")))\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"helper listen failed: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n\tif os.Getenv(\"COMPANION_PORT\") == \"0\" {\n\t\tfmt.Println(\"helper requested port 0\")\n\t}\n\tif nonce := os.Getenv(\"COMPANION_STARTUP_NONCE\"); nonce != \"\" {\n\t\tfmt.Printf(\"helper ready nonce %s\\n\", nonce)\n\t}\n\tif sessionStore := os.Getenv(\"COMPANION_SESSION_STORE\"); sessionStore != \"\" {\n\t\tfmt.Printf(\"helper session store %s\\n\", sessionStore)\n\t}\n\tif agentWorkdir := os.Getenv(\"WEB3_AGENT_WORKDIR\"); agentWorkdir != \"\" {\n\t\tfmt.Printf(\"helper agent workdir %s\\n\", agentWorkdir)\n\t}\n\tif readyFile := os.Getenv(\"COMPANION_READY_FILE\"); readyFile != \"\" {\n\t\tport := ln.Addr().(*net.TCPAddr).Port\n\t\t_ = os.MkdirAll(filepath.Dir(readyFile), 0o700)\n\t\t_ = os.WriteFile(readyFile, []byte(fmt.Sprintf(`{\"host\":%q,\"port\":%d,\"pid\":%d,\"nonce\":%q,\"sdk\":\"claude-agent-sdk\"}`+\"\\n\", host, port, os.Getpid(), os.Getenv(\"COMPANION_STARTUP_NONCE\"))), 0o600)\n\t}\n\tfmt.Println(\"helper listening\")\n\n\tsetupSignalMarker(ln)\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tbuf := make([]byte, 1024)\n\t\tn, _ := conn.Read(buf)\n\t\tif strings.Contains(string(buf[:n]), \"GET /api/health \") {\n\t\t\tbody := fmt.Sprintf(`{\"ok\":true,\"sdk\":\"claude-agent-sdk\",\"nonce\":%q}`, os.Getenv(\"COMPANION_STARTUP_NONCE\"))\n\t\t\t_, _ = conn.Write([]byte(fmt.Sprintf(\"HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\n\\r\\n%s\", len(body), body)))\n\t\t} else {\n\t\t\t_, _ = conn.Write([]byte(\"HTTP/1.1 404 Not Found\\r\\nContent-Length: 0\\r\\n\\r\\n\"))\n\t\t}\n\t\t_ = conn.Close()\n\t}\n}\n\nfunc runMockHealthHelper() {\n\thost := os.Getenv(\"COMPANION_HOST\")\n\tif host == \"\" {\n\t\thost = \"127.0.0.1\"\n\t}\n\tln, err := net.Listen(\"tcp\", net.JoinHostPort(host, os.Getenv(\"COMPANION_PORT\")))\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"mock helper listen failed: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n\tif readyFile := os.Getenv(\"COMPANION_READY_FILE\"); readyFile != \"\" {\n\t\tport := ln.Addr().(*net.TCPAddr).Port\n\t\t_ = os.MkdirAll(filepath.Dir(readyFile), 0o700)\n\t\t_ = os.WriteFile(readyFile, []byte(fmt.Sprintf(`{\"host\":%q,\"port\":%d,\"pid\":%d,\"nonce\":%q,\"sdk\":\"mock\"}`+\"\\n\", host, port, os.Getpid(), os.Getenv(\"COMPANION_STARTUP_NONCE\"))), 0o600)\n\t}\n\tsetupSignalMarker(ln)\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tbuf := make([]byte, 1024)\n\t\tn, _ := conn.Read(buf)\n\t\tif strings.Contains(string(buf[:n]), \"GET /api/health \") {\n\t\t\tbody := fmt.Sprintf(`{\"ok\":true,\"sdk\":\"mock\",\"nonce\":%q}`, os.Getenv(\"COMPANION_STARTUP_NONCE\"))\n\t\t\t_, _ = conn.Write([]byte(fmt.Sprintf(\"HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\n\\r\\n%s\", len(body), body)))\n\t\t} else {\n\t\t\t_, _ = conn.Write([]byte(\"HTTP/1.1 404 Not Found\\r\\nContent-Length: 0\\r\\n\\r\\n\"))\n\t\t}\n\t\t_ = conn.Close()\n\t}\n}\n\nfunc setupSignalMarker(listener net.Listener) {\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)\n\tgo func() {\n\t\t<-signals\n\t\tif marker := os.Getenv(\"COMPANION_TEST_TERM_MARKER\"); marker != \"\" {\n\t\t\t_ = os.WriteFile(marker, []byte(\"terminated\\n\"), 0o600)\n\t\t}\n\t\tif listener != nil {\n\t\t\t_ = listener.Close()\n\t\t}\n\t\tos.Exit(0)\n\t}()\n}\n"}}}}