@phuetz/code-buddy 1.0.0 → 1.2.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/README.md +160 -164
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +101 -13
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/lesson-auto-proposer.js +10 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/session-duration.d.ts +36 -0
- package/dist/agent/middleware/session-duration.js +78 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +71 -0
- package/dist/agent/session-end-flush.js +233 -0
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +107 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +5 -0
- package/dist/commands/goal-cli.d.ts +71 -0
- package/dist/commands/goal-cli.js +280 -0
- package/dist/commands/handlers/goal-handler.d.ts +34 -0
- package/dist/commands/handlers/goal-handler.js +155 -0
- package/dist/commands/handlers/index.d.ts +1 -0
- package/dist/commands/handlers/index.js +2 -0
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/slash/builtin-commands.js +20 -0
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +770 -0
- package/dist/config/feature-flags.js +7 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.d.ts +39 -0
- package/dist/context/context-manager-v2.js +91 -0
- package/dist/daemon/agent-task-executor.js +12 -1
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +5 -3
- package/dist/daemon/autonomous-loop.d.ts +21 -1
- package/dist/daemon/autonomous-loop.js +69 -0
- package/dist/daemon/colab-goal.d.ts +38 -0
- package/dist/daemon/colab-goal.js +81 -0
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.d.ts +21 -0
- package/dist/fleet/colab-store.js +28 -0
- package/dist/fleet/peer-session-bridge.d.ts +1 -1
- package/dist/fleet/peer-session-bridge.js +243 -2
- package/dist/fleet/peer-session-store.d.ts +3 -0
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/fleet/privacy-lint.d.ts +8 -0
- package/dist/fleet/privacy-lint.js +22 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +38 -0
- package/dist/goals/goal-judge.js +158 -0
- package/dist/goals/goal-loop.d.ts +37 -0
- package/dist/goals/goal-loop.js +101 -0
- package/dist/goals/goal-manager.d.ts +77 -0
- package/dist/goals/goal-manager.js +289 -0
- package/dist/goals/goal-state.d.ts +99 -0
- package/dist/goals/goal-state.js +315 -0
- package/dist/goals/goal-store.d.ts +28 -0
- package/dist/goals/goal-store.js +87 -0
- package/dist/goals/index.d.ts +5 -0
- package/dist/goals/index.js +6 -0
- package/dist/hooks/use-input-handler.js +36 -1
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +506 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/observability/run-store.d.ts +1 -1
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.d.ts +13 -1
- package/dist/server/websocket/fleet-bridge.js +16 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional TLS for the API server.
|
|
3
|
+
*
|
|
4
|
+
* The mobile-supervision endpoint (`/api/mobile`) rides the main HTTP server.
|
|
5
|
+
* To expose it off-device securely it can be served over HTTPS. This is purely
|
|
6
|
+
* transport security — it does not change any product gate (the dispatch gate
|
|
7
|
+
* `CODEBUDDY_MOBILE_ALLOW_DISPATCH` is untouched).
|
|
8
|
+
*
|
|
9
|
+
* Behaviour, gated by env (HTTP stays the default — no env, no TLS):
|
|
10
|
+
* - `CODEBUDDY_HTTPS=1` (or `CODEBUDDY_MOBILE_TLS=1`) enables HTTPS.
|
|
11
|
+
* - `CODEBUDDY_TLS_CERT` / `CODEBUDDY_TLS_KEY` = paths to a PEM cert/key. This
|
|
12
|
+
* is the standard production approach (Let's Encrypt / reverse proxy / the
|
|
13
|
+
* operator provisions the cert).
|
|
14
|
+
* - If TLS is enabled but no cert is provided, a dev self-signed cert is
|
|
15
|
+
* generated via `openssl` (if on PATH) into `~/.codebuddy/tls/` and reused.
|
|
16
|
+
* - If TLS is enabled but it cannot be satisfied (cert unreadable, or openssl
|
|
17
|
+
* absent), this **throws** with a clear message. It never silently falls back
|
|
18
|
+
* to plain HTTP — an operator who asked for TLS must not be downgraded.
|
|
19
|
+
*
|
|
20
|
+
* Uses only Node built-ins (`https`, `fs`, `child_process`) — no new dependency.
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import os from 'os';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import { execFileSync } from 'child_process';
|
|
26
|
+
import { logger } from '../utils/logger.js';
|
|
27
|
+
/** Truthy env flag check — accepts `1`, `true`, `yes`, `on` (case-insensitive). */
|
|
28
|
+
function isEnvTrue(value) {
|
|
29
|
+
if (!value)
|
|
30
|
+
return false;
|
|
31
|
+
const v = value.trim().toLowerCase();
|
|
32
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
33
|
+
}
|
|
34
|
+
/** Whether HTTPS is requested for the API server. */
|
|
35
|
+
export function isTlsEnabled(env = process.env) {
|
|
36
|
+
return isEnvTrue(env.CODEBUDDY_HTTPS) || isEnvTrue(env.CODEBUDDY_MOBILE_TLS);
|
|
37
|
+
}
|
|
38
|
+
function tlsDir() {
|
|
39
|
+
const home = process.env.CODEBUDDY_HOME || os.homedir();
|
|
40
|
+
return path.join(home, '.codebuddy', 'tls');
|
|
41
|
+
}
|
|
42
|
+
function hasOpenssl() {
|
|
43
|
+
try {
|
|
44
|
+
execFileSync('openssl', ['version'], { stdio: 'ignore' });
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate (once) and return a dev self-signed cert/key under `~/.codebuddy/tls/`.
|
|
53
|
+
* Reuses an existing pair if both files are present. Throws if openssl is absent.
|
|
54
|
+
*/
|
|
55
|
+
function ensureDevSelfSignedCert() {
|
|
56
|
+
const dir = tlsDir();
|
|
57
|
+
const certPath = path.join(dir, 'dev-cert.pem');
|
|
58
|
+
const keyPath = path.join(dir, 'dev-key.pem');
|
|
59
|
+
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
|
|
60
|
+
return { cert: fs.readFileSync(certPath), key: fs.readFileSync(keyPath) };
|
|
61
|
+
}
|
|
62
|
+
if (!hasOpenssl()) {
|
|
63
|
+
throw new Error('TLS is enabled (CODEBUDDY_HTTPS) but no cert/key was provided and `openssl` ' +
|
|
64
|
+
'is not available on PATH to generate a dev self-signed certificate. ' +
|
|
65
|
+
'Provide CODEBUDDY_TLS_CERT and CODEBUDDY_TLS_KEY (PEM paths), or install openssl.');
|
|
66
|
+
}
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
68
|
+
try {
|
|
69
|
+
execFileSync('openssl', [
|
|
70
|
+
'req',
|
|
71
|
+
'-x509',
|
|
72
|
+
'-newkey',
|
|
73
|
+
'rsa:2048',
|
|
74
|
+
'-nodes',
|
|
75
|
+
'-keyout',
|
|
76
|
+
keyPath,
|
|
77
|
+
'-out',
|
|
78
|
+
certPath,
|
|
79
|
+
'-subj',
|
|
80
|
+
'/CN=localhost',
|
|
81
|
+
'-days',
|
|
82
|
+
'365',
|
|
83
|
+
], { stdio: 'ignore' });
|
|
84
|
+
// Restrict the private key to the owner.
|
|
85
|
+
try {
|
|
86
|
+
fs.chmodSync(keyPath, 0o600);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* best-effort on platforms without POSIX perms */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
throw new Error('TLS is enabled (CODEBUDDY_HTTPS) but generating a dev self-signed certificate ' +
|
|
94
|
+
`with openssl failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
95
|
+
'Provide CODEBUDDY_TLS_CERT and CODEBUDDY_TLS_KEY (PEM paths) instead.');
|
|
96
|
+
}
|
|
97
|
+
logger.warn(`TLS: generated a dev self-signed certificate at ${certPath}. ` +
|
|
98
|
+
'This is for development/off-device testing only — use a real cert ' +
|
|
99
|
+
'(CODEBUDDY_TLS_CERT / CODEBUDDY_TLS_KEY) in production.');
|
|
100
|
+
return { cert: fs.readFileSync(certPath), key: fs.readFileSync(keyPath) };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve TLS options for the server, or `null` when TLS is not requested.
|
|
104
|
+
*
|
|
105
|
+
* Returns `null` (HTTP, unchanged default) when neither `CODEBUDDY_HTTPS` nor
|
|
106
|
+
* `CODEBUDDY_MOBILE_TLS` is set. Throws a clear error if TLS is requested but
|
|
107
|
+
* cannot be satisfied — it never downgrades to HTTP behind the operator's back.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveServerTlsOptions(env = process.env) {
|
|
110
|
+
if (!isTlsEnabled(env)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const certPath = env.CODEBUDDY_TLS_CERT?.trim();
|
|
114
|
+
const keyPath = env.CODEBUDDY_TLS_KEY?.trim();
|
|
115
|
+
// One of the two paths set but not the other → operator misconfiguration.
|
|
116
|
+
if ((certPath && !keyPath) || (!certPath && keyPath)) {
|
|
117
|
+
throw new Error('TLS is enabled (CODEBUDDY_HTTPS) but only one of CODEBUDDY_TLS_CERT / ' +
|
|
118
|
+
'CODEBUDDY_TLS_KEY is set. Provide both, or neither (to use a dev self-signed cert).');
|
|
119
|
+
}
|
|
120
|
+
if (certPath && keyPath) {
|
|
121
|
+
let cert;
|
|
122
|
+
let key;
|
|
123
|
+
try {
|
|
124
|
+
cert = fs.readFileSync(certPath);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
throw new Error(`TLS is enabled but CODEBUDDY_TLS_CERT (${certPath}) could not be read: ` +
|
|
128
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
key = fs.readFileSync(keyPath);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
throw new Error(`TLS is enabled but CODEBUDDY_TLS_KEY (${keyPath}) could not be read: ` +
|
|
135
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
136
|
+
}
|
|
137
|
+
return { cert, key };
|
|
138
|
+
}
|
|
139
|
+
// TLS requested with no explicit cert — generate/reuse a dev self-signed cert.
|
|
140
|
+
return ensureDevSelfSignedCert();
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=tls-config.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface TunnelOptions {
|
|
2
|
+
port: number;
|
|
3
|
+
authtoken?: string;
|
|
4
|
+
domain?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class TunnelManager {
|
|
7
|
+
private static listener;
|
|
8
|
+
/**
|
|
9
|
+
* Start an ngrok tunnel forwarding to the specified port.
|
|
10
|
+
*/
|
|
11
|
+
static startTunnel(options: TunnelOptions): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Stop the active ngrok tunnel.
|
|
14
|
+
*/
|
|
15
|
+
static stopTunnel(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Get the URL of the currently active tunnel, if any.
|
|
18
|
+
*/
|
|
19
|
+
static getActiveTunnelUrl(): string | null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as ngrok from '@ngrok/ngrok';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
export class TunnelManager {
|
|
4
|
+
static listener = null;
|
|
5
|
+
/**
|
|
6
|
+
* Start an ngrok tunnel forwarding to the specified port.
|
|
7
|
+
*/
|
|
8
|
+
static async startTunnel(options) {
|
|
9
|
+
if (this.listener) {
|
|
10
|
+
logger.warn('A tunnel is already running. Disconnecting existing tunnel.');
|
|
11
|
+
await this.stopTunnel();
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
logger.info(`Starting ngrok tunnel for port ${options.port}...`);
|
|
15
|
+
const ngrokOptions = {
|
|
16
|
+
addr: options.port,
|
|
17
|
+
authtoken_from_env: true,
|
|
18
|
+
};
|
|
19
|
+
if (options.authtoken) {
|
|
20
|
+
ngrokOptions.authtoken = options.authtoken;
|
|
21
|
+
}
|
|
22
|
+
if (options.domain) {
|
|
23
|
+
ngrokOptions.domain = options.domain;
|
|
24
|
+
}
|
|
25
|
+
this.listener = await ngrok.forward(ngrokOptions);
|
|
26
|
+
const url = this.listener.url() ?? '';
|
|
27
|
+
logger.info(`ngrok tunnel established at: ${url}`);
|
|
28
|
+
return url;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
logger.error('Failed to start ngrok tunnel', { error });
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stop the active ngrok tunnel.
|
|
37
|
+
*/
|
|
38
|
+
static async stopTunnel() {
|
|
39
|
+
if (this.listener) {
|
|
40
|
+
try {
|
|
41
|
+
await this.listener.close();
|
|
42
|
+
this.listener = null;
|
|
43
|
+
logger.info('ngrok tunnel closed.');
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error('Failed to close ngrok tunnel', { error });
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the URL of the currently active tunnel, if any.
|
|
53
|
+
*/
|
|
54
|
+
static getActiveTunnelUrl() {
|
|
55
|
+
return this.listener ? this.listener.url() ?? null : null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=tunnel-manager.js.map
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* agent-executor events so receivers can map them 1:1 to local
|
|
39
39
|
* AgentRuntime events for re-display.
|
|
40
40
|
*/
|
|
41
|
-
export declare const FLEET_EVENT_TYPES: readonly ["fleet:agent:tool_started", "fleet:agent:tool_completed", "fleet:agent:tool_error", "fleet:agent:reasoning", "fleet:workflow:event", "fleet:workflow:start", "fleet:workflow:complete", "fleet:session:spawn", "fleet:session:message", "fleet:peer:heartbeat", "fleet:peer:compacting:start", "fleet:peer:compacting:complete", "fleet:chat-session:start", "fleet:chat-session:turn", "fleet:chat-session:end"];
|
|
41
|
+
export declare const FLEET_EVENT_TYPES: readonly ["fleet:agent:tool_started", "fleet:agent:tool_completed", "fleet:agent:tool_error", "fleet:agent:reasoning", "fleet:workflow:event", "fleet:workflow:start", "fleet:workflow:complete", "fleet:session:spawn", "fleet:session:message", "fleet:peer:heartbeat", "fleet:peer:compacting:start", "fleet:peer:compacting:complete", "fleet:chat-session:start", "fleet:chat-session:turn", "fleet:chat-session:end", "fleet:chat-session:goal"];
|
|
42
42
|
export type FleetEventType = (typeof FLEET_EVENT_TYPES)[number];
|
|
43
43
|
/**
|
|
44
44
|
* Source identification carried with every fleet event so receivers know
|
|
@@ -137,3 +137,15 @@ export declare function broadcastChatSessionEnd(payload: {
|
|
|
137
137
|
sessionId: string;
|
|
138
138
|
reason?: 'end' | 'expired';
|
|
139
139
|
}): void;
|
|
140
|
+
/**
|
|
141
|
+
* Goal Ralph-loop on a peer session (Hermes gateway parity) — emitted on
|
|
142
|
+
* goal attach/pause/resume/clear and after each post-turn judge verdict.
|
|
143
|
+
* Metadata only: status/verdict/counters, never goal text or judge reasons.
|
|
144
|
+
*/
|
|
145
|
+
export declare function broadcastChatSessionGoal(payload: {
|
|
146
|
+
sessionId: string;
|
|
147
|
+
status: string;
|
|
148
|
+
verdict?: string;
|
|
149
|
+
turnsUsed?: number;
|
|
150
|
+
maxTurns?: number;
|
|
151
|
+
}): void;
|
|
@@ -67,6 +67,9 @@ export const FLEET_EVENT_TYPES = [
|
|
|
67
67
|
'fleet:chat-session:start',
|
|
68
68
|
'fleet:chat-session:turn',
|
|
69
69
|
'fleet:chat-session:end',
|
|
70
|
+
// Goal Ralph-loop on peer sessions (Hermes gateway parity). Metadata
|
|
71
|
+
// only — status/verdict/turn counters, never goal text or reasons.
|
|
72
|
+
'fleet:chat-session:goal',
|
|
70
73
|
];
|
|
71
74
|
/**
|
|
72
75
|
* Cached source. Resolved lazily on first emit so tests can stub
|
|
@@ -167,4 +170,17 @@ export function broadcastChatSessionTurn(payload) {
|
|
|
167
170
|
export function broadcastChatSessionEnd(payload) {
|
|
168
171
|
broadcastFleetEvent('fleet:chat-session:end', { ...payload });
|
|
169
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Goal Ralph-loop on a peer session (Hermes gateway parity) — emitted on
|
|
175
|
+
* goal attach/pause/resume/clear and after each post-turn judge verdict.
|
|
176
|
+
* Metadata only: status/verdict/counters, never goal text or judge reasons.
|
|
177
|
+
*/
|
|
178
|
+
export function broadcastChatSessionGoal(payload) {
|
|
179
|
+
broadcastFleetEvent('fleet:chat-session:goal', { ...payload });
|
|
180
|
+
}
|
|
181
|
+
// Wire up the agent runtime hooks (breaks circular dependency #2)
|
|
182
|
+
import { registerFleetBroadcaster } from '../../agent/execution/tool-hooks.js';
|
|
183
|
+
registerFleetBroadcaster(broadcastFleetEvent);
|
|
184
|
+
import { registerSessionFleetBroadcaster } from '../../agent/multi-agent/session-fleet-bridge.js';
|
|
185
|
+
registerSessionFleetBroadcaster(broadcastFleetEvent);
|
|
170
186
|
//# sourceMappingURL=fleet-bridge.js.map
|
|
@@ -30,6 +30,7 @@ const ALL_BLOCKS = {
|
|
|
30
30
|
includeWritingRules: true,
|
|
31
31
|
includeCodingStyle: true,
|
|
32
32
|
includeWorkflowRules: true,
|
|
33
|
+
includeExecutionDiscipline: true,
|
|
33
34
|
includeVariation: true,
|
|
34
35
|
};
|
|
35
36
|
const EXTERNAL_PROMPT_MANAGER_TOOLS = [
|
|
@@ -83,13 +84,17 @@ export class PromptBuilder {
|
|
|
83
84
|
// confuse the LLM into hallucinating JSON tool calls. Force-off here.
|
|
84
85
|
const toolCfg = getModelToolConfig(modelName);
|
|
85
86
|
const activeToolFilter = getToolFilter();
|
|
86
|
-
|
|
87
|
+
const forceTools = process.env.GROK_FORCE_TOOLS === 'true';
|
|
88
|
+
if (toolCfg.supportsToolCalls === false && !forceTools) {
|
|
87
89
|
gates.includeMemoryDirective = false;
|
|
88
90
|
gates.includeLessonsDirective = false;
|
|
89
91
|
gates.includeUserModelDirective = false;
|
|
90
92
|
gates.includeWorkflowRules = false;
|
|
93
|
+
gates.includeExecutionDiscipline = false;
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
const rememberToolAllowed = isToolNameAllowed('remember', activeToolFilter);
|
|
96
|
+
const memoryProposeToolAllowed = isToolNameAllowed('memory_propose', activeToolFilter);
|
|
97
|
+
if (!rememberToolAllowed && !memoryProposeToolAllowed) {
|
|
93
98
|
gates.includeMemoryDirective = false;
|
|
94
99
|
}
|
|
95
100
|
if (!['lessons_add', 'lessons_search', 'lessons_graph'].every(toolName => isToolNameAllowed(toolName, activeToolFilter))) {
|
|
@@ -188,7 +193,7 @@ export class PromptBuilder {
|
|
|
188
193
|
});
|
|
189
194
|
logger.debug(`Auto-selected prompt: ${autoId} (based on ${modelName})`);
|
|
190
195
|
}
|
|
191
|
-
else if (toolCfg.supportsToolCalls === false) {
|
|
196
|
+
else if (toolCfg.supportsToolCalls === false && !forceTools) {
|
|
192
197
|
// Chat-only base prompt for models that can't dispatch tool calls.
|
|
193
198
|
// The default body lists `bash`, `view_file`, `str_replace_editor`,
|
|
194
199
|
// etc. by name — small Ollama models read those mentions and try to
|
|
@@ -223,6 +228,26 @@ export class PromptBuilder {
|
|
|
223
228
|
systemPrompt = `# Role & Instructions (from intro_hook.txt)\n\n${introHookContent}\n\n---\n\n${systemPrompt}`;
|
|
224
229
|
logger.debug("Prepended intro hook content to system prompt");
|
|
225
230
|
}
|
|
231
|
+
// Inject execution-discipline guidance (tool-use enforcement, anti-stub
|
|
232
|
+
// completion, mandatory-tool, pre-finalize self-check). Borrowed from the
|
|
233
|
+
// Hermes Agent prompt audit — its highest-leverage agentic-reliability
|
|
234
|
+
// block. Placed HERE in the STABLE PREFIX (not near the footer) on
|
|
235
|
+
// purpose: the footer reminder section is shuffled by the variation
|
|
236
|
+
// injector (varySystemPrompt → extractBlocks splits on bullet lines), so
|
|
237
|
+
// a late placement fragmented this block — foreign bullets got interleaved
|
|
238
|
+
// between its lines. Up here it stays contiguous, cache-stable, and
|
|
239
|
+
// survives head-truncation. Gated off for trivial/lite + tool-callless
|
|
240
|
+
// models (see gating above).
|
|
241
|
+
if (gates.includeExecutionDiscipline) {
|
|
242
|
+
try {
|
|
243
|
+
const { getExecutionDisciplineBlock } = await import('../prompts/execution-discipline.js');
|
|
244
|
+
systemPrompt += '\n\n' + getExecutionDisciplineBlock();
|
|
245
|
+
logger.debug('Injected execution-discipline guidance into system prompt');
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
logger.warn('Failed to inject execution-discipline block', { error: getErrorMessage(err) });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
226
251
|
// Inject bootstrap context files (BOOTSTRAP.md, AGENTS.md, SOUL.md, etc.)
|
|
227
252
|
if (gates.includeBootstrap) {
|
|
228
253
|
try {
|
|
@@ -361,17 +386,32 @@ export class PromptBuilder {
|
|
|
361
386
|
}
|
|
362
387
|
catch { /* fleet registry optional — module not loaded yet */ }
|
|
363
388
|
}
|
|
364
|
-
// Inject auto-memory directive — tells the LLM WHEN to call
|
|
365
|
-
// `
|
|
389
|
+
// Inject auto-memory directive — tells the LLM WHEN to call `remember`
|
|
390
|
+
// or the review-gated `memory_propose` tool for non-obvious facts from
|
|
366
391
|
// .codebuddy/CODEBUDDY_MEMORY.md (project) or ~/.codebuddy/memory.md
|
|
367
392
|
// (user). Without this directive, the tool is registered but the LLM
|
|
368
393
|
// has no instruction on when to use it, so the file stays empty.
|
|
369
|
-
// Paired with `alwaysInclude: ['remember']` in
|
|
394
|
+
// Paired with `alwaysInclude: ['remember', 'memory_propose']` in
|
|
395
|
+
// tool-selection-strategy.
|
|
370
396
|
if (this.config.memoryEnabled && this.persistentMemory && gates.includeMemoryDirective) {
|
|
397
|
+
const memoryWriteInstruction = memoryProposeToolAllowed
|
|
398
|
+
? 'Use `remember` only for explicit, high-confidence durable facts. Use `memory_propose` for inferred, ambiguous, or model-derived facts so the user can review them before they become prompt-injected memory.'
|
|
399
|
+
: 'Use `remember` only for explicit, high-confidence durable facts. Do not store inferred or ambiguous facts silently.';
|
|
400
|
+
const inferredFactInstruction = memoryProposeToolAllowed
|
|
401
|
+
? 'When to call `memory_propose`:'
|
|
402
|
+
: 'When to consider durable memory, but skip if it is only inferred or ambiguous:';
|
|
403
|
+
const closingMemoryInstruction = memoryProposeToolAllowed
|
|
404
|
+
? 'Prefer `memory_propose` over `remember` when the fact came from your interpretation rather than an explicit user instruction.'
|
|
405
|
+
: 'Only call `remember` when the fact came from an explicit user instruction or correction.';
|
|
371
406
|
systemPrompt += `\n\n<auto_memory_directive>
|
|
372
|
-
You have a persistent memory system at .codebuddy/CODEBUDDY_MEMORY.md (project-scoped) and ~/.codebuddy/memory.md (user-scoped, all projects).
|
|
407
|
+
You have a persistent memory system at .codebuddy/CODEBUDDY_MEMORY.md (project-scoped) and ~/.codebuddy/memory.md (user-scoped, all projects).
|
|
408
|
+
${memoryWriteInstruction}
|
|
373
409
|
|
|
374
410
|
When to call \`remember\`:
|
|
411
|
+
- The user explicitly says to remember/store a fact or preference
|
|
412
|
+
- The user directly corrects an existing stable fact
|
|
413
|
+
|
|
414
|
+
${inferredFactInstruction}
|
|
375
415
|
- User preferences ("user prefers single quotes", "always use Vitest, not Jest")
|
|
376
416
|
- Architectural decisions ("API uses JWT in HttpOnly cookies, not localStorage")
|
|
377
417
|
- Non-obvious gotchas ("vi.hoisted() needed for mock factories in this repo")
|
|
@@ -388,7 +428,7 @@ Format:
|
|
|
388
428
|
- \`scope\`: \`project\` (default — fact is specific to THIS repo) or \`user\` (preference across all projects)
|
|
389
429
|
- \`category\`: \`preferences\`, \`decisions\`, \`patterns\`, \`project\`, or \`custom\`
|
|
390
430
|
|
|
391
|
-
|
|
431
|
+
${closingMemoryInstruction}
|
|
392
432
|
</auto_memory_directive>`;
|
|
393
433
|
logger.debug('Injected auto-memory directive into system prompt');
|
|
394
434
|
}
|
|
@@ -642,6 +682,7 @@ export function gatesForComplexity(complexity) {
|
|
|
642
682
|
includeWritingRules: true,
|
|
643
683
|
includeCodingStyle: false,
|
|
644
684
|
includeWorkflowRules: false,
|
|
685
|
+
includeExecutionDiscipline: false,
|
|
645
686
|
includeVariation: false,
|
|
646
687
|
};
|
|
647
688
|
case 'simple':
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module shared/engine-types
|
|
9
9
|
*/
|
|
10
|
-
export type EngineStreamEventType = 'content' | 'thinking' | 'tool_start' | 'tool_end' | 'tool_stream' | 'token_count' | 'cost' | 'done' | 'error' | 'ask_user' | 'plan_progress' | 'diff_preview';
|
|
10
|
+
export type EngineStreamEventType = 'content' | 'thinking' | 'tool_start' | 'tool_end' | 'tool_stream' | 'token_count' | 'cost' | 'done' | 'error' | 'ask_user' | 'plan_progress' | 'steer' | 'diff_preview' | 'goal_status';
|
|
11
11
|
export interface EngineStreamEvent {
|
|
12
12
|
type: EngineStreamEventType;
|
|
13
13
|
/** Incremental text delta */
|
|
@@ -47,6 +47,11 @@ export interface EngineStreamEvent {
|
|
|
47
47
|
completed: number;
|
|
48
48
|
message?: string;
|
|
49
49
|
};
|
|
50
|
+
/** User guidance delivered to an active run. */
|
|
51
|
+
steer?: {
|
|
52
|
+
content: string;
|
|
53
|
+
source: string;
|
|
54
|
+
};
|
|
50
55
|
/** Diff preview */
|
|
51
56
|
diffPreview?: {
|
|
52
57
|
turnId: number;
|
|
@@ -59,6 +64,15 @@ export interface EngineStreamEvent {
|
|
|
59
64
|
}>;
|
|
60
65
|
plan?: string;
|
|
61
66
|
};
|
|
67
|
+
/** Autonomous goal-loop progress (for host UIs like the Cowork goal banner). */
|
|
68
|
+
goalStatus?: {
|
|
69
|
+
goal: string;
|
|
70
|
+
status: 'active' | 'paused' | 'done' | 'cleared';
|
|
71
|
+
turnsUsed: number;
|
|
72
|
+
maxTurns: number;
|
|
73
|
+
lastVerdict?: 'done' | 'continue' | 'skipped';
|
|
74
|
+
lastReason?: string;
|
|
75
|
+
};
|
|
62
76
|
}
|
|
63
77
|
export interface EngineMessage {
|
|
64
78
|
role: 'user' | 'assistant' | 'system';
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
12
|
import { createInterface } from 'readline';
|
|
13
13
|
import { join } from 'path';
|
|
14
|
-
import { existsSync } from 'fs';
|
|
14
|
+
import { accessSync, constants, existsSync } from 'fs';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
16
|
// ============================================================================
|
|
17
17
|
// Sidecar Bridge
|
|
@@ -27,23 +27,34 @@ export class SidecarBridge {
|
|
|
27
27
|
* Find the sidecar binary
|
|
28
28
|
*/
|
|
29
29
|
findBinary() {
|
|
30
|
+
const localBinary = process.platform === 'win32' ? 'codebuddy-sidecar.exe' : 'codebuddy-sidecar';
|
|
30
31
|
const candidates = [
|
|
31
32
|
// Development: built via `cargo build`
|
|
32
|
-
join(process.cwd(), 'src-sidecar', 'target', 'release',
|
|
33
|
-
join(process.cwd(), 'src-sidecar', 'target', '
|
|
34
|
-
join(process.cwd(), 'src-sidecar', 'target', 'debug', 'codebuddy-sidecar.exe'),
|
|
35
|
-
join(process.cwd(), 'src-sidecar', 'target', 'debug', 'codebuddy-sidecar'),
|
|
33
|
+
join(process.cwd(), 'src-sidecar', 'target', 'release', localBinary),
|
|
34
|
+
join(process.cwd(), 'src-sidecar', 'target', 'debug', localBinary),
|
|
36
35
|
// Installed globally
|
|
37
|
-
join(process.env.HOME || process.env.USERPROFILE || '', '.cargo', 'bin',
|
|
38
|
-
join(process.env.HOME || process.env.USERPROFILE || '', '.cargo', 'bin', 'codebuddy-sidecar'),
|
|
36
|
+
join(process.env.HOME || process.env.USERPROFILE || '', '.cargo', 'bin', localBinary),
|
|
39
37
|
];
|
|
40
|
-
for (const
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
if (this.isExecutableCandidate(candidate)) {
|
|
40
|
+
return candidate;
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
return null;
|
|
46
44
|
}
|
|
45
|
+
isExecutableCandidate(filePath) {
|
|
46
|
+
if (!existsSync(filePath))
|
|
47
|
+
return false;
|
|
48
|
+
if (process.platform === 'win32')
|
|
49
|
+
return true;
|
|
50
|
+
try {
|
|
51
|
+
accessSync(filePath, constants.X_OK);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
47
58
|
/**
|
|
48
59
|
* Check if sidecar is available
|
|
49
60
|
*/
|
package/dist/spec/spec-store.js
CHANGED
|
@@ -58,6 +58,7 @@ export class SpecTransitionError extends Error {
|
|
|
58
58
|
// Singleton registry (one store per working directory)
|
|
59
59
|
// ============================================================================
|
|
60
60
|
const registry = new Map();
|
|
61
|
+
let lastStoryCreatedAt = 0;
|
|
61
62
|
export function getSpecStore(workDir = process.cwd()) {
|
|
62
63
|
const key = path.resolve(workDir);
|
|
63
64
|
if (!registry.has(key)) {
|
|
@@ -73,6 +74,7 @@ export function getSpecStore(workDir = process.cwd()) {
|
|
|
73
74
|
/** Test helper: drop cached store instances. */
|
|
74
75
|
export function resetSpecStores() {
|
|
75
76
|
registry.clear();
|
|
77
|
+
lastStoryCreatedAt = 0;
|
|
76
78
|
}
|
|
77
79
|
/** Whether `from → to` is a legal story status transition. */
|
|
78
80
|
export function isLegalTransition(from, to) {
|
|
@@ -211,7 +213,7 @@ export class SpecStore {
|
|
|
211
213
|
const title = (input.title ?? '').trim();
|
|
212
214
|
if (!title)
|
|
213
215
|
throw new Error('Story title is required.');
|
|
214
|
-
const now =
|
|
216
|
+
const now = nextStoryCreatedAt();
|
|
215
217
|
const allowedPaths = cleanList(input.allowedPaths);
|
|
216
218
|
const verification = cleanList(input.verification);
|
|
217
219
|
const story = {
|
|
@@ -479,6 +481,11 @@ function parseFrontmatter(content) {
|
|
|
479
481
|
function randomId() {
|
|
480
482
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`;
|
|
481
483
|
}
|
|
484
|
+
function nextStoryCreatedAt() {
|
|
485
|
+
const now = Date.now();
|
|
486
|
+
lastStoryCreatedAt = now > lastStoryCreatedAt ? now : lastStoryCreatedAt + 1;
|
|
487
|
+
return lastStoryCreatedAt;
|
|
488
|
+
}
|
|
482
489
|
/** Trim, drop blanks, and de-duplicate a string list (used for contract fields). */
|
|
483
490
|
function cleanList(values) {
|
|
484
491
|
if (!Array.isArray(values))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AudioReader TTS Provider
|
|
3
3
|
*
|
|
4
|
-
* Uses a local AudioReader API
|
|
4
|
+
* Uses a local AudioReader API for high-quality,
|
|
5
5
|
* free text-to-speech synthesis via an OpenAI-compatible REST API.
|
|
6
6
|
*/
|
|
7
7
|
import type { TTSProviderConfig, Voice, SynthesisOptions, SynthesisResult } from '../types.js';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AudioReader TTS Provider
|
|
3
3
|
*
|
|
4
|
-
* Uses a local AudioReader API
|
|
4
|
+
* Uses a local AudioReader API for high-quality,
|
|
5
5
|
* free text-to-speech synthesis via an OpenAI-compatible REST API.
|
|
6
6
|
*/
|
|
7
7
|
const DEFAULT_BASE_URL = 'http://localhost:8000';
|
|
8
|
-
const DEFAULT_MODEL = '
|
|
8
|
+
const DEFAULT_MODEL = 'audioreader';
|
|
9
9
|
const DEFAULT_VOICE = 'ff_siwis';
|
|
10
|
-
/** Known
|
|
11
|
-
const
|
|
10
|
+
/** Known AudioReader voices with metadata */
|
|
11
|
+
const AUDIOREADER_VOICES = {
|
|
12
12
|
ff_siwis: { name: 'Siwis', language: 'fr-FR', gender: 'female' },
|
|
13
13
|
af_bella: { name: 'Bella', language: 'en-US', gender: 'female' },
|
|
14
14
|
af_heart: { name: 'Heart', language: 'en-US', gender: 'female' },
|
|
@@ -20,7 +20,7 @@ const KOKORO_VOICES = {
|
|
|
20
20
|
bf_emma: { name: 'Emma', language: 'en-GB', gender: 'female' },
|
|
21
21
|
bm_george: { name: 'George', language: 'en-GB', gender: 'male' },
|
|
22
22
|
};
|
|
23
|
-
/** OpenAI voice name to
|
|
23
|
+
/** OpenAI voice name to AudioReader voice mapping */
|
|
24
24
|
const OPENAI_VOICE_MAP = {
|
|
25
25
|
alloy: 'af_bella',
|
|
26
26
|
echo: 'am_adam',
|
|
@@ -74,7 +74,7 @@ export class AudioReaderTTSProvider {
|
|
|
74
74
|
catch {
|
|
75
75
|
// Fall back to known voices
|
|
76
76
|
}
|
|
77
|
-
return Object.keys(
|
|
77
|
+
return Object.keys(AUDIOREADER_VOICES).map(id => this.buildVoice(id));
|
|
78
78
|
}
|
|
79
79
|
async synthesize(text, options) {
|
|
80
80
|
if (!this.initialized) {
|
|
@@ -131,11 +131,11 @@ export class AudioReaderTTSProvider {
|
|
|
131
131
|
return voice;
|
|
132
132
|
}
|
|
133
133
|
buildVoice(id) {
|
|
134
|
-
const meta =
|
|
134
|
+
const meta = AUDIOREADER_VOICES[id];
|
|
135
135
|
return {
|
|
136
136
|
id: `audioreader-${id}`,
|
|
137
137
|
name: meta?.name ?? id,
|
|
138
|
-
description: `AudioReader
|
|
138
|
+
description: `AudioReader voice - ${meta?.name ?? id}`,
|
|
139
139
|
language: meta?.language ?? 'en-US',
|
|
140
140
|
gender: meta?.gender ?? 'female',
|
|
141
141
|
provider: 'audioreader',
|
|
@@ -12,6 +12,7 @@ import * as path from 'path';
|
|
|
12
12
|
import { existsSync } from 'fs';
|
|
13
13
|
import { EventEmitter } from 'events';
|
|
14
14
|
import { spawn } from 'child_process';
|
|
15
|
+
import { performance } from 'perf_hooks';
|
|
15
16
|
// ============================================================================
|
|
16
17
|
// Built-in Templates
|
|
17
18
|
// ============================================================================
|
|
@@ -641,7 +642,7 @@ export class TemplateEngine extends EventEmitter {
|
|
|
641
642
|
* Generate project from template
|
|
642
643
|
*/
|
|
643
644
|
async generate(options) {
|
|
644
|
-
const startTime =
|
|
645
|
+
const startTime = performance.now();
|
|
645
646
|
const warnings = [];
|
|
646
647
|
const filesCreated = [];
|
|
647
648
|
// Get template
|
|
@@ -737,7 +738,7 @@ export class TemplateEngine extends EventEmitter {
|
|
|
737
738
|
success: true,
|
|
738
739
|
projectPath,
|
|
739
740
|
filesCreated,
|
|
740
|
-
duration:
|
|
741
|
+
duration: Math.max(1, Math.ceil(performance.now() - startTime)),
|
|
741
742
|
warnings,
|
|
742
743
|
nextSteps,
|
|
743
744
|
};
|