@vellumai/assistant 0.4.49 → 0.4.51
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 +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -1,324 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getAttachmentsForMessage,
|
|
3
|
-
getFilePathForAttachment,
|
|
4
|
-
setAttachmentThumbnail,
|
|
5
|
-
} from "../../memory/attachments-store.js";
|
|
6
1
|
import { getMessageById } from "../../memory/conversation-crud.js";
|
|
7
2
|
import {
|
|
8
|
-
getMessagesPaginated,
|
|
9
3
|
listConversations,
|
|
10
4
|
searchConversations,
|
|
11
5
|
} from "../../memory/conversation-queries.js";
|
|
12
|
-
import {
|
|
13
|
-
import { truncate } from "../../util/truncate.js";
|
|
14
|
-
import type {
|
|
15
|
-
ConversationSearchRequest,
|
|
16
|
-
HistoryRequest,
|
|
17
|
-
MessageContentRequest,
|
|
18
|
-
UserMessageAttachment,
|
|
19
|
-
} from "../message-protocol.js";
|
|
20
|
-
import { generateVideoThumbnail } from "../video-thumbnail.js";
|
|
21
|
-
import {
|
|
22
|
-
type HandlerContext,
|
|
23
|
-
type HistorySurface,
|
|
24
|
-
type HistoryToolCall,
|
|
25
|
-
log,
|
|
26
|
-
type ParsedHistoryMessage,
|
|
27
|
-
renderHistoryContent,
|
|
28
|
-
} from "./shared.js";
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* In light mode, strip heavy payloads (e.g. full HTML) from surface data
|
|
32
|
-
* but preserve the fields the client needs to parse and render the surface.
|
|
33
|
-
*/
|
|
34
|
-
function lightModeSurfaceData(s: HistorySurface): Record<string, unknown> {
|
|
35
|
-
switch (s.surfaceType) {
|
|
36
|
-
case "dynamic_page":
|
|
37
|
-
return {
|
|
38
|
-
...(s.data.preview ? { preview: s.data.preview } : {}),
|
|
39
|
-
...(s.data.appId ? { appId: s.data.appId } : {}),
|
|
40
|
-
};
|
|
41
|
-
case "card":
|
|
42
|
-
return {
|
|
43
|
-
...(typeof s.data.title === "string" ? { title: s.data.title } : {}),
|
|
44
|
-
...(typeof s.data.body === "string" ? { body: s.data.body } : {}),
|
|
45
|
-
...(typeof s.data.template === "string"
|
|
46
|
-
? { template: s.data.template }
|
|
47
|
-
: {}),
|
|
48
|
-
...(s.data.templateData ? { templateData: s.data.templateData } : {}),
|
|
49
|
-
};
|
|
50
|
-
case "document_preview":
|
|
51
|
-
return {
|
|
52
|
-
...(typeof s.data.surfaceId === "string"
|
|
53
|
-
? { surfaceId: s.data.surfaceId }
|
|
54
|
-
: {}),
|
|
55
|
-
...(typeof s.data.title === "string" ? { title: s.data.title } : {}),
|
|
56
|
-
...(typeof s.data.content === "string"
|
|
57
|
-
? { content: s.data.content }
|
|
58
|
-
: {}),
|
|
59
|
-
};
|
|
60
|
-
default:
|
|
61
|
-
// For other types (list, table, form, confirmation, etc.),
|
|
62
|
-
// preserve the full data — these are generally small.
|
|
63
|
-
return s.data;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function handleHistoryRequest(
|
|
68
|
-
msg: HistoryRequest,
|
|
69
|
-
ctx: HandlerContext,
|
|
70
|
-
): void {
|
|
71
|
-
// No limit means return all messages.
|
|
72
|
-
const limit = msg.limit;
|
|
73
|
-
|
|
74
|
-
// Resolve include flags: explicit flags override mode, mode provides defaults.
|
|
75
|
-
// Default mode is 'light' when no mode and no include flags are specified.
|
|
76
|
-
const isFullMode = msg.mode === "full";
|
|
77
|
-
const includeAttachments = msg.includeAttachments ?? isFullMode;
|
|
78
|
-
const includeToolImages = msg.includeToolImages ?? isFullMode;
|
|
79
|
-
const includeSurfaceData = msg.includeSurfaceData ?? isFullMode;
|
|
80
|
-
|
|
81
|
-
const { messages: dbMessages, hasMore } = getMessagesPaginated(
|
|
82
|
-
msg.sessionId,
|
|
83
|
-
limit,
|
|
84
|
-
msg.beforeTimestamp,
|
|
85
|
-
msg.beforeMessageId,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const parsed: ParsedHistoryMessage[] = dbMessages.map((m) => {
|
|
89
|
-
let text = "";
|
|
90
|
-
let toolCalls: HistoryToolCall[] = [];
|
|
91
|
-
let toolCallsBeforeText = false;
|
|
92
|
-
let textSegments: string[] = [];
|
|
93
|
-
let contentOrder: string[] = [];
|
|
94
|
-
let surfaces: HistorySurface[] = [];
|
|
95
|
-
try {
|
|
96
|
-
const content = JSON.parse(m.content);
|
|
97
|
-
const rendered = renderHistoryContent(content);
|
|
98
|
-
text = rendered.text;
|
|
99
|
-
toolCalls = rendered.toolCalls;
|
|
100
|
-
toolCallsBeforeText = rendered.toolCallsBeforeText;
|
|
101
|
-
textSegments = rendered.textSegments;
|
|
102
|
-
contentOrder = rendered.contentOrder;
|
|
103
|
-
surfaces = rendered.surfaces;
|
|
104
|
-
if (m.role === "assistant" && toolCalls.length > 0) {
|
|
105
|
-
log.info(
|
|
106
|
-
{
|
|
107
|
-
messageId: m.id,
|
|
108
|
-
toolCallCount: toolCalls.length,
|
|
109
|
-
text: truncate(text, 100, ""),
|
|
110
|
-
},
|
|
111
|
-
"History message with tool calls",
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
} catch (err) {
|
|
115
|
-
log.debug(
|
|
116
|
-
{ err, messageId: m.id },
|
|
117
|
-
"Failed to parse message content as JSON, using raw text",
|
|
118
|
-
);
|
|
119
|
-
text = m.content;
|
|
120
|
-
textSegments = text ? [text] : [];
|
|
121
|
-
contentOrder = text ? ["text:0"] : [];
|
|
122
|
-
surfaces = [];
|
|
123
|
-
}
|
|
124
|
-
let subagentNotification: ParsedHistoryMessage["subagentNotification"];
|
|
125
|
-
if (m.metadata) {
|
|
126
|
-
try {
|
|
127
|
-
subagentNotification = (
|
|
128
|
-
JSON.parse(m.metadata) as {
|
|
129
|
-
subagentNotification?: ParsedHistoryMessage["subagentNotification"];
|
|
130
|
-
}
|
|
131
|
-
).subagentNotification;
|
|
132
|
-
} catch (err) {
|
|
133
|
-
log.debug(
|
|
134
|
-
{ err, messageId: m.id },
|
|
135
|
-
"Failed to parse message metadata as JSON, ignoring",
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
id: m.id,
|
|
141
|
-
role: m.role,
|
|
142
|
-
text,
|
|
143
|
-
timestamp: m.createdAt,
|
|
144
|
-
toolCalls,
|
|
145
|
-
toolCallsBeforeText,
|
|
146
|
-
textSegments,
|
|
147
|
-
contentOrder,
|
|
148
|
-
surfaces,
|
|
149
|
-
...(subagentNotification ? { subagentNotification } : {}),
|
|
150
|
-
};
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const historyMessages = parsed.map((m) => {
|
|
154
|
-
let attachments: UserMessageAttachment[] | undefined;
|
|
155
|
-
if (m.role === "assistant" && m.id) {
|
|
156
|
-
const linked = getAttachmentsForMessage(m.id);
|
|
157
|
-
if (linked.length > 0) {
|
|
158
|
-
if (includeAttachments) {
|
|
159
|
-
// Full attachment data: same behavior as before
|
|
160
|
-
const MAX_INLINE_B64_SIZE = 512 * 1024;
|
|
161
|
-
attachments = linked.map((a) => {
|
|
162
|
-
const isFileBacked = !a.dataBase64;
|
|
163
|
-
const omit =
|
|
164
|
-
isFileBacked ||
|
|
165
|
-
(a.mimeType.startsWith("video/") &&
|
|
166
|
-
a.dataBase64.length > MAX_INLINE_B64_SIZE);
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
a.mimeType.startsWith("video/") &&
|
|
170
|
-
!a.thumbnailBase64 &&
|
|
171
|
-
a.dataBase64
|
|
172
|
-
) {
|
|
173
|
-
const attachmentId = a.id;
|
|
174
|
-
const base64 = a.dataBase64;
|
|
175
|
-
silentlyWithLog(
|
|
176
|
-
generateVideoThumbnail(base64).then((thumb) => {
|
|
177
|
-
if (thumb) setAttachmentThumbnail(attachmentId, thumb);
|
|
178
|
-
}),
|
|
179
|
-
"video thumbnail generation",
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const fp = getFilePathForAttachment(a.id);
|
|
184
|
-
return {
|
|
185
|
-
id: a.id,
|
|
186
|
-
filename: a.originalFilename,
|
|
187
|
-
mimeType: a.mimeType,
|
|
188
|
-
data: omit ? "" : a.dataBase64,
|
|
189
|
-
...(omit ? { sizeBytes: a.sizeBytes } : {}),
|
|
190
|
-
...(a.thumbnailBase64
|
|
191
|
-
? { thumbnailData: a.thumbnailBase64 }
|
|
192
|
-
: {}),
|
|
193
|
-
...(fp ? { filePath: fp } : {}),
|
|
194
|
-
};
|
|
195
|
-
});
|
|
196
|
-
} else {
|
|
197
|
-
// Light mode: metadata only, strip base64 data
|
|
198
|
-
attachments = linked.map((a) => {
|
|
199
|
-
const fp = getFilePathForAttachment(a.id);
|
|
200
|
-
return {
|
|
201
|
-
id: a.id,
|
|
202
|
-
filename: a.originalFilename,
|
|
203
|
-
mimeType: a.mimeType,
|
|
204
|
-
data: "",
|
|
205
|
-
sizeBytes: a.sizeBytes,
|
|
206
|
-
...(a.thumbnailBase64
|
|
207
|
-
? { thumbnailData: a.thumbnailBase64 }
|
|
208
|
-
: {}),
|
|
209
|
-
...(fp ? { filePath: fp } : {}),
|
|
210
|
-
};
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// In light mode, strip imageData from tool calls
|
|
217
|
-
const filteredToolCalls =
|
|
218
|
-
m.toolCalls.length > 0
|
|
219
|
-
? includeToolImages
|
|
220
|
-
? m.toolCalls
|
|
221
|
-
: m.toolCalls.map((tc) => {
|
|
222
|
-
if (tc.imageData) {
|
|
223
|
-
const { imageData: _, ...rest } = tc;
|
|
224
|
-
return rest;
|
|
225
|
-
}
|
|
226
|
-
return tc;
|
|
227
|
-
})
|
|
228
|
-
: m.toolCalls;
|
|
229
|
-
|
|
230
|
-
// In light mode, strip heavy payloads but keep essential fields so
|
|
231
|
-
// the client can still parse and render surfaces (e.g. card title/body,
|
|
232
|
-
// dynamic_page preview, document_preview metadata).
|
|
233
|
-
const filteredSurfaces =
|
|
234
|
-
m.surfaces.length > 0
|
|
235
|
-
? includeSurfaceData
|
|
236
|
-
? m.surfaces
|
|
237
|
-
: m.surfaces.map((s) => ({
|
|
238
|
-
surfaceId: s.surfaceId,
|
|
239
|
-
surfaceType: s.surfaceType,
|
|
240
|
-
title: s.title,
|
|
241
|
-
data: lightModeSurfaceData(s),
|
|
242
|
-
...(s.actions ? { actions: s.actions } : {}),
|
|
243
|
-
...(s.display ? { display: s.display } : {}),
|
|
244
|
-
}))
|
|
245
|
-
: m.surfaces;
|
|
246
|
-
|
|
247
|
-
// Apply text truncation when maxTextChars is set
|
|
248
|
-
let wasTruncated = false;
|
|
249
|
-
let textWasTruncated = false;
|
|
250
|
-
let text = m.text;
|
|
251
|
-
if (msg.maxTextChars !== undefined && text.length > msg.maxTextChars) {
|
|
252
|
-
text = text.slice(0, msg.maxTextChars) + " \u2026 [truncated]";
|
|
253
|
-
wasTruncated = true;
|
|
254
|
-
textWasTruncated = true;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Apply tool result truncation when maxToolResultChars is set
|
|
258
|
-
const truncatedToolCalls =
|
|
259
|
-
msg.maxToolResultChars !== undefined && filteredToolCalls.length > 0
|
|
260
|
-
? filteredToolCalls.map((tc) => {
|
|
261
|
-
if (
|
|
262
|
-
tc.result !== undefined &&
|
|
263
|
-
tc.result.length > msg.maxToolResultChars!
|
|
264
|
-
) {
|
|
265
|
-
wasTruncated = true;
|
|
266
|
-
return {
|
|
267
|
-
...tc,
|
|
268
|
-
result:
|
|
269
|
-
tc.result.slice(0, msg.maxToolResultChars!) +
|
|
270
|
-
" \u2026 [truncated]",
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return tc;
|
|
274
|
-
})
|
|
275
|
-
: filteredToolCalls;
|
|
276
|
-
|
|
277
|
-
return {
|
|
278
|
-
...(m.id ? { id: m.id } : {}),
|
|
279
|
-
role: m.role,
|
|
280
|
-
text,
|
|
281
|
-
timestamp: m.timestamp,
|
|
282
|
-
...(truncatedToolCalls.length > 0
|
|
283
|
-
? {
|
|
284
|
-
toolCalls: truncatedToolCalls,
|
|
285
|
-
toolCallsBeforeText: m.toolCallsBeforeText,
|
|
286
|
-
}
|
|
287
|
-
: {}),
|
|
288
|
-
...(attachments ? { attachments } : {}),
|
|
289
|
-
...(!textWasTruncated && m.textSegments.length > 0
|
|
290
|
-
? { textSegments: m.textSegments }
|
|
291
|
-
: {}),
|
|
292
|
-
...(!textWasTruncated && m.contentOrder.length > 0
|
|
293
|
-
? { contentOrder: m.contentOrder }
|
|
294
|
-
: {}),
|
|
295
|
-
...(filteredSurfaces.length > 0 ? { surfaces: filteredSurfaces } : {}),
|
|
296
|
-
...(m.subagentNotification
|
|
297
|
-
? { subagentNotification: m.subagentNotification }
|
|
298
|
-
: {}),
|
|
299
|
-
...(wasTruncated ? { wasTruncated: true } : {}),
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const oldestTimestamp =
|
|
304
|
-
historyMessages.length > 0 ? historyMessages[0].timestamp : undefined;
|
|
305
|
-
// Provide the oldest message ID as a tie-breaker cursor so clients can
|
|
306
|
-
// paginate without skipping same-millisecond messages at page boundaries.
|
|
307
|
-
const oldestMessageId =
|
|
308
|
-
historyMessages.length > 0 ? historyMessages[0].id : undefined;
|
|
309
|
-
|
|
310
|
-
ctx.send({
|
|
311
|
-
type: "history_response",
|
|
312
|
-
sessionId: msg.sessionId,
|
|
313
|
-
messages: historyMessages,
|
|
314
|
-
hasMore,
|
|
315
|
-
...(oldestTimestamp !== undefined ? { oldestTimestamp } : {}),
|
|
316
|
-
...(oldestMessageId ? { oldestMessageId } : {}),
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Surfaces are now included directly in the history_response message (in the surfaces array),
|
|
320
|
-
// so we no longer emit separate ui_surface_show messages during history loading.
|
|
321
|
-
}
|
|
6
|
+
import { renderHistoryContent } from "./shared.js";
|
|
322
7
|
|
|
323
8
|
// ---------------------------------------------------------------------------
|
|
324
9
|
// Shared business logic (transport-agnostic)
|
|
@@ -400,45 +85,3 @@ export function getMessageContent(
|
|
|
400
85
|
...(toolCalls ? { toolCalls } : {}),
|
|
401
86
|
};
|
|
402
87
|
}
|
|
403
|
-
|
|
404
|
-
// ---------------------------------------------------------------------------
|
|
405
|
-
// HTTP handlers (delegate to shared logic)
|
|
406
|
-
// ---------------------------------------------------------------------------
|
|
407
|
-
|
|
408
|
-
export function handleConversationSearch(
|
|
409
|
-
msg: ConversationSearchRequest,
|
|
410
|
-
ctx: HandlerContext,
|
|
411
|
-
): void {
|
|
412
|
-
const results = performConversationSearch({
|
|
413
|
-
query: msg.query,
|
|
414
|
-
limit: msg.limit,
|
|
415
|
-
maxMessagesPerConversation: msg.maxMessagesPerConversation,
|
|
416
|
-
});
|
|
417
|
-
ctx.send({
|
|
418
|
-
type: "conversation_search_response",
|
|
419
|
-
query: msg.query,
|
|
420
|
-
results,
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
export function handleMessageContentRequest(
|
|
425
|
-
msg: MessageContentRequest,
|
|
426
|
-
ctx: HandlerContext,
|
|
427
|
-
): void {
|
|
428
|
-
const result = getMessageContent(msg.messageId, msg.sessionId);
|
|
429
|
-
if (!result) {
|
|
430
|
-
ctx.send({
|
|
431
|
-
type: "error",
|
|
432
|
-
message: `Message ${msg.messageId} not found in session ${msg.sessionId}`,
|
|
433
|
-
});
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
ctx.send({
|
|
438
|
-
type: "message_content_response",
|
|
439
|
-
sessionId: msg.sessionId,
|
|
440
|
-
messageId: msg.messageId,
|
|
441
|
-
...(result.text !== undefined ? { text: result.text } : {}),
|
|
442
|
-
...(result.toolCalls ? { toolCalls: result.toolCalls } : {}),
|
|
443
|
-
});
|
|
444
|
-
}
|
|
@@ -421,8 +421,11 @@ export async function handleSessionCreate(
|
|
|
421
421
|
pendingInteractions.resolve(requestId);
|
|
422
422
|
});
|
|
423
423
|
session.setHostFileProxy(fileProxy);
|
|
424
|
-
const cuProxy = new HostCuProxy(sendEvent)
|
|
424
|
+
const cuProxy = new HostCuProxy(sendEvent, (requestId) => {
|
|
425
|
+
pendingInteractions.resolve(requestId);
|
|
426
|
+
});
|
|
425
427
|
session.setHostCuProxy(cuProxy);
|
|
428
|
+
session.addPreactivatedSkillId("computer-use");
|
|
426
429
|
}
|
|
427
430
|
session.updateClient(sendEvent, false);
|
|
428
431
|
session
|
|
@@ -575,23 +578,24 @@ export function handleCancel(msg: CancelRequest, ctx: HandlerContext): void {
|
|
|
575
578
|
}
|
|
576
579
|
|
|
577
580
|
/**
|
|
578
|
-
* Undo the last message in a session. Returns the removed count, or null if
|
|
581
|
+
* Undo the last message in a session. Returns the removed count, or null if
|
|
582
|
+
* the conversation does not exist. Restores evicted sessions from the database.
|
|
579
583
|
*/
|
|
580
|
-
export function undoLastMessage(
|
|
584
|
+
export async function undoLastMessage(
|
|
581
585
|
sessionId: string,
|
|
582
586
|
ctx: HandlerContext,
|
|
583
|
-
): { removedCount: number } | null {
|
|
584
|
-
|
|
585
|
-
if (!session) {
|
|
587
|
+
): Promise<{ removedCount: number } | null> {
|
|
588
|
+
if (!getConversation(sessionId)) {
|
|
586
589
|
return null;
|
|
587
590
|
}
|
|
591
|
+
const session = await ctx.getOrCreateSession(sessionId);
|
|
588
592
|
ctx.touchSession(sessionId);
|
|
589
593
|
const removedCount = session.undo();
|
|
590
594
|
return { removedCount };
|
|
591
595
|
}
|
|
592
596
|
|
|
593
|
-
export function handleUndo(msg: UndoRequest, ctx: HandlerContext): void {
|
|
594
|
-
const result = undoLastMessage(msg.sessionId, ctx);
|
|
597
|
+
export async function handleUndo(msg: UndoRequest, ctx: HandlerContext): Promise<void> {
|
|
598
|
+
const result = await undoLastMessage(msg.sessionId, ctx);
|
|
595
599
|
if (!result) {
|
|
596
600
|
ctx.send({ type: "error", message: "No active session" });
|
|
597
601
|
return;
|
|
@@ -606,17 +610,18 @@ export function handleUndo(msg: UndoRequest, ctx: HandlerContext): void {
|
|
|
606
610
|
/**
|
|
607
611
|
* Regenerate the last assistant response for a session. The caller provides
|
|
608
612
|
* a `sendEvent` callback for delivering streaming events via HTTP/SSE.
|
|
609
|
-
* Returns null if the
|
|
613
|
+
* Returns null if the conversation does not exist. Restores evicted sessions
|
|
614
|
+
* from the database when needed. Throws on regeneration errors.
|
|
610
615
|
*/
|
|
611
616
|
export async function regenerateResponse(
|
|
612
617
|
sessionId: string,
|
|
613
618
|
ctx: HandlerContext,
|
|
614
619
|
sendEvent: (event: ServerMessage) => void,
|
|
615
620
|
): Promise<{ requestId: string } | null> {
|
|
616
|
-
|
|
617
|
-
if (!session) {
|
|
621
|
+
if (!getConversation(sessionId)) {
|
|
618
622
|
return null;
|
|
619
623
|
}
|
|
624
|
+
const session = await ctx.getOrCreateSession(sessionId);
|
|
620
625
|
ctx.touchSession(sessionId);
|
|
621
626
|
session.updateClient(sendEvent, false);
|
|
622
627
|
const requestId = uuid();
|
|
@@ -647,11 +652,11 @@ export async function handleRegenerate(
|
|
|
647
652
|
msg: RegenerateRequest,
|
|
648
653
|
ctx: HandlerContext,
|
|
649
654
|
): Promise<void> {
|
|
650
|
-
|
|
651
|
-
if (!session) {
|
|
655
|
+
if (!getConversation(msg.sessionId)) {
|
|
652
656
|
ctx.send({ type: "error", message: "No active session" });
|
|
653
657
|
return;
|
|
654
658
|
}
|
|
659
|
+
const session = await ctx.getOrCreateSession(msg.sessionId);
|
|
655
660
|
|
|
656
661
|
const regenerateChannel =
|
|
657
662
|
parseChannelId(session.getTurnChannelContext()?.assistantMessageChannel) ??
|
|
@@ -176,22 +176,6 @@ function clampAttachmentText(text: string): string {
|
|
|
176
176
|
return `${text.slice(0, HISTORY_ATTACHMENT_TEXT_LIMIT)}<truncated />`;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
function renderImageBlockForHistory(block: Record<string, unknown>): string {
|
|
180
|
-
const source = isRecord(block.source) ? block.source : null;
|
|
181
|
-
const mediaType =
|
|
182
|
-
source && typeof source.media_type === "string"
|
|
183
|
-
? source.media_type
|
|
184
|
-
: "image/*";
|
|
185
|
-
const sizeBytes =
|
|
186
|
-
source && typeof source.data === "string"
|
|
187
|
-
? estimateBase64Bytes(source.data)
|
|
188
|
-
: 0;
|
|
189
|
-
if (sizeBytes <= 0) {
|
|
190
|
-
return `[Image attachment] ${mediaType}`;
|
|
191
|
-
}
|
|
192
|
-
return `[Image attachment] ${mediaType}, ${formatBytes(sizeBytes)}`;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
179
|
function renderFileBlockForHistory(block: Record<string, unknown>): string {
|
|
196
180
|
const source = isRecord(block.source) ? block.source : null;
|
|
197
181
|
const mediaType =
|
|
@@ -328,7 +312,9 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
|
|
|
328
312
|
continue;
|
|
329
313
|
}
|
|
330
314
|
if (block.type === "image") {
|
|
331
|
-
|
|
315
|
+
// Image data is sent as a separate attachment — skip the placeholder
|
|
316
|
+
// text so the client doesn't render both "[Image attachment]" and the
|
|
317
|
+
// actual image thumbnail.
|
|
332
318
|
continue;
|
|
333
319
|
}
|
|
334
320
|
if (block.type === "tool_use") {
|
|
@@ -250,12 +250,20 @@ export interface SkillListItem {
|
|
|
250
250
|
provenance: SkillProvenance;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
/** Sorting rank for provenance-based ordering: first-party first, local last. */
|
|
254
|
+
function provenanceSortRank(p: SkillProvenance): number {
|
|
255
|
+
if (p.kind === "first-party") return 0;
|
|
256
|
+
if (p.kind === "third-party" && p.provider) return 1;
|
|
257
|
+
if (p.kind === "third-party") return 2;
|
|
258
|
+
return 3; // local
|
|
259
|
+
}
|
|
260
|
+
|
|
253
261
|
export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
|
|
254
262
|
const config = getConfig();
|
|
255
263
|
const catalog = loadSkillCatalog();
|
|
256
264
|
const resolved = resolveSkillStates(catalog, config);
|
|
257
265
|
|
|
258
|
-
|
|
266
|
+
const items = resolved.map((r) => ({
|
|
259
267
|
id: r.summary.id,
|
|
260
268
|
name: r.summary.displayName,
|
|
261
269
|
description: r.summary.description,
|
|
@@ -272,6 +280,17 @@ export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
|
|
|
272
280
|
userInvocable: r.summary.userInvocable,
|
|
273
281
|
provenance: resolveProvenance(r.summary),
|
|
274
282
|
}));
|
|
283
|
+
|
|
284
|
+
// Sort: first-party > third-party with provider > third-party without > local,
|
|
285
|
+
// alphabetical by name within each tier.
|
|
286
|
+
items.sort((a, b) => {
|
|
287
|
+
const rankDiff =
|
|
288
|
+
provenanceSortRank(a.provenance) - provenanceSortRank(b.provenance);
|
|
289
|
+
if (rankDiff !== 0) return rankDiff;
|
|
290
|
+
return a.name.localeCompare(b.name);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return items;
|
|
275
294
|
}
|
|
276
295
|
|
|
277
296
|
export function enableSkill(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ContentBlock,
|
|
3
3
|
Message,
|
|
4
|
+
ServerToolUseContent,
|
|
4
5
|
ToolResultContent,
|
|
5
6
|
ToolUseContent,
|
|
6
7
|
} from "../providers/types.js";
|
|
@@ -20,6 +21,11 @@ export interface RepairResult {
|
|
|
20
21
|
const SYNTHETIC_RESULT =
|
|
21
22
|
"<synthesized_result>tool result missing from history</synthesized_result>";
|
|
22
23
|
|
|
24
|
+
const SYNTHETIC_WEB_SEARCH_ERROR = {
|
|
25
|
+
type: "web_search_tool_result_error",
|
|
26
|
+
error_code: "unavailable",
|
|
27
|
+
};
|
|
28
|
+
|
|
23
29
|
export function repairHistory(messages: Message[]): RepairResult {
|
|
24
30
|
const stats: RepairStats = {
|
|
25
31
|
assistantToolResultsMigrated: 0,
|
|
@@ -45,12 +51,15 @@ export function repairHistory(messages: Message[]): RepairResult {
|
|
|
45
51
|
recoveredResults = new Map();
|
|
46
52
|
}
|
|
47
53
|
|
|
48
|
-
// Strip tool_result blocks from assistant messages,
|
|
49
|
-
// so they can be migrated to the correct user message
|
|
54
|
+
// Strip client-side tool_result blocks from assistant messages,
|
|
55
|
+
// preserving them so they can be migrated to the correct user message.
|
|
56
|
+
// Server-side tools (server_tool_use / web_search_tool_result) are
|
|
57
|
+
// self-paired within the assistant message and must NOT be separated.
|
|
50
58
|
const cleanedContent: ContentBlock[] = [];
|
|
51
59
|
const newRecovered = new Map<string, ToolResultContent>();
|
|
52
60
|
for (const block of msg.content) {
|
|
53
61
|
if (block.type === "tool_result") {
|
|
62
|
+
// guard:allow-tool-result-only — only client-side tool_result belongs in recovered; web_search_tool_result stays in the assistant message
|
|
54
63
|
const tr = block as ToolResultContent;
|
|
55
64
|
newRecovered.set(tr.tool_use_id, tr);
|
|
56
65
|
stats.assistantToolResultsMigrated++;
|
|
@@ -59,9 +68,34 @@ export function repairHistory(messages: Message[]): RepairResult {
|
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
70
|
|
|
71
|
+
// Ensure every server_tool_use has a paired web_search_tool_result
|
|
72
|
+
// in the same assistant message (handles interrupted streams)
|
|
73
|
+
const serverToolIds = new Set(
|
|
74
|
+
cleanedContent
|
|
75
|
+
.filter(
|
|
76
|
+
(b): b is ServerToolUseContent => b.type === "server_tool_use",
|
|
77
|
+
)
|
|
78
|
+
.map((b) => b.id),
|
|
79
|
+
);
|
|
80
|
+
const matchedServerIds = new Set(
|
|
81
|
+
cleanedContent
|
|
82
|
+
.filter((b) => b.type === "web_search_tool_result")
|
|
83
|
+
.map((b) => (b as { tool_use_id: string }).tool_use_id),
|
|
84
|
+
);
|
|
85
|
+
for (const id of serverToolIds) {
|
|
86
|
+
if (!matchedServerIds.has(id)) {
|
|
87
|
+
cleanedContent.push({
|
|
88
|
+
type: "web_search_tool_result",
|
|
89
|
+
tool_use_id: id,
|
|
90
|
+
content: SYNTHETIC_WEB_SEARCH_ERROR,
|
|
91
|
+
});
|
|
92
|
+
stats.missingToolResultsInserted++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
62
96
|
result.push({ role: "assistant", content: cleanedContent });
|
|
63
97
|
|
|
64
|
-
//
|
|
98
|
+
// Only track client-side tool_use IDs as pending (not server_tool_use)
|
|
65
99
|
pendingToolUseIds = new Set(
|
|
66
100
|
cleanedContent
|
|
67
101
|
.filter((b): b is ToolUseContent => b.type === "tool_use")
|
|
@@ -76,14 +110,28 @@ export function repairHistory(messages: Message[]): RepairResult {
|
|
|
76
110
|
|
|
77
111
|
for (const block of msg.content) {
|
|
78
112
|
if (block.type === "tool_result") {
|
|
113
|
+
// guard:allow-tool-result-only — matches client-side tool_use; web_search_tool_result is handled separately below
|
|
79
114
|
const tr = block as ToolResultContent;
|
|
80
115
|
if (pendingToolUseIds.has(tr.tool_use_id)) {
|
|
81
116
|
matchedIds.add(tr.tool_use_id);
|
|
82
117
|
newContent.push(block);
|
|
83
118
|
} else {
|
|
84
119
|
stats.orphanToolResultsDowngraded++;
|
|
85
|
-
newContent.push(
|
|
120
|
+
newContent.push(downgradeResult(tr));
|
|
86
121
|
}
|
|
122
|
+
} else if (block.type === "web_search_tool_result") {
|
|
123
|
+
// web_search_tool_result in a user message is orphaned — server-side
|
|
124
|
+
// results belong in the assistant message, not here
|
|
125
|
+
stats.orphanToolResultsDowngraded++;
|
|
126
|
+
newContent.push(
|
|
127
|
+
downgradeResult(
|
|
128
|
+
block as {
|
|
129
|
+
type: "web_search_tool_result";
|
|
130
|
+
tool_use_id: string;
|
|
131
|
+
content: unknown;
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
);
|
|
87
135
|
} else {
|
|
88
136
|
newContent.push(block);
|
|
89
137
|
}
|
|
@@ -112,11 +160,21 @@ export function repairHistory(messages: Message[]): RepairResult {
|
|
|
112
160
|
pendingToolUseIds = new Set();
|
|
113
161
|
recoveredResults = new Map();
|
|
114
162
|
} else {
|
|
115
|
-
// No pending tool_use — any tool_result here is orphaned
|
|
163
|
+
// No pending tool_use — any tool_result/web_search_tool_result here is orphaned
|
|
116
164
|
const newContent: ContentBlock[] = msg.content.map((block) => {
|
|
117
165
|
if (block.type === "tool_result") {
|
|
118
166
|
stats.orphanToolResultsDowngraded++;
|
|
119
|
-
return
|
|
167
|
+
return downgradeResult(block as ToolResultContent);
|
|
168
|
+
}
|
|
169
|
+
if (block.type === "web_search_tool_result") {
|
|
170
|
+
stats.orphanToolResultsDowngraded++;
|
|
171
|
+
return downgradeResult(
|
|
172
|
+
block as {
|
|
173
|
+
type: "web_search_tool_result";
|
|
174
|
+
tool_use_id: string;
|
|
175
|
+
content: unknown;
|
|
176
|
+
},
|
|
177
|
+
);
|
|
120
178
|
}
|
|
121
179
|
return block;
|
|
122
180
|
});
|
|
@@ -207,9 +265,15 @@ export function deepRepairHistory(messages: Message[]): RepairResult {
|
|
|
207
265
|
return repairHistory(merged);
|
|
208
266
|
}
|
|
209
267
|
|
|
210
|
-
function
|
|
268
|
+
function downgradeResult(tr: {
|
|
269
|
+
type: string;
|
|
270
|
+
tool_use_id: string;
|
|
271
|
+
content?: unknown;
|
|
272
|
+
}): ContentBlock {
|
|
273
|
+
const content =
|
|
274
|
+
tr.type === "tool_result" ? tr.content : "[web search result]"; // guard:allow-tool-result-only — distinguishes content format between the two types
|
|
211
275
|
return {
|
|
212
276
|
type: "text",
|
|
213
|
-
text: `[orphaned
|
|
277
|
+
text: `[orphaned ${tr.type} for ${tr.tool_use_id}]: ${content}`,
|
|
214
278
|
};
|
|
215
279
|
}
|