@vellumai/assistant 0.7.2 → 0.7.3
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/ARCHITECTURE.md +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +27 -3
- package/src/config/loader.ts +127 -8
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +75 -11
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +0 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
type FSWatcher,
|
|
9
9
|
mkdirSync,
|
|
10
10
|
readdirSync,
|
|
11
|
+
unwatchFile,
|
|
11
12
|
watch,
|
|
13
|
+
watchFile,
|
|
12
14
|
} from "node:fs";
|
|
13
15
|
import { join } from "node:path";
|
|
14
16
|
|
|
@@ -17,7 +19,6 @@ import type { MemoryCleanupConfig } from "../config/schemas/memory-lifecycle.js"
|
|
|
17
19
|
import { resetCleanupScheduleThrottle } from "../memory/cleanup-schedule-state.js";
|
|
18
20
|
import { clearEmbeddingBackendCache } from "../memory/embedding-backend.js";
|
|
19
21
|
import { initializeProviders } from "../providers/registry.js";
|
|
20
|
-
import { handleBashSignal } from "../signals/bash.js";
|
|
21
22
|
import { handleCancelSignal } from "../signals/cancel.js";
|
|
22
23
|
import { handleConversationUndoSignal } from "../signals/conversation-undo.js";
|
|
23
24
|
import { handleEmitEventSignal } from "../signals/emit-event.js";
|
|
@@ -47,19 +48,44 @@ function attachWatcherErrorHandler(watcher: FSWatcher, dir: string): void {
|
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Poll interval for `fs.watchFile()`. Use the stat-polling watcher
|
|
53
|
+
* because Bun's per-file `fs.watch()` doesn't detect renames on Linux
|
|
54
|
+
* (seemingly works on macOS). See https://github.com/oven-sh/bun/issues/15010.
|
|
55
|
+
*/
|
|
56
|
+
const WATCH_FILE_POLL_MS = 2_000;
|
|
57
|
+
|
|
50
58
|
export class ConfigWatcher {
|
|
51
59
|
private watchers: FSWatcher[] = [];
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
protectedKeyPrefix: "__",
|
|
56
|
-
});
|
|
60
|
+
private watchedFiles: Set<string> = new Set();
|
|
61
|
+
private stopped = false;
|
|
62
|
+
private debounceTimers: DebouncerMap;
|
|
57
63
|
private suppressReload = false;
|
|
58
64
|
lastFingerprint = "";
|
|
65
|
+
private lastConfig: ReturnType<typeof getConfig> | null = null;
|
|
59
66
|
private lastRefreshTime = 0;
|
|
60
67
|
|
|
61
68
|
static readonly REFRESH_INTERVAL_MS = 30_000;
|
|
62
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param pollIntervalMs Per-file stat poll interval (passed to
|
|
72
|
+
* `fs.watchFile`). Default `WATCH_FILE_POLL_MS` (2s); tests pass a
|
|
73
|
+
* smaller value for fast turnaround.
|
|
74
|
+
* @param debounceMs Debounce window applied to any detected file
|
|
75
|
+
* change before invoking its handler. Default 200ms; tests pass a
|
|
76
|
+
* smaller value to avoid sleeping unnecessarily.
|
|
77
|
+
*/
|
|
78
|
+
constructor(
|
|
79
|
+
private readonly pollIntervalMs: number = WATCH_FILE_POLL_MS,
|
|
80
|
+
debounceMs = 200,
|
|
81
|
+
) {
|
|
82
|
+
this.debounceTimers = new DebouncerMap({
|
|
83
|
+
defaultDelayMs: debounceMs,
|
|
84
|
+
maxEntries: 1000,
|
|
85
|
+
protectedKeyPrefix: "__",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
/** Expose the debounce timers so handlers can schedule debounced work. */
|
|
64
90
|
get timers(): DebouncerMap {
|
|
65
91
|
return this.debounceTimers;
|
|
@@ -88,12 +114,15 @@ export class ConfigWatcher {
|
|
|
88
114
|
|
|
89
115
|
/** Initialize the config fingerprint (call after first config load). */
|
|
90
116
|
initFingerprint(config: ReturnType<typeof getConfig>): void {
|
|
117
|
+
this.lastConfig = config;
|
|
91
118
|
this.lastFingerprint = this.configFingerprint(config);
|
|
92
119
|
}
|
|
93
120
|
|
|
94
121
|
/** Update the fingerprint to match the current config. */
|
|
95
122
|
updateFingerprint(): void {
|
|
96
|
-
|
|
123
|
+
const config = getConfig();
|
|
124
|
+
this.lastConfig = config;
|
|
125
|
+
this.lastFingerprint = this.configFingerprint(config);
|
|
97
126
|
this.lastRefreshTime = Date.now();
|
|
98
127
|
}
|
|
99
128
|
|
|
@@ -103,7 +132,7 @@ export class ConfigWatcher {
|
|
|
103
132
|
* Returns true if config actually changed.
|
|
104
133
|
*/
|
|
105
134
|
async refreshConfigFromSources(): Promise<boolean> {
|
|
106
|
-
const prevCleanup =
|
|
135
|
+
const prevCleanup = this.lastConfig?.memory?.cleanup;
|
|
107
136
|
invalidateConfigCache();
|
|
108
137
|
const config = getConfig();
|
|
109
138
|
const fingerprint = this.configFingerprint(config);
|
|
@@ -120,6 +149,7 @@ export class ConfigWatcher {
|
|
|
120
149
|
}
|
|
121
150
|
const isFirstInit = this.lastFingerprint === "";
|
|
122
151
|
await initializeProviders(config);
|
|
152
|
+
this.lastConfig = config;
|
|
123
153
|
this.lastFingerprint = fingerprint;
|
|
124
154
|
return !isFirstInit;
|
|
125
155
|
}
|
|
@@ -136,19 +166,24 @@ export class ConfigWatcher {
|
|
|
136
166
|
onAvatarChanged?: () => void,
|
|
137
167
|
onConfigChanged?: () => void,
|
|
138
168
|
): void {
|
|
169
|
+
// Reset the stopped flag so a stop()→start() cycle on the same
|
|
170
|
+
// instance resumes hot-reload instead of silently bailing in every
|
|
171
|
+
// watchFile callback. This matters because getConfigWatcher() is a
|
|
172
|
+
// module-level singleton — a daemon restart path that reuses it
|
|
173
|
+
// would otherwise be permanently mute.
|
|
174
|
+
this.stopped = false;
|
|
139
175
|
const workspaceDir = getWorkspaceDir();
|
|
140
176
|
|
|
141
177
|
const workspaceHandlers: Record<string, () => void> = {
|
|
142
178
|
"config.json": async () => {
|
|
143
179
|
if (this.suppressReload) return;
|
|
144
180
|
try {
|
|
145
|
-
const
|
|
146
|
-
const prevMcpFingerprint = JSON.stringify(prevConfig.mcp ?? {});
|
|
181
|
+
const prevMcpFingerprint = JSON.stringify(this.lastConfig?.mcp ?? {});
|
|
147
182
|
const changed = await this.refreshConfigFromSources();
|
|
148
183
|
if (changed) {
|
|
149
184
|
onConversationEvict();
|
|
150
185
|
onConfigChanged?.();
|
|
151
|
-
const newConfig = getConfig();
|
|
186
|
+
const newConfig = this.lastConfig ?? getConfig();
|
|
152
187
|
const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
|
|
153
188
|
if (newMcpFingerprint !== prevMcpFingerprint) {
|
|
154
189
|
reloadMcpServers().catch((err: unknown) => {
|
|
@@ -170,37 +205,11 @@ export class ConfigWatcher {
|
|
|
170
205
|
},
|
|
171
206
|
};
|
|
172
207
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const watcher = watch(dir, (_eventType, filename) => {
|
|
180
|
-
if (!filename) return;
|
|
181
|
-
const file = String(filename);
|
|
182
|
-
if (!handlers[file]) return;
|
|
183
|
-
this.debounceTimers.schedule(`file:${file}`, () => {
|
|
184
|
-
log.info({ file }, "File changed, reloading");
|
|
185
|
-
handlers[file]();
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
attachWatcherErrorHandler(watcher, dir);
|
|
189
|
-
this.watchers.push(watcher);
|
|
190
|
-
log.info({ dir }, `Watching ${label}`);
|
|
191
|
-
} catch (err) {
|
|
192
|
-
log.warn(
|
|
193
|
-
{ err, dir },
|
|
194
|
-
`Failed to watch ${label}. Hot-reload will be unavailable.`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
watchDir(
|
|
200
|
-
workspaceDir,
|
|
201
|
-
workspaceHandlers,
|
|
202
|
-
"workspace directory for config/prompt changes",
|
|
203
|
-
);
|
|
208
|
+
// Per-file watches; don't watch the workspace directory itself because
|
|
209
|
+
// it contains socket files.
|
|
210
|
+
for (const [filename, handler] of Object.entries(workspaceHandlers)) {
|
|
211
|
+
this.watchFile(join(workspaceDir, filename), handler, filename);
|
|
212
|
+
}
|
|
204
213
|
|
|
205
214
|
if (onSoundsConfigChanged) {
|
|
206
215
|
this.startSoundsWatcher(onSoundsConfigChanged);
|
|
@@ -215,13 +224,46 @@ export class ConfigWatcher {
|
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
stop(): void {
|
|
227
|
+
this.stopped = true;
|
|
218
228
|
this.debounceTimers.cancelAll();
|
|
229
|
+
for (const filePath of this.watchedFiles) {
|
|
230
|
+
unwatchFile(filePath);
|
|
231
|
+
}
|
|
232
|
+
this.watchedFiles.clear();
|
|
219
233
|
for (const watcher of this.watchers) {
|
|
220
234
|
watcher.close();
|
|
221
235
|
}
|
|
222
236
|
this.watchers = [];
|
|
223
237
|
}
|
|
224
238
|
|
|
239
|
+
private watchFile(
|
|
240
|
+
filePath: string,
|
|
241
|
+
handler: () => void,
|
|
242
|
+
label: string,
|
|
243
|
+
): void {
|
|
244
|
+
// Match the defensive pattern used by every other startXWatcher in
|
|
245
|
+
// this file: log the failure and continue. Per AGENTS.md, the daemon
|
|
246
|
+
// must never block startup — a watchFile() throw on some platform
|
|
247
|
+
// edge case must not propagate up to DaemonServer.start().
|
|
248
|
+
try {
|
|
249
|
+
watchFile(filePath, { interval: this.pollIntervalMs }, (curr, prev) => {
|
|
250
|
+
if (this.stopped) return;
|
|
251
|
+
if (curr.ino === prev.ino && curr.mtimeMs === prev.mtimeMs) return;
|
|
252
|
+
this.debounceTimers.schedule(`file:${filePath}`, () => {
|
|
253
|
+
log.info({ file: filePath }, "File changed, reloading");
|
|
254
|
+
handler();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
this.watchedFiles.add(filePath);
|
|
258
|
+
log.info({ file: filePath }, `Watching ${label}`);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
log.warn(
|
|
261
|
+
{ err, file: filePath },
|
|
262
|
+
`Failed to watch ${label}. Hot-reload will be unavailable until restart.`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
225
267
|
private startSoundsWatcher(onSoundsConfigChanged: () => void): void {
|
|
226
268
|
const soundsDir = getSoundsDir();
|
|
227
269
|
try {
|
|
@@ -341,7 +383,6 @@ export class ConfigWatcher {
|
|
|
341
383
|
string,
|
|
342
384
|
(filename: string) => void | Promise<void>
|
|
343
385
|
> = {
|
|
344
|
-
"bash.": handleBashSignal,
|
|
345
386
|
"user-message.": handleUserMessageSignal,
|
|
346
387
|
};
|
|
347
388
|
|
|
@@ -486,20 +527,6 @@ export class ConfigWatcher {
|
|
|
486
527
|
}
|
|
487
528
|
}
|
|
488
529
|
|
|
489
|
-
/**
|
|
490
|
-
* Snapshot the current cleanup config so we can compare it against the
|
|
491
|
-
* post-reload value. Tolerant of config-load failures — if the config can't
|
|
492
|
-
* be read (e.g. first-load), returns undefined so the comparison below
|
|
493
|
-
* treats it as "no previous value".
|
|
494
|
-
*/
|
|
495
|
-
function safeGetCleanupConfig(): MemoryCleanupConfig | undefined {
|
|
496
|
-
try {
|
|
497
|
-
return getConfig().memory?.cleanup;
|
|
498
|
-
} catch {
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
530
|
/**
|
|
504
531
|
* Return true if any cleanup field the user can change via the UI differs
|
|
505
532
|
* between the previous and next config snapshots. Used to decide whether to
|
|
@@ -530,6 +557,7 @@ export function cleanupSettingsChanged(
|
|
|
530
557
|
return (
|
|
531
558
|
prev.llmRequestLogRetentionMs !== next.llmRequestLogRetentionMs ||
|
|
532
559
|
prev.conversationRetentionDays !== next.conversationRetentionDays ||
|
|
560
|
+
prev.traceEventRetentionDays !== next.traceEventRetentionDays ||
|
|
533
561
|
prev.enabled !== next.enabled
|
|
534
562
|
);
|
|
535
563
|
}
|
|
@@ -137,6 +137,8 @@ export interface EventHandlerState {
|
|
|
137
137
|
readonly directiveWarnings: string[];
|
|
138
138
|
readonly toolUseIdToName: Map<string, string>;
|
|
139
139
|
currentTurnToolNames: string[];
|
|
140
|
+
/** Sticky for the whole run: this turn created/refreshed an app. */
|
|
141
|
+
appBuildToolUsedThisRun: boolean;
|
|
140
142
|
/** Tracks whether the first text delta has been emitted this turn for activity state transitions. */
|
|
141
143
|
firstTextDeltaEmitted: boolean;
|
|
142
144
|
/** Tracks whether a thinking delta has been emitted this turn for activity state transitions. */
|
|
@@ -219,6 +221,7 @@ export function createEventHandlerState(): EventHandlerState {
|
|
|
219
221
|
directiveWarnings: [],
|
|
220
222
|
toolUseIdToName: new Map(),
|
|
221
223
|
currentTurnToolNames: [],
|
|
224
|
+
appBuildToolUsedThisRun: false,
|
|
222
225
|
firstTextDeltaEmitted: false,
|
|
223
226
|
firstThinkingDeltaEmitted: false,
|
|
224
227
|
lastCompletedToolName: undefined,
|
|
@@ -365,6 +368,9 @@ export function handleToolUse(
|
|
|
365
368
|
): void {
|
|
366
369
|
state.toolUseIdToName.set(event.id, event.name);
|
|
367
370
|
state.currentTurnToolNames.push(event.name);
|
|
371
|
+
if (event.name === "app_create" || event.name === "app_refresh") {
|
|
372
|
+
state.appBuildToolUsedThisRun = true;
|
|
373
|
+
}
|
|
368
374
|
state.toolCallTimestamps.set(event.id, { startedAt: Date.now() });
|
|
369
375
|
state.currentToolUseId = event.id;
|
|
370
376
|
state.currentTurnToolUseIds.push(event.id);
|
|
@@ -73,7 +73,10 @@ import type { ConversationGraphMemory } from "../memory/graph/conversation-graph
|
|
|
73
73
|
import { recordMemoryRecallLog } from "../memory/memory-recall-log-store.js";
|
|
74
74
|
import { PKB_WORKSPACE_SCOPE } from "../memory/pkb/types.js";
|
|
75
75
|
import type { QdrantSparseVector } from "../memory/qdrant-client.js";
|
|
76
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
readMemoryV2StaticContent,
|
|
78
|
+
shouldLoadMemoryV2Static,
|
|
79
|
+
} from "../memory/v2/static-context.js";
|
|
77
80
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
78
81
|
import { defaultCompactionTerminal } from "../plugins/defaults/compaction.js";
|
|
79
82
|
import { defaultHistoryRepairTerminal } from "../plugins/defaults/history-repair.js";
|
|
@@ -106,6 +109,11 @@ import type {
|
|
|
106
109
|
TurnContext as PluginTurnContext,
|
|
107
110
|
} from "../plugins/types.js";
|
|
108
111
|
import { PluginExecutionError, PluginTimeoutError } from "../plugins/types.js";
|
|
112
|
+
import {
|
|
113
|
+
hasProactiveArtifactCompleted,
|
|
114
|
+
runProactiveArtifactJob,
|
|
115
|
+
tryClaimProactiveArtifactTrigger,
|
|
116
|
+
} from "../proactive-artifact/index.js";
|
|
109
117
|
import type {
|
|
110
118
|
ContentBlock,
|
|
111
119
|
Message,
|
|
@@ -113,6 +121,7 @@ import type {
|
|
|
113
121
|
} from "../providers/types.js";
|
|
114
122
|
import type { Provider } from "../providers/types.js";
|
|
115
123
|
import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
|
|
124
|
+
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
116
125
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
117
126
|
import { redactSecrets } from "../security/secret-scanner.js";
|
|
118
127
|
import { getSubagentManager } from "../subagent/index.js";
|
|
@@ -175,7 +184,12 @@ import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
|
175
184
|
import { markSurfaceCompleted } from "./conversation-surfaces.js";
|
|
176
185
|
import { resolveTrustClass } from "./conversation-tool-setup.js";
|
|
177
186
|
import { recordUsage } from "./conversation-usage.js";
|
|
178
|
-
import {
|
|
187
|
+
import {
|
|
188
|
+
formatTurnTimestamp,
|
|
189
|
+
resolveTurnTimezoneContext,
|
|
190
|
+
} from "./date-context.js";
|
|
191
|
+
import { getDiskPressureStatus } from "./disk-pressure-guard.js";
|
|
192
|
+
import { classifyDiskPressureTurnPolicy } from "./disk-pressure-policy.js";
|
|
179
193
|
import { deepRepairHistory } from "./history-repair.js";
|
|
180
194
|
import type {
|
|
181
195
|
DynamicPageSurfaceData,
|
|
@@ -192,6 +206,8 @@ import type { TrustContext } from "./trust-context.js";
|
|
|
192
206
|
import { stripHistoricalWebSearchResults } from "./web-search-history.js";
|
|
193
207
|
|
|
194
208
|
const log = getLogger("conversation-agent-loop");
|
|
209
|
+
const DISK_PRESSURE_ERROR_CODE = "DISK_SPACE_CRITICAL" as const;
|
|
210
|
+
const DISK_PRESSURE_ERROR_CATEGORY = "disk_pressure";
|
|
195
211
|
|
|
196
212
|
/** Title-cased friendly labels for tool names, used in confirmation chips. */
|
|
197
213
|
const TOOL_FRIENDLY_LABEL: Record<string, string> = {
|
|
@@ -211,6 +227,10 @@ type GitServiceInitializer = {
|
|
|
211
227
|
ensureInitialized(): Promise<void>;
|
|
212
228
|
};
|
|
213
229
|
|
|
230
|
+
function formatDiskPressureBlockedMessage(): string {
|
|
231
|
+
return "Storage is critically low, so background processes are paused and remote messages are ignored until the guardian frees enough space. Remote senders should try again later.";
|
|
232
|
+
}
|
|
233
|
+
|
|
214
234
|
// ── Compaction circuit-breaker pipeline helpers ─────────────────────
|
|
215
235
|
//
|
|
216
236
|
// The circuit-breaker behavior (3 consecutive summary-LLM failures trips a
|
|
@@ -438,7 +458,6 @@ export interface AgentLoopConversationContext {
|
|
|
438
458
|
/** Timestamp (ms since epoch) until which the circuit breaker is open. */
|
|
439
459
|
compactionCircuitOpenUntil: number | null;
|
|
440
460
|
|
|
441
|
-
readonly memoryPolicy: { scopeId: string; includeDefaultFallback: boolean };
|
|
442
461
|
readonly graphMemory: ConversationGraphMemory;
|
|
443
462
|
|
|
444
463
|
currentActiveSurfaceId?: string;
|
|
@@ -495,9 +514,11 @@ export interface AgentLoopConversationContext {
|
|
|
495
514
|
voiceCallControlPrompt?: string;
|
|
496
515
|
transportHints?: string[];
|
|
497
516
|
slackRuntimeContextNotice?: string;
|
|
517
|
+
clientTimezone?: string;
|
|
498
518
|
|
|
499
519
|
readonly coreToolNames: Set<string>;
|
|
500
520
|
allowedToolNames?: Set<string>;
|
|
521
|
+
diskPressureCleanupModeActive?: boolean;
|
|
501
522
|
toolsDisabledDepth: number;
|
|
502
523
|
preactivatedSkillIds?: string[];
|
|
503
524
|
readonly skillProjectionState: Map<string, string>;
|
|
@@ -707,19 +728,39 @@ export async function runAgentLoopImpl(
|
|
|
707
728
|
};
|
|
708
729
|
})();
|
|
709
730
|
|
|
731
|
+
const isInteractiveResolved =
|
|
732
|
+
options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
|
|
733
|
+
const diskPressureDecision = classifyDiskPressureTurnPolicy(
|
|
734
|
+
getDiskPressureStatus(),
|
|
735
|
+
{
|
|
736
|
+
conversationType: turnStartConversation?.conversationType ?? null,
|
|
737
|
+
conversationSource: turnStartConversation?.source ?? null,
|
|
738
|
+
callSite: turnCallSite,
|
|
739
|
+
isInteractive: isInteractiveResolved,
|
|
740
|
+
sourceChannel:
|
|
741
|
+
ctx.trustContext?.sourceChannel ??
|
|
742
|
+
capturedTurnChannelContext.userMessageChannel,
|
|
743
|
+
sourceInterface:
|
|
744
|
+
ctx.channelCapabilities?.clientOS ??
|
|
745
|
+
capturedTurnInterfaceContext.userMessageInterface,
|
|
746
|
+
trustContext: ctx.trustContext
|
|
747
|
+
? {
|
|
748
|
+
sourceChannel: ctx.trustContext.sourceChannel,
|
|
749
|
+
trustClass: ctx.trustContext.trustClass,
|
|
750
|
+
}
|
|
751
|
+
: null,
|
|
752
|
+
},
|
|
753
|
+
);
|
|
754
|
+
const diskPressureContext =
|
|
755
|
+
diskPressureDecision.action === "allow-cleanup-mode"
|
|
756
|
+
? { cleanupModeActive: true }
|
|
757
|
+
: null;
|
|
758
|
+
ctx.diskPressureCleanupModeActive =
|
|
759
|
+
diskPressureDecision.action === "allow-cleanup-mode";
|
|
760
|
+
|
|
710
761
|
ctx.lastAssistantAttachments = [];
|
|
711
762
|
ctx.lastAttachmentWarnings = [];
|
|
712
763
|
|
|
713
|
-
// Ensure workspace git repo is initialized before any tools run.
|
|
714
|
-
try {
|
|
715
|
-
const getWorkspaceGitServiceFn =
|
|
716
|
-
ctx.getWorkspaceGitService ?? getWorkspaceGitService;
|
|
717
|
-
const gitService = getWorkspaceGitServiceFn(ctx.workingDir);
|
|
718
|
-
await gitService.ensureInitialized();
|
|
719
|
-
} catch (err) {
|
|
720
|
-
rlog.warn({ err }, "Failed to initialize workspace git repo (non-fatal)");
|
|
721
|
-
}
|
|
722
|
-
|
|
723
764
|
ctx.profiler.startRequest();
|
|
724
765
|
let turnStarted = false;
|
|
725
766
|
|
|
@@ -736,6 +777,52 @@ export async function runAgentLoopImpl(
|
|
|
736
777
|
});
|
|
737
778
|
|
|
738
779
|
try {
|
|
780
|
+
if (diskPressureDecision.action === "block") {
|
|
781
|
+
const message = formatDiskPressureBlockedMessage();
|
|
782
|
+
rlog.warn(
|
|
783
|
+
{ reason: diskPressureDecision.reason },
|
|
784
|
+
"Blocked turn during disk pressure cleanup mode",
|
|
785
|
+
);
|
|
786
|
+
ctx.emitActivityState("idle", "error_terminal", "global", reqId);
|
|
787
|
+
ctx.traceEmitter.emit("request_error", message, {
|
|
788
|
+
requestId: reqId,
|
|
789
|
+
status: "error",
|
|
790
|
+
attributes: {
|
|
791
|
+
errorCategory: DISK_PRESSURE_ERROR_CATEGORY,
|
|
792
|
+
errorCode: DISK_PRESSURE_ERROR_CODE,
|
|
793
|
+
diskPressureReason: diskPressureDecision.reason,
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
onEvent({
|
|
797
|
+
type: "error",
|
|
798
|
+
conversationId: ctx.conversationId,
|
|
799
|
+
requestId: reqId,
|
|
800
|
+
code: DISK_PRESSURE_ERROR_CODE,
|
|
801
|
+
message,
|
|
802
|
+
category: DISK_PRESSURE_ERROR_CATEGORY,
|
|
803
|
+
errorCategory: DISK_PRESSURE_ERROR_CATEGORY,
|
|
804
|
+
});
|
|
805
|
+
onEvent({
|
|
806
|
+
type: "conversation_error",
|
|
807
|
+
conversationId: ctx.conversationId,
|
|
808
|
+
code: DISK_PRESSURE_ERROR_CODE,
|
|
809
|
+
userMessage: message,
|
|
810
|
+
retryable: true,
|
|
811
|
+
errorCategory: DISK_PRESSURE_ERROR_CATEGORY,
|
|
812
|
+
});
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Ensure workspace git repo is initialized before any tools run.
|
|
817
|
+
try {
|
|
818
|
+
const getWorkspaceGitServiceFn =
|
|
819
|
+
ctx.getWorkspaceGitService ?? getWorkspaceGitService;
|
|
820
|
+
const gitService = getWorkspaceGitServiceFn(ctx.workingDir);
|
|
821
|
+
await gitService.ensureInitialized();
|
|
822
|
+
} catch (err) {
|
|
823
|
+
rlog.warn({ err }, "Failed to initialize workspace git repo (non-fatal)");
|
|
824
|
+
}
|
|
825
|
+
|
|
739
826
|
// Auto-complete stale interactive surfaces from previous turns.
|
|
740
827
|
// Only dismiss when the user sends a new message (not a surface action
|
|
741
828
|
// response), so internal turns (subagent notifications, lifecycle
|
|
@@ -1238,14 +1325,16 @@ export async function runAgentLoopImpl(
|
|
|
1238
1325
|
|
|
1239
1326
|
// Compute fresh turn timestamp for date grounding.
|
|
1240
1327
|
// Absolute "now" is always anchored to assistant host clock, while local
|
|
1241
|
-
// date semantics prefer configured user timezone, then
|
|
1328
|
+
// date semantics prefer configured user timezone, then device timezones.
|
|
1242
1329
|
const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
|
|
1330
|
+
const timezoneContext = resolveTurnTimezoneContext({
|
|
1331
|
+
configuredUserTimeZone: config.ui.userTimezone ?? null,
|
|
1332
|
+
clientTimezone: ctx.clientTimezone ?? null,
|
|
1333
|
+
detectedTimezone: config.ui.detectedTimezone ?? null,
|
|
1246
1334
|
hostTimeZone,
|
|
1247
|
-
|
|
1248
|
-
|
|
1335
|
+
});
|
|
1336
|
+
const timestamp = formatTurnTimestamp({
|
|
1337
|
+
timeZone: timezoneContext.effectiveTimezone,
|
|
1249
1338
|
});
|
|
1250
1339
|
|
|
1251
1340
|
// Resolve the inbound actor context for the unified <turn_context> block.
|
|
@@ -1299,23 +1388,26 @@ export async function runAgentLoopImpl(
|
|
|
1299
1388
|
}
|
|
1300
1389
|
}
|
|
1301
1390
|
|
|
1391
|
+
const baseTurnContext = {
|
|
1392
|
+
timestamp,
|
|
1393
|
+
interfaceName,
|
|
1394
|
+
channelName,
|
|
1395
|
+
configuredUserTimezone: timezoneContext.configuredUserTimezone,
|
|
1396
|
+
clientTimezone: timezoneContext.clientTimezone,
|
|
1397
|
+
detectedTimezone: timezoneContext.detectedTimezone,
|
|
1398
|
+
timeSinceLastMessage,
|
|
1399
|
+
};
|
|
1302
1400
|
const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
|
|
1303
1401
|
isGuardian
|
|
1304
|
-
?
|
|
1402
|
+
? baseTurnContext
|
|
1305
1403
|
: {
|
|
1306
|
-
|
|
1307
|
-
interfaceName,
|
|
1308
|
-
channelName,
|
|
1404
|
+
...baseTurnContext,
|
|
1309
1405
|
actorContext: resolvedInboundActorContext,
|
|
1310
|
-
timeSinceLastMessage,
|
|
1311
1406
|
},
|
|
1312
1407
|
);
|
|
1313
1408
|
|
|
1314
1409
|
// The `remember` tool handles scratchpad-style memory writes directly to the graph.
|
|
1315
1410
|
|
|
1316
|
-
const isInteractiveResolved =
|
|
1317
|
-
options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
|
|
1318
|
-
|
|
1319
1411
|
// Inject NOW.md and PKB content only on the first turn (or after
|
|
1320
1412
|
// compaction re-strips them). Old injections persist in history and
|
|
1321
1413
|
// are never stripped on normal turns — this preserves the cached prefix.
|
|
@@ -1330,10 +1422,16 @@ export async function runAgentLoopImpl(
|
|
|
1330
1422
|
const pkbActive = currentPkbContent !== null;
|
|
1331
1423
|
|
|
1332
1424
|
// V2 static memory block (essentials/threads/recent/buffer). Same
|
|
1333
|
-
// first-turn / post-compaction cadence as PKB
|
|
1334
|
-
//
|
|
1335
|
-
//
|
|
1336
|
-
|
|
1425
|
+
// first-turn / post-compaction cadence as PKB. `shouldLoadMemoryV2Static`
|
|
1426
|
+
// also blocks remote-channel non-guardian actors from inducing the
|
|
1427
|
+
// model to recite private memory; `readMemoryV2StaticContent` self-gates
|
|
1428
|
+
// on the v2 flag + config and returns null when v2 is off, so the file
|
|
1429
|
+
// reads are skipped on non-injection turns.
|
|
1430
|
+
const currentMemoryV2Static = shouldLoadMemoryV2Static({
|
|
1431
|
+
shouldInjectNowAndPkb,
|
|
1432
|
+
sourceChannel: ctx.trustContext?.sourceChannel,
|
|
1433
|
+
isTrustedActor,
|
|
1434
|
+
})
|
|
1337
1435
|
? readMemoryV2StaticContent()
|
|
1338
1436
|
: null;
|
|
1339
1437
|
const memoryV2Static = currentMemoryV2Static;
|
|
@@ -1349,8 +1447,8 @@ export async function runAgentLoopImpl(
|
|
|
1349
1447
|
// `getInContextPkbPaths` re-reads `conversation.messages` on each call,
|
|
1350
1448
|
// so post-compaction re-injects see the updated history.
|
|
1351
1449
|
const pkbConversation = pkbActive ? ctx : undefined;
|
|
1352
|
-
// PKB points live under a single workspace sentinel scope
|
|
1353
|
-
//
|
|
1450
|
+
// PKB points live under a single workspace sentinel scope.
|
|
1451
|
+
// See `PKB_WORKSPACE_SCOPE` for why.
|
|
1354
1452
|
const pkbScopeId = pkbActive ? PKB_WORKSPACE_SCOPE : undefined;
|
|
1355
1453
|
|
|
1356
1454
|
// Subagent status injection — gives the parent LLM visibility into active/completed children.
|
|
@@ -1417,6 +1515,7 @@ export async function runAgentLoopImpl(
|
|
|
1417
1515
|
|
|
1418
1516
|
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
1419
1517
|
const injectionOpts = {
|
|
1518
|
+
diskPressureContext,
|
|
1420
1519
|
activeSurface,
|
|
1421
1520
|
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1422
1521
|
? ctx.workspaceTopLevelContext
|
|
@@ -2874,6 +2973,42 @@ export async function runAgentLoopImpl(
|
|
|
2874
2973
|
"Failed to build home-feed event for background conversation",
|
|
2875
2974
|
);
|
|
2876
2975
|
}
|
|
2976
|
+
|
|
2977
|
+
// Proactive artifact: fire once when the processed turn was the 4th user message.
|
|
2978
|
+
// Only trigger for real user-authored turns (not subagent/system messages).
|
|
2979
|
+
{
|
|
2980
|
+
const paConv = getConversation(ctx.conversationId);
|
|
2981
|
+
if (
|
|
2982
|
+
paConv &&
|
|
2983
|
+
paConv.conversationType === "standard" &&
|
|
2984
|
+
options?.isUserMessage
|
|
2985
|
+
) {
|
|
2986
|
+
void (async () => {
|
|
2987
|
+
try {
|
|
2988
|
+
if (hasProactiveArtifactCompleted()) return;
|
|
2989
|
+
const userMsg = getMessageById(
|
|
2990
|
+
userMessageId,
|
|
2991
|
+
ctx.conversationId,
|
|
2992
|
+
);
|
|
2993
|
+
if (!userMsg) return;
|
|
2994
|
+
if (!tryClaimProactiveArtifactTrigger(userMsg.createdAt))
|
|
2995
|
+
return;
|
|
2996
|
+
await runProactiveArtifactJob({
|
|
2997
|
+
conversationId: ctx.conversationId,
|
|
2998
|
+
userMessageCutoff: userMsg.createdAt,
|
|
2999
|
+
assistantMessageId: state.lastAssistantMessageId,
|
|
3000
|
+
suppressAppBuild: state.appBuildToolUsedThisRun,
|
|
3001
|
+
broadcastMessage,
|
|
3002
|
+
});
|
|
3003
|
+
} catch (err) {
|
|
3004
|
+
log.warn(
|
|
3005
|
+
{ err, conversationId: ctx.conversationId },
|
|
3006
|
+
"Proactive artifact trigger failed",
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
})();
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
2877
3012
|
}
|
|
2878
3013
|
}
|
|
2879
3014
|
|
|
@@ -2937,6 +3072,7 @@ export async function runAgentLoopImpl(
|
|
|
2937
3072
|
conversationId: ctx.conversationId,
|
|
2938
3073
|
code: classified.code,
|
|
2939
3074
|
message: classified.userMessage,
|
|
3075
|
+
errorCategory: classified.errorCategory,
|
|
2940
3076
|
});
|
|
2941
3077
|
onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
|
|
2942
3078
|
}
|
|
@@ -2988,6 +3124,7 @@ export async function runAgentLoopImpl(
|
|
|
2988
3124
|
ctx.currentRequestId = undefined;
|
|
2989
3125
|
ctx.currentActiveSurfaceId = undefined;
|
|
2990
3126
|
ctx.allowedToolNames = undefined;
|
|
3127
|
+
ctx.diskPressureCleanupModeActive = false;
|
|
2991
3128
|
ctx.preactivatedSkillIds = undefined;
|
|
2992
3129
|
ctx.currentTurnOverrideProfile = undefined;
|
|
2993
3130
|
ctx.slackRuntimeContextNotice = undefined;
|