@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
|
@@ -38,6 +38,10 @@ import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
|
38
38
|
import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
|
|
39
39
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
40
40
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
41
|
+
import type {
|
|
42
|
+
MacosTransportMetadata,
|
|
43
|
+
NonMacosTransportMetadata,
|
|
44
|
+
} from "../../daemon/message-types/conversations.js";
|
|
41
45
|
import type { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
42
46
|
import * as attachmentsStore from "../../memory/attachments-store.js";
|
|
43
47
|
import {
|
|
@@ -410,8 +414,15 @@ export function handleListMessages(
|
|
|
410
414
|
rawMessages = getMessages(resolvedConversationId);
|
|
411
415
|
}
|
|
412
416
|
|
|
417
|
+
// During streaming, tool_use (assistant) and tool_result (user) events are
|
|
418
|
+
// assembled client-side into a single assistant ChatMessage. On reload, they
|
|
419
|
+
// are separate DB rows. Merge tool_result blocks from user messages into the
|
|
420
|
+
// preceding assistant message so renderHistoryContent can pair them via its
|
|
421
|
+
// pendingToolUses map — otherwise they render as "Unknown" tool calls.
|
|
422
|
+
const mergedMessages = mergeToolResultsIntoAssistantMessages(rawMessages);
|
|
423
|
+
|
|
413
424
|
// Parse content blocks and extract text + tool calls
|
|
414
|
-
const parsed =
|
|
425
|
+
const parsed = mergedMessages.map((msg) => {
|
|
415
426
|
let content: unknown;
|
|
416
427
|
try {
|
|
417
428
|
content = JSON.parse(msg.content);
|
|
@@ -424,10 +435,33 @@ export function handleListMessages(
|
|
|
424
435
|
// was queued or its persistence was delayed (long assistant generation),
|
|
425
436
|
// sentAt captures the actual event time. Falls back to createdAt.
|
|
426
437
|
let sentAt: number | undefined;
|
|
438
|
+
let subagentNotification:
|
|
439
|
+
| {
|
|
440
|
+
subagentId: string;
|
|
441
|
+
label: string;
|
|
442
|
+
status: string;
|
|
443
|
+
error?: string;
|
|
444
|
+
conversationId?: string;
|
|
445
|
+
}
|
|
446
|
+
| undefined;
|
|
427
447
|
if (msg.metadata) {
|
|
428
448
|
try {
|
|
429
449
|
const meta = JSON.parse(msg.metadata);
|
|
430
450
|
if (typeof meta.sentAt === "number") sentAt = meta.sentAt;
|
|
451
|
+
if (meta.subagentNotification) {
|
|
452
|
+
const n = meta.subagentNotification;
|
|
453
|
+
if (typeof n.subagentId === "string" && typeof n.label === "string") {
|
|
454
|
+
subagentNotification = {
|
|
455
|
+
subagentId: n.subagentId,
|
|
456
|
+
label: n.label,
|
|
457
|
+
status: typeof n.status === "string" ? n.status : "completed",
|
|
458
|
+
...(typeof n.error === "string" ? { error: n.error } : {}),
|
|
459
|
+
...(typeof n.conversationId === "string"
|
|
460
|
+
? { conversationId: n.conversationId }
|
|
461
|
+
: {}),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
431
465
|
} catch {
|
|
432
466
|
// Ignore malformed metadata
|
|
433
467
|
}
|
|
@@ -475,6 +509,7 @@ export function handleListMessages(
|
|
|
475
509
|
? { thinkingSegments: rendered.thinkingSegments }
|
|
476
510
|
: {}),
|
|
477
511
|
id: msg.id,
|
|
512
|
+
subagentNotification,
|
|
478
513
|
};
|
|
479
514
|
}
|
|
480
515
|
|
|
@@ -492,6 +527,7 @@ export function handleListMessages(
|
|
|
492
527
|
? { thinkingSegments: rendered.thinkingSegments }
|
|
493
528
|
: {}),
|
|
494
529
|
id: msg.id,
|
|
530
|
+
subagentNotification,
|
|
495
531
|
};
|
|
496
532
|
});
|
|
497
533
|
|
|
@@ -580,6 +616,9 @@ export function handleListMessages(
|
|
|
580
616
|
? { thinkingSegments: m.thinkingSegments }
|
|
581
617
|
: {}),
|
|
582
618
|
...(m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
|
|
619
|
+
...(m.subagentNotification
|
|
620
|
+
? { subagentNotification: m.subagentNotification }
|
|
621
|
+
: {}),
|
|
583
622
|
};
|
|
584
623
|
});
|
|
585
624
|
|
|
@@ -599,6 +638,157 @@ export function handleListMessages(
|
|
|
599
638
|
return Response.json({ messages });
|
|
600
639
|
}
|
|
601
640
|
|
|
641
|
+
// ── Tool-result merging ─────────────────────────────────────────────
|
|
642
|
+
|
|
643
|
+
function isToolResultType(type: string): boolean {
|
|
644
|
+
return type === "tool_result" || type === "web_search_tool_result";
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function isSystemNoticeText(block: Record<string, unknown>): boolean {
|
|
648
|
+
if (block.type !== "text") return false;
|
|
649
|
+
const text = typeof block.text === "string" ? block.text : "";
|
|
650
|
+
return (
|
|
651
|
+
text.startsWith("<system_notice>") && text.endsWith("</system_notice>")
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Merge tool_result blocks from user messages into the preceding assistant
|
|
657
|
+
* message's content array. This lets renderHistoryContent's pendingToolUses
|
|
658
|
+
* map pair tool_use and tool_result blocks, preventing "unknown" tool names.
|
|
659
|
+
*
|
|
660
|
+
* User messages that consist entirely of tool_result blocks (and optional
|
|
661
|
+
* system_notice text) are removed from the output. Mixed messages (tool_result
|
|
662
|
+
* + real user text) keep only the non-tool-result blocks.
|
|
663
|
+
*/
|
|
664
|
+
function mergeToolResultsIntoAssistantMessages(
|
|
665
|
+
messages: MessageRow[],
|
|
666
|
+
): MessageRow[] {
|
|
667
|
+
// Index of the most recent assistant message in the output array.
|
|
668
|
+
let lastAssistantIdx = -1;
|
|
669
|
+
// Parsed content caches — lazily populated per assistant message.
|
|
670
|
+
const parsedAssistantContent = new Map<number, unknown[]>();
|
|
671
|
+
|
|
672
|
+
const result: MessageRow[] = [];
|
|
673
|
+
|
|
674
|
+
for (const msg of messages) {
|
|
675
|
+
if (msg.role === "assistant") {
|
|
676
|
+
lastAssistantIdx = result.length;
|
|
677
|
+
result.push(msg);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Only process user messages — other roles pass through.
|
|
682
|
+
if (msg.role !== "user") {
|
|
683
|
+
result.push(msg);
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
let blocks: unknown[];
|
|
688
|
+
try {
|
|
689
|
+
const parsed = JSON.parse(msg.content);
|
|
690
|
+
if (!Array.isArray(parsed)) {
|
|
691
|
+
result.push(msg);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
blocks = parsed;
|
|
695
|
+
} catch {
|
|
696
|
+
result.push(msg);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Separate tool-result blocks from real user content.
|
|
701
|
+
const toolResultBlocks: unknown[] = [];
|
|
702
|
+
const otherBlocks: unknown[] = [];
|
|
703
|
+
for (const block of blocks) {
|
|
704
|
+
if (
|
|
705
|
+
typeof block === "object" &&
|
|
706
|
+
block !== null &&
|
|
707
|
+
typeof (block as Record<string, unknown>).type === "string"
|
|
708
|
+
) {
|
|
709
|
+
const rec = block as Record<string, unknown>;
|
|
710
|
+
if (isToolResultType(rec.type as string)) {
|
|
711
|
+
toolResultBlocks.push(block);
|
|
712
|
+
} else if (isSystemNoticeText(rec)) {
|
|
713
|
+
// System notices don't count as user content — drop them when
|
|
714
|
+
// the message is otherwise tool-result-only.
|
|
715
|
+
otherBlocks.push(block);
|
|
716
|
+
} else {
|
|
717
|
+
otherBlocks.push(block);
|
|
718
|
+
}
|
|
719
|
+
} else {
|
|
720
|
+
otherBlocks.push(block);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// No tool results → pass through unchanged.
|
|
725
|
+
if (toolResultBlocks.length === 0) {
|
|
726
|
+
result.push(msg);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Append tool_result blocks to the preceding assistant message's content.
|
|
731
|
+
if (lastAssistantIdx >= 0) {
|
|
732
|
+
const assistant = result[lastAssistantIdx];
|
|
733
|
+
let assistantContent = parsedAssistantContent.get(lastAssistantIdx);
|
|
734
|
+
if (!assistantContent) {
|
|
735
|
+
try {
|
|
736
|
+
const parsed = JSON.parse(assistant.content);
|
|
737
|
+
assistantContent = Array.isArray(parsed) ? parsed : [parsed];
|
|
738
|
+
} catch {
|
|
739
|
+
assistantContent = [];
|
|
740
|
+
}
|
|
741
|
+
parsedAssistantContent.set(lastAssistantIdx, assistantContent);
|
|
742
|
+
}
|
|
743
|
+
assistantContent.push(...toolResultBlocks);
|
|
744
|
+
} else {
|
|
745
|
+
// No preceding assistant message (pagination boundary) — keep the
|
|
746
|
+
// original message as-is to avoid permanent data loss. The preceding
|
|
747
|
+
// assistant tool_use lives in the previous page; dropping the result
|
|
748
|
+
// here would be unrecoverable.
|
|
749
|
+
// Still strip system notices so internal prompt text isn't exposed.
|
|
750
|
+
const filteredBlocks = blocks.filter(
|
|
751
|
+
(b) =>
|
|
752
|
+
!(
|
|
753
|
+
typeof b === "object" &&
|
|
754
|
+
b !== null &&
|
|
755
|
+
isSystemNoticeText(b as Record<string, unknown>)
|
|
756
|
+
),
|
|
757
|
+
);
|
|
758
|
+
result.push({
|
|
759
|
+
...msg,
|
|
760
|
+
content:
|
|
761
|
+
filteredBlocks.length === blocks.length
|
|
762
|
+
? msg.content
|
|
763
|
+
: JSON.stringify(filteredBlocks),
|
|
764
|
+
});
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// If the user message had only tool_result (+ system_notice) blocks,
|
|
769
|
+
// suppress it entirely. Otherwise keep the non-tool-result content.
|
|
770
|
+
const realUserContent = otherBlocks.filter(
|
|
771
|
+
(b) =>
|
|
772
|
+
!(
|
|
773
|
+
typeof b === "object" &&
|
|
774
|
+
b !== null &&
|
|
775
|
+
isSystemNoticeText(b as Record<string, unknown>)
|
|
776
|
+
),
|
|
777
|
+
);
|
|
778
|
+
if (realUserContent.length > 0) {
|
|
779
|
+
result.push({ ...msg, content: JSON.stringify(otherBlocks) });
|
|
780
|
+
}
|
|
781
|
+
// else: tool-result-only → suppressed (results already merged above)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Write back any modified assistant message content.
|
|
785
|
+
for (const [idx, content] of parsedAssistantContent) {
|
|
786
|
+
result[idx] = { ...result[idx], content: JSON.stringify(content) };
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return result;
|
|
790
|
+
}
|
|
791
|
+
|
|
602
792
|
/**
|
|
603
793
|
* Build an `onEvent` callback that publishes every outbound event to the
|
|
604
794
|
* assistant event hub, maintaining ordered delivery through a serial chain.
|
|
@@ -658,13 +848,10 @@ function makeHubPublisher(
|
|
|
658
848
|
guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
|
|
659
849
|
toolName: msg.toolName,
|
|
660
850
|
commandPreview:
|
|
661
|
-
redactSecrets(
|
|
662
|
-
|
|
663
|
-
) || undefined,
|
|
851
|
+
redactSecrets(summarizeToolInput(msg.toolName, inputRecord)) ||
|
|
852
|
+
undefined,
|
|
664
853
|
riskLevel: msg.riskLevel,
|
|
665
|
-
activityText: activityRaw
|
|
666
|
-
? redactSecrets(activityRaw)
|
|
667
|
-
: undefined,
|
|
854
|
+
activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
|
|
668
855
|
executionTarget: msg.executionTarget,
|
|
669
856
|
status: "pending",
|
|
670
857
|
requestCode: generateCanonicalRequestCode(),
|
|
@@ -759,6 +946,8 @@ export async function handleSendMessage(
|
|
|
759
946
|
conversationType?: string;
|
|
760
947
|
automated?: boolean;
|
|
761
948
|
bypassSecretCheck?: boolean;
|
|
949
|
+
hostHomeDir?: string;
|
|
950
|
+
hostUsername?: string;
|
|
762
951
|
};
|
|
763
952
|
|
|
764
953
|
const { conversationKey, content, attachmentIds } = body;
|
|
@@ -791,9 +980,11 @@ export async function handleSendMessage(
|
|
|
791
980
|
);
|
|
792
981
|
}
|
|
793
982
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
983
|
+
// When conversationKey is omitted, derive a stable default from
|
|
984
|
+
// sourceChannel + sourceInterface so that repeated calls from the same
|
|
985
|
+
// channel/interface pair share a single conversation thread.
|
|
986
|
+
const resolvedConversationKey =
|
|
987
|
+
conversationKey ?? `default:${sourceChannel}:${sourceInterface}`;
|
|
797
988
|
|
|
798
989
|
// Reject non-string content values (numbers, objects, etc.)
|
|
799
990
|
if (content != null && typeof content !== "string") {
|
|
@@ -856,12 +1047,29 @@ export async function handleSendMessage(
|
|
|
856
1047
|
|
|
857
1048
|
const conversationType =
|
|
858
1049
|
body.conversationType === "private" ? ("private" as const) : undefined;
|
|
859
|
-
const mapping = getOrCreateConversation(
|
|
1050
|
+
const mapping = getOrCreateConversation(resolvedConversationKey, {
|
|
860
1051
|
conversationType,
|
|
861
1052
|
});
|
|
862
1053
|
const smDeps = deps.sendMessageDeps;
|
|
1054
|
+
|
|
1055
|
+
// Build transport metadata from the request so the daemon can inject
|
|
1056
|
+
// host environment hints (home directory, username) into the LLM context.
|
|
1057
|
+
const transport =
|
|
1058
|
+
sourceInterface === "macos"
|
|
1059
|
+
? ({
|
|
1060
|
+
channelId: sourceChannel,
|
|
1061
|
+
interfaceId: "macos" as const,
|
|
1062
|
+
hostHomeDir: body.hostHomeDir,
|
|
1063
|
+
hostUsername: body.hostUsername,
|
|
1064
|
+
} satisfies MacosTransportMetadata)
|
|
1065
|
+
: ({
|
|
1066
|
+
channelId: sourceChannel,
|
|
1067
|
+
interfaceId: sourceInterface,
|
|
1068
|
+
} satisfies NonMacosTransportMetadata);
|
|
1069
|
+
|
|
863
1070
|
const conversation = await smDeps.getOrCreateConversation(
|
|
864
1071
|
mapping.conversationId,
|
|
1072
|
+
{ transport },
|
|
865
1073
|
);
|
|
866
1074
|
|
|
867
1075
|
// Resolve guardian context from the AuthContext's actorPrincipalId.
|
|
@@ -932,7 +1140,7 @@ export async function handleSendMessage(
|
|
|
932
1140
|
// channels, headless) fall back to local execution.
|
|
933
1141
|
// Set the proxy BEFORE updateClient so updateClient's call to
|
|
934
1142
|
// hostBashProxy.updateSender targets the correct (new) proxy.
|
|
935
|
-
if (sourceInterface === "macos"
|
|
1143
|
+
if (sourceInterface === "macos") {
|
|
936
1144
|
// Reuse the existing proxy if the conversation is actively processing a
|
|
937
1145
|
// host bash request to avoid orphaning in-flight requests.
|
|
938
1146
|
if (!conversation.isProcessing() || !conversation.hostBashProxy) {
|
|
@@ -969,9 +1177,7 @@ export async function handleSendMessage(
|
|
|
969
1177
|
// When proxies are preserved during an active turn (non-desktop request while
|
|
970
1178
|
// processing), skip updating proxy senders to avoid degrading them.
|
|
971
1179
|
const preservingProxies =
|
|
972
|
-
conversation.isProcessing() &&
|
|
973
|
-
sourceInterface !== "macos" &&
|
|
974
|
-
sourceInterface !== "ios";
|
|
1180
|
+
conversation.isProcessing() && sourceInterface !== "macos";
|
|
975
1181
|
conversation.updateClient(onEvent, !isInteractive, {
|
|
976
1182
|
skipProxySenderUpdate: preservingProxies,
|
|
977
1183
|
});
|
|
@@ -1143,36 +1349,47 @@ export async function handleSendMessage(
|
|
|
1143
1349
|
// Auto-deny pending confirmations only after enqueue succeeds, so we
|
|
1144
1350
|
// don't cancel approval-gated workflows when the replacement message
|
|
1145
1351
|
// is itself rejected by the queue budget.
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
)) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1352
|
+
// Wrapped in try-catch: the message is already enqueued, so a failure
|
|
1353
|
+
// here must not turn the 202 response into a 500 — that would leave
|
|
1354
|
+
// the client showing "Failed to send" for a message the daemon will
|
|
1355
|
+
// process from the queue.
|
|
1356
|
+
try {
|
|
1357
|
+
if (conversation.hasAnyPendingConfirmation()) {
|
|
1358
|
+
// Emit authoritative denial state for each pending request.
|
|
1359
|
+
// sendToClient (wired to the SSE hub) delivers these to the client.
|
|
1360
|
+
for (const interaction of pendingInteractions.getByConversation(
|
|
1361
|
+
mapping.conversationId,
|
|
1362
|
+
)) {
|
|
1363
|
+
if (
|
|
1364
|
+
interaction.conversation === conversation &&
|
|
1365
|
+
interaction.kind === "confirmation"
|
|
1366
|
+
) {
|
|
1367
|
+
conversation.emitConfirmationStateChanged({
|
|
1368
|
+
conversationId: mapping.conversationId,
|
|
1369
|
+
requestId: interaction.requestId,
|
|
1370
|
+
state: "denied" as const,
|
|
1371
|
+
source: "auto_deny" as const,
|
|
1372
|
+
});
|
|
1373
|
+
// Sync canonical guardian request status so stale "pending" DB
|
|
1374
|
+
// records don't get matched by later guardian reply routing.
|
|
1375
|
+
resolveCanonicalGuardianRequest(interaction.requestId, "pending", {
|
|
1376
|
+
status: "denied",
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1167
1379
|
}
|
|
1380
|
+
conversation.denyAllPendingConfirmations();
|
|
1381
|
+
pendingInteractions.removeByConversation(conversation);
|
|
1168
1382
|
}
|
|
1169
|
-
conversation.denyAllPendingConfirmations();
|
|
1170
|
-
pendingInteractions.removeByConversation(conversation);
|
|
1171
|
-
}
|
|
1172
1383
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1384
|
+
// Expire any orphaned canonical requests that survived without a
|
|
1385
|
+
// matching in-memory pending interaction (e.g. prompter timeouts).
|
|
1386
|
+
expireOrphanedCanonicalRequests(mapping.conversationId);
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
log.warn(
|
|
1389
|
+
{ err, conversationId: mapping.conversationId },
|
|
1390
|
+
"Post-enqueue auto-deny failed — queued message unaffected",
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1176
1393
|
|
|
1177
1394
|
return Response.json(
|
|
1178
1395
|
{ accepted: true, queued: true, conversationId: mapping.conversationId },
|
|
@@ -1397,7 +1614,10 @@ export async function handleSendMessage(
|
|
|
1397
1614
|
conversationId,
|
|
1398
1615
|
});
|
|
1399
1616
|
conversation.processing = false;
|
|
1400
|
-
silentlyWithLog(
|
|
1617
|
+
silentlyWithLog(
|
|
1618
|
+
conversation.drainQueue(),
|
|
1619
|
+
"compact-command queue drain",
|
|
1620
|
+
);
|
|
1401
1621
|
}, 0);
|
|
1402
1622
|
|
|
1403
1623
|
cleanupDeferred = true;
|
|
@@ -1713,7 +1933,7 @@ export function conversationRouteDefinitions(deps: {
|
|
|
1713
1933
|
"Send a user message to a conversation and trigger the assistant response.",
|
|
1714
1934
|
tags: ["messages"],
|
|
1715
1935
|
requestBody: z.object({
|
|
1716
|
-
conversationKey: z.string(),
|
|
1936
|
+
conversationKey: z.string().optional(),
|
|
1717
1937
|
content: z.string().describe("Message text content"),
|
|
1718
1938
|
attachments: z
|
|
1719
1939
|
.array(z.unknown())
|
|
@@ -47,16 +47,10 @@ function handleUpdateConfig(
|
|
|
47
47
|
if (typeof body.enabled === "boolean") heartbeat.enabled = body.enabled;
|
|
48
48
|
if (typeof body.intervalMs === "number")
|
|
49
49
|
heartbeat.intervalMs = body.intervalMs;
|
|
50
|
-
if (
|
|
51
|
-
heartbeat.activeHoursStart =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
: undefined;
|
|
55
|
-
}
|
|
56
|
-
if ("activeHoursEnd" in body) {
|
|
57
|
-
heartbeat.activeHoursEnd =
|
|
58
|
-
typeof body.activeHoursEnd === "number" ? body.activeHoursEnd : undefined;
|
|
59
|
-
}
|
|
50
|
+
if (typeof body.activeHoursStart === "number")
|
|
51
|
+
heartbeat.activeHoursStart = body.activeHoursStart;
|
|
52
|
+
if (typeof body.activeHoursEnd === "number")
|
|
53
|
+
heartbeat.activeHoursEnd = body.activeHoursEnd;
|
|
60
54
|
|
|
61
55
|
try {
|
|
62
56
|
saveConfig({ ...config, heartbeat });
|
|
@@ -10,6 +10,7 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
13
|
+
import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
|
|
13
14
|
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
14
15
|
import {
|
|
15
16
|
getWorkspaceDir,
|
|
@@ -144,6 +145,13 @@ export function handleHealth(): Response {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export function handleDetailedHealth(): Response {
|
|
148
|
+
let profiler: ReturnType<typeof getProfilerRuntimeStatus> | undefined;
|
|
149
|
+
try {
|
|
150
|
+
profiler = getProfilerRuntimeStatus();
|
|
151
|
+
} catch {
|
|
152
|
+
// Profiler status is non-critical — omit on error
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
return Response.json({
|
|
148
156
|
status: "healthy",
|
|
149
157
|
timestamp: new Date().toISOString(),
|
|
@@ -156,6 +164,7 @@ export function handleDetailedHealth(): Response {
|
|
|
156
164
|
lastWorkspaceMigrationId:
|
|
157
165
|
getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS),
|
|
158
166
|
},
|
|
167
|
+
...(profiler ? { profiler } : {}),
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
170
|
|
|
@@ -239,6 +248,48 @@ export function handleGetIdentityIntro(): Response {
|
|
|
239
248
|
return Response.json({ text: cached.text });
|
|
240
249
|
}
|
|
241
250
|
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// Zod schemas for profiler health metadata
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
const profilerBudgetSchema = z.object({
|
|
256
|
+
maxBytes: z.number(),
|
|
257
|
+
remainingBytes: z.number(),
|
|
258
|
+
minFreeMb: z.number(),
|
|
259
|
+
freeMb: z.number(),
|
|
260
|
+
overBudget: z.boolean(),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const profilerLastCompletedRunSchema = z.object({
|
|
264
|
+
runId: z.string(),
|
|
265
|
+
totalBytes: z.number(),
|
|
266
|
+
artifactCount: z.number(),
|
|
267
|
+
hasSummaries: z.boolean(),
|
|
268
|
+
completedAt: z.string(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const profilerStatusSchema = z.object({
|
|
272
|
+
enabled: z.boolean(),
|
|
273
|
+
mode: z.string().nullable(),
|
|
274
|
+
runId: z.string().nullable(),
|
|
275
|
+
runDir: z.string().nullable(),
|
|
276
|
+
totalBytes: z.number(),
|
|
277
|
+
artifactCount: z.number(),
|
|
278
|
+
budget: profilerBudgetSchema.nullable(),
|
|
279
|
+
lastCompletedRun: profilerLastCompletedRunSchema.nullable(),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const detailedHealthSchema = z.object({
|
|
283
|
+
status: z.string(),
|
|
284
|
+
timestamp: z.string(),
|
|
285
|
+
version: z.string(),
|
|
286
|
+
disk: z.object({}).passthrough(),
|
|
287
|
+
memory: z.object({}).passthrough(),
|
|
288
|
+
cpu: z.object({}).passthrough(),
|
|
289
|
+
migrations: z.object({}).passthrough(),
|
|
290
|
+
profiler: profilerStatusSchema.optional(),
|
|
291
|
+
});
|
|
292
|
+
|
|
242
293
|
// ---------------------------------------------------------------------------
|
|
243
294
|
// Route definitions
|
|
244
295
|
// ---------------------------------------------------------------------------
|
|
@@ -253,15 +304,7 @@ export function identityRouteDefinitions(): RouteDefinition[] {
|
|
|
253
304
|
description:
|
|
254
305
|
"Returns runtime health including version, disk, memory, CPU, and migration status.",
|
|
255
306
|
tags: ["system"],
|
|
256
|
-
responseBody:
|
|
257
|
-
status: z.string(),
|
|
258
|
-
timestamp: z.string(),
|
|
259
|
-
version: z.string(),
|
|
260
|
-
disk: z.object({}).passthrough(),
|
|
261
|
-
memory: z.object({}).passthrough(),
|
|
262
|
-
cpu: z.object({}).passthrough(),
|
|
263
|
-
migrations: z.object({}).passthrough(),
|
|
264
|
-
}),
|
|
307
|
+
responseBody: detailedHealthSchema,
|
|
265
308
|
},
|
|
266
309
|
{
|
|
267
310
|
endpoint: "healthz",
|
|
@@ -272,15 +315,7 @@ export function identityRouteDefinitions(): RouteDefinition[] {
|
|
|
272
315
|
description:
|
|
273
316
|
"Alias for /v1/health. Returns runtime health including version, disk, memory, CPU, and migration status.",
|
|
274
317
|
tags: ["system"],
|
|
275
|
-
responseBody:
|
|
276
|
-
status: z.string(),
|
|
277
|
-
timestamp: z.string(),
|
|
278
|
-
version: z.string(),
|
|
279
|
-
disk: z.object({}).passthrough(),
|
|
280
|
-
memory: z.object({}).passthrough(),
|
|
281
|
-
cpu: z.object({}).passthrough(),
|
|
282
|
-
migrations: z.object({}).passthrough(),
|
|
283
|
-
}),
|
|
318
|
+
responseBody: detailedHealthSchema,
|
|
284
319
|
},
|
|
285
320
|
{
|
|
286
321
|
endpoint: "identity",
|
|
@@ -570,16 +570,8 @@ function anthropicMessageSections(
|
|
|
570
570
|
const role = asString(message.role) ?? "unknown";
|
|
571
571
|
const content = message.content;
|
|
572
572
|
const sections: LlmContextSection[] = [];
|
|
573
|
-
const text = collectAnthropicMessageText(content);
|
|
574
|
-
if (text) {
|
|
575
|
-
sections.push({
|
|
576
|
-
kind: "message",
|
|
577
|
-
label,
|
|
578
|
-
role,
|
|
579
|
-
text,
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
573
|
|
|
574
|
+
// Collect reasoning sections first so they appear before the message text.
|
|
583
575
|
for (const block of asRecordArray(content) ?? []) {
|
|
584
576
|
const type = asString(block.type);
|
|
585
577
|
if (type === "thinking" || type === "redacted_thinking") {
|
|
@@ -589,9 +581,21 @@ function anthropicMessageSections(
|
|
|
589
581
|
role,
|
|
590
582
|
text: collectAnthropicReasoningText(block),
|
|
591
583
|
});
|
|
592
|
-
continue;
|
|
593
584
|
}
|
|
585
|
+
}
|
|
594
586
|
|
|
587
|
+
const text = collectAnthropicMessageText(content);
|
|
588
|
+
if (text) {
|
|
589
|
+
sections.push({
|
|
590
|
+
kind: "message",
|
|
591
|
+
label,
|
|
592
|
+
role,
|
|
593
|
+
text,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
for (const block of asRecordArray(content) ?? []) {
|
|
598
|
+
const type = asString(block.type);
|
|
595
599
|
if (isAnthropicToolUseType(type)) {
|
|
596
600
|
sections.push({
|
|
597
601
|
kind: "tool_use",
|