@vellumai/assistant 0.8.2 → 0.8.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 +11 -12
- package/docker-entrypoint.sh +13 -1
- package/docker-init-apt-root.sh +79 -6
- package/openapi.yaml +336 -21
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/context-token-estimator.test.ts +30 -65
- package/src/__tests__/conversation-agent-loop.test.ts +57 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-runtime-assembly.test.ts +26 -4
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +5 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +218 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +6 -73
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/loop.ts +167 -18
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/notifications.ts +65 -35
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +21 -29
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +1 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +72 -12
- package/src/context/token-estimator.ts +32 -34
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +29 -2
- package/src/daemon/conversation-runtime-assembly.ts +9 -0
- package/src/daemon/conversation.ts +0 -7
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/conversations.ts +1 -0
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +49 -61
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/process-message.ts +3 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -0
- package/src/heartbeat/heartbeat-service.ts +34 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +14 -2
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +190 -3
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +49 -20
- package/src/memory/v2/page-index.ts +38 -13
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/home-feed-side-effect.ts +85 -6
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/plugins/defaults/injectors.ts +38 -19
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +6 -51
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/system-prompt.ts +0 -8
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/system-sections.ts +0 -9
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +9 -20
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +2 -0
- package/src/providers/model-catalog.ts +199 -244
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +159 -6
- package/src/providers/openrouter/client.ts +42 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/runtime/agent-wake.ts +61 -1
- package/src/runtime/auth/route-policy.ts +13 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +0 -47
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +66 -4
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/conversation-query-routes.ts +70 -11
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +134 -1
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/subagent/manager.ts +2 -0
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/registry.ts +2 -2
- package/src/tools/types.ts +37 -2
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { startMemoryJobsWorker } from "../memory/jobs-worker.js";
|
|
|
49
49
|
import { initQdrantClient, resolveQdrantUrl } from "../memory/qdrant-client.js";
|
|
50
50
|
import { QdrantManager } from "../memory/qdrant-manager.js";
|
|
51
51
|
import { rotateToolInvocations } from "../memory/tool-usage-store.js";
|
|
52
|
+
import { sweepConceptPageFrontmatter } from "../memory/v2/frontmatter-sweep.js";
|
|
52
53
|
import {
|
|
53
54
|
emitNotificationSignal,
|
|
54
55
|
registerBroadcastFn,
|
|
@@ -96,10 +97,6 @@ import {
|
|
|
96
97
|
import { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
|
|
97
98
|
import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
|
|
98
99
|
import { runWorkspaceMigrations } from "../workspace/migrations/runner.js";
|
|
99
|
-
import {
|
|
100
|
-
createApprovalConversationGenerator,
|
|
101
|
-
createApprovalCopyGenerator,
|
|
102
|
-
} from "./approval-generators.js";
|
|
103
100
|
import {
|
|
104
101
|
cleanupPidFile,
|
|
105
102
|
cleanupPidFileIfOwner,
|
|
@@ -111,10 +108,6 @@ import {
|
|
|
111
108
|
stopDiskPressureGuard,
|
|
112
109
|
} from "./disk-pressure-guard.js";
|
|
113
110
|
import { bootstrapPlugins } from "./external-plugins-bootstrap.js";
|
|
114
|
-
import {
|
|
115
|
-
createGuardianActionCopyGenerator,
|
|
116
|
-
createGuardianFollowUpConversationGenerator,
|
|
117
|
-
} from "./guardian-action-generators.js";
|
|
118
111
|
import { backfillSlackInjectionTemplates } from "./handlers/config-slack-channel.js";
|
|
119
112
|
import { installAssistantSymlink } from "./install-symlink.js";
|
|
120
113
|
import {
|
|
@@ -322,6 +315,35 @@ export async function runDaemon(): Promise<void> {
|
|
|
322
315
|
const signingKey = resolveSigningKey();
|
|
323
316
|
initAuthSigningKey(signingKey);
|
|
324
317
|
|
|
318
|
+
// Start the runtime HTTP server early so /healthz answers ASAP.
|
|
319
|
+
let runtimeHttp: RuntimeHttpServer | null = null;
|
|
320
|
+
const httpPort = getRuntimeHttpPort();
|
|
321
|
+
const httpHostname = getRuntimeHttpHost();
|
|
322
|
+
log.info({ httpPort }, "Daemon startup: starting runtime HTTP server");
|
|
323
|
+
|
|
324
|
+
runtimeHttp = new RuntimeHttpServer({
|
|
325
|
+
port: httpPort,
|
|
326
|
+
hostname: httpHostname,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Isolated try/catch around start() — a bind failure (port in use,
|
|
330
|
+
// permission denied, fd exhaustion) must not tear down the rest of
|
|
331
|
+
// daemon startup. The daemon falls back to IPC-only operation when
|
|
332
|
+
// runtimeHttp is null.
|
|
333
|
+
try {
|
|
334
|
+
await runtimeHttp.start();
|
|
335
|
+
log.info(
|
|
336
|
+
{ port: httpPort, hostname: httpHostname },
|
|
337
|
+
"Daemon startup: runtime HTTP server listening",
|
|
338
|
+
);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
log.warn(
|
|
341
|
+
{ err, port: httpPort },
|
|
342
|
+
"Failed to start runtime HTTP server, continuing without it",
|
|
343
|
+
);
|
|
344
|
+
runtimeHttp = null;
|
|
345
|
+
}
|
|
346
|
+
|
|
325
347
|
// Pre-populate feature flag overrides so subsequent sync
|
|
326
348
|
// isAssistantFeatureFlagEnabled() calls have data. Fired non-blocking
|
|
327
349
|
// so a slow or unreachable gateway doesn't delay daemon startup (the
|
|
@@ -861,34 +883,16 @@ export async function runDaemon(): Promise<void> {
|
|
|
861
883
|
})();
|
|
862
884
|
}
|
|
863
885
|
|
|
864
|
-
// Build the BM25 corpus stats (per-token document frequencies and
|
|
865
|
-
// average document length) used by the v2 sparse channel, then
|
|
866
|
-
// re-seed v2 skill entries so any skill vectors written during the
|
|
867
|
-
// cold-start window with the legacy TF encoder get rewritten with
|
|
868
|
-
// stemmed BM25 vectors. Fire-and-forget for the same reason as PKB
|
|
869
|
-
// reconcile — the stats and skill reseed are optional optimizations,
|
|
870
|
-
// never boot-blocking dependencies.
|
|
871
886
|
void rebuildBm25CorpusStatsAndReseedSkills(config);
|
|
872
887
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
try {
|
|
882
|
-
const { sweepConceptPageFrontmatter } =
|
|
883
|
-
await import("../memory/v2/frontmatter-sweep.js");
|
|
884
|
-
await sweepConceptPageFrontmatter(getWorkspaceDir());
|
|
885
|
-
} catch (err) {
|
|
886
|
-
log.warn(
|
|
887
|
-
{ err },
|
|
888
|
-
"Concept page frontmatter sweep threw — continuing startup",
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
})();
|
|
888
|
+
try {
|
|
889
|
+
await sweepConceptPageFrontmatter(config, getWorkspaceDir());
|
|
890
|
+
} catch (err) {
|
|
891
|
+
log.warn(
|
|
892
|
+
{ err },
|
|
893
|
+
"Concept page frontmatter sweep threw — continuing startup",
|
|
894
|
+
);
|
|
895
|
+
}
|
|
892
896
|
}
|
|
893
897
|
|
|
894
898
|
log.info("Daemon startup: starting memory worker");
|
|
@@ -1009,24 +1013,12 @@ export async function runDaemon(): Promise<void> {
|
|
|
1009
1013
|
},
|
|
1010
1014
|
);
|
|
1011
1015
|
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
const hostname = getRuntimeHttpHost();
|
|
1019
|
-
|
|
1020
|
-
runtimeHttp = new RuntimeHttpServer({
|
|
1021
|
-
port: httpPort,
|
|
1022
|
-
hostname,
|
|
1023
|
-
approvalCopyGenerator: createApprovalCopyGenerator(),
|
|
1024
|
-
approvalConversationGenerator: createApprovalConversationGenerator(),
|
|
1025
|
-
guardianActionCopyGenerator: createGuardianActionCopyGenerator(),
|
|
1026
|
-
guardianFollowUpConversationGenerator:
|
|
1027
|
-
createGuardianFollowUpConversationGenerator(),
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1016
|
+
// Wire up the runtime HTTP server's deferred dependencies. The server
|
|
1017
|
+
// itself was bound early in runDaemon (right after the auth signing key
|
|
1018
|
+
// was loaded) so /healthz and /readyz already answer 200 OK; these
|
|
1019
|
+
// registrations attach the live DaemonServer / CES / relay handlers to
|
|
1020
|
+
// the running routes. They're module-level state, so they're effective
|
|
1021
|
+
// even when the HTTP server failed to bind (IPC clients still work).
|
|
1030
1022
|
registerSecretsDeps({
|
|
1031
1023
|
getCesClient: () => server.getCesClient(),
|
|
1032
1024
|
onProviderCredentialsChanged: () =>
|
|
@@ -1043,8 +1035,9 @@ export async function runDaemon(): Promise<void> {
|
|
|
1043
1035
|
log.warn({ err }, "Background Qdrant init failed"),
|
|
1044
1036
|
);
|
|
1045
1037
|
|
|
1046
|
-
// Inject voice bridge deps
|
|
1047
|
-
//
|
|
1038
|
+
// Inject voice bridge deps so route handlers + the relay pipeline can
|
|
1039
|
+
// resolve a conversation by ID once a call lands. Module-level state,
|
|
1040
|
+
// so available even when the HTTP server failed to bind.
|
|
1048
1041
|
setVoiceBridgeDeps({
|
|
1049
1042
|
getOrCreateConversation: (conversationId, _transport) =>
|
|
1050
1043
|
server.getConversationForMessages(conversationId),
|
|
@@ -1063,7 +1056,6 @@ export async function runDaemon(): Promise<void> {
|
|
|
1063
1056
|
},
|
|
1064
1057
|
});
|
|
1065
1058
|
try {
|
|
1066
|
-
await runtimeHttp.start();
|
|
1067
1059
|
setRelayBroadcast((msg) => broadcastMessage(msg));
|
|
1068
1060
|
setPointerMessageProcessor(
|
|
1069
1061
|
async (conversationId, instruction, requiredFacts) => {
|
|
@@ -1201,14 +1193,10 @@ export async function runDaemon(): Promise<void> {
|
|
|
1201
1193
|
},
|
|
1202
1194
|
);
|
|
1203
1195
|
server.broadcastStatus();
|
|
1204
|
-
log.info(
|
|
1205
|
-
{ port: httpPort, hostname },
|
|
1206
|
-
"Daemon startup: runtime HTTP server listening",
|
|
1207
|
-
);
|
|
1208
1196
|
} catch (err) {
|
|
1209
1197
|
log.warn(
|
|
1210
|
-
{ err
|
|
1211
|
-
"Failed to
|
|
1198
|
+
{ err },
|
|
1199
|
+
"Failed to wire runtime HTTP server deps, continuing without them",
|
|
1212
1200
|
);
|
|
1213
1201
|
runtimeHttp = null;
|
|
1214
1202
|
}
|
|
@@ -35,6 +35,19 @@ export function maybeSeedMemoryV2Skills(config: AssistantConfig): void {
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Fire-and-forget seed of the v2 CLI-subcommand entries (indexed alongside
|
|
40
|
+
* concept pages and skills in `memory_v2_concept_pages` under the
|
|
41
|
+
* `cli-commands/<name>` slug prefix). Dynamic import keeps v2 code out of the
|
|
42
|
+
* startup graph when the gate is off. Never awaits — startup must not block.
|
|
43
|
+
*/
|
|
44
|
+
export function maybeSeedMemoryV2CliCommands(config: AssistantConfig): void {
|
|
45
|
+
if (!config.memory.v2.enabled) return;
|
|
46
|
+
void import("../memory/v2/cli-command-store.js")
|
|
47
|
+
.then(({ seedV2CliCommandEntries }) => seedV2CliCommandEntries())
|
|
48
|
+
.catch((err) => log.warn({ err }, "Failed to seed v2 CLI-command entries"));
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
/**
|
|
39
52
|
* Build the v2 BM25 corpus stats (per-token document frequencies + avg doc
|
|
40
53
|
* length), then re-seed the v2 skill entries so any skills written during
|
|
@@ -56,6 +69,8 @@ export function maybeSeedMemoryV2Skills(config: AssistantConfig): void {
|
|
|
56
69
|
export async function rebuildBm25CorpusStatsAndReseedSkills(
|
|
57
70
|
config: AssistantConfig,
|
|
58
71
|
): Promise<void> {
|
|
72
|
+
if (!config.memory.v2.enabled) return;
|
|
73
|
+
|
|
59
74
|
try {
|
|
60
75
|
const { rebuildConceptPageCorpusStats } =
|
|
61
76
|
await import("../memory/v2/sparse-bm25.js");
|
|
@@ -69,19 +84,40 @@ export async function rebuildBm25CorpusStatsAndReseedSkills(
|
|
|
69
84
|
return;
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
// Skills and CLI commands share the unified collection but are independent
|
|
88
|
+
// catalogs — reseed in parallel so the second one isn't gated on the first.
|
|
89
|
+
await Promise.all([
|
|
90
|
+
(async () => {
|
|
91
|
+
try {
|
|
92
|
+
const { seedV2SkillEntries } =
|
|
93
|
+
await import("../memory/v2/skill-store.js");
|
|
94
|
+
await seedV2SkillEntries({ throwOnError: true });
|
|
95
|
+
log.info(
|
|
96
|
+
"Memory v2 skill embeddings re-seeded with BM25 vectors after corpus-stats build",
|
|
97
|
+
);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
log.warn(
|
|
100
|
+
{ err },
|
|
101
|
+
"Failed to re-seed v2 skill entries after BM25 corpus-stats build — skills seeded during cold start may keep TF-only sparse vectors until next reseed",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
})(),
|
|
105
|
+
(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const { seedV2CliCommandEntries } =
|
|
108
|
+
await import("../memory/v2/cli-command-store.js");
|
|
109
|
+
await seedV2CliCommandEntries({ throwOnError: true });
|
|
110
|
+
log.info(
|
|
111
|
+
"Memory v2 CLI-command embeddings re-seeded with BM25 vectors after corpus-stats build",
|
|
112
|
+
);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
log.warn(
|
|
115
|
+
{ err },
|
|
116
|
+
"Failed to re-seed v2 CLI-command entries after BM25 corpus-stats build — entries seeded during cold start may keep TF-only sparse vectors until next reseed",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
})(),
|
|
120
|
+
]);
|
|
85
121
|
}
|
|
86
122
|
|
|
87
123
|
/**
|
|
@@ -14,6 +14,20 @@ export interface NotificationIntent {
|
|
|
14
14
|
* Clients not bound to this guardian should ignore the notification.
|
|
15
15
|
*/
|
|
16
16
|
targetGuardianPrincipalId?: string;
|
|
17
|
+
/**
|
|
18
|
+
* When true, the client must NOT post this intent to the OS notification
|
|
19
|
+
* surface (`UNUserNotificationCenter` on macOS). Non-banner side effects
|
|
20
|
+
* (guardian filtering, fallback dedup, mark-unseen + history catch-up on
|
|
21
|
+
* the paired conversation) still run. The home-feed inbox entry is
|
|
22
|
+
* written independently by `home-feed-side-effect.ts` and is unaffected
|
|
23
|
+
* by this flag.
|
|
24
|
+
*
|
|
25
|
+
* Set by the server based on `attentionHints.urgency`: true for
|
|
26
|
+
* `low`/`medium`, false for `high`/`critical`. The notification center
|
|
27
|
+
* is the always-on canonical inbox; the OS banner is reserved for
|
|
28
|
+
* signals the user opted into push for (urgency >= high).
|
|
29
|
+
*/
|
|
30
|
+
silent?: boolean;
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
/** Server push — broadcast when a notification creates a new vellum conversation. */
|
|
@@ -39,6 +53,13 @@ export interface NotificationConversationCreated {
|
|
|
39
53
|
* conversation is attributed correctly.
|
|
40
54
|
*/
|
|
41
55
|
source?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Mirrors `NotificationIntent.silent`. When true the client must not
|
|
58
|
+
* post a fallback OS banner for this conversation — the sidebar entry
|
|
59
|
+
* still appears, but the always-on inbox is the only surfaced channel.
|
|
60
|
+
* Derived from the originating signal's `attentionHints.urgency`.
|
|
61
|
+
*/
|
|
62
|
+
silent?: boolean;
|
|
42
63
|
}
|
|
43
64
|
|
|
44
65
|
/** Client ack sent after UNUserNotificationCenter.add() completes (or fails). */
|
|
@@ -2,32 +2,24 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { buildPkbReminder } from "./pkb-reminder-builder.js";
|
|
4
4
|
|
|
5
|
-
// Byte-for-byte fixture of the
|
|
6
|
-
//
|
|
7
|
-
const
|
|
8
|
-
"<system_reminder>" +
|
|
9
|
-
"\n**CRITICAL:** Call `remember` this turn for anything concrete the user said — facts, preferences, plans, names, dates, decisions, corrections, felt moments. Default to remembering; skip only obvious noise. This should be your most frequently used tool." +
|
|
10
|
-
"\nIf you're unsure about something that may live in the workspace — past decisions, prior conversations, files — use `recall` before asking or guessing." +
|
|
11
|
-
"\n</system_reminder>";
|
|
12
|
-
|
|
13
|
-
// Byte-for-byte fixture of the relaxed PKB reminder used when the
|
|
14
|
-
// `memory-retrospective` feature flag is on.
|
|
15
|
-
const BASE_REMINDER_RELAXED =
|
|
5
|
+
// Byte-for-byte fixture of the PKB reminder. Asserted verbatim so that any
|
|
6
|
+
// future edit to the BODY text is caught by tests.
|
|
7
|
+
const BASE_REMINDER =
|
|
16
8
|
"<system_reminder>" +
|
|
17
9
|
"\nStay present in this conversation. Use `remember` when something feels worth pausing to mark — corrections (highest priority), plans, decisions, felt moments. You don't have to capture everything in the moment — a retrospective pass reviews this conversation in the background and saves what you didn't capture." +
|
|
18
10
|
"\nIf you're unsure about something that may live in the workspace — past decisions, prior conversations, files — use `recall` before asking or guessing." +
|
|
19
11
|
"\n</system_reminder>";
|
|
20
12
|
|
|
21
|
-
describe("buildPkbReminder
|
|
13
|
+
describe("buildPkbReminder", () => {
|
|
22
14
|
test("empty hints returns exact base reminder byte-for-byte", () => {
|
|
23
|
-
expect(buildPkbReminder([]
|
|
15
|
+
expect(buildPkbReminder([])).toBe(BASE_REMINDER);
|
|
24
16
|
});
|
|
25
17
|
|
|
26
18
|
test("single hint renders one bullet with no duplicates or trailing blank line", () => {
|
|
27
|
-
const out = buildPkbReminder(["projects/alpha.md"]
|
|
19
|
+
const out = buildPkbReminder(["projects/alpha.md"]);
|
|
28
20
|
const expected =
|
|
29
21
|
"<system_reminder>" +
|
|
30
|
-
"\
|
|
22
|
+
"\nStay present in this conversation. Use `remember` when something feels worth pausing to mark — corrections (highest priority), plans, decisions, felt moments. You don't have to capture everything in the moment — a retrospective pass reviews this conversation in the background and saves what you didn't capture." +
|
|
31
23
|
"\nIf you're unsure about something that may live in the workspace — past decisions, prior conversations, files — use `recall` before asking or guessing." +
|
|
32
24
|
"\nBased on the current context, these files look especially relevant:" +
|
|
33
25
|
"\n- projects/alpha.md" +
|
|
@@ -44,10 +36,10 @@ describe("buildPkbReminder — default body (relaxed=false)", () => {
|
|
|
44
36
|
|
|
45
37
|
test("three hints render all three in order", () => {
|
|
46
38
|
const hints = ["a.md", "sub/b.md", "c/d/e.md"];
|
|
47
|
-
const out = buildPkbReminder(hints
|
|
39
|
+
const out = buildPkbReminder(hints);
|
|
48
40
|
const expected =
|
|
49
41
|
"<system_reminder>" +
|
|
50
|
-
"\
|
|
42
|
+
"\nStay present in this conversation. Use `remember` when something feels worth pausing to mark — corrections (highest priority), plans, decisions, felt moments. You don't have to capture everything in the moment — a retrospective pass reviews this conversation in the background and saves what you didn't capture." +
|
|
51
43
|
"\nIf you're unsure about something that may live in the workspace — past decisions, prior conversations, files — use `recall` before asking or guessing." +
|
|
52
44
|
"\nBased on the current context, these files look especially relevant:" +
|
|
53
45
|
"\n- a.md" +
|
|
@@ -67,7 +59,7 @@ describe("buildPkbReminder — default body (relaxed=false)", () => {
|
|
|
67
59
|
|
|
68
60
|
test("hints with special chars (< and &) are emitted verbatim (no escaping)", () => {
|
|
69
61
|
const hints = ["weird<name>.md", "foo&bar.md"];
|
|
70
|
-
const out = buildPkbReminder(hints
|
|
62
|
+
const out = buildPkbReminder(hints);
|
|
71
63
|
expect(out).toContain("- weird<name>.md");
|
|
72
64
|
expect(out).toContain("- foo&bar.md");
|
|
73
65
|
// Ensure no HTML-style escaping happened.
|
|
@@ -75,38 +67,3 @@ describe("buildPkbReminder — default body (relaxed=false)", () => {
|
|
|
75
67
|
expect(out).not.toContain("&");
|
|
76
68
|
});
|
|
77
69
|
});
|
|
78
|
-
|
|
79
|
-
describe("buildPkbReminder — relaxed body (relaxed=true)", () => {
|
|
80
|
-
test("empty hints returns the relaxed base reminder byte-for-byte", () => {
|
|
81
|
-
expect(buildPkbReminder([], true)).toBe(BASE_REMINDER_RELAXED);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("relaxed BODY does NOT contain the default high-pressure phrasing", () => {
|
|
85
|
-
const out = buildPkbReminder([], true);
|
|
86
|
-
expect(out).not.toContain("**CRITICAL:**");
|
|
87
|
-
expect(out).not.toContain("most frequently used tool");
|
|
88
|
-
expect(out).not.toContain("Default to remembering");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("relaxed BODY mentions the retrospective backstop framing", () => {
|
|
92
|
-
const out = buildPkbReminder([], true);
|
|
93
|
-
expect(out).toContain("Stay present");
|
|
94
|
-
expect(out).toContain("retrospective pass");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("hints render below the relaxed body in the same shape as default", () => {
|
|
98
|
-
const out = buildPkbReminder(["projects/alpha.md"], true);
|
|
99
|
-
expect(out).toContain("- projects/alpha.md");
|
|
100
|
-
expect(out).toContain(
|
|
101
|
-
"Based on the current context, these files look especially relevant:",
|
|
102
|
-
);
|
|
103
|
-
// Should still close cleanly with no double newline before the tag.
|
|
104
|
-
expect(out.includes("\n\n</system_reminder>")).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("buildPkbReminder — relaxed vs default differ", () => {
|
|
109
|
-
test("the two BODY variants are NOT byte-identical", () => {
|
|
110
|
-
expect(buildPkbReminder([], false)).not.toBe(buildPkbReminder([], true));
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
"\n**CRITICAL:** Call `remember` this turn for anything concrete the user said — facts, preferences, plans, names, dates, decisions, corrections, felt moments. Default to remembering; skip only obvious noise. This should be your most frequently used tool." +
|
|
3
|
-
"\nIf you're unsure about something that may live in the workspace — past decisions, prior conversations, files — use `recall` before asking or guessing.";
|
|
4
|
-
|
|
5
|
-
const BODY_RELAXED =
|
|
1
|
+
const BODY =
|
|
6
2
|
"\nStay present in this conversation. Use `remember` when something feels worth pausing to mark — corrections (highest priority), plans, decisions, felt moments. You don't have to capture everything in the moment — a retrospective pass reviews this conversation in the background and saves what you didn't capture." +
|
|
7
3
|
"\nIf you're unsure about something that may live in the workspace — past decisions, prior conversations, files — use `recall` before asking or guessing.";
|
|
8
4
|
|
|
@@ -15,23 +11,12 @@ const BODY_RELAXED =
|
|
|
15
11
|
* hint. Hints are emitted verbatim — they are trusted internal paths, not
|
|
16
12
|
* user input, so no escaping is performed.
|
|
17
13
|
*
|
|
18
|
-
* The `relaxed` flag selects between the default high-pressure body and the
|
|
19
|
-
* relaxed "judgment framing" body used when the `memory-retrospective`
|
|
20
|
-
* feature flag is on. With the flag on, the in-conversation remember pressure
|
|
21
|
-
* eases because the retrospective is the backstop. Callers must pass
|
|
22
|
-
* `relaxed` explicitly — no default — so the contract is visible at every
|
|
23
|
-
* call site.
|
|
24
|
-
*
|
|
25
14
|
* Caller is responsible for capping the hints array at 3 entries.
|
|
26
15
|
*/
|
|
27
|
-
export function buildPkbReminder(
|
|
28
|
-
hints: ReadonlyArray<string>,
|
|
29
|
-
relaxed: boolean,
|
|
30
|
-
): string {
|
|
31
|
-
const body = relaxed ? BODY_RELAXED : BODY_DEFAULT;
|
|
16
|
+
export function buildPkbReminder(hints: ReadonlyArray<string>): string {
|
|
32
17
|
if (hints.length === 0) {
|
|
33
|
-
return `<system_reminder>${
|
|
18
|
+
return `<system_reminder>${BODY}\n</system_reminder>`;
|
|
34
19
|
}
|
|
35
20
|
const bullets = hints.map((h) => `- ${h}`).join("\n");
|
|
36
|
-
return `<system_reminder>${
|
|
21
|
+
return `<system_reminder>${BODY}\nBased on the current context, these files look especially relevant:\n${bullets}\n</system_reminder>`;
|
|
37
22
|
}
|
|
@@ -31,6 +31,7 @@ import { updateMetaFile } from "../memory/conversation-disk-view.js";
|
|
|
31
31
|
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
32
32
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
33
33
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
34
|
+
import { getSubagentManager } from "../subagent/index.js";
|
|
34
35
|
import { getLogger } from "../util/logger.js";
|
|
35
36
|
import type { Conversation } from "./conversation.js";
|
|
36
37
|
import {
|
|
@@ -455,6 +456,7 @@ export async function processMessage(
|
|
|
455
456
|
|
|
456
457
|
if (options?.isInteractive === true) {
|
|
457
458
|
conversation.updateClient(broadcastMessage, false);
|
|
459
|
+
getSubagentManager().updateParentSender(conversationId, broadcastMessage);
|
|
458
460
|
}
|
|
459
461
|
|
|
460
462
|
try {
|
|
@@ -521,6 +523,7 @@ export async function processMessageInBackground(
|
|
|
521
523
|
|
|
522
524
|
if (options?.isInteractive === true) {
|
|
523
525
|
conversation.updateClient(broadcastMessage, false);
|
|
526
|
+
getSubagentManager().updateParentSender(conversationId, broadcastMessage);
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
conversation.setSlackRuntimeContextNotice(options?.slackRuntimeContextNotice);
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
seedUninstalledCatalogSkillMemories,
|
|
6
6
|
} from "../memory/graph/capability-seed.js";
|
|
7
7
|
import { getLogger } from "../util/logger.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
maybeSeedMemoryV2CliCommands,
|
|
10
|
+
maybeSeedMemoryV2Skills,
|
|
11
|
+
} from "./memory-v2-startup.js";
|
|
9
12
|
|
|
10
13
|
const log = getLogger("skill-memory-refresh");
|
|
11
14
|
|
|
@@ -14,6 +17,7 @@ export function refreshSkillCapabilityMemories(
|
|
|
14
17
|
): void {
|
|
15
18
|
seedSkillGraphNodes();
|
|
16
19
|
maybeSeedMemoryV2Skills(config);
|
|
20
|
+
maybeSeedMemoryV2CliCommands(config);
|
|
17
21
|
void seedUninstalledCatalogSkillMemories()
|
|
18
22
|
.then(() => {
|
|
19
23
|
// Re-run after the async catalog fetch populates the cache so stale
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
type TestMessage = {
|
|
6
|
+
id: string;
|
|
7
|
+
conversationId: string;
|
|
8
|
+
role: string;
|
|
9
|
+
content: string;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
metadata: string | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const parentMessages: TestMessage[] = [
|
|
15
|
+
{
|
|
16
|
+
id: "msg-parent-1",
|
|
17
|
+
conversationId: "parent-conv",
|
|
18
|
+
role: "user",
|
|
19
|
+
content: JSON.stringify([{ type: "text", text: "go research foo" }]),
|
|
20
|
+
createdAt: 1_700_000_000_000,
|
|
21
|
+
metadata: null,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "msg-parent-2",
|
|
25
|
+
conversationId: "parent-conv",
|
|
26
|
+
role: "assistant",
|
|
27
|
+
content: JSON.stringify([{ type: "text", text: "spawning subagent" }]),
|
|
28
|
+
createdAt: 1_700_000_001_000,
|
|
29
|
+
metadata: JSON.stringify({
|
|
30
|
+
subagentNotification: {
|
|
31
|
+
subagentId: "sa-1",
|
|
32
|
+
label: "research-foo",
|
|
33
|
+
status: "completed",
|
|
34
|
+
conversationId: "child-conv-1",
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const childMessages: TestMessage[] = [
|
|
41
|
+
{
|
|
42
|
+
id: "msg-child-1",
|
|
43
|
+
conversationId: "child-conv-1",
|
|
44
|
+
role: "user",
|
|
45
|
+
content: JSON.stringify([
|
|
46
|
+
{ type: "text", text: "Objective: research foo and report back." },
|
|
47
|
+
]),
|
|
48
|
+
createdAt: 1_700_000_002_000,
|
|
49
|
+
metadata: null,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "msg-child-2",
|
|
53
|
+
conversationId: "child-conv-1",
|
|
54
|
+
role: "assistant",
|
|
55
|
+
content: JSON.stringify([
|
|
56
|
+
{ type: "text", text: "I found that foo is a bar." },
|
|
57
|
+
]),
|
|
58
|
+
createdAt: 1_700_000_003_000,
|
|
59
|
+
metadata: null,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const messageMetadataSchema = z.object({
|
|
64
|
+
subagentNotification: z
|
|
65
|
+
.object({
|
|
66
|
+
subagentId: z.string(),
|
|
67
|
+
label: z.string(),
|
|
68
|
+
status: z.enum(["running", "completed", "failed", "aborted"]),
|
|
69
|
+
conversationId: z.string().optional(),
|
|
70
|
+
})
|
|
71
|
+
.optional(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
mock.module("../../memory/conversation-crud.js", () => ({
|
|
75
|
+
getConversation: (_id: string) => null,
|
|
76
|
+
getMessages: (id: string) =>
|
|
77
|
+
id === "child-conv-1" ? childMessages : parentMessages,
|
|
78
|
+
messageMetadataSchema,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
mock.module("../../util/truncate.js", () => ({
|
|
82
|
+
truncate: (s: string) => s,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
mock.module("../../daemon/date-context.js", () => ({
|
|
86
|
+
formatLocalTimestamp: (_ts: number, _tz?: string) => "TIME",
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
const { formatMessageSliceForTranscript } =
|
|
90
|
+
await import("../transcript-formatter.js");
|
|
91
|
+
|
|
92
|
+
describe("formatMessageSliceForTranscript subagent labels", () => {
|
|
93
|
+
test("embedded subagent transcripts render with generic role labels even when parent display names are provided", () => {
|
|
94
|
+
const out = formatMessageSliceForTranscript(parentMessages, {
|
|
95
|
+
assistantName: "Bob",
|
|
96
|
+
userName: "Alice",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Parent messages use the provided display names.
|
|
100
|
+
expect(out).toContain("## Alice (TIME)");
|
|
101
|
+
expect(out).toContain("## Bob (TIME)");
|
|
102
|
+
|
|
103
|
+
// Subagent block headers must use generic labels — the child "user" message
|
|
104
|
+
// is actually the parent assistant's objective, so labeling it "Alice"
|
|
105
|
+
// would misattribute the assistant's tasking text to the human user.
|
|
106
|
+
expect(out).toContain("### Subagent: research-foo (completed)");
|
|
107
|
+
expect(out).toContain("> **User** (TIME)");
|
|
108
|
+
expect(out).toContain("> **Assistant** (TIME)");
|
|
109
|
+
expect(out).not.toContain("> **Alice**");
|
|
110
|
+
expect(out).not.toContain("> **Bob**");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("without display-name options, parent and subagent both use generic labels", () => {
|
|
114
|
+
const out = formatMessageSliceForTranscript(parentMessages);
|
|
115
|
+
|
|
116
|
+
expect(out).toContain("## User (TIME)");
|
|
117
|
+
expect(out).toContain("## Assistant (TIME)");
|
|
118
|
+
expect(out).toContain("> **User** (TIME)");
|
|
119
|
+
expect(out).toContain("> **Assistant** (TIME)");
|
|
120
|
+
});
|
|
121
|
+
});
|