@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,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hibernate/wake lifecycle semantics for Hermes runtime backends.
|
|
3
|
+
*
|
|
4
|
+
* Each backend gets a strategy that maps to concrete CLI invocations:
|
|
5
|
+
* - local/wsl/os-sandbox/singularity: lifecycle unsupported (always running or job-based)
|
|
6
|
+
* - docker: pause / unpause / inspect
|
|
7
|
+
* - ssh: connection close / reconnect
|
|
8
|
+
* - modal: app stop / run
|
|
9
|
+
* - daytona: workspace stop / start
|
|
10
|
+
* - vercel-sandbox: unsupported (no lifecycle API)
|
|
11
|
+
*/
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { spawnSync } from 'child_process';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const UNSUPPORTED_BACKEND_IDS = new Set([
|
|
18
|
+
'local',
|
|
19
|
+
'wsl',
|
|
20
|
+
'os-sandbox',
|
|
21
|
+
'singularity',
|
|
22
|
+
'vercel-sandbox',
|
|
23
|
+
]);
|
|
24
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
25
|
+
const BACKEND_LABELS = {
|
|
26
|
+
'local': 'Local process',
|
|
27
|
+
'docker': 'Docker sandbox',
|
|
28
|
+
'wsl': 'WSL',
|
|
29
|
+
'os-sandbox': 'Native OS sandbox',
|
|
30
|
+
'ssh': 'SSH remote shell',
|
|
31
|
+
'singularity': 'Singularity/Apptainer',
|
|
32
|
+
'modal': 'Modal',
|
|
33
|
+
'daytona': 'Daytona',
|
|
34
|
+
'vercel-sandbox': 'Vercel Sandbox',
|
|
35
|
+
};
|
|
36
|
+
const ALL_BACKEND_IDS = Object.keys(BACKEND_LABELS);
|
|
37
|
+
function runSpawn(command, args, env, timeoutMs) {
|
|
38
|
+
try {
|
|
39
|
+
const result = spawnSync(command, args, {
|
|
40
|
+
env,
|
|
41
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
42
|
+
timeout: timeoutMs,
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
});
|
|
45
|
+
const stdout = decodeBuffer(result.stdout).trim();
|
|
46
|
+
const stderr = decodeBuffer(result.stderr).trim();
|
|
47
|
+
return {
|
|
48
|
+
exitCode: result.status,
|
|
49
|
+
ok: !result.error && result.status === 0,
|
|
50
|
+
stdout,
|
|
51
|
+
stderr,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { exitCode: null, ok: false, stdout: '', stderr: '' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function decodeBuffer(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return '';
|
|
61
|
+
if (typeof value === 'string')
|
|
62
|
+
return value;
|
|
63
|
+
return value.toString('utf8');
|
|
64
|
+
}
|
|
65
|
+
function unsupportedAction(backendId, action, durationMs) {
|
|
66
|
+
return {
|
|
67
|
+
backendId,
|
|
68
|
+
action,
|
|
69
|
+
result: 'unsupported',
|
|
70
|
+
state: 'unsupported',
|
|
71
|
+
detail: `${BACKEND_LABELS[backendId] ?? backendId} does not support managed lifecycle operations.`,
|
|
72
|
+
durationMs,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function failedAction(backendId, action, detail, durationMs) {
|
|
76
|
+
return {
|
|
77
|
+
backendId,
|
|
78
|
+
action,
|
|
79
|
+
result: 'failed',
|
|
80
|
+
state: 'unknown',
|
|
81
|
+
detail,
|
|
82
|
+
durationMs,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Docker lifecycle
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
function dockerHibernate(containerId, env, timeoutMs) {
|
|
89
|
+
const start = Date.now();
|
|
90
|
+
if (!containerId) {
|
|
91
|
+
return failedAction('docker', 'hibernate', 'containerId is required for docker hibernate.', 0);
|
|
92
|
+
}
|
|
93
|
+
const result = runSpawn('docker', ['pause', containerId], env, timeoutMs);
|
|
94
|
+
const durationMs = Date.now() - start;
|
|
95
|
+
if (result.ok) {
|
|
96
|
+
return {
|
|
97
|
+
backendId: 'docker',
|
|
98
|
+
action: 'hibernate',
|
|
99
|
+
result: 'success',
|
|
100
|
+
state: 'hibernated',
|
|
101
|
+
detail: `Container ${containerId} paused.`,
|
|
102
|
+
durationMs,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return failedAction('docker', 'hibernate', result.stderr || `docker pause failed (exit ${result.exitCode}).`, durationMs);
|
|
106
|
+
}
|
|
107
|
+
function dockerWake(containerId, env, timeoutMs) {
|
|
108
|
+
const start = Date.now();
|
|
109
|
+
if (!containerId) {
|
|
110
|
+
return failedAction('docker', 'wake', 'containerId is required for docker wake.', 0);
|
|
111
|
+
}
|
|
112
|
+
const result = runSpawn('docker', ['unpause', containerId], env, timeoutMs);
|
|
113
|
+
const durationMs = Date.now() - start;
|
|
114
|
+
if (result.ok) {
|
|
115
|
+
return {
|
|
116
|
+
backendId: 'docker',
|
|
117
|
+
action: 'wake',
|
|
118
|
+
result: 'success',
|
|
119
|
+
state: 'running',
|
|
120
|
+
detail: `Container ${containerId} unpaused.`,
|
|
121
|
+
durationMs,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return failedAction('docker', 'wake', result.stderr || `docker unpause failed (exit ${result.exitCode}).`, durationMs);
|
|
125
|
+
}
|
|
126
|
+
function dockerStatus(containerId, env, timeoutMs) {
|
|
127
|
+
const start = Date.now();
|
|
128
|
+
if (!containerId) {
|
|
129
|
+
return failedAction('docker', 'status', 'containerId is required for docker status.', 0);
|
|
130
|
+
}
|
|
131
|
+
const result = runSpawn('docker', ['inspect', '--format', '{{.State.Status}}', containerId], env, timeoutMs);
|
|
132
|
+
const durationMs = Date.now() - start;
|
|
133
|
+
if (!result.ok) {
|
|
134
|
+
return failedAction('docker', 'status', result.stderr || `docker inspect failed (exit ${result.exitCode}).`, durationMs);
|
|
135
|
+
}
|
|
136
|
+
const raw = result.stdout.trim().toLowerCase();
|
|
137
|
+
const state = dockerStatusToLifecycleState(raw);
|
|
138
|
+
return {
|
|
139
|
+
backendId: 'docker',
|
|
140
|
+
action: 'status',
|
|
141
|
+
result: 'success',
|
|
142
|
+
state,
|
|
143
|
+
detail: `Container ${containerId} status: ${raw}.`,
|
|
144
|
+
durationMs,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function dockerStatusToLifecycleState(dockerState) {
|
|
148
|
+
switch (dockerState) {
|
|
149
|
+
case 'running':
|
|
150
|
+
return 'running';
|
|
151
|
+
case 'paused':
|
|
152
|
+
return 'hibernated';
|
|
153
|
+
case 'restarting':
|
|
154
|
+
return 'starting';
|
|
155
|
+
case 'removing':
|
|
156
|
+
case 'exited':
|
|
157
|
+
case 'dead':
|
|
158
|
+
return 'stopping';
|
|
159
|
+
default:
|
|
160
|
+
return 'unknown';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// SSH lifecycle
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
function sshHibernate(_sshHost, _env, _timeoutMs) {
|
|
167
|
+
// SSH "hibernate" = drop connection. We don't manage persistent connections,
|
|
168
|
+
// so we just report success — the SSH session will close naturally.
|
|
169
|
+
return {
|
|
170
|
+
backendId: 'ssh',
|
|
171
|
+
action: 'hibernate',
|
|
172
|
+
result: 'success',
|
|
173
|
+
state: 'hibernated',
|
|
174
|
+
detail: 'SSH connection released; keepalive stopped.',
|
|
175
|
+
durationMs: 0,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function sshWake(sshHost, env, timeoutMs) {
|
|
179
|
+
const start = Date.now();
|
|
180
|
+
if (!sshHost) {
|
|
181
|
+
return failedAction('ssh', 'wake', 'sshHost is required for SSH wake.', 0);
|
|
182
|
+
}
|
|
183
|
+
const result = runSpawn('ssh', ['-T', '-o', 'ConnectTimeout=10', sshHost, 'true'], env, timeoutMs);
|
|
184
|
+
const durationMs = Date.now() - start;
|
|
185
|
+
if (result.ok) {
|
|
186
|
+
return {
|
|
187
|
+
backendId: 'ssh',
|
|
188
|
+
action: 'wake',
|
|
189
|
+
result: 'success',
|
|
190
|
+
state: 'running',
|
|
191
|
+
detail: `SSH connection to ${sshHost} verified.`,
|
|
192
|
+
durationMs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return failedAction('ssh', 'wake', result.stderr || `SSH connection to ${sshHost} failed (exit ${result.exitCode}).`, durationMs);
|
|
196
|
+
}
|
|
197
|
+
function sshStatus(sshHost, env, timeoutMs) {
|
|
198
|
+
const start = Date.now();
|
|
199
|
+
if (!sshHost) {
|
|
200
|
+
return failedAction('ssh', 'status', 'sshHost is required for SSH status.', 0);
|
|
201
|
+
}
|
|
202
|
+
const result = runSpawn('ssh', ['-T', '-o', 'ConnectTimeout=5', sshHost, 'true'], env, timeoutMs);
|
|
203
|
+
const durationMs = Date.now() - start;
|
|
204
|
+
if (result.ok) {
|
|
205
|
+
return {
|
|
206
|
+
backendId: 'ssh',
|
|
207
|
+
action: 'status',
|
|
208
|
+
result: 'success',
|
|
209
|
+
state: 'running',
|
|
210
|
+
detail: `SSH host ${sshHost} is reachable.`,
|
|
211
|
+
durationMs,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
backendId: 'ssh',
|
|
216
|
+
action: 'status',
|
|
217
|
+
result: 'success',
|
|
218
|
+
state: 'hibernated',
|
|
219
|
+
detail: `SSH host ${sshHost} is not reachable: ${result.stderr || `exit ${result.exitCode}`}.`,
|
|
220
|
+
durationMs,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Modal lifecycle
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
function modalHibernate(modalApp, env, timeoutMs) {
|
|
227
|
+
const start = Date.now();
|
|
228
|
+
if (!modalApp) {
|
|
229
|
+
return failedAction('modal', 'hibernate', 'modalApp is required for Modal hibernate.', 0);
|
|
230
|
+
}
|
|
231
|
+
const result = runSpawn('modal', ['app', 'stop', modalApp], env, timeoutMs);
|
|
232
|
+
const durationMs = Date.now() - start;
|
|
233
|
+
if (result.ok) {
|
|
234
|
+
return {
|
|
235
|
+
backendId: 'modal',
|
|
236
|
+
action: 'hibernate',
|
|
237
|
+
result: 'success',
|
|
238
|
+
state: 'hibernated',
|
|
239
|
+
detail: `Modal app ${modalApp} stopped.`,
|
|
240
|
+
durationMs,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return failedAction('modal', 'hibernate', result.stderr || `modal app stop failed (exit ${result.exitCode}).`, durationMs);
|
|
244
|
+
}
|
|
245
|
+
function modalWake(modalApp, env, timeoutMs) {
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
if (!modalApp) {
|
|
248
|
+
return failedAction('modal', 'wake', 'modalApp is required for Modal wake.', 0);
|
|
249
|
+
}
|
|
250
|
+
const result = runSpawn('modal', ['run', modalApp], env, timeoutMs);
|
|
251
|
+
const durationMs = Date.now() - start;
|
|
252
|
+
if (result.ok) {
|
|
253
|
+
return {
|
|
254
|
+
backendId: 'modal',
|
|
255
|
+
action: 'wake',
|
|
256
|
+
result: 'success',
|
|
257
|
+
state: 'running',
|
|
258
|
+
detail: `Modal app ${modalApp} started.`,
|
|
259
|
+
durationMs,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return failedAction('modal', 'wake', result.stderr || `modal run failed (exit ${result.exitCode}).`, durationMs);
|
|
263
|
+
}
|
|
264
|
+
function modalStatus(modalApp, env, timeoutMs) {
|
|
265
|
+
const start = Date.now();
|
|
266
|
+
if (!modalApp) {
|
|
267
|
+
return failedAction('modal', 'status', 'modalApp is required for Modal status.', 0);
|
|
268
|
+
}
|
|
269
|
+
const result = runSpawn('modal', ['app', 'list'], env, timeoutMs);
|
|
270
|
+
const durationMs = Date.now() - start;
|
|
271
|
+
if (!result.ok) {
|
|
272
|
+
return failedAction('modal', 'status', result.stderr || `modal app list failed (exit ${result.exitCode}).`, durationMs);
|
|
273
|
+
}
|
|
274
|
+
const isRunning = result.stdout.includes(modalApp);
|
|
275
|
+
return {
|
|
276
|
+
backendId: 'modal',
|
|
277
|
+
action: 'status',
|
|
278
|
+
result: 'success',
|
|
279
|
+
state: isRunning ? 'running' : 'hibernated',
|
|
280
|
+
detail: isRunning
|
|
281
|
+
? `Modal app ${modalApp} appears in app list.`
|
|
282
|
+
: `Modal app ${modalApp} not found in app list.`,
|
|
283
|
+
durationMs,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Daytona lifecycle
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
function daytonaHibernate(workspace, env, timeoutMs) {
|
|
290
|
+
const start = Date.now();
|
|
291
|
+
if (!workspace) {
|
|
292
|
+
return failedAction('daytona', 'hibernate', 'daytonaWorkspace is required for Daytona hibernate.', 0);
|
|
293
|
+
}
|
|
294
|
+
const result = runSpawn('daytona', ['workspace', 'stop', workspace], env, timeoutMs);
|
|
295
|
+
const durationMs = Date.now() - start;
|
|
296
|
+
if (result.ok) {
|
|
297
|
+
return {
|
|
298
|
+
backendId: 'daytona',
|
|
299
|
+
action: 'hibernate',
|
|
300
|
+
result: 'success',
|
|
301
|
+
state: 'hibernated',
|
|
302
|
+
detail: `Daytona workspace ${workspace} stopped.`,
|
|
303
|
+
durationMs,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return failedAction('daytona', 'hibernate', result.stderr || `daytona workspace stop failed (exit ${result.exitCode}).`, durationMs);
|
|
307
|
+
}
|
|
308
|
+
function daytonaWake(workspace, env, timeoutMs) {
|
|
309
|
+
const start = Date.now();
|
|
310
|
+
if (!workspace) {
|
|
311
|
+
return failedAction('daytona', 'wake', 'daytonaWorkspace is required for Daytona wake.', 0);
|
|
312
|
+
}
|
|
313
|
+
const result = runSpawn('daytona', ['workspace', 'start', workspace], env, timeoutMs);
|
|
314
|
+
const durationMs = Date.now() - start;
|
|
315
|
+
if (result.ok) {
|
|
316
|
+
return {
|
|
317
|
+
backendId: 'daytona',
|
|
318
|
+
action: 'wake',
|
|
319
|
+
result: 'success',
|
|
320
|
+
state: 'running',
|
|
321
|
+
detail: `Daytona workspace ${workspace} started.`,
|
|
322
|
+
durationMs,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return failedAction('daytona', 'wake', result.stderr || `daytona workspace start failed (exit ${result.exitCode}).`, durationMs);
|
|
326
|
+
}
|
|
327
|
+
function daytonaStatus(workspace, env, timeoutMs) {
|
|
328
|
+
const start = Date.now();
|
|
329
|
+
if (!workspace) {
|
|
330
|
+
return failedAction('daytona', 'status', 'daytonaWorkspace is required for Daytona status.', 0);
|
|
331
|
+
}
|
|
332
|
+
const result = runSpawn('daytona', ['workspace', 'list'], env, timeoutMs);
|
|
333
|
+
const durationMs = Date.now() - start;
|
|
334
|
+
if (!result.ok) {
|
|
335
|
+
return failedAction('daytona', 'status', result.stderr || `daytona workspace list failed (exit ${result.exitCode}).`, durationMs);
|
|
336
|
+
}
|
|
337
|
+
const isRunning = result.stdout.includes(workspace);
|
|
338
|
+
return {
|
|
339
|
+
backendId: 'daytona',
|
|
340
|
+
action: 'status',
|
|
341
|
+
result: 'success',
|
|
342
|
+
state: isRunning ? 'running' : 'hibernated',
|
|
343
|
+
detail: isRunning
|
|
344
|
+
? `Daytona workspace ${workspace} appears in workspace list.`
|
|
345
|
+
: `Daytona workspace ${workspace} not found in workspace list.`,
|
|
346
|
+
durationMs,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Public API
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
/**
|
|
353
|
+
* Execute a lifecycle action on a backend.
|
|
354
|
+
*/
|
|
355
|
+
export function executeBackendLifecycleAction(backendId, action, options = {}) {
|
|
356
|
+
const env = options.env ?? process.env;
|
|
357
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
358
|
+
if (UNSUPPORTED_BACKEND_IDS.has(backendId)) {
|
|
359
|
+
return unsupportedAction(backendId, action, 0);
|
|
360
|
+
}
|
|
361
|
+
if (!BACKEND_LABELS[backendId]) {
|
|
362
|
+
return failedAction(backendId, action, `Unknown backend: ${backendId}.`, 0);
|
|
363
|
+
}
|
|
364
|
+
switch (backendId) {
|
|
365
|
+
case 'docker': {
|
|
366
|
+
const containerId = options.containerId ?? env.CODEBUDDY_DOCKER_CONTAINER ?? '';
|
|
367
|
+
if (action === 'hibernate')
|
|
368
|
+
return dockerHibernate(containerId, env, timeoutMs);
|
|
369
|
+
if (action === 'wake')
|
|
370
|
+
return dockerWake(containerId, env, timeoutMs);
|
|
371
|
+
return dockerStatus(containerId, env, timeoutMs);
|
|
372
|
+
}
|
|
373
|
+
case 'ssh': {
|
|
374
|
+
const sshHost = options.sshHost
|
|
375
|
+
?? env.CODEBUDDY_SSH_HOST
|
|
376
|
+
?? env.SSH_HOST
|
|
377
|
+
?? env.CODEBUDDY_REMOTE_HOST
|
|
378
|
+
?? '';
|
|
379
|
+
if (action === 'hibernate')
|
|
380
|
+
return sshHibernate(sshHost, env, timeoutMs);
|
|
381
|
+
if (action === 'wake')
|
|
382
|
+
return sshWake(sshHost, env, timeoutMs);
|
|
383
|
+
return sshStatus(sshHost, env, timeoutMs);
|
|
384
|
+
}
|
|
385
|
+
case 'modal': {
|
|
386
|
+
const modalApp = options.modalApp ?? env.CODEBUDDY_MODAL_APP ?? '';
|
|
387
|
+
if (action === 'hibernate')
|
|
388
|
+
return modalHibernate(modalApp, env, timeoutMs);
|
|
389
|
+
if (action === 'wake')
|
|
390
|
+
return modalWake(modalApp, env, timeoutMs);
|
|
391
|
+
return modalStatus(modalApp, env, timeoutMs);
|
|
392
|
+
}
|
|
393
|
+
case 'daytona': {
|
|
394
|
+
const workspace = options.daytonaWorkspace ?? env.CODEBUDDY_DAYTONA_WORKSPACE ?? '';
|
|
395
|
+
if (action === 'hibernate')
|
|
396
|
+
return daytonaHibernate(workspace, env, timeoutMs);
|
|
397
|
+
if (action === 'wake')
|
|
398
|
+
return daytonaWake(workspace, env, timeoutMs);
|
|
399
|
+
return daytonaStatus(workspace, env, timeoutMs);
|
|
400
|
+
}
|
|
401
|
+
default:
|
|
402
|
+
return unsupportedAction(backendId, action, 0);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Build a lifecycle status report for all backends.
|
|
407
|
+
*/
|
|
408
|
+
export function buildBackendLifecycleStatusReport(options = {}) {
|
|
409
|
+
const now = options.now ?? (() => new Date());
|
|
410
|
+
const entries = ALL_BACKEND_IDS.map((backendId) => {
|
|
411
|
+
const lifecycleSupported = !UNSUPPORTED_BACKEND_IDS.has(backendId);
|
|
412
|
+
if (!lifecycleSupported) {
|
|
413
|
+
return {
|
|
414
|
+
backendId,
|
|
415
|
+
label: BACKEND_LABELS[backendId] ?? backendId,
|
|
416
|
+
lifecycleSupported: false,
|
|
417
|
+
state: 'unsupported',
|
|
418
|
+
detail: `${BACKEND_LABELS[backendId] ?? backendId} does not support managed lifecycle operations.`,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const statusAction = executeBackendLifecycleAction(backendId, 'status', options);
|
|
422
|
+
return {
|
|
423
|
+
backendId,
|
|
424
|
+
label: BACKEND_LABELS[backendId] ?? backendId,
|
|
425
|
+
lifecycleSupported: true,
|
|
426
|
+
state: statusAction.state,
|
|
427
|
+
detail: statusAction.detail,
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
kind: 'hermes_runtime_lifecycle_status',
|
|
432
|
+
schemaVersion: 1,
|
|
433
|
+
generatedAt: now().toISOString(),
|
|
434
|
+
platform: os.platform(),
|
|
435
|
+
arch: os.arch(),
|
|
436
|
+
backends: entries,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Render a lifecycle status report as human-readable text.
|
|
441
|
+
*/
|
|
442
|
+
export function renderBackendLifecycleStatusReport(report) {
|
|
443
|
+
const lines = [
|
|
444
|
+
'Hermes runtime lifecycle status',
|
|
445
|
+
`Platform: ${report.platform}/${report.arch}`,
|
|
446
|
+
`Generated: ${report.generatedAt}`,
|
|
447
|
+
'',
|
|
448
|
+
'Backends:',
|
|
449
|
+
...report.backends.map((entry) => {
|
|
450
|
+
const supported = entry.lifecycleSupported ? 'lifecycle' : 'no-lifecycle';
|
|
451
|
+
return ` ${entry.backendId}: ${entry.state} (${supported}) — ${entry.detail}`;
|
|
452
|
+
}),
|
|
453
|
+
];
|
|
454
|
+
return lines.join('\n');
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Render a single lifecycle action result as human-readable text.
|
|
458
|
+
*/
|
|
459
|
+
export function renderBackendLifecycleAction(action) {
|
|
460
|
+
const lines = [
|
|
461
|
+
`Hermes runtime lifecycle ${action.action} (${action.backendId}): ${action.result}`,
|
|
462
|
+
` State: ${action.state}`,
|
|
463
|
+
` Detail: ${action.detail}`,
|
|
464
|
+
` Duration: ${action.durationMs}ms`,
|
|
465
|
+
];
|
|
466
|
+
return lines.join('\n');
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=hermes-runtime-lifecycle.js.map
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { CodeBuddyClient } from '../codebuddy/client.js';
|
|
16
16
|
import { detectProviderFromEnv } from '../utils/provider-detector.js';
|
|
17
17
|
import { getLessonCandidateQueue, } from './lesson-candidate-queue.js';
|
|
18
|
+
import { scanForSecrets } from '../fleet/privacy-lint.js';
|
|
18
19
|
import { logger } from '../utils/logger.js';
|
|
19
20
|
const VALID = ['PATTERN', 'RULE', 'CONTEXT', 'INSIGHT'];
|
|
20
21
|
const SYSTEM_PROMPT = `You extract REUSABLE PROCEDURAL LESSONS from a coding session that just completed.
|
|
@@ -98,6 +99,15 @@ export async function proposeLessonsFromSession(chatHistory, workDir = process.c
|
|
|
98
99
|
const content = (cand.content || '').trim();
|
|
99
100
|
if (!content || !VALID.includes(category))
|
|
100
101
|
continue;
|
|
102
|
+
// WS3 guard-rail: never persist secrets/PII into memory — even into the
|
|
103
|
+
// review queue, where a reviewer could approve them into lessons.md.
|
|
104
|
+
const lint = scanForSecrets(`${content}\n${cand.context ?? ''}`);
|
|
105
|
+
if (lint.hasSecrets) {
|
|
106
|
+
logger.warn('[lesson-auto-proposer] candidate dropped: contains secret/PII material', {
|
|
107
|
+
kinds: lint.matches.map((m) => m.kind),
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
101
111
|
try {
|
|
102
112
|
const { candidate } = queue.propose({
|
|
103
113
|
category,
|
|
@@ -18,3 +18,4 @@ export type { AutoRepairConfig } from './auto-repair-middleware.js';
|
|
|
18
18
|
export { QualityGateMiddleware, createQualityGateMiddleware } from './quality-gate-middleware.js';
|
|
19
19
|
export type { QualityGateConfig, QualityGate } from './quality-gate-middleware.js';
|
|
20
20
|
export { ReasoningMiddleware, createReasoningMiddleware } from './reasoning-middleware.js';
|
|
21
|
+
export { VisualValidationMiddleware } from './visual-validation-middleware.js';
|
|
@@ -14,4 +14,5 @@ export { WorkflowGuardMiddleware } from './workflow-guard.js';
|
|
|
14
14
|
export { AutoRepairMiddleware, createAutoRepairMiddleware } from './auto-repair-middleware.js';
|
|
15
15
|
export { QualityGateMiddleware, createQualityGateMiddleware } from './quality-gate-middleware.js';
|
|
16
16
|
export { ReasoningMiddleware, createReasoningMiddleware } from './reasoning-middleware.js';
|
|
17
|
+
export { VisualValidationMiddleware } from './visual-validation-middleware.js';
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Duration Middleware (WS3-T3 — auto-pause & resume)
|
|
3
|
+
*
|
|
4
|
+
* Very long sessions (12 h+) degrade context quality and pile up risk.
|
|
5
|
+
* Past a configurable threshold this middleware suggests a clean pause,
|
|
6
|
+
* takes a fresh context snapshot as the resume point (WS3-T2), and
|
|
7
|
+
* reminds periodically instead of nagging every turn.
|
|
8
|
+
*
|
|
9
|
+
* It never stops the loop — pausing stays the operator's decision; the
|
|
10
|
+
* autonomous runner has its own bounded budgets (WS1).
|
|
11
|
+
*
|
|
12
|
+
* @module agent/middleware
|
|
13
|
+
*/
|
|
14
|
+
import { ConversationMiddleware, MiddlewareContext, MiddlewareResult } from './types.js';
|
|
15
|
+
export interface SessionDurationOptions {
|
|
16
|
+
/** Pause-suggestion threshold in ms. Default: CODEBUDDY_SESSION_PAUSE_HOURS (12 h); 0 disables. */
|
|
17
|
+
maxSessionMs?: number;
|
|
18
|
+
/** Re-warn cadence once past the threshold. Default 60 min. */
|
|
19
|
+
remindEveryMs?: number;
|
|
20
|
+
/** Injectable clock for tests. */
|
|
21
|
+
now?: () => number;
|
|
22
|
+
/** Take a fresh context snapshot when the pause is suggested (WS3-T2 hook). */
|
|
23
|
+
takeSnapshot?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare class SessionDurationMiddleware implements ConversationMiddleware {
|
|
26
|
+
readonly name = "session-duration";
|
|
27
|
+
readonly priority = 35;
|
|
28
|
+
private readonly startedAt;
|
|
29
|
+
private readonly thresholdMs;
|
|
30
|
+
private readonly remindEveryMs;
|
|
31
|
+
private readonly now;
|
|
32
|
+
private readonly takeSnapshot;
|
|
33
|
+
private lastWarnedAt;
|
|
34
|
+
constructor(options?: SessionDurationOptions);
|
|
35
|
+
beforeTurn(_context: MiddlewareContext): MiddlewareResult;
|
|
36
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Duration Middleware (WS3-T3 — auto-pause & resume)
|
|
3
|
+
*
|
|
4
|
+
* Very long sessions (12 h+) degrade context quality and pile up risk.
|
|
5
|
+
* Past a configurable threshold this middleware suggests a clean pause,
|
|
6
|
+
* takes a fresh context snapshot as the resume point (WS3-T2), and
|
|
7
|
+
* reminds periodically instead of nagging every turn.
|
|
8
|
+
*
|
|
9
|
+
* It never stops the loop — pausing stays the operator's decision; the
|
|
10
|
+
* autonomous runner has its own bounded budgets (WS1).
|
|
11
|
+
*
|
|
12
|
+
* @module agent/middleware
|
|
13
|
+
*/
|
|
14
|
+
import { RunStore } from '../../observability/run-store.js';
|
|
15
|
+
import { logger } from '../../utils/logger.js';
|
|
16
|
+
const HOUR_MS = 3_600_000;
|
|
17
|
+
function defaultThresholdMs() {
|
|
18
|
+
const hours = parseFloat(process.env.CODEBUDDY_SESSION_PAUSE_HOURS || '12');
|
|
19
|
+
if (!Number.isFinite(hours) || hours <= 0)
|
|
20
|
+
return 0; // 0/invalid → disabled
|
|
21
|
+
return hours * HOUR_MS;
|
|
22
|
+
}
|
|
23
|
+
export class SessionDurationMiddleware {
|
|
24
|
+
name = 'session-duration';
|
|
25
|
+
priority = 35;
|
|
26
|
+
startedAt;
|
|
27
|
+
thresholdMs;
|
|
28
|
+
remindEveryMs;
|
|
29
|
+
now;
|
|
30
|
+
takeSnapshot;
|
|
31
|
+
lastWarnedAt = null;
|
|
32
|
+
constructor(options = {}) {
|
|
33
|
+
this.now = options.now ?? Date.now;
|
|
34
|
+
this.startedAt = this.now();
|
|
35
|
+
this.thresholdMs = options.maxSessionMs ?? defaultThresholdMs();
|
|
36
|
+
this.remindEveryMs = options.remindEveryMs ?? HOUR_MS;
|
|
37
|
+
this.takeSnapshot = options.takeSnapshot;
|
|
38
|
+
}
|
|
39
|
+
beforeTurn(_context) {
|
|
40
|
+
if (this.thresholdMs <= 0)
|
|
41
|
+
return { action: 'continue' };
|
|
42
|
+
const elapsed = this.now() - this.startedAt;
|
|
43
|
+
if (elapsed < this.thresholdMs)
|
|
44
|
+
return { action: 'continue' };
|
|
45
|
+
if (this.lastWarnedAt !== null && this.now() - this.lastWarnedAt < this.remindEveryMs) {
|
|
46
|
+
return { action: 'continue' };
|
|
47
|
+
}
|
|
48
|
+
this.lastWarnedAt = this.now();
|
|
49
|
+
// Fresh resume point right when the pause is suggested.
|
|
50
|
+
try {
|
|
51
|
+
this.takeSnapshot?.();
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.debug('[session-duration] snapshot on pause suggestion failed', { error: String(err) });
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const runStore = RunStore.getInstance();
|
|
58
|
+
if (runStore.getCurrentRunId()) {
|
|
59
|
+
runStore.appendEvent('pause_suggested', {
|
|
60
|
+
elapsedMs: elapsed,
|
|
61
|
+
thresholdMs: this.thresholdMs,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Observability must never break the loop.
|
|
67
|
+
}
|
|
68
|
+
const hours = (elapsed / HOUR_MS).toFixed(1);
|
|
69
|
+
return {
|
|
70
|
+
action: 'warn',
|
|
71
|
+
message: `⏸️ Session running for ${hours} h. Consider a clean pause: a fresh context ` +
|
|
72
|
+
`snapshot was just written (.codebuddy/context-snapshot.json), a handoff is ` +
|
|
73
|
+
`written on exit (.codebuddy/HANDOFF.md), and \`buddy --continue\` reloads the ` +
|
|
74
|
+
`session. Long sessions degrade context quality — \`/compact\` also helps.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=session-duration.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Validation Middleware
|
|
3
|
+
*
|
|
4
|
+
* Checks if the agent has saved an Office document (Excel, PowerPoint, Word).
|
|
5
|
+
* If so, it warns the agent to consider running a visual validation step
|
|
6
|
+
* using `snapshot_with_screenshot` to verify the layout, fonts, and alignments.
|
|
7
|
+
*
|
|
8
|
+
* Priority 156
|
|
9
|
+
*/
|
|
10
|
+
import type { ConversationMiddleware, MiddlewareContext, MiddlewareResult } from './types.js';
|
|
11
|
+
export declare class VisualValidationMiddleware implements ConversationMiddleware {
|
|
12
|
+
readonly name = "visual-validation";
|
|
13
|
+
readonly priority = 156;
|
|
14
|
+
private hasWarnedForFiles;
|
|
15
|
+
afterTurn(context: MiddlewareContext): Promise<MiddlewareResult>;
|
|
16
|
+
private getRecentlySavedOfficeFiles;
|
|
17
|
+
}
|