@vellumai/assistant 0.6.0 → 0.6.1
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/openapi.yaml +538 -3
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/checker.test.ts +38 -6
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +72 -105
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +62 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/agent/loop.ts +6 -0
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/cli/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/connect.ts +14 -5
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
- package/src/config/bundled-skills/gmail/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +158 -65
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +300 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +18 -0
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +63 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +199 -157
- package/src/daemon/message-types/conversations.ts +25 -6
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +89 -9
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +136 -107
- package/src/memory/conversation-group-migration.ts +1 -1
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -15
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/permissions/checker.ts +12 -1
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -0
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
package/src/daemon/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createAssistantMessage,
|
|
12
12
|
createUserMessage,
|
|
13
13
|
} from "../agent/message-types.js";
|
|
14
|
+
import { compileApp } from "../bundler/app-compiler.js";
|
|
14
15
|
import {
|
|
15
16
|
type ChannelId,
|
|
16
17
|
type InterfaceId,
|
|
@@ -22,6 +23,7 @@ import { onContactChange } from "../contacts/contact-events.js";
|
|
|
22
23
|
import type { CesClient } from "../credential-execution/client.js";
|
|
23
24
|
import type { CesProcessManager } from "../credential-execution/process-manager.js";
|
|
24
25
|
import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
26
|
+
import { getApp, getAppDirPath, isMultifileApp } from "../memory/app-store.js";
|
|
25
27
|
import * as attachmentsStore from "../memory/attachments-store.js";
|
|
26
28
|
import {
|
|
27
29
|
createCanonicalGuardianRequest,
|
|
@@ -49,6 +51,7 @@ import { bridgeConfirmationRequestToGuardian } from "../runtime/confirmation-req
|
|
|
49
51
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
50
52
|
import { checkIngressForSecrets } from "../security/secret-ingress.js";
|
|
51
53
|
import { redactSecrets } from "../security/secret-scanner.js";
|
|
54
|
+
import { updatePublishedAppDeployment } from "../services/published-app-updater.js";
|
|
52
55
|
import { registerCancelCallback } from "../signals/cancel.js";
|
|
53
56
|
import { registerConversationUndoCallback } from "../signals/conversation-undo.js";
|
|
54
57
|
import { appendEventToStream } from "../signals/event-stream.js";
|
|
@@ -57,10 +60,12 @@ import { getSubagentManager } from "../subagent/index.js";
|
|
|
57
60
|
import { summarizeToolInput } from "../tools/tool-input-summary.js";
|
|
58
61
|
import { getLogger } from "../util/logger.js";
|
|
59
62
|
import {
|
|
63
|
+
getAvatarImagePath,
|
|
60
64
|
getSandboxWorkingDir,
|
|
61
65
|
getWorkspacePromptPath,
|
|
62
66
|
} from "../util/platform.js";
|
|
63
67
|
import { registerDaemonCallbacks } from "../work-items/work-item-runner.js";
|
|
68
|
+
import { AppSourceWatcher } from "./app-source-watcher.js";
|
|
64
69
|
import { ConfigWatcher } from "./config-watcher.js";
|
|
65
70
|
import {
|
|
66
71
|
Conversation,
|
|
@@ -71,6 +76,7 @@ import { ConversationEvictor } from "./conversation-evictor.js";
|
|
|
71
76
|
import { formatCompactResult } from "./conversation-process.js";
|
|
72
77
|
import { resolveChannelCapabilities } from "./conversation-runtime-assembly.js";
|
|
73
78
|
import { resolveSlash, type SlashContext } from "./conversation-slash.js";
|
|
79
|
+
import { refreshSurfacesForApp } from "./conversation-surfaces.js";
|
|
74
80
|
import { undoLastMessage } from "./handlers/conversations.js";
|
|
75
81
|
import { parseIdentityFields } from "./handlers/identity.js";
|
|
76
82
|
import type {
|
|
@@ -201,13 +207,10 @@ function makePendingInteractionRegistrar(
|
|
|
201
207
|
guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
|
|
202
208
|
toolName: msg.toolName,
|
|
203
209
|
commandPreview:
|
|
204
|
-
redactSecrets(
|
|
205
|
-
|
|
206
|
-
) || undefined,
|
|
210
|
+
redactSecrets(summarizeToolInput(msg.toolName, inputRecord)) ||
|
|
211
|
+
undefined,
|
|
207
212
|
riskLevel: msg.riskLevel,
|
|
208
|
-
activityText: activityRaw
|
|
209
|
-
? redactSecrets(activityRaw)
|
|
210
|
-
: undefined,
|
|
213
|
+
activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
|
|
211
214
|
executionTarget: msg.executionTarget,
|
|
212
215
|
status: "pending",
|
|
213
216
|
requestCode: generateCanonicalRequestCode(),
|
|
@@ -271,6 +274,7 @@ export class DaemonServer {
|
|
|
271
274
|
|
|
272
275
|
// Composed subsystems
|
|
273
276
|
private configWatcher = new ConfigWatcher();
|
|
277
|
+
private appSourceWatcher = new AppSourceWatcher();
|
|
274
278
|
|
|
275
279
|
// CES (Credential Execution Service) — process-level singleton.
|
|
276
280
|
// Lifecycle is managed by startCesProcess() in lifecycle.ts; the server
|
|
@@ -369,7 +373,28 @@ export class DaemonServer {
|
|
|
369
373
|
{ channelId: transport.channelId },
|
|
370
374
|
"Transport metadata received",
|
|
371
375
|
);
|
|
372
|
-
|
|
376
|
+
|
|
377
|
+
// Build enriched hints: interface ID first, then host environment (macOS
|
|
378
|
+
// only), then any client-provided hints.
|
|
379
|
+
const enrichedHints: string[] = [];
|
|
380
|
+
|
|
381
|
+
const interfaceLabel = parseInterfaceId(transport.interfaceId) ?? "vellum";
|
|
382
|
+
enrichedHints.push(`User is messaging from interface: ${interfaceLabel}`);
|
|
383
|
+
|
|
384
|
+
if (transport.interfaceId === "macos") {
|
|
385
|
+
if (transport.hostHomeDir) {
|
|
386
|
+
enrichedHints.push(`Host home directory: ${transport.hostHomeDir}`);
|
|
387
|
+
}
|
|
388
|
+
if (transport.hostUsername) {
|
|
389
|
+
enrichedHints.push(`Host username: ${transport.hostUsername}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (transport.hints) {
|
|
394
|
+
enrichedHints.push(...transport.hints);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
conversation.setTransportHints(enrichedHints);
|
|
373
398
|
}
|
|
374
399
|
|
|
375
400
|
constructor() {
|
|
@@ -523,6 +548,55 @@ export class DaemonServer {
|
|
|
523
548
|
}
|
|
524
549
|
}
|
|
525
550
|
|
|
551
|
+
private broadcastSoundsConfigUpdated(): void {
|
|
552
|
+
this.broadcast({ type: "sounds_config_updated" });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private broadcastAvatarUpdated(): void {
|
|
556
|
+
this.broadcast({
|
|
557
|
+
type: "avatar_updated",
|
|
558
|
+
avatarPath: getAvatarImagePath(),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Handle a detected app source file change from the filesystem watcher.
|
|
564
|
+
* Recompiles multifile apps and refreshes surfaces across ALL conversations.
|
|
565
|
+
*/
|
|
566
|
+
private handleAppSourceChange(appId: string): void {
|
|
567
|
+
const app = getApp(appId);
|
|
568
|
+
if (!app) return;
|
|
569
|
+
|
|
570
|
+
const doRefresh = () => {
|
|
571
|
+
for (const conversation of this.conversations.values()) {
|
|
572
|
+
refreshSurfacesForApp(conversation, appId, { fileChange: true });
|
|
573
|
+
}
|
|
574
|
+
this.broadcast({ type: "app_files_changed", appId });
|
|
575
|
+
void updatePublishedAppDeployment(appId);
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
if (isMultifileApp(app)) {
|
|
579
|
+
const appDir = getAppDirPath(appId);
|
|
580
|
+
void compileApp(appDir)
|
|
581
|
+
.then((result) => {
|
|
582
|
+
if (!result.ok) {
|
|
583
|
+
log.warn(
|
|
584
|
+
{ appId, errors: result.errors },
|
|
585
|
+
"Recompile failed on app source change",
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
doRefresh();
|
|
589
|
+
})
|
|
590
|
+
.catch((err) => {
|
|
591
|
+
log.warn({ appId, err }, "Recompile threw on app source change");
|
|
592
|
+
doRefresh();
|
|
593
|
+
});
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
doRefresh();
|
|
598
|
+
}
|
|
599
|
+
|
|
526
600
|
// ── Server lifecycle ────────────────────────────────────────────────
|
|
527
601
|
|
|
528
602
|
async start(): Promise<void> {
|
|
@@ -672,8 +746,12 @@ export class DaemonServer {
|
|
|
672
746
|
this.configWatcher.start(
|
|
673
747
|
() => this.evictConversationsForReload(),
|
|
674
748
|
() => this.broadcastIdentityChanged(),
|
|
749
|
+
() => this.broadcastSoundsConfigUpdated(),
|
|
750
|
+
() => this.broadcastAvatarUpdated(),
|
|
675
751
|
);
|
|
676
752
|
|
|
753
|
+
this.appSourceWatcher.start((appId) => this.handleAppSourceChange(appId));
|
|
754
|
+
|
|
677
755
|
// Broadcast contacts_changed to all clients when any contact mutation occurs.
|
|
678
756
|
this.unsubscribeContactChange = onContactChange(() => {
|
|
679
757
|
this.broadcast({ type: "contacts_changed" });
|
|
@@ -687,6 +765,7 @@ export class DaemonServer {
|
|
|
687
765
|
disposeAcpSessionManager();
|
|
688
766
|
this.evictor.stop();
|
|
689
767
|
this.configWatcher.stop();
|
|
768
|
+
this.appSourceWatcher.stop();
|
|
690
769
|
if (this.unsubscribeContactChange) {
|
|
691
770
|
this.unsubscribeContactChange();
|
|
692
771
|
this.unsubscribeContactChange = null;
|
|
@@ -1012,7 +1091,7 @@ export class DaemonServer {
|
|
|
1012
1091
|
// Guard: don't replace an active proxy during concurrent turn races —
|
|
1013
1092
|
// another request may have started processing between the isProcessing()
|
|
1014
1093
|
// check above and the await on ensureActorScopedHistory().
|
|
1015
|
-
if (resolvedInterface === "macos"
|
|
1094
|
+
if (resolvedInterface === "macos") {
|
|
1016
1095
|
if (!conversation.isProcessing() || !conversation.hostBashProxy) {
|
|
1017
1096
|
conversation.setHostBashProxy(
|
|
1018
1097
|
new HostBashProxy(conversation.getCurrentSender(), (requestId) => {
|
|
@@ -1369,8 +1448,9 @@ export class DaemonServer {
|
|
|
1369
1448
|
*/
|
|
1370
1449
|
async getConversationForMessages(
|
|
1371
1450
|
conversationId: string,
|
|
1451
|
+
options?: ConversationCreateOptions,
|
|
1372
1452
|
): Promise<Conversation> {
|
|
1373
|
-
return this.getOrCreateConversation(conversationId);
|
|
1453
|
+
return this.getOrCreateConversation(conversationId, options);
|
|
1374
1454
|
}
|
|
1375
1455
|
|
|
1376
1456
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node";
|
|
2
2
|
|
|
3
|
+
import type { FilingService } from "../filing/filing-service.js";
|
|
3
4
|
import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
4
5
|
import type { HookManager } from "../hooks/manager.js";
|
|
5
6
|
import type { McpServerManager } from "../mcp/manager.js";
|
|
@@ -7,6 +8,7 @@ import { getSqlite, resetDb } from "../memory/db.js";
|
|
|
7
8
|
import type { QdrantManager } from "../memory/qdrant-manager.js";
|
|
8
9
|
import type { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
9
10
|
import { browserManager } from "../tools/browser/browser-manager.js";
|
|
11
|
+
import { cleanupShellOutputTempFiles } from "../tools/shared/shell-output.js";
|
|
10
12
|
import { getLogger } from "../util/logger.js";
|
|
11
13
|
import { getEnrichmentService } from "../workspace/commit-message-enrichment-service.js";
|
|
12
14
|
import type { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
|
|
@@ -18,6 +20,7 @@ export interface ShutdownDeps {
|
|
|
18
20
|
server: DaemonServer;
|
|
19
21
|
workspaceHeartbeat: WorkspaceHeartbeatService;
|
|
20
22
|
heartbeat: HeartbeatService;
|
|
23
|
+
filing: FilingService;
|
|
21
24
|
hookManager: HookManager;
|
|
22
25
|
runtimeHttp: RuntimeHttpServer | null;
|
|
23
26
|
scheduler: { stop(): void };
|
|
@@ -51,6 +54,7 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
|
|
|
51
54
|
|
|
52
55
|
await deps.workspaceHeartbeat.stop();
|
|
53
56
|
await deps.heartbeat.stop();
|
|
57
|
+
await deps.filing.stop();
|
|
54
58
|
|
|
55
59
|
try {
|
|
56
60
|
await deps.hookManager.trigger("daemon-stop", { pid: process.pid });
|
|
@@ -105,6 +109,7 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
|
|
|
105
109
|
|
|
106
110
|
if (deps.runtimeHttp) await deps.runtimeHttp.stop();
|
|
107
111
|
await browserManager.closeAllPages();
|
|
112
|
+
cleanupShellOutputTempFiles();
|
|
108
113
|
deps.scheduler.stop();
|
|
109
114
|
deps.getMemoryWorker()?.stop();
|
|
110
115
|
|
|
@@ -158,12 +158,32 @@ registerHook(
|
|
|
158
158
|
},
|
|
159
159
|
);
|
|
160
160
|
|
|
161
|
-
// Trigger
|
|
161
|
+
// Trigger surface refresh + broadcast when an app is refreshed.
|
|
162
|
+
// If the executor already compiled (multifile path), skip the redundant
|
|
163
|
+
// recompile and just refresh surfaces / broadcast / deploy directly.
|
|
162
164
|
registerHook(
|
|
163
165
|
"app_refresh",
|
|
164
|
-
(_name, input,
|
|
166
|
+
(_name, input, result, { ctx, broadcastToAllClients }) => {
|
|
165
167
|
const appId = input.app_id as string | undefined;
|
|
166
|
-
if (appId)
|
|
168
|
+
if (!appId) return;
|
|
169
|
+
|
|
170
|
+
// executeAppRefresh already compiled multifile apps and included a
|
|
171
|
+
// "compiled" field in the result. Skip the expensive recompile and
|
|
172
|
+
// go straight to surface refresh + broadcast + deploy.
|
|
173
|
+
let alreadyCompiled = false;
|
|
174
|
+
try {
|
|
175
|
+
const parsed = JSON.parse(result.content) as { compiled?: boolean };
|
|
176
|
+
alreadyCompiled = parsed.compiled !== undefined;
|
|
177
|
+
} catch {
|
|
178
|
+
// Result wasn't valid JSON — fall through to handleAppChange.
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (alreadyCompiled) {
|
|
182
|
+
const opts = { fileChange: true };
|
|
183
|
+
refreshSurfacesForApp(ctx, appId, opts);
|
|
184
|
+
broadcastToAllClients?.({ type: "app_files_changed", appId });
|
|
185
|
+
void updatePublishedAppDeployment(appId);
|
|
186
|
+
} else {
|
|
167
187
|
handleAppChange(ctx, appId, broadcastToAllClients, { fileChange: true });
|
|
168
188
|
}
|
|
169
189
|
},
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript formatter for conversation analysis.
|
|
3
|
+
*
|
|
4
|
+
* Builds a markdown transcript of a conversation, including inline
|
|
5
|
+
* subagent conversation sections when present in message metadata.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getConversation,
|
|
10
|
+
getMessages,
|
|
11
|
+
messageMetadataSchema,
|
|
12
|
+
} from "../memory/conversation-crud.js";
|
|
13
|
+
import { truncate } from "../util/truncate.js";
|
|
14
|
+
|
|
15
|
+
interface ContentBlock {
|
|
16
|
+
type: string;
|
|
17
|
+
text?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
input?: Record<string, unknown>;
|
|
20
|
+
content?: string;
|
|
21
|
+
tool_use_id?: string;
|
|
22
|
+
is_error?: boolean;
|
|
23
|
+
source?: { media_type?: string; filename?: string };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatTimestamp(ms: number): string {
|
|
27
|
+
return new Date(ms).toISOString().replace("T", " ").slice(0, 19);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractAnalysisText(blocks: ContentBlock[]): string {
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
for (const block of blocks) {
|
|
33
|
+
switch (block.type) {
|
|
34
|
+
case "text":
|
|
35
|
+
if (block.text) parts.push(block.text);
|
|
36
|
+
break;
|
|
37
|
+
case "tool_use":
|
|
38
|
+
parts.push(
|
|
39
|
+
`[Tool: ${block.name}] ${JSON.stringify(block.input ?? {})}`,
|
|
40
|
+
);
|
|
41
|
+
break;
|
|
42
|
+
case "tool_result":
|
|
43
|
+
if (block.is_error) {
|
|
44
|
+
parts.push(`[Error: ${block.content ?? ""}]`);
|
|
45
|
+
} else {
|
|
46
|
+
parts.push(`[Result: ${truncate(block.content ?? "", 500)}]`);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "server_tool_use":
|
|
50
|
+
parts.push(`[Web search: ${block.name ?? "web_search"}]`);
|
|
51
|
+
break;
|
|
52
|
+
case "web_search_tool_result":
|
|
53
|
+
parts.push("[Web search results]");
|
|
54
|
+
break;
|
|
55
|
+
case "image":
|
|
56
|
+
parts.push("[Image attachment]");
|
|
57
|
+
break;
|
|
58
|
+
case "file":
|
|
59
|
+
parts.push(`[File: ${block.source?.filename ?? "unknown"}]`);
|
|
60
|
+
break;
|
|
61
|
+
case "thinking":
|
|
62
|
+
case "redacted_thinking":
|
|
63
|
+
// Skip internal model reasoning blocks
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return parts.join("\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatRole(role: string): string {
|
|
71
|
+
return role === "user" ? "User" : "Assistant";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatSubagentMessages(msgs: ReturnType<typeof getMessages>): string {
|
|
75
|
+
const lines: string[] = [];
|
|
76
|
+
for (const msg of msgs) {
|
|
77
|
+
const role = formatRole(msg.role);
|
|
78
|
+
const time = formatTimestamp(msg.createdAt);
|
|
79
|
+
const content = parseContent(msg.content);
|
|
80
|
+
const text = extractAnalysisText(content);
|
|
81
|
+
if (text) {
|
|
82
|
+
lines.push(`> **${role}** (${time})`);
|
|
83
|
+
for (const line of text.split("\n")) {
|
|
84
|
+
lines.push(`> ${line}`);
|
|
85
|
+
}
|
|
86
|
+
lines.push(">");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseContent(raw: string): ContentBlock[] {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(raw) as ContentBlock[];
|
|
95
|
+
} catch {
|
|
96
|
+
return [{ type: "text", text: raw }];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function buildAnalysisTranscript(conversationId: string): string {
|
|
101
|
+
const conversation = getConversation(conversationId);
|
|
102
|
+
if (!conversation) {
|
|
103
|
+
return `# Conversation not found: ${conversationId}\n`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const allMessages = getMessages(conversationId);
|
|
107
|
+
const title = conversation.title ?? "Untitled";
|
|
108
|
+
const lines: string[] = [];
|
|
109
|
+
|
|
110
|
+
lines.push(`# Conversation: ${title}`);
|
|
111
|
+
lines.push(`Created: ${formatTimestamp(conversation.createdAt)}`);
|
|
112
|
+
lines.push("");
|
|
113
|
+
|
|
114
|
+
for (const msg of allMessages) {
|
|
115
|
+
const role = formatRole(msg.role);
|
|
116
|
+
const time = formatTimestamp(msg.createdAt);
|
|
117
|
+
const content = parseContent(msg.content);
|
|
118
|
+
const text = extractAnalysisText(content);
|
|
119
|
+
|
|
120
|
+
lines.push(`## ${role} (${time})`);
|
|
121
|
+
lines.push(text);
|
|
122
|
+
lines.push("");
|
|
123
|
+
|
|
124
|
+
// Check for subagent notifications in metadata
|
|
125
|
+
if (msg.metadata) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = messageMetadataSchema.safeParse(JSON.parse(msg.metadata));
|
|
128
|
+
if (parsed.success && parsed.data.subagentNotification) {
|
|
129
|
+
const notif = parsed.data.subagentNotification;
|
|
130
|
+
if (
|
|
131
|
+
(notif.status === "completed" || notif.status === "failed" || notif.status === "aborted") &&
|
|
132
|
+
notif.conversationId
|
|
133
|
+
) {
|
|
134
|
+
const subMessages = getMessages(notif.conversationId);
|
|
135
|
+
lines.push(`### Subagent: ${notif.label} (${notif.status})`);
|
|
136
|
+
lines.push("");
|
|
137
|
+
lines.push(formatSubagentMessages(subMessages));
|
|
138
|
+
lines.push("");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Skip unparseable metadata
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getConfig } from "../config/loader.js";
|
|
5
|
+
import type { Speed } from "../config/schemas/inference.js";
|
|
6
|
+
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
7
|
+
import { getLogger } from "../util/logger.js";
|
|
8
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
9
|
+
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
10
|
+
|
|
11
|
+
const log = getLogger("filing-service");
|
|
12
|
+
|
|
13
|
+
const FILING_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
|
|
14
|
+
|
|
15
|
+
const FILING_PROMPT_TEMPLATE = `You are running a periodic knowledge base filing job. This is a background maintenance task.
|
|
16
|
+
|
|
17
|
+
## Part 1: File the buffer
|
|
18
|
+
|
|
19
|
+
Read \`pkb/buffer.md\`. For each item in the buffer:
|
|
20
|
+
1. Determine which topic file it belongs in. Check \`pkb/INDEX.md\` to see what topic files exist.
|
|
21
|
+
2. Read the target topic file, then append or integrate the new fact.
|
|
22
|
+
3. If the fact is important enough to always be in context, add it to \`pkb/essentials.md\` instead.
|
|
23
|
+
4. If the fact is a commitment, follow-up, or active project, add it to \`pkb/threads.md\`.
|
|
24
|
+
5. If no existing topic file fits, create a new one and update \`pkb/INDEX.md\`.
|
|
25
|
+
|
|
26
|
+
After all items are filed, clear the processed items from \`pkb/buffer.md\` (leave the file empty, don't delete it).
|
|
27
|
+
|
|
28
|
+
## Part 2: Nest
|
|
29
|
+
|
|
30
|
+
Pick 1-2 topic files from your knowledge base and review them:
|
|
31
|
+
- Is the information still accurate and up to date?
|
|
32
|
+
- Are there duplicates that should be consolidated?
|
|
33
|
+
- Is anything important enough to promote to \`pkb/essentials.md\`?
|
|
34
|
+
- Is anything in \`pkb/essentials.md\` that's no longer essential? Demote it to a topic file.
|
|
35
|
+
- Are any threads in \`pkb/threads.md\` completed or stale? Remove them.
|
|
36
|
+
- Is any file getting too long? Consider splitting it.
|
|
37
|
+
- Should any topic file be restructured for clarity?
|
|
38
|
+
|
|
39
|
+
Make improvements as you see fit. This is your knowledge base — keep it sharp.`;
|
|
40
|
+
|
|
41
|
+
export interface FilingDeps {
|
|
42
|
+
processMessage: (
|
|
43
|
+
conversationId: string,
|
|
44
|
+
content: string,
|
|
45
|
+
options?: { speed?: Speed },
|
|
46
|
+
) => Promise<{ messageId: string }>;
|
|
47
|
+
onConversationCreated?: (info: {
|
|
48
|
+
conversationId: string;
|
|
49
|
+
title: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
getCurrentHour?: () => number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class FilingService {
|
|
55
|
+
private readonly deps: FilingDeps;
|
|
56
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
57
|
+
private activeRun: Promise<void> | null = null;
|
|
58
|
+
private _lastRunAt: number | null = null;
|
|
59
|
+
private _nextRunAt: number | null = null;
|
|
60
|
+
|
|
61
|
+
constructor(deps: FilingDeps) {
|
|
62
|
+
this.deps = deps;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get lastRunAt(): number | null {
|
|
66
|
+
return this._lastRunAt;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get nextRunAt(): number | null {
|
|
70
|
+
return this._nextRunAt;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
start(): void {
|
|
74
|
+
const config = getConfig().filing;
|
|
75
|
+
if (!config.enabled) {
|
|
76
|
+
log.info("Filing service disabled by config");
|
|
77
|
+
this._nextRunAt = null;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (this.timer) return;
|
|
81
|
+
|
|
82
|
+
log.info({ intervalMs: config.intervalMs }, "Filing service started");
|
|
83
|
+
this.scheduleNextRun(config.intervalMs);
|
|
84
|
+
this.timer = setInterval(() => {
|
|
85
|
+
this.runOnce().catch((err) => {
|
|
86
|
+
log.error({ err }, "Filing runOnce failed");
|
|
87
|
+
});
|
|
88
|
+
}, config.intervalMs);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
reconfigure(): void {
|
|
92
|
+
if (this.timer) {
|
|
93
|
+
clearInterval(this.timer);
|
|
94
|
+
this.timer = null;
|
|
95
|
+
}
|
|
96
|
+
this._nextRunAt = null;
|
|
97
|
+
this.start();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async stop(): Promise<void> {
|
|
101
|
+
if (this.timer) {
|
|
102
|
+
clearInterval(this.timer);
|
|
103
|
+
this.timer = null;
|
|
104
|
+
}
|
|
105
|
+
this._nextRunAt = null;
|
|
106
|
+
if (this.activeRun) {
|
|
107
|
+
let timerId: ReturnType<typeof setTimeout>;
|
|
108
|
+
const timeout = new Promise<void>((resolve) => {
|
|
109
|
+
timerId = setTimeout(resolve, 5_000);
|
|
110
|
+
});
|
|
111
|
+
await Promise.race([this.activeRun, timeout]);
|
|
112
|
+
clearTimeout(timerId!);
|
|
113
|
+
}
|
|
114
|
+
log.info("Filing service stopped");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async runOnce({ force = false }: { force?: boolean } = {}): Promise<boolean> {
|
|
118
|
+
const config = getConfig().filing;
|
|
119
|
+
if (!force && !config.enabled) return false;
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
!force &&
|
|
123
|
+
config.activeHoursStart != null &&
|
|
124
|
+
config.activeHoursEnd != null
|
|
125
|
+
) {
|
|
126
|
+
const hour = this.deps.getCurrentHour?.() ?? new Date().getHours();
|
|
127
|
+
if (
|
|
128
|
+
!isWithinActiveHours(
|
|
129
|
+
hour,
|
|
130
|
+
config.activeHoursStart,
|
|
131
|
+
config.activeHoursEnd,
|
|
132
|
+
)
|
|
133
|
+
) {
|
|
134
|
+
log.debug("Outside active hours, skipping filing");
|
|
135
|
+
this.scheduleNextRun(config.intervalMs);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.activeRun) {
|
|
141
|
+
log.debug("Previous filing run still active, skipping");
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Skip if buffer is empty — no work to do
|
|
146
|
+
if (!force && !this.hasBufferContent()) {
|
|
147
|
+
log.debug("Buffer is empty, skipping filing");
|
|
148
|
+
this.scheduleNextRun(config.intervalMs);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const run = this.executeRun();
|
|
153
|
+
this.activeRun = run;
|
|
154
|
+
try {
|
|
155
|
+
await Promise.race([
|
|
156
|
+
run,
|
|
157
|
+
new Promise<never>((_, reject) =>
|
|
158
|
+
setTimeout(
|
|
159
|
+
() => reject(new Error("Filing execution timed out")),
|
|
160
|
+
FILING_TIMEOUT_MS,
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
]);
|
|
164
|
+
} finally {
|
|
165
|
+
this.activeRun = null;
|
|
166
|
+
this._lastRunAt = Date.now();
|
|
167
|
+
this.scheduleNextRun(getConfig().filing.intervalMs);
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private scheduleNextRun(intervalMs: number): void {
|
|
173
|
+
this._nextRunAt = Date.now() + intervalMs;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private hasBufferContent(): boolean {
|
|
177
|
+
const bufferPath = join(getWorkspaceDir(), "pkb", "buffer.md");
|
|
178
|
+
if (!existsSync(bufferPath)) return false;
|
|
179
|
+
try {
|
|
180
|
+
const content = stripCommentLines(
|
|
181
|
+
readFileSync(bufferPath, "utf-8"),
|
|
182
|
+
).trim();
|
|
183
|
+
return content.length > 0;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async executeRun(): Promise<void> {
|
|
190
|
+
log.info("Running filing job");
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const config = getConfig().filing;
|
|
194
|
+
|
|
195
|
+
const conversation = bootstrapConversation({
|
|
196
|
+
conversationType: "background",
|
|
197
|
+
source: "filing",
|
|
198
|
+
groupId: "system:background",
|
|
199
|
+
origin: "filing",
|
|
200
|
+
systemHint: "Filing",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.deps.onConversationCreated?.({
|
|
204
|
+
conversationId: conversation.id,
|
|
205
|
+
title: "Filing",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await this.deps.processMessage(conversation.id, FILING_PROMPT_TEMPLATE, {
|
|
209
|
+
speed: config.speed,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
log.info({ conversationId: conversation.id }, "Filing job completed");
|
|
213
|
+
} catch (err) {
|
|
214
|
+
log.error({ err }, "Filing job failed");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isWithinActiveHours(
|
|
220
|
+
hour: number,
|
|
221
|
+
start: number,
|
|
222
|
+
end: number,
|
|
223
|
+
): boolean {
|
|
224
|
+
if (start <= end) {
|
|
225
|
+
return hour >= start && hour < end;
|
|
226
|
+
}
|
|
227
|
+
return hour >= start || hour < end;
|
|
228
|
+
}
|