@vellumai/assistant 0.8.8-dev.202606071835.08695c1 → 0.8.8-dev.202606072033.0e97ff6
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/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +6 -11
- package/src/__tests__/compaction-events.test.ts +0 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +96 -45
- package/src/__tests__/conversation-runtime-workspace.test.ts +20 -6
- package/src/__tests__/injector-chain.test.ts +7 -1
- package/src/agent/loop.ts +52 -70
- package/src/context/window-manager.ts +25 -12
- package/src/daemon/conversation-agent-loop.ts +10 -24
- package/src/daemon/conversation-lifecycle.ts +2 -0
- package/src/daemon/conversation-runtime-assembly.ts +9 -15
- package/src/daemon/conversation.ts +4 -6
- package/src/plugins/defaults/compaction/manager-store.ts +44 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +53 -61
- package/src/runtime/__tests__/agent-wake.test.ts +6 -8
- package/src/runtime/agent-wake.ts +17 -16
package/package.json
CHANGED
|
@@ -22,8 +22,8 @@ import type {
|
|
|
22
22
|
CheckpointInfo,
|
|
23
23
|
} from "../agent/loop.js";
|
|
24
24
|
import { AgentLoop, isMaxTokensStopReason } from "../agent/loop.js";
|
|
25
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
25
26
|
import type { PostCompactionHookInput } from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
|
|
26
|
-
import type { TurnContext } from "../plugins/types.js";
|
|
27
27
|
import type {
|
|
28
28
|
Message,
|
|
29
29
|
Provider,
|
|
@@ -120,21 +120,16 @@ const userMessage: Message = {
|
|
|
120
120
|
content: [{ type: "text", text: "Hello" }],
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
-
// A
|
|
123
|
+
// A trust snapshot plus a context-window manager whose `maybeCompact` returns a
|
|
124
124
|
// canned result, so the loop's compaction call runs without the real
|
|
125
|
-
// orchestrator machinery.
|
|
126
|
-
//
|
|
125
|
+
// orchestrator machinery. Both are supplied to the loop as dedicated run
|
|
126
|
+
// options.
|
|
127
127
|
function fakeCompaction(result: { compacted: boolean; exhausted: boolean }): {
|
|
128
|
-
|
|
128
|
+
trust: TrustContext;
|
|
129
129
|
contextWindowManager: AgentLoopRunOptions["contextWindowManager"];
|
|
130
130
|
} {
|
|
131
131
|
return {
|
|
132
|
-
|
|
133
|
-
requestId: "req-compact",
|
|
134
|
-
conversationId: "conv-compact",
|
|
135
|
-
turnIndex: 0,
|
|
136
|
-
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
137
|
-
},
|
|
132
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
138
133
|
contextWindowManager: {
|
|
139
134
|
maybeCompact: async () => result,
|
|
140
135
|
} as unknown as AgentLoopRunOptions["contextWindowManager"],
|
|
@@ -190,7 +190,6 @@ let mockCompactResult: ContextWindowResult = {
|
|
|
190
190
|
mock.module("../context/window-manager.js", () => ({
|
|
191
191
|
ContextWindowManager: class {
|
|
192
192
|
nonPersistedPrefixCount = 0;
|
|
193
|
-
summaryIsInjected = false;
|
|
194
193
|
constructor() {}
|
|
195
194
|
updateConfig() {}
|
|
196
195
|
shouldCompact() {
|
|
@@ -53,6 +53,10 @@ mock.module("../memory/pkb/pkb-search.js", () => ({
|
|
|
53
53
|
// `buildUnifiedTurnContextBlock` expectations below. The rest of the module
|
|
54
54
|
// (timezone canonicalization/resolution) keeps its real behavior.
|
|
55
55
|
const FIXED_TURN_TIMESTAMP = "2026-04-02T12:00:00Z";
|
|
56
|
+
// Conversation id the runtime-injection tests register their live conversation
|
|
57
|
+
// under; passed explicitly now that `applyRuntimeInjections` requires the
|
|
58
|
+
// caller to name the conversation it resolves through.
|
|
59
|
+
const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
|
|
56
60
|
const realDateContextForAssemblyTest =
|
|
57
61
|
await import("../daemon/date-context.js");
|
|
58
62
|
mock.module("../daemon/date-context.js", () => ({
|
|
@@ -695,7 +699,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
|
695
699
|
};
|
|
696
700
|
|
|
697
701
|
seedChannelCapabilitiesConversation(caps);
|
|
698
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
702
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
703
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
704
|
+
});
|
|
699
705
|
|
|
700
706
|
expect(result.length).toBe(1);
|
|
701
707
|
expect(result[0].content.length).toBe(2);
|
|
@@ -707,14 +713,18 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
|
707
713
|
|
|
708
714
|
test("does not inject when channelCapabilities is null", async () => {
|
|
709
715
|
seedChannelCapabilitiesConversation(null);
|
|
710
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
716
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
717
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
718
|
+
});
|
|
711
719
|
|
|
712
720
|
expect(result.length).toBe(1);
|
|
713
721
|
expect(result[0].content.length).toBe(1);
|
|
714
722
|
});
|
|
715
723
|
|
|
716
724
|
test("does not inject when channelCapabilities is omitted", async () => {
|
|
717
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
725
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
726
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
727
|
+
});
|
|
718
728
|
|
|
719
729
|
expect(result.length).toBe(1);
|
|
720
730
|
expect(result[0].content.length).toBe(1);
|
|
@@ -729,7 +739,9 @@ describe("applyRuntimeInjections with channelCapabilities", () => {
|
|
|
729
739
|
};
|
|
730
740
|
|
|
731
741
|
seedChannelCapabilitiesConversation(caps);
|
|
732
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
742
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
743
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
744
|
+
});
|
|
733
745
|
|
|
734
746
|
expect(result.length).toBe(1);
|
|
735
747
|
// channelCapabilities prepends
|
|
@@ -1007,20 +1019,17 @@ describe("applyRuntimeInjections — injection mode", () => {
|
|
|
1007
1019
|
expect(texts).toContain("Hello");
|
|
1008
1020
|
});
|
|
1009
1021
|
|
|
1010
|
-
test("post-compaction re-injection resolves the live conversation from the
|
|
1022
|
+
test("post-compaction re-injection resolves the live conversation from the conversation id", async () => {
|
|
1011
1023
|
// GIVEN the seeded live conversation `injection-mode-conv` whose
|
|
1012
1024
|
// conversation-scoped blocks (`<active_workspace>`, `<channel_capabilities>`)
|
|
1013
1025
|
// only resolve when `applyRuntimeInjections` finds it by conversation id
|
|
1014
|
-
// AND the agent loop hands the post-compaction hook
|
|
1015
|
-
//
|
|
1026
|
+
// AND the agent loop hands the post-compaction hook the turn-identity fields
|
|
1027
|
+
// flat (`requestId`, `conversationId`, `trust`)
|
|
1016
1028
|
const { messages: result } = await postCompactReinject({
|
|
1017
1029
|
history: baseMessages,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
turnIndex: 0,
|
|
1022
|
-
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
1023
|
-
},
|
|
1030
|
+
requestId: "reinject-req",
|
|
1031
|
+
conversationId: "injection-mode-conv",
|
|
1032
|
+
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
1024
1033
|
isNonInteractive: false,
|
|
1025
1034
|
mode: "full",
|
|
1026
1035
|
modelProfile: null,
|
|
@@ -1031,9 +1040,9 @@ describe("applyRuntimeInjections — injection mode", () => {
|
|
|
1031
1040
|
.map((b) => b.text)
|
|
1032
1041
|
.join("\n");
|
|
1033
1042
|
|
|
1034
|
-
// THEN the hook
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1043
|
+
// THEN the hook forwards `conversationId` onto the flat injection options,
|
|
1044
|
+
// so the re-injection resolves the same live conversation as the turn's
|
|
1045
|
+
// initial assembly rather than the synthetic fallback.
|
|
1037
1046
|
expect(allText).toContain("<active_workspace>");
|
|
1038
1047
|
expect(allText).toContain("<channel_capabilities>");
|
|
1039
1048
|
});
|
|
@@ -1376,7 +1385,9 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1376
1385
|
|
|
1377
1386
|
test("injects NOW.md block when the file has content", async () => {
|
|
1378
1387
|
seedNowScratchpad("Current focus: fix the bug");
|
|
1379
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1388
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1389
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1390
|
+
});
|
|
1380
1391
|
|
|
1381
1392
|
expect(result.length).toBe(1);
|
|
1382
1393
|
expect(result[0].content.length).toBe(2);
|
|
@@ -1388,7 +1399,9 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1388
1399
|
|
|
1389
1400
|
test("scratchpad appears before user's original text content", async () => {
|
|
1390
1401
|
seedNowScratchpad("scratchpad notes");
|
|
1391
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1402
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1403
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1404
|
+
});
|
|
1392
1405
|
|
|
1393
1406
|
// Scratchpad comes first (before user content)
|
|
1394
1407
|
expect(
|
|
@@ -1401,7 +1414,9 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1401
1414
|
});
|
|
1402
1415
|
|
|
1403
1416
|
test("does not inject when the NOW.md file is absent", async () => {
|
|
1404
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1417
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1418
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1419
|
+
});
|
|
1405
1420
|
|
|
1406
1421
|
expect(result.length).toBe(1);
|
|
1407
1422
|
expect(result[0].content.length).toBe(1);
|
|
@@ -1410,6 +1425,7 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1410
1425
|
test("skipped in minimal mode", async () => {
|
|
1411
1426
|
seedNowScratchpad("Current focus: fix the bug");
|
|
1412
1427
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1428
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1413
1429
|
mode: "minimal",
|
|
1414
1430
|
});
|
|
1415
1431
|
|
|
@@ -1876,7 +1892,9 @@ describe("applyRuntimeInjections with unifiedTurnContext", () => {
|
|
|
1876
1892
|
test("injects the turn-context block when the inputs are provided", async () => {
|
|
1877
1893
|
seedTemporalSnapshot();
|
|
1878
1894
|
|
|
1879
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1895
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1896
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1897
|
+
});
|
|
1880
1898
|
|
|
1881
1899
|
expect(result).toHaveLength(1);
|
|
1882
1900
|
expect(result[0].content).toHaveLength(2);
|
|
@@ -1890,7 +1908,9 @@ describe("applyRuntimeInjections with unifiedTurnContext", () => {
|
|
|
1890
1908
|
});
|
|
1891
1909
|
|
|
1892
1910
|
test("does not inject when the timestamp snapshot is absent", async () => {
|
|
1893
|
-
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1911
|
+
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1912
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1913
|
+
});
|
|
1894
1914
|
|
|
1895
1915
|
expect(result).toHaveLength(1);
|
|
1896
1916
|
expect(result[0].content).toHaveLength(1);
|
|
@@ -1900,6 +1920,7 @@ describe("applyRuntimeInjections with unifiedTurnContext", () => {
|
|
|
1900
1920
|
seedTemporalSnapshot();
|
|
1901
1921
|
|
|
1902
1922
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1923
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1903
1924
|
...sampleOptions,
|
|
1904
1925
|
mode: "full",
|
|
1905
1926
|
});
|
|
@@ -1916,6 +1937,7 @@ describe("applyRuntimeInjections with unifiedTurnContext", () => {
|
|
|
1916
1937
|
seedTemporalSnapshot();
|
|
1917
1938
|
|
|
1918
1939
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1940
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1919
1941
|
...sampleOptions,
|
|
1920
1942
|
mode: "minimal",
|
|
1921
1943
|
});
|
|
@@ -1990,7 +2012,9 @@ describe("applyRuntimeInjections timezone resolution", () => {
|
|
|
1990
2012
|
seedTemporalSnapshot(null);
|
|
1991
2013
|
|
|
1992
2014
|
// WHEN a turn is assembled
|
|
1993
|
-
const result = await applyRuntimeInjections(baseMessages, {
|
|
2015
|
+
const result = await applyRuntimeInjections(baseMessages, {
|
|
2016
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2017
|
+
});
|
|
1994
2018
|
|
|
1995
2019
|
// THEN the injector surfaces the mismatch using the config-sourced values
|
|
1996
2020
|
const injected = injectedText(result);
|
|
@@ -2012,7 +2036,9 @@ describe("applyRuntimeInjections timezone resolution", () => {
|
|
|
2012
2036
|
seedTemporalSnapshot("America/Los_Angeles");
|
|
2013
2037
|
|
|
2014
2038
|
// WHEN a turn is assembled
|
|
2015
|
-
const result = await applyRuntimeInjections(baseMessages, {
|
|
2039
|
+
const result = await applyRuntimeInjections(baseMessages, {
|
|
2040
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2041
|
+
});
|
|
2016
2042
|
|
|
2017
2043
|
// THEN the injector surfaces the mismatch using the snapshot's client value
|
|
2018
2044
|
const injected = injectedText(result);
|
|
@@ -2032,7 +2058,9 @@ describe("applyRuntimeInjections timezone resolution", () => {
|
|
|
2032
2058
|
seedTemporalSnapshot("America/New_York");
|
|
2033
2059
|
|
|
2034
2060
|
// WHEN a turn is assembled
|
|
2035
|
-
const result = await applyRuntimeInjections(baseMessages, {
|
|
2061
|
+
const result = await applyRuntimeInjections(baseMessages, {
|
|
2062
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2063
|
+
});
|
|
2036
2064
|
|
|
2037
2065
|
// THEN no timezone-mismatch lines are injected
|
|
2038
2066
|
const injected = injectedText(result);
|
|
@@ -2050,7 +2078,9 @@ describe("applyRuntimeInjections timezone resolution", () => {
|
|
|
2050
2078
|
seedTemporalSnapshot(null, "2d ago");
|
|
2051
2079
|
|
|
2052
2080
|
// WHEN a turn is assembled without the gap in the options bag
|
|
2053
|
-
const result = await applyRuntimeInjections(baseMessages, {
|
|
2081
|
+
const result = await applyRuntimeInjections(baseMessages, {
|
|
2082
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2083
|
+
});
|
|
2054
2084
|
|
|
2055
2085
|
// THEN the injector surfaces the gap from the snapshot
|
|
2056
2086
|
const injected = injectedText(result);
|
|
@@ -2066,7 +2096,9 @@ describe("applyRuntimeInjections timezone resolution", () => {
|
|
|
2066
2096
|
seedTemporalSnapshot(null, null);
|
|
2067
2097
|
|
|
2068
2098
|
// WHEN a turn is assembled
|
|
2069
|
-
const result = await applyRuntimeInjections(baseMessages, {
|
|
2099
|
+
const result = await applyRuntimeInjections(baseMessages, {
|
|
2100
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2101
|
+
});
|
|
2070
2102
|
|
|
2071
2103
|
// THEN no gap line is injected
|
|
2072
2104
|
const injected = injectedText(result);
|
|
@@ -2122,7 +2154,9 @@ describe("applyRuntimeInjections blocks.unifiedTurnContext", () => {
|
|
|
2122
2154
|
test("captures unifiedTurnContext when tail is a user message", async () => {
|
|
2123
2155
|
seedTemporalSnapshot();
|
|
2124
2156
|
|
|
2125
|
-
const result = await applyRuntimeInjections(userTailMessages, {
|
|
2157
|
+
const result = await applyRuntimeInjections(userTailMessages, {
|
|
2158
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2159
|
+
});
|
|
2126
2160
|
|
|
2127
2161
|
expect(result.blocks.unifiedTurnContext).toBe(sampleBlock);
|
|
2128
2162
|
});
|
|
@@ -2141,13 +2175,17 @@ describe("applyRuntimeInjections blocks.unifiedTurnContext", () => {
|
|
|
2141
2175
|
},
|
|
2142
2176
|
];
|
|
2143
2177
|
|
|
2144
|
-
const result = await applyRuntimeInjections(assistantTailMessages, {
|
|
2178
|
+
const result = await applyRuntimeInjections(assistantTailMessages, {
|
|
2179
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2180
|
+
});
|
|
2145
2181
|
|
|
2146
2182
|
expect(result.blocks.unifiedTurnContext).toBeUndefined();
|
|
2147
2183
|
});
|
|
2148
2184
|
|
|
2149
2185
|
test("does not capture when unifiedTurnContext option is absent", async () => {
|
|
2150
|
-
const result = await applyRuntimeInjections(userTailMessages, {
|
|
2186
|
+
const result = await applyRuntimeInjections(userTailMessages, {
|
|
2187
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2188
|
+
});
|
|
2151
2189
|
|
|
2152
2190
|
expect(result.blocks.unifiedTurnContext).toBeUndefined();
|
|
2153
2191
|
});
|
|
@@ -2303,6 +2341,7 @@ describe("applyRuntimeInjections — subagent status", () => {
|
|
|
2303
2341
|
|
|
2304
2342
|
// WHEN runtime injections run in full mode
|
|
2305
2343
|
const { messages: result } = await applyRuntimeInjections([userMsg], {
|
|
2344
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2306
2345
|
mode: "full",
|
|
2307
2346
|
});
|
|
2308
2347
|
|
|
@@ -2325,6 +2364,7 @@ describe("applyRuntimeInjections — subagent status", () => {
|
|
|
2325
2364
|
|
|
2326
2365
|
// WHEN runtime injections run in minimal mode
|
|
2327
2366
|
const { messages: result } = await applyRuntimeInjections([userMsg], {
|
|
2367
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2328
2368
|
mode: "minimal",
|
|
2329
2369
|
});
|
|
2330
2370
|
|
|
@@ -2354,6 +2394,7 @@ describe("applyRuntimeInjections — subagent status", () => {
|
|
|
2354
2394
|
|
|
2355
2395
|
// WHEN runtime injections run in full mode
|
|
2356
2396
|
const { messages: result } = await applyRuntimeInjections([userMsg], {
|
|
2397
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2357
2398
|
mode: "full",
|
|
2358
2399
|
});
|
|
2359
2400
|
|
|
@@ -2430,6 +2471,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2430
2471
|
|
|
2431
2472
|
function makePkbOptions(overrides: Record<string, unknown> = {}) {
|
|
2432
2473
|
return {
|
|
2474
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2433
2475
|
...overrides,
|
|
2434
2476
|
};
|
|
2435
2477
|
}
|
|
@@ -2716,7 +2758,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2716
2758
|
// filtered out.
|
|
2717
2759
|
const { messages: initialResult } = await applyRuntimeInjections(
|
|
2718
2760
|
preCompactionMessages,
|
|
2719
|
-
{},
|
|
2761
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
2720
2762
|
);
|
|
2721
2763
|
// Unwrap the injected reminder from the last user message.
|
|
2722
2764
|
const initialTexts = extractTexts(initialResult);
|
|
@@ -2750,7 +2792,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2750
2792
|
// should be filtered, and beta (no longer "in context") should appear.
|
|
2751
2793
|
const { messages: rebuiltResult } = await applyRuntimeInjections(
|
|
2752
2794
|
postCompactionMessages,
|
|
2753
|
-
{},
|
|
2795
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
2754
2796
|
);
|
|
2755
2797
|
const rebuiltTexts = extractTexts(rebuiltResult);
|
|
2756
2798
|
const rebuiltReminder = rebuiltTexts.find(
|
|
@@ -2867,7 +2909,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
2867
2909
|
// self-resolves the chronological transcript from conversation state,
|
|
2868
2910
|
// exactly as production does.
|
|
2869
2911
|
seedSlackChannelConversationWithRows(slackChannelCaps, rows);
|
|
2870
|
-
const { messages } = await applyRuntimeInjections([lastUserMessage], {
|
|
2912
|
+
const { messages } = await applyRuntimeInjections([lastUserMessage], {
|
|
2913
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2914
|
+
});
|
|
2871
2915
|
return messages;
|
|
2872
2916
|
}
|
|
2873
2917
|
|
|
@@ -3161,7 +3205,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3161
3205
|
});
|
|
3162
3206
|
const { messages: result } = await applyRuntimeInjections(
|
|
3163
3207
|
[lastUserMessage],
|
|
3164
|
-
{},
|
|
3208
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3165
3209
|
);
|
|
3166
3210
|
expect(result.length).toBe(1);
|
|
3167
3211
|
const allText = result[0].content
|
|
@@ -3208,7 +3252,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3208
3252
|
);
|
|
3209
3253
|
const { messages: result } = await applyRuntimeInjections(
|
|
3210
3254
|
[lastUserMessage],
|
|
3211
|
-
{},
|
|
3255
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3212
3256
|
);
|
|
3213
3257
|
// The self-resolved chronological transcript REPLACES the default
|
|
3214
3258
|
// runMessages, so the inbound `DM question` text does not appear — only
|
|
@@ -3265,7 +3309,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3265
3309
|
content: [{ type: "text", text: "post-compaction question" }],
|
|
3266
3310
|
},
|
|
3267
3311
|
],
|
|
3268
|
-
{},
|
|
3312
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3269
3313
|
);
|
|
3270
3314
|
const allText = result
|
|
3271
3315
|
.flatMap((m) => m.content)
|
|
@@ -3319,7 +3363,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3319
3363
|
seedSlackChannelConversationWithRows(slackCaps, rows);
|
|
3320
3364
|
const { messages: result } = await applyRuntimeInjections(
|
|
3321
3365
|
runMessagesWithMemory,
|
|
3322
|
-
{},
|
|
3366
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3323
3367
|
);
|
|
3324
3368
|
const tail = result[result.length - 1];
|
|
3325
3369
|
expect(tail.role).toBe("user");
|
|
@@ -3385,7 +3429,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3385
3429
|
seedSlackChannelConversationWithRows(slackCaps, rows);
|
|
3386
3430
|
const { messages: result } = await applyRuntimeInjections(
|
|
3387
3431
|
runMessagesWithMemory,
|
|
3388
|
-
{},
|
|
3432
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3389
3433
|
);
|
|
3390
3434
|
const tail = result[result.length - 1];
|
|
3391
3435
|
expect(tail.role).toBe("user");
|
|
@@ -3426,7 +3470,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3426
3470
|
seedSlackChannelConversationWithRows(slackCaps, rows);
|
|
3427
3471
|
const { messages: result } = await applyRuntimeInjections(
|
|
3428
3472
|
[{ role: "user", content: [{ type: "text", text: "inbound" }] }],
|
|
3429
|
-
{},
|
|
3473
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3430
3474
|
);
|
|
3431
3475
|
const tail = result[result.length - 1];
|
|
3432
3476
|
const allText = tail.content
|
|
@@ -3451,7 +3495,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3451
3495
|
]);
|
|
3452
3496
|
const { messages: result } = await applyRuntimeInjections(
|
|
3453
3497
|
[{ role: "user", content: [{ type: "text", text: "current turn" }] }],
|
|
3454
|
-
{},
|
|
3498
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3455
3499
|
);
|
|
3456
3500
|
|
|
3457
3501
|
const allText = result
|
|
@@ -3478,7 +3522,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3478
3522
|
seedChannelCapabilitiesConversation(slackDmCaps, ["dm context: ..."]);
|
|
3479
3523
|
const { messages: result } = await applyRuntimeInjections(
|
|
3480
3524
|
[{ role: "user", content: [{ type: "text", text: "hi DM" }] }],
|
|
3481
|
-
{},
|
|
3525
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3482
3526
|
);
|
|
3483
3527
|
|
|
3484
3528
|
const allText = result
|
|
@@ -3504,7 +3548,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3504
3548
|
);
|
|
3505
3549
|
const { messages: result } = await applyRuntimeInjections(
|
|
3506
3550
|
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
3507
|
-
{},
|
|
3551
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
3508
3552
|
);
|
|
3509
3553
|
const allText = result
|
|
3510
3554
|
.flatMap((m) => m.content)
|
|
@@ -3892,7 +3936,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
3892
3936
|
role: "user",
|
|
3893
3937
|
content: [{ type: "text", text: "current turn" }],
|
|
3894
3938
|
};
|
|
3895
|
-
const { messages } = await applyRuntimeInjections([lastUserMessage], {
|
|
3939
|
+
const { messages } = await applyRuntimeInjections([lastUserMessage], {
|
|
3940
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
3941
|
+
});
|
|
3896
3942
|
return { messages, focusBlock };
|
|
3897
3943
|
}
|
|
3898
3944
|
|
|
@@ -4280,7 +4326,9 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
4280
4326
|
// Re-run injection on the stripped messages — the conversation is still
|
|
4281
4327
|
// seeded, so the rebuild re-derives the focus block; only ONE
|
|
4282
4328
|
// `<active_thread>` block must end up in the result.
|
|
4283
|
-
const { messages: reInjected } = await applyRuntimeInjections(stripped, {
|
|
4329
|
+
const { messages: reInjected } = await applyRuntimeInjections(stripped, {
|
|
4330
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
4331
|
+
});
|
|
4284
4332
|
const reInjectedTexts = reInjected
|
|
4285
4333
|
.flatMap((m) => m.content)
|
|
4286
4334
|
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
@@ -4306,7 +4354,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
4306
4354
|
});
|
|
4307
4355
|
const { messages: result } = await applyRuntimeInjections(
|
|
4308
4356
|
[{ role: "user", content: [{ type: "text", text: "vellum question" }] }],
|
|
4309
|
-
{},
|
|
4357
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
4310
4358
|
);
|
|
4311
4359
|
const allText = result
|
|
4312
4360
|
.flatMap((m) => m.content)
|
|
@@ -4330,7 +4378,7 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
4330
4378
|
});
|
|
4331
4379
|
const { messages: result } = await applyRuntimeInjections(
|
|
4332
4380
|
[{ role: "user", content: [{ type: "text", text: "DM question" }] }],
|
|
4333
|
-
{},
|
|
4381
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
4334
4382
|
);
|
|
4335
4383
|
const allText = result
|
|
4336
4384
|
.flatMap((m) => m.content)
|
|
@@ -5686,6 +5734,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
5686
5734
|
pkbSearchResults = [];
|
|
5687
5735
|
pkbSearchThrows = null;
|
|
5688
5736
|
const { blocks } = await applyRuntimeInjections(baseMessages, {
|
|
5737
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
5689
5738
|
mode: "full",
|
|
5690
5739
|
});
|
|
5691
5740
|
|
|
@@ -5696,6 +5745,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
5696
5745
|
test("not captured in minimal mode", async () => {
|
|
5697
5746
|
seedPkbContent();
|
|
5698
5747
|
const { blocks } = await applyRuntimeInjections(baseMessages, {
|
|
5748
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
5699
5749
|
mode: "minimal",
|
|
5700
5750
|
});
|
|
5701
5751
|
|
|
@@ -5704,6 +5754,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
5704
5754
|
|
|
5705
5755
|
test("not captured when PKB inactive", async () => {
|
|
5706
5756
|
const { blocks } = await applyRuntimeInjections(baseMessages, {
|
|
5757
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
5707
5758
|
mode: "full",
|
|
5708
5759
|
});
|
|
5709
5760
|
|
|
@@ -78,7 +78,9 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
78
78
|
test("injects workspace context when registered", async () => {
|
|
79
79
|
seedWorkspaceContext(sampleContext);
|
|
80
80
|
const messages: Message[] = [userMsg("Hello")];
|
|
81
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
81
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
82
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
83
|
+
});
|
|
82
84
|
|
|
83
85
|
expect(result).toHaveLength(1);
|
|
84
86
|
expect(result[0].content).toHaveLength(2);
|
|
@@ -88,7 +90,9 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
88
90
|
|
|
89
91
|
test("does not inject when no workspace context is registered", async () => {
|
|
90
92
|
const messages: Message[] = [userMsg("Hello")];
|
|
91
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
93
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
94
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
95
|
+
});
|
|
92
96
|
|
|
93
97
|
expect(result).toHaveLength(1);
|
|
94
98
|
expect(result[0].content).toHaveLength(1);
|
|
@@ -101,7 +105,9 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
101
105
|
const messages: Message[] = [userMsg(sampleContext), userMsg("Hello")];
|
|
102
106
|
|
|
103
107
|
// WHEN injections are applied
|
|
104
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
108
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
109
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
110
|
+
});
|
|
105
111
|
|
|
106
112
|
// THEN presence detection skips the block to keep the prefix stable.
|
|
107
113
|
expect(result).toHaveLength(2);
|
|
@@ -117,7 +123,9 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
117
123
|
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
118
124
|
});
|
|
119
125
|
const messages: Message[] = [userMsg("Hello")];
|
|
120
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
126
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
127
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
128
|
+
});
|
|
121
129
|
|
|
122
130
|
// Workspace is injected last (in applyRuntimeInjections order) so it
|
|
123
131
|
// prepends to whatever was already prepended by activeSurface.
|
|
@@ -144,7 +152,9 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
144
152
|
}),
|
|
145
153
|
});
|
|
146
154
|
const messages: Message[] = [userMsg("Edit this app")];
|
|
147
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
155
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
156
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
157
|
+
});
|
|
148
158
|
|
|
149
159
|
const activeWorkspaceText = (result[0].content[0] as { text: string }).text;
|
|
150
160
|
expect(activeWorkspaceText).toContain('skill: "app-builder"');
|
|
@@ -161,6 +171,7 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
161
171
|
seedWorkspaceContext(sampleContext);
|
|
162
172
|
const messages: Message[] = [userMsg("Hello")];
|
|
163
173
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
174
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
164
175
|
mode: "minimal",
|
|
165
176
|
});
|
|
166
177
|
|
|
@@ -176,6 +187,7 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
176
187
|
});
|
|
177
188
|
const messages: Message[] = [userMsg("Hello")];
|
|
178
189
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
190
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
179
191
|
mode: "minimal",
|
|
180
192
|
});
|
|
181
193
|
|
|
@@ -192,7 +204,9 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
192
204
|
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
193
205
|
});
|
|
194
206
|
const messages: Message[] = [userMsg("Hello")];
|
|
195
|
-
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
207
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
208
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
209
|
+
});
|
|
196
210
|
|
|
197
211
|
expect(result[0].content).toHaveLength(3);
|
|
198
212
|
expect((result[0].content[0] as { text: string }).text).toBe(sampleContext);
|
|
@@ -88,6 +88,10 @@ initializeDb();
|
|
|
88
88
|
// `makeTurnContext` and the workspace registry seed share this id so the
|
|
89
89
|
// `workspace-context` injector resolves the seeded block for the turn.
|
|
90
90
|
const TEST_CONVERSATION_ID = "conv-test-1";
|
|
91
|
+
// Conversation id the fallback-context test registers its live conversation
|
|
92
|
+
// under; passed explicitly now that `applyRuntimeInjections` requires the
|
|
93
|
+
// caller to name the conversation it resolves through.
|
|
94
|
+
const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
|
|
91
95
|
|
|
92
96
|
/** A fake TurnContext sufficient for driving `composeInjectorChain`. */
|
|
93
97
|
function makeTurnContext(): TurnContext {
|
|
@@ -402,7 +406,9 @@ describe("injector chain", () => {
|
|
|
402
406
|
interfaceName: "web",
|
|
403
407
|
});
|
|
404
408
|
seedFallbackTemporalSnapshot();
|
|
405
|
-
const result = await applyRuntimeInjections(runMessages, {
|
|
409
|
+
const result = await applyRuntimeInjections(runMessages, {
|
|
410
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
411
|
+
});
|
|
406
412
|
|
|
407
413
|
// The unified-turn-context injector fires even without a caller-supplied
|
|
408
414
|
// turnContext, proving the chain runs under the synthesized context.
|