@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 0.8.8
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/bun.lock +2 -2
- package/docs/plugins.md +832 -0
- package/examples/plugins/echo/README.md +60 -61
- package/examples/plugins/echo/package.json +2 -1
- package/examples/plugins/echo/register.ts +143 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
- package/openapi.yaml +5 -15
- package/package.json +2 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -4
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
- package/src/__tests__/conversation-agent-loop.test.ts +39 -42
- package/src/__tests__/conversation-clean-command.test.ts +2 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
- package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
- package/src/__tests__/conversation-starter-routes.test.ts +6 -14
- package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
- package/src/__tests__/db-acp-history.test.ts +0 -101
- package/src/__tests__/dynamic-page-surface.test.ts +0 -31
- package/src/__tests__/file-write-tool.test.ts +0 -63
- package/src/__tests__/gateway-only-guard.test.ts +2 -12
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/injector-background-turn.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +6 -34
- package/src/__tests__/injector-disk-pressure.test.ts +34 -77
- package/src/__tests__/injector-document-comments.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
- package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
- package/src/__tests__/pipeline-runner.test.ts +554 -0
- package/src/__tests__/plugin-api-shim.test.ts +6 -3
- package/src/__tests__/plugin-bootstrap.test.ts +23 -12
- package/src/__tests__/plugin-registry.test.ts +49 -3
- package/src/__tests__/plugin-types.test.ts +70 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +1 -4
- package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +3 -1
- package/src/__tests__/subagent-notify-parent.test.ts +3 -1
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +286 -54
- package/src/acp/__tests__/client-handler.test.ts +0 -40
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
- package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
- package/src/acp/agent-process.ts +1 -61
- package/src/acp/client-handler.ts +0 -31
- package/src/acp/prepare-agent-env.ts +29 -83
- package/src/acp/resolve-agent.test.ts +7 -320
- package/src/acp/resolve-agent.ts +18 -182
- package/src/acp/session-manager.ts +73 -495
- package/src/acp/types.ts +0 -8
- package/src/agent/compaction-circuit.ts +102 -60
- package/src/agent/loop.ts +59 -32
- package/src/api/responses/conversation-message.ts +1 -7
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +0 -1
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +31 -83
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
- package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
- package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +5 -14
- package/src/config/schemas/heartbeat.ts +0 -9
- package/src/context/strip-injections.ts +2 -8
- package/src/context/window-manager.ts +1 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
- package/src/daemon/conversation-agent-loop.ts +279 -62
- package/src/daemon/conversation-runtime-assembly.ts +69 -106
- package/src/daemon/conversation-store.ts +90 -9
- package/src/daemon/conversation-workspace.ts +0 -17
- package/src/daemon/conversation.ts +6 -0
- package/src/daemon/external-plugins-bootstrap.ts +11 -11
- package/src/daemon/handlers/conversations.ts +1 -3
- package/src/daemon/handlers/skills.ts +1 -4
- package/src/daemon/lifecycle.ts +0 -21
- package/src/daemon/server.ts +0 -2
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
- package/src/heartbeat/heartbeat-run-store.ts +1 -23
- package/src/heartbeat/heartbeat-service.ts +0 -26
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
- package/src/ipc/skill-routes/memory.ts +2 -4
- package/src/memory/conversation-starter-checkpoints.ts +0 -1
- package/src/memory/db-init.ts +0 -2
- package/src/memory/job-handlers/conversation-starters.ts +2 -13
- package/src/memory/jobs-worker.ts +1 -1
- package/src/memory/migrations/index.ts +0 -1
- package/src/memory/schema/acp.ts +0 -4
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +4 -13
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
- package/src/plugin-api/index.ts +5 -0
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
- package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
- package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +19 -8
- package/src/plugins/defaults/compaction/terminal.ts +73 -0
- package/src/plugins/defaults/index.ts +5 -3
- package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
- package/src/plugins/defaults/overflow-reduce/package.json +15 -0
- package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +293 -6
- package/src/plugins/registry.ts +37 -9
- package/src/plugins/types.ts +336 -32
- package/src/plugins/user-loader.ts +127 -30
- package/src/proactive-artifact/aux-message-injector.ts +1 -1
- package/src/proactive-artifact/job.test.ts +1 -1
- package/src/prompts/__tests__/system-prompt.test.ts +0 -6
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
- package/src/runtime/__tests__/agent-wake.test.ts +5 -5
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +3 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
- package/src/runtime/routes/acp-routes.test.ts +25 -89
- package/src/runtime/routes/acp-routes.ts +29 -81
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +10 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -15
- package/src/runtime/routes/conversation-starter-routes.ts +7 -13
- package/src/runtime/routes/conversations-import-routes.ts +7 -24
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -18
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/memory-v3-routes.ts +6 -16
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
- package/src/runtime/routes/work-items-routes.ts +4 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +4 -2
- package/src/subagent/manager.ts +5 -17
- package/src/tools/acp/list-agents.test.ts +1 -7
- package/src/tools/acp/spawn.test.ts +55 -158
- package/src/tools/acp/spawn.ts +72 -47
- package/src/tools/acp/steer.test.ts +8 -105
- package/src/tools/acp/steer.ts +17 -48
- package/src/tools/apps/executors.ts +8 -13
- package/src/tools/filesystem/write.ts +0 -34
- package/src/tools/subagent/spawn.ts +4 -2
- package/src/tools/ui-surface/definitions.ts +4 -25
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
- package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
- package/examples/plugins/echo/hooks/stop.ts +0 -16
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
- package/examples/plugins/echo/src/emit.ts +0 -19
- package/src/__tests__/compaction-circuit.test.ts +0 -258
- package/src/__tests__/compaction-direct.test.ts +0 -132
- package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
- package/src/acp/__tests__/agent-process.test.ts +0 -161
- package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
- package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
- package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
- package/src/acp/auto-install.test.ts +0 -196
- package/src/acp/auto-install.ts +0 -177
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/acp/resume-hint.ts +0 -25
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
- package/src/daemon/conversation-registry.ts +0 -159
- package/src/daemon/overflow-reduction-loop.ts +0 -230
- package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
- package/src/plugins/defaults/compaction/compact.ts +0 -59
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
- package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
- package/src/tools/acp/context.ts +0 -20
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
|
@@ -35,10 +35,6 @@ mock.module("../memory/pkb/pkb-search.js", () => ({
|
|
|
35
35
|
},
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
import {
|
|
39
|
-
clearConversations,
|
|
40
|
-
setConversation,
|
|
41
|
-
} from "../daemon/conversation-registry.js";
|
|
42
38
|
import type {
|
|
43
39
|
ChannelCapabilities,
|
|
44
40
|
SlackTranscriptInputRow,
|
|
@@ -63,7 +59,6 @@ import {
|
|
|
63
59
|
stripInjectionsForCompaction,
|
|
64
60
|
stripNowScratchpad,
|
|
65
61
|
} from "../daemon/conversation-runtime-assembly.js";
|
|
66
|
-
import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
|
|
67
62
|
import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
|
|
68
63
|
import type { MessageRow } from "../memory/conversation-crud.js";
|
|
69
64
|
import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
@@ -108,62 +103,6 @@ function clearNowScratchpad(): void {
|
|
|
108
103
|
rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
|
|
109
104
|
}
|
|
110
105
|
|
|
111
|
-
// The workspace-context injector sources its block off the live `Conversation`
|
|
112
|
-
// looked up by `conversationId`. Register a fake instance carrying both a
|
|
113
|
-
// non-dirty workspace context (non-null content, not dirty) so
|
|
114
|
-
// `resolveWorkspaceTopLevelContext` returns it verbatim without rescanning the
|
|
115
|
-
// filesystem, and an active dynamic-page surface, so `applyRuntimeInjections`
|
|
116
|
-
// resolves the `<workspace>` and `<active_workspace>` blocks from it;
|
|
117
|
-
// `clearConversations()` between tests keeps suites that assert the blocks are
|
|
118
|
-
// absent unaffected.
|
|
119
|
-
function seedActiveSurfaceConversation(
|
|
120
|
-
conversationId: string,
|
|
121
|
-
workspaceText: string,
|
|
122
|
-
surfaceId: string,
|
|
123
|
-
data: SurfaceData,
|
|
124
|
-
channelCapabilities?: ChannelCapabilities,
|
|
125
|
-
commandIntent?: { type: string; payload?: string; languageCode?: string },
|
|
126
|
-
): void {
|
|
127
|
-
setConversation(conversationId, {
|
|
128
|
-
conversationId,
|
|
129
|
-
workingDir: "/sandbox",
|
|
130
|
-
workspaceTopLevelContext: workspaceText,
|
|
131
|
-
workspaceTopLevelDirty: false,
|
|
132
|
-
currentActiveSurfaceId: surfaceId,
|
|
133
|
-
surfaceState: new Map<
|
|
134
|
-
string,
|
|
135
|
-
{ surfaceType: SurfaceType; data: SurfaceData }
|
|
136
|
-
>([[surfaceId, { surfaceType: "dynamic_page", data }]]),
|
|
137
|
-
channelCapabilities: channelCapabilities ?? undefined,
|
|
138
|
-
commandIntent,
|
|
139
|
-
} as never);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function clearWorkspaceContext(): void {
|
|
143
|
-
clearConversations();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// The `<channel_capabilities>` branch and the Slack gates source the channel
|
|
147
|
-
// capabilities off the live `Conversation` looked up by `conversationId`.
|
|
148
|
-
// Register a fake fallback instance carrying the given capabilities (and a
|
|
149
|
-
// non-dirty empty workspace + empty surface so the other live-sourced branches
|
|
150
|
-
// stay inert) so `applyRuntimeInjections` resolves them; `clearConversations()`
|
|
151
|
-
// between tests keeps suites asserting absence unaffected.
|
|
152
|
-
function seedChannelCapabilitiesConversation(
|
|
153
|
-
caps: ChannelCapabilities | null,
|
|
154
|
-
transportHints?: string[],
|
|
155
|
-
): void {
|
|
156
|
-
setConversation("runtime-assembly-fallback", {
|
|
157
|
-
conversationId: "runtime-assembly-fallback",
|
|
158
|
-
workingDir: "/sandbox",
|
|
159
|
-
workspaceTopLevelContext: "",
|
|
160
|
-
workspaceTopLevelDirty: false,
|
|
161
|
-
surfaceState: new Map(),
|
|
162
|
-
channelCapabilities: caps ?? undefined,
|
|
163
|
-
transportHints,
|
|
164
|
-
} as never);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
106
|
// ---------------------------------------------------------------------------
|
|
168
107
|
// resolveChannelCapabilities
|
|
169
108
|
// ---------------------------------------------------------------------------
|
|
@@ -636,8 +575,6 @@ describe("stripChannelCapabilityContext", () => {
|
|
|
636
575
|
// ---------------------------------------------------------------------------
|
|
637
576
|
|
|
638
577
|
describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
639
|
-
afterEach(clearConversations);
|
|
640
|
-
|
|
641
578
|
const baseMessages: Message[] = [
|
|
642
579
|
{
|
|
643
580
|
role: "user",
|
|
@@ -653,8 +590,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
|
653
590
|
supportsVoiceInput: false,
|
|
654
591
|
};
|
|
655
592
|
|
|
656
|
-
|
|
657
|
-
|
|
593
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
594
|
+
channelCapabilities: caps,
|
|
595
|
+
});
|
|
658
596
|
|
|
659
597
|
expect(result.length).toBe(1);
|
|
660
598
|
expect(result[0].content.length).toBe(2);
|
|
@@ -665,8 +603,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
|
665
603
|
});
|
|
666
604
|
|
|
667
605
|
test("does not inject when channelCapabilities is null", async () => {
|
|
668
|
-
|
|
669
|
-
|
|
606
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
607
|
+
channelCapabilities: null,
|
|
608
|
+
});
|
|
670
609
|
|
|
671
610
|
expect(result.length).toBe(1);
|
|
672
611
|
expect(result[0].content.length).toBe(1);
|
|
@@ -687,8 +626,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
|
687
626
|
supportsVoiceInput: false,
|
|
688
627
|
};
|
|
689
628
|
|
|
690
|
-
|
|
691
|
-
|
|
629
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
630
|
+
channelCapabilities: caps,
|
|
631
|
+
});
|
|
692
632
|
|
|
693
633
|
expect(result.length).toBe(1);
|
|
694
634
|
// channelCapabilities prepends
|
|
@@ -815,14 +755,16 @@ describe("applyRuntimeInjections — injection mode", () => {
|
|
|
815
755
|
},
|
|
816
756
|
];
|
|
817
757
|
|
|
818
|
-
const channelCapabilities: ChannelCapabilities = {
|
|
819
|
-
channel: "telegram",
|
|
820
|
-
dashboardCapable: false,
|
|
821
|
-
supportsDynamicUi: false,
|
|
822
|
-
supportsVoiceInput: false,
|
|
823
|
-
};
|
|
824
|
-
|
|
825
758
|
const fullOptions = {
|
|
759
|
+
workspaceTopLevelContext: "<workspace>\nRoot: /sandbox\n</workspace>",
|
|
760
|
+
channelCommandContext: { type: "start" } as const,
|
|
761
|
+
activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
|
|
762
|
+
channelCapabilities: {
|
|
763
|
+
channel: "telegram",
|
|
764
|
+
dashboardCapable: false,
|
|
765
|
+
supportsDynamicUi: false,
|
|
766
|
+
supportsVoiceInput: false,
|
|
767
|
+
} as ChannelCapabilities,
|
|
826
768
|
unifiedTurnContext:
|
|
827
769
|
"<turn_context>\ncurrent_time: 2026-03-04 (Tuesday) 12:00:00 +00:00 (UTC)\ninterface: telegram\n</turn_context>",
|
|
828
770
|
isNonInteractive: true,
|
|
@@ -846,19 +788,10 @@ describe("applyRuntimeInjections — injection mode", () => {
|
|
|
846
788
|
beforeEach(() => {
|
|
847
789
|
seedPkbContent();
|
|
848
790
|
seedNowScratchpad("Current focus: shipping PR 3");
|
|
849
|
-
seedActiveSurfaceConversation(
|
|
850
|
-
"injection-mode-conv",
|
|
851
|
-
"<workspace>\nRoot: /sandbox\n</workspace>",
|
|
852
|
-
"sf_1",
|
|
853
|
-
{ html: "<div>test</div>" },
|
|
854
|
-
channelCapabilities,
|
|
855
|
-
{ type: "start" },
|
|
856
|
-
);
|
|
857
791
|
});
|
|
858
792
|
afterEach(() => {
|
|
859
793
|
clearPkbContent();
|
|
860
794
|
clearNowScratchpad();
|
|
861
|
-
clearWorkspaceContext();
|
|
862
795
|
});
|
|
863
796
|
|
|
864
797
|
test("full mode (default) includes all injections", async () => {
|
|
@@ -1118,7 +1051,7 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
|
|
|
1118
1051
|
).toContain("<turn_context>");
|
|
1119
1052
|
});
|
|
1120
1053
|
|
|
1121
|
-
test("<workspace> blocks
|
|
1054
|
+
test("<workspace> blocks are NOT stripped", () => {
|
|
1122
1055
|
const messages: Message[] = [
|
|
1123
1056
|
{
|
|
1124
1057
|
role: "user",
|
|
@@ -1134,10 +1067,10 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
|
|
|
1134
1067
|
|
|
1135
1068
|
const result = stripInjectionsForCompaction(messages);
|
|
1136
1069
|
expect(result.length).toBe(1);
|
|
1137
|
-
expect(result[0].content.length).toBe(
|
|
1138
|
-
expect(
|
|
1139
|
-
"
|
|
1140
|
-
);
|
|
1070
|
+
expect(result[0].content.length).toBe(2);
|
|
1071
|
+
expect(
|
|
1072
|
+
(result[0].content[0] as { type: "text"; text: string }).text,
|
|
1073
|
+
).toContain("<workspace>");
|
|
1141
1074
|
});
|
|
1142
1075
|
|
|
1143
1076
|
test("legacy <workspace_top_level> blocks ARE stripped for backward compat", () => {
|
|
@@ -2419,8 +2352,6 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2419
2352
|
// ---------------------------------------------------------------------------
|
|
2420
2353
|
|
|
2421
2354
|
describe("Slack channel chronological rendering — multi-thread", () => {
|
|
2422
|
-
afterEach(clearConversations);
|
|
2423
|
-
|
|
2424
2355
|
// Slack ts values are seconds-since-epoch with microsecond precision.
|
|
2425
2356
|
// Pick a few stable anchors so thread aliases (sha-derived) stay
|
|
2426
2357
|
// predictable across the scenarios.
|
|
@@ -2506,8 +2437,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
2506
2437
|
role: "user",
|
|
2507
2438
|
content: [{ type: "text", text: "current turn" }],
|
|
2508
2439
|
};
|
|
2509
|
-
seedChannelCapabilitiesConversation(slackChannelCaps);
|
|
2510
2440
|
const { messages } = await applyRuntimeInjections([lastUserMessage], {
|
|
2441
|
+
channelCapabilities: slackChannelCaps,
|
|
2511
2442
|
slackChronologicalMessages,
|
|
2512
2443
|
});
|
|
2513
2444
|
return messages;
|
|
@@ -2795,15 +2726,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
2795
2726
|
role: "user",
|
|
2796
2727
|
content: [{ type: "text", text: "vellum question" }],
|
|
2797
2728
|
};
|
|
2798
|
-
seedChannelCapabilitiesConversation({
|
|
2799
|
-
channel: "vellum",
|
|
2800
|
-
dashboardCapable: true,
|
|
2801
|
-
supportsDynamicUi: true,
|
|
2802
|
-
supportsVoiceInput: true,
|
|
2803
|
-
});
|
|
2804
2729
|
const { messages: result } = await applyRuntimeInjections(
|
|
2805
2730
|
[lastUserMessage],
|
|
2806
2731
|
{
|
|
2732
|
+
channelCapabilities: {
|
|
2733
|
+
channel: "vellum",
|
|
2734
|
+
dashboardCapable: true,
|
|
2735
|
+
supportsDynamicUi: true,
|
|
2736
|
+
supportsVoiceInput: true,
|
|
2737
|
+
},
|
|
2807
2738
|
// Even if we accidentally pass a chronological transcript, the
|
|
2808
2739
|
// branch must be a no-op for non-slack channels.
|
|
2809
2740
|
slackChronologicalMessages: [
|
|
@@ -2833,16 +2764,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
2833
2764
|
role: "user",
|
|
2834
2765
|
content: [{ type: "text", text: "DM question" }],
|
|
2835
2766
|
};
|
|
2836
|
-
seedChannelCapabilitiesConversation({
|
|
2837
|
-
channel: "slack",
|
|
2838
|
-
dashboardCapable: false,
|
|
2839
|
-
supportsDynamicUi: false,
|
|
2840
|
-
supportsVoiceInput: false,
|
|
2841
|
-
chatType: "im",
|
|
2842
|
-
});
|
|
2843
2767
|
const { messages: result } = await applyRuntimeInjections(
|
|
2844
2768
|
[lastUserMessage],
|
|
2845
2769
|
{
|
|
2770
|
+
channelCapabilities: {
|
|
2771
|
+
channel: "slack",
|
|
2772
|
+
dashboardCapable: false,
|
|
2773
|
+
supportsDynamicUi: false,
|
|
2774
|
+
supportsVoiceInput: false,
|
|
2775
|
+
chatType: "im",
|
|
2776
|
+
},
|
|
2846
2777
|
slackChronologicalMessages: [
|
|
2847
2778
|
{
|
|
2848
2779
|
role: "user",
|
|
@@ -2901,10 +2832,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
2901
2832
|
],
|
|
2902
2833
|
},
|
|
2903
2834
|
];
|
|
2904
|
-
seedChannelCapabilitiesConversation(slackCaps);
|
|
2905
2835
|
const { messages: result } = await applyRuntimeInjections(
|
|
2906
2836
|
runMessagesWithMemory,
|
|
2907
2837
|
{
|
|
2838
|
+
channelCapabilities: slackCaps,
|
|
2908
2839
|
slackChronologicalMessages: [
|
|
2909
2840
|
{
|
|
2910
2841
|
role: "user",
|
|
@@ -2965,10 +2896,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
2965
2896
|
],
|
|
2966
2897
|
},
|
|
2967
2898
|
];
|
|
2968
|
-
seedChannelCapabilitiesConversation(slackCaps);
|
|
2969
2899
|
const { messages: result } = await applyRuntimeInjections(
|
|
2970
2900
|
runMessagesWithMemory,
|
|
2971
2901
|
{
|
|
2902
|
+
channelCapabilities: slackCaps,
|
|
2972
2903
|
slackChronologicalMessages: [
|
|
2973
2904
|
{
|
|
2974
2905
|
role: "user",
|
|
@@ -3004,10 +2935,10 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3004
2935
|
supportsVoiceInput: false,
|
|
3005
2936
|
chatType: "im",
|
|
3006
2937
|
};
|
|
3007
|
-
seedChannelCapabilitiesConversation(slackCaps);
|
|
3008
2938
|
const { messages: result } = await applyRuntimeInjections(
|
|
3009
2939
|
[{ role: "user", content: [{ type: "text", text: "inbound" }] }],
|
|
3010
2940
|
{
|
|
2941
|
+
channelCapabilities: slackCaps,
|
|
3011
2942
|
slackChronologicalMessages: [
|
|
3012
2943
|
{
|
|
3013
2944
|
role: "user",
|
|
@@ -3048,13 +2979,12 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3048
2979
|
{ loader: () => rows, trustClass: "guardian" },
|
|
3049
2980
|
);
|
|
3050
2981
|
|
|
3051
|
-
seedChannelCapabilitiesConversation(slackChannelCaps, [
|
|
3052
|
-
"thread context: ...",
|
|
3053
|
-
]);
|
|
3054
2982
|
const { messages: result } = await applyRuntimeInjections(
|
|
3055
2983
|
[{ role: "user", content: [{ type: "text", text: "current turn" }] }],
|
|
3056
2984
|
{
|
|
2985
|
+
channelCapabilities: slackChannelCaps,
|
|
3057
2986
|
slackChronologicalMessages,
|
|
2987
|
+
transportHints: ["thread context: ..."],
|
|
3058
2988
|
},
|
|
3059
2989
|
);
|
|
3060
2990
|
|
|
@@ -3079,10 +3009,12 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3079
3009
|
chatType: "im",
|
|
3080
3010
|
};
|
|
3081
3011
|
|
|
3082
|
-
seedChannelCapabilitiesConversation(slackDmCaps, ["dm context: ..."]);
|
|
3083
3012
|
const { messages: result } = await applyRuntimeInjections(
|
|
3084
3013
|
[{ role: "user", content: [{ type: "text", text: "hi DM" }] }],
|
|
3085
|
-
{
|
|
3014
|
+
{
|
|
3015
|
+
channelCapabilities: slackDmCaps,
|
|
3016
|
+
transportHints: ["dm context: ..."],
|
|
3017
|
+
},
|
|
3086
3018
|
);
|
|
3087
3019
|
|
|
3088
3020
|
const allText = result
|
|
@@ -3096,19 +3028,18 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3096
3028
|
|
|
3097
3029
|
// ── transport_hints kept for non-slack channels ───────────────────────
|
|
3098
3030
|
test("non-slack conversations still receive <transport_hints>", async () => {
|
|
3099
|
-
seedChannelCapabilitiesConversation(
|
|
3100
|
-
{
|
|
3101
|
-
channel: "telegram",
|
|
3102
|
-
dashboardCapable: false,
|
|
3103
|
-
supportsDynamicUi: false,
|
|
3104
|
-
supportsVoiceInput: false,
|
|
3105
|
-
chatType: "private",
|
|
3106
|
-
},
|
|
3107
|
-
["please answer concisely"],
|
|
3108
|
-
);
|
|
3109
3031
|
const { messages: result } = await applyRuntimeInjections(
|
|
3110
3032
|
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
3111
|
-
{
|
|
3033
|
+
{
|
|
3034
|
+
channelCapabilities: {
|
|
3035
|
+
channel: "telegram",
|
|
3036
|
+
dashboardCapable: false,
|
|
3037
|
+
supportsDynamicUi: false,
|
|
3038
|
+
supportsVoiceInput: false,
|
|
3039
|
+
chatType: "private",
|
|
3040
|
+
},
|
|
3041
|
+
transportHints: ["please answer concisely"],
|
|
3042
|
+
},
|
|
3112
3043
|
);
|
|
3113
3044
|
const allText = result
|
|
3114
3045
|
.flatMap((m) => m.content)
|
|
@@ -3433,8 +3364,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3433
3364
|
role: "user",
|
|
3434
3365
|
content: [{ type: "text", text: "current turn" }],
|
|
3435
3366
|
};
|
|
3436
|
-
seedChannelCapabilitiesConversation(slackChannelCaps);
|
|
3437
3367
|
const { messages } = await applyRuntimeInjections([lastUserMessage], {
|
|
3368
|
+
channelCapabilities: slackChannelCaps,
|
|
3438
3369
|
slackChronologicalMessages,
|
|
3439
3370
|
slackActiveThreadFocusBlock: focusBlock,
|
|
3440
3371
|
});
|
|
@@ -3703,8 +3634,8 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3703
3634
|
chatType: "channel",
|
|
3704
3635
|
};
|
|
3705
3636
|
const newFocus = "<active_thread>\nnewly built\n</active_thread>";
|
|
3706
|
-
seedChannelCapabilitiesConversation(slackChannelCaps);
|
|
3707
3637
|
const { messages: reInjected } = await applyRuntimeInjections(stripped, {
|
|
3638
|
+
channelCapabilities: slackChannelCaps,
|
|
3708
3639
|
slackActiveThreadFocusBlock: newFocus,
|
|
3709
3640
|
});
|
|
3710
3641
|
const reInjectedTexts = reInjected
|
|
@@ -3724,15 +3655,15 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3724
3655
|
// Defensive: the focus injection is gated on `slackChannel` (i.e.
|
|
3725
3656
|
// `isSlackChannelConversation`). Even if a caller mistakenly forwards
|
|
3726
3657
|
// a focus block on a non-Slack channel, it must NOT be appended.
|
|
3727
|
-
seedChannelCapabilitiesConversation({
|
|
3728
|
-
channel: "vellum",
|
|
3729
|
-
dashboardCapable: true,
|
|
3730
|
-
supportsDynamicUi: true,
|
|
3731
|
-
supportsVoiceInput: true,
|
|
3732
|
-
});
|
|
3733
3658
|
const { messages: result } = await applyRuntimeInjections(
|
|
3734
3659
|
[{ role: "user", content: [{ type: "text", text: "vellum question" }] }],
|
|
3735
3660
|
{
|
|
3661
|
+
channelCapabilities: {
|
|
3662
|
+
channel: "vellum",
|
|
3663
|
+
dashboardCapable: true,
|
|
3664
|
+
supportsDynamicUi: true,
|
|
3665
|
+
supportsVoiceInput: true,
|
|
3666
|
+
},
|
|
3736
3667
|
slackActiveThreadFocusBlock: "<active_thread>\nbogus\n</active_thread>",
|
|
3737
3668
|
},
|
|
3738
3669
|
);
|
|
@@ -3749,16 +3680,16 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3749
3680
|
// Same as above but for Slack DMs (chatType === "im"). The focus
|
|
3750
3681
|
// injection is keyed on `isSlackChannelConversation` which excludes
|
|
3751
3682
|
// DMs, so the block must not appear.
|
|
3752
|
-
seedChannelCapabilitiesConversation({
|
|
3753
|
-
channel: "slack",
|
|
3754
|
-
dashboardCapable: false,
|
|
3755
|
-
supportsDynamicUi: false,
|
|
3756
|
-
supportsVoiceInput: false,
|
|
3757
|
-
chatType: "im",
|
|
3758
|
-
});
|
|
3759
3683
|
const { messages: result } = await applyRuntimeInjections(
|
|
3760
3684
|
[{ role: "user", content: [{ type: "text", text: "DM question" }] }],
|
|
3761
3685
|
{
|
|
3686
|
+
channelCapabilities: {
|
|
3687
|
+
channel: "slack",
|
|
3688
|
+
dashboardCapable: false,
|
|
3689
|
+
supportsDynamicUi: false,
|
|
3690
|
+
supportsVoiceInput: false,
|
|
3691
|
+
chatType: "im",
|
|
3692
|
+
},
|
|
3762
3693
|
slackActiveThreadFocusBlock: "<active_thread>\nbogus\n</active_thread>",
|
|
3763
3694
|
},
|
|
3764
3695
|
);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
clearConversations,
|
|
5
|
-
setConversation,
|
|
6
|
-
} from "../daemon/conversation-registry.js";
|
|
7
3
|
import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
|
|
8
|
-
import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
|
|
9
|
-
import { createApp } from "../memory/app-store.js";
|
|
10
4
|
import type { Message } from "../providers/types.js";
|
|
11
5
|
|
|
12
6
|
// ---------------------------------------------------------------------------
|
|
@@ -17,46 +11,6 @@ function userMsg(text: string): Message {
|
|
|
17
11
|
return { role: "user", content: [{ type: "text", text }] };
|
|
18
12
|
}
|
|
19
13
|
|
|
20
|
-
// `applyRuntimeInjections` synthesizes this conversation id when no
|
|
21
|
-
// `turnContext` is supplied, so the `workspace-context` injector resolves the
|
|
22
|
-
// live workspace block from the registry under this key.
|
|
23
|
-
const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
|
|
24
|
-
|
|
25
|
-
// Register the fallback conversation in the live registry so the runtime
|
|
26
|
-
// injectors resolve their blocks from it (the orchestrator no longer threads
|
|
27
|
-
// workspace or active-surface content as options).
|
|
28
|
-
function registerFallbackConversation(fields: Record<string, unknown>): void {
|
|
29
|
-
setConversation(FALLBACK_CONVERSATION_ID, {
|
|
30
|
-
conversationId: FALLBACK_CONVERSATION_ID,
|
|
31
|
-
workingDir: "/sandbox",
|
|
32
|
-
// Non-dirty empty workspace by default so the workspace-context injector
|
|
33
|
-
// skips both the filesystem rescan and the DB refresh unless a test
|
|
34
|
-
// explicitly seeds a block via `workspaceTopLevelContext`.
|
|
35
|
-
workspaceTopLevelContext: "",
|
|
36
|
-
workspaceTopLevelDirty: false,
|
|
37
|
-
...fields,
|
|
38
|
-
} as never);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Seed the live conversation registry with a pre-rendered top-level block. The
|
|
42
|
-
// cache is non-dirty with non-null content, so `resolveWorkspaceTopLevelContext`
|
|
43
|
-
// returns it verbatim without rescanning the filesystem.
|
|
44
|
-
function seedWorkspaceContext(text: string): void {
|
|
45
|
-
registerFallbackConversation({
|
|
46
|
-
workspaceTopLevelContext: text,
|
|
47
|
-
workspaceTopLevelDirty: false,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Build the conversation surface-state map that `buildActiveSurfaceContext`
|
|
52
|
-
// reads to render the `<active_workspace>` block.
|
|
53
|
-
function makeSurfaceState(
|
|
54
|
-
surfaceId: string,
|
|
55
|
-
data: SurfaceData,
|
|
56
|
-
): Map<string, { surfaceType: SurfaceType; data: SurfaceData }> {
|
|
57
|
-
return new Map([[surfaceId, { surfaceType: "dynamic_page", data }]]);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
14
|
// ---------------------------------------------------------------------------
|
|
61
15
|
// Tests
|
|
62
16
|
// ---------------------------------------------------------------------------
|
|
@@ -65,20 +19,15 @@ const sampleContext =
|
|
|
65
19
|
"<workspace>\nRoot: /sandbox\nDirectories: src, lib, tests\n</workspace>";
|
|
66
20
|
|
|
67
21
|
// The workspace-context default injector emits the workspace block as a
|
|
68
|
-
// `prepend-user-tail` placement during `applyRuntimeInjections`.
|
|
69
|
-
//
|
|
70
|
-
// (re)injects only when the block is absent from the working messages, so the
|
|
71
|
-
// suite seeds the registry and exercises that end-to-end path.
|
|
22
|
+
// `prepend-user-tail` placement during `applyRuntimeInjections`. The suite
|
|
23
|
+
// below exercises that end-to-end path.
|
|
72
24
|
|
|
73
25
|
describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
74
|
-
|
|
75
|
-
clearConversations();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("injects workspace context when registered", async () => {
|
|
79
|
-
seedWorkspaceContext(sampleContext);
|
|
26
|
+
test("injects workspace context when provided", async () => {
|
|
80
27
|
const messages: Message[] = [userMsg("Hello")];
|
|
81
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
28
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
29
|
+
workspaceTopLevelContext: sampleContext,
|
|
30
|
+
});
|
|
82
31
|
|
|
83
32
|
expect(result).toHaveLength(1);
|
|
84
33
|
expect(result[0].content).toHaveLength(2);
|
|
@@ -86,38 +35,22 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
86
35
|
expect((result[0].content[1] as { text: string }).text).toBe("Hello");
|
|
87
36
|
});
|
|
88
37
|
|
|
89
|
-
test("does not inject when
|
|
38
|
+
test("does not inject when workspace context is null", async () => {
|
|
90
39
|
const messages: Message[] = [userMsg("Hello")];
|
|
91
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
40
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
41
|
+
workspaceTopLevelContext: null,
|
|
42
|
+
});
|
|
92
43
|
|
|
93
44
|
expect(result).toHaveLength(1);
|
|
94
45
|
expect(result[0].content).toHaveLength(1);
|
|
95
46
|
});
|
|
96
47
|
|
|
97
|
-
test("does not re-inject when the workspace block is already present", async () => {
|
|
98
|
-
// GIVEN the registry holds a workspace block AND the working messages
|
|
99
|
-
// already carry that block (a normal cached turn, post-injection).
|
|
100
|
-
seedWorkspaceContext(sampleContext);
|
|
101
|
-
const messages: Message[] = [userMsg(sampleContext), userMsg("Hello")];
|
|
102
|
-
|
|
103
|
-
// WHEN injections are applied
|
|
104
|
-
const { messages: result } = await applyRuntimeInjections(messages, {});
|
|
105
|
-
|
|
106
|
-
// THEN presence detection skips the block to keep the prefix stable.
|
|
107
|
-
expect(result).toHaveLength(2);
|
|
108
|
-
expect(result[1].content).toHaveLength(1);
|
|
109
|
-
expect((result[1].content[0] as { text: string }).text).toBe("Hello");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
48
|
test("workspace context appears before active surface context in content", async () => {
|
|
113
|
-
|
|
49
|
+
const messages: Message[] = [userMsg("Hello")];
|
|
50
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
51
|
+
activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
|
|
114
52
|
workspaceTopLevelContext: sampleContext,
|
|
115
|
-
workspaceTopLevelDirty: false,
|
|
116
|
-
currentActiveSurfaceId: "sf_1",
|
|
117
|
-
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
118
53
|
});
|
|
119
|
-
const messages: Message[] = [userMsg("Hello")];
|
|
120
|
-
const { messages: result } = await applyRuntimeInjections(messages, {});
|
|
121
54
|
|
|
122
55
|
// Workspace is injected last (in applyRuntimeInjections order) so it
|
|
123
56
|
// prepends to whatever was already prepended by activeSurface.
|
|
@@ -131,20 +64,16 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
131
64
|
});
|
|
132
65
|
|
|
133
66
|
test("app-backed active surface tells the model to load app-builder with the right argument", async () => {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
registerFallbackConversation({
|
|
140
|
-
currentActiveSurfaceId: "sf_1",
|
|
141
|
-
surfaceState: makeSurfaceState("sf_1", {
|
|
67
|
+
const messages: Message[] = [userMsg("Edit this app")];
|
|
68
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
69
|
+
activeSurface: {
|
|
70
|
+
surfaceId: "sf_1",
|
|
142
71
|
html: "<div>test</div>",
|
|
143
|
-
appId: app
|
|
144
|
-
|
|
72
|
+
appId: "app-1",
|
|
73
|
+
appName: "Example App",
|
|
74
|
+
},
|
|
75
|
+
workspaceTopLevelContext: null,
|
|
145
76
|
});
|
|
146
|
-
const messages: Message[] = [userMsg("Edit this app")];
|
|
147
|
-
const { messages: result } = await applyRuntimeInjections(messages, {});
|
|
148
77
|
|
|
149
78
|
const activeWorkspaceText = (result[0].content[0] as { text: string }).text;
|
|
150
79
|
expect(activeWorkspaceText).toContain('skill: "app-builder"');
|
|
@@ -153,14 +82,10 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
153
82
|
});
|
|
154
83
|
|
|
155
84
|
describe("applyRuntimeInjections — minimal mode skips workspace blocks", () => {
|
|
156
|
-
afterEach(() => {
|
|
157
|
-
clearConversations();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
85
|
test("minimal mode skips workspace top-level context", async () => {
|
|
161
|
-
seedWorkspaceContext(sampleContext);
|
|
162
86
|
const messages: Message[] = [userMsg("Hello")];
|
|
163
87
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
88
|
+
workspaceTopLevelContext: sampleContext,
|
|
164
89
|
mode: "minimal",
|
|
165
90
|
});
|
|
166
91
|
|
|
@@ -170,12 +95,9 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
170
95
|
});
|
|
171
96
|
|
|
172
97
|
test("minimal mode skips active surface context", async () => {
|
|
173
|
-
registerFallbackConversation({
|
|
174
|
-
currentActiveSurfaceId: "sf_1",
|
|
175
|
-
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
176
|
-
});
|
|
177
98
|
const messages: Message[] = [userMsg("Hello")];
|
|
178
99
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
100
|
+
activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
|
|
179
101
|
mode: "minimal",
|
|
180
102
|
});
|
|
181
103
|
|
|
@@ -185,14 +107,11 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
185
107
|
});
|
|
186
108
|
|
|
187
109
|
test("full mode (default) still includes workspace blocks", async () => {
|
|
188
|
-
|
|
110
|
+
const messages: Message[] = [userMsg("Hello")];
|
|
111
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
189
112
|
workspaceTopLevelContext: sampleContext,
|
|
190
|
-
|
|
191
|
-
currentActiveSurfaceId: "sf_1",
|
|
192
|
-
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
113
|
+
activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
|
|
193
114
|
});
|
|
194
|
-
const messages: Message[] = [userMsg("Hello")];
|
|
195
|
-
const { messages: result } = await applyRuntimeInjections(messages, {});
|
|
196
115
|
|
|
197
116
|
expect(result[0].content).toHaveLength(3);
|
|
198
117
|
expect((result[0].content[0] as { text: string }).text).toBe(sampleContext);
|
|
@@ -339,8 +339,8 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
339
339
|
|
|
340
340
|
test("does not re-enqueue for invalid items when within cooldown period", async () => {
|
|
341
341
|
const now = Date.now();
|
|
342
|
-
//
|
|
343
|
-
const
|
|
342
|
+
// Generation happened 30 seconds ago (within the 60s cooldown)
|
|
343
|
+
const recentGenAt = now - 30_000;
|
|
344
344
|
insertStarter({
|
|
345
345
|
label: "Let me check calendar",
|
|
346
346
|
prompt: "Let me check what Alice has today.",
|
|
@@ -353,13 +353,9 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
353
353
|
category: "productivity",
|
|
354
354
|
createdAt: now - 1,
|
|
355
355
|
});
|
|
356
|
-
setCheckpoint(
|
|
357
|
-
"conversation_starters:last_attempt_at:default",
|
|
358
|
-
String(recentAttemptAt),
|
|
359
|
-
);
|
|
360
356
|
setCheckpoint(
|
|
361
357
|
"conversation_starters:last_gen_at:default",
|
|
362
|
-
String(
|
|
358
|
+
String(recentGenAt),
|
|
363
359
|
);
|
|
364
360
|
setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
|
|
365
361
|
insertMemoryItem();
|
|
@@ -379,8 +375,8 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
379
375
|
|
|
380
376
|
test("re-enqueues for invalid items after cooldown expires", async () => {
|
|
381
377
|
const now = Date.now();
|
|
382
|
-
//
|
|
383
|
-
const
|
|
378
|
+
// Generation happened 90 seconds ago (past the 60s cooldown)
|
|
379
|
+
const oldGenAt = now - 90_000;
|
|
384
380
|
insertStarter({
|
|
385
381
|
label: "Let me check calendar",
|
|
386
382
|
prompt: "Let me check what Alice has today.",
|
|
@@ -393,13 +389,9 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
393
389
|
category: "productivity",
|
|
394
390
|
createdAt: now - 1,
|
|
395
391
|
});
|
|
396
|
-
setCheckpoint(
|
|
397
|
-
"conversation_starters:last_attempt_at:default",
|
|
398
|
-
String(oldAttemptAt),
|
|
399
|
-
);
|
|
400
392
|
setCheckpoint(
|
|
401
393
|
"conversation_starters:last_gen_at:default",
|
|
402
|
-
String(
|
|
394
|
+
String(oldGenAt),
|
|
403
395
|
);
|
|
404
396
|
setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
|
|
405
397
|
insertMemoryItem();
|