@vellumai/assistant 0.10.3-dev.202606301742.2b63687 → 0.10.3-dev.202606301940.da6000c
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/openapi.yaml +194 -0
- package/package.json +1 -1
- package/src/__tests__/background-shell-bash.test.ts +9 -0
- package/src/__tests__/background-shell-host-bash.test.ts +11 -0
- package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
- package/src/__tests__/onboarding-template-contract.test.ts +27 -43
- package/src/__tests__/plugin-import-boundary-guard.test.ts +7 -0
- package/src/__tests__/system-prompt.test.ts +4 -4
- package/src/api/index.ts +1 -0
- package/src/api/responses/conversation-message.ts +29 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
- package/src/daemon/conversation-lifecycle.ts +25 -1
- package/src/daemon/lifecycle.ts +2 -201
- package/src/daemon/wake-conversation-ops.ts +3 -0
- package/src/persistence/conversation-crud.ts +18 -0
- package/src/plugins/defaults/memory/startup.ts +211 -0
- package/src/prompts/templates/BOOTSTRAP.md +16 -119
- package/src/runtime/agent-wake.ts +13 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -0
- package/src/runtime/routes/conversation-routes.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +17 -8
- package/src/tools/terminal/shell.ts +24 -19
package/openapi.yaml
CHANGED
|
@@ -4632,6 +4632,162 @@ paths:
|
|
|
4632
4632
|
required: true
|
|
4633
4633
|
schema:
|
|
4634
4634
|
type: string
|
|
4635
|
+
requestBody:
|
|
4636
|
+
required: true
|
|
4637
|
+
content:
|
|
4638
|
+
application/json:
|
|
4639
|
+
schema:
|
|
4640
|
+
type: object
|
|
4641
|
+
properties:
|
|
4642
|
+
provider:
|
|
4643
|
+
$ref: "#/components/schemas/LLMProvider"
|
|
4644
|
+
model:
|
|
4645
|
+
type: string
|
|
4646
|
+
minLength: 1
|
|
4647
|
+
maxTokens:
|
|
4648
|
+
type: integer
|
|
4649
|
+
exclusiveMinimum: 0
|
|
4650
|
+
maximum: 9007199254740991
|
|
4651
|
+
effort:
|
|
4652
|
+
type: string
|
|
4653
|
+
enum:
|
|
4654
|
+
- none
|
|
4655
|
+
- low
|
|
4656
|
+
- medium
|
|
4657
|
+
- high
|
|
4658
|
+
- xhigh
|
|
4659
|
+
- max
|
|
4660
|
+
speed:
|
|
4661
|
+
type: string
|
|
4662
|
+
enum:
|
|
4663
|
+
- standard
|
|
4664
|
+
- fast
|
|
4665
|
+
verbosity:
|
|
4666
|
+
type: string
|
|
4667
|
+
enum:
|
|
4668
|
+
- low
|
|
4669
|
+
- medium
|
|
4670
|
+
- high
|
|
4671
|
+
temperature:
|
|
4672
|
+
anyOf:
|
|
4673
|
+
- type: number
|
|
4674
|
+
minimum: 0
|
|
4675
|
+
maximum: 2
|
|
4676
|
+
- type: "null"
|
|
4677
|
+
topP:
|
|
4678
|
+
anyOf:
|
|
4679
|
+
- type: number
|
|
4680
|
+
minimum: 0
|
|
4681
|
+
maximum: 1
|
|
4682
|
+
- type: "null"
|
|
4683
|
+
thinking:
|
|
4684
|
+
type: object
|
|
4685
|
+
properties:
|
|
4686
|
+
enabled:
|
|
4687
|
+
type: boolean
|
|
4688
|
+
streamThinking:
|
|
4689
|
+
type: boolean
|
|
4690
|
+
level:
|
|
4691
|
+
type: string
|
|
4692
|
+
enum:
|
|
4693
|
+
- minimal
|
|
4694
|
+
- low
|
|
4695
|
+
- medium
|
|
4696
|
+
- high
|
|
4697
|
+
contextWindow:
|
|
4698
|
+
type: object
|
|
4699
|
+
properties:
|
|
4700
|
+
enabled:
|
|
4701
|
+
type: boolean
|
|
4702
|
+
maxInputTokens:
|
|
4703
|
+
type: integer
|
|
4704
|
+
exclusiveMinimum: 0
|
|
4705
|
+
maximum: 9007199254740991
|
|
4706
|
+
targetBudgetRatio:
|
|
4707
|
+
type: number
|
|
4708
|
+
exclusiveMinimum: 0
|
|
4709
|
+
maximum: 1
|
|
4710
|
+
compactThreshold:
|
|
4711
|
+
type: number
|
|
4712
|
+
exclusiveMinimum: 0
|
|
4713
|
+
maximum: 1
|
|
4714
|
+
summaryBudgetRatio:
|
|
4715
|
+
type: number
|
|
4716
|
+
exclusiveMinimum: 0
|
|
4717
|
+
maximum: 1
|
|
4718
|
+
overflowRecovery:
|
|
4719
|
+
type: object
|
|
4720
|
+
properties:
|
|
4721
|
+
enabled:
|
|
4722
|
+
type: boolean
|
|
4723
|
+
safetyMarginRatio:
|
|
4724
|
+
type: number
|
|
4725
|
+
exclusiveMinimum: 0
|
|
4726
|
+
exclusiveMaximum: 1
|
|
4727
|
+
maxAttempts:
|
|
4728
|
+
type: integer
|
|
4729
|
+
exclusiveMinimum: 0
|
|
4730
|
+
maximum: 9007199254740991
|
|
4731
|
+
interactiveLatestTurnCompression:
|
|
4732
|
+
type: string
|
|
4733
|
+
enum:
|
|
4734
|
+
- truncate
|
|
4735
|
+
- summarize
|
|
4736
|
+
- drop
|
|
4737
|
+
nonInteractiveLatestTurnCompression:
|
|
4738
|
+
type: string
|
|
4739
|
+
enum:
|
|
4740
|
+
- truncate
|
|
4741
|
+
- summarize
|
|
4742
|
+
- drop
|
|
4743
|
+
openrouter:
|
|
4744
|
+
type: object
|
|
4745
|
+
properties:
|
|
4746
|
+
only:
|
|
4747
|
+
type: array
|
|
4748
|
+
items:
|
|
4749
|
+
type: string
|
|
4750
|
+
minLength: 1
|
|
4751
|
+
logitBias:
|
|
4752
|
+
type: string
|
|
4753
|
+
enum:
|
|
4754
|
+
- suppress-cjk
|
|
4755
|
+
disableCache:
|
|
4756
|
+
type: boolean
|
|
4757
|
+
source:
|
|
4758
|
+
type: string
|
|
4759
|
+
enum:
|
|
4760
|
+
- managed
|
|
4761
|
+
- user
|
|
4762
|
+
label:
|
|
4763
|
+
anyOf:
|
|
4764
|
+
- type: string
|
|
4765
|
+
minLength: 1
|
|
4766
|
+
- type: "null"
|
|
4767
|
+
description:
|
|
4768
|
+
type: string
|
|
4769
|
+
provider_connection:
|
|
4770
|
+
type: string
|
|
4771
|
+
minLength: 1
|
|
4772
|
+
status:
|
|
4773
|
+
anyOf:
|
|
4774
|
+
- $ref: "#/components/schemas/ProfileStatus"
|
|
4775
|
+
- type: "null"
|
|
4776
|
+
mix:
|
|
4777
|
+
minItems: 2
|
|
4778
|
+
type: array
|
|
4779
|
+
items:
|
|
4780
|
+
type: object
|
|
4781
|
+
properties:
|
|
4782
|
+
profile:
|
|
4783
|
+
type: string
|
|
4784
|
+
minLength: 1
|
|
4785
|
+
weight:
|
|
4786
|
+
type: number
|
|
4787
|
+
exclusiveMinimum: 0
|
|
4788
|
+
required:
|
|
4789
|
+
- profile
|
|
4790
|
+
- weight
|
|
4635
4791
|
responses:
|
|
4636
4792
|
"200":
|
|
4637
4793
|
description: Successful response
|
|
@@ -17760,6 +17916,44 @@ paths:
|
|
|
17760
17916
|
additionalProperties: false
|
|
17761
17917
|
backgroundToolNotification:
|
|
17762
17918
|
type: boolean
|
|
17919
|
+
backgroundToolCompletion:
|
|
17920
|
+
type: object
|
|
17921
|
+
properties:
|
|
17922
|
+
id:
|
|
17923
|
+
type: string
|
|
17924
|
+
toolName:
|
|
17925
|
+
type: string
|
|
17926
|
+
conversationId:
|
|
17927
|
+
type: string
|
|
17928
|
+
command:
|
|
17929
|
+
type: string
|
|
17930
|
+
startedAt:
|
|
17931
|
+
type: number
|
|
17932
|
+
status:
|
|
17933
|
+
type: string
|
|
17934
|
+
enum:
|
|
17935
|
+
- completed
|
|
17936
|
+
- failed
|
|
17937
|
+
- cancelled
|
|
17938
|
+
exitCode:
|
|
17939
|
+
anyOf:
|
|
17940
|
+
- type: number
|
|
17941
|
+
- type: "null"
|
|
17942
|
+
output:
|
|
17943
|
+
type: string
|
|
17944
|
+
completedAt:
|
|
17945
|
+
type: number
|
|
17946
|
+
required:
|
|
17947
|
+
- id
|
|
17948
|
+
- toolName
|
|
17949
|
+
- conversationId
|
|
17950
|
+
- command
|
|
17951
|
+
- startedAt
|
|
17952
|
+
- status
|
|
17953
|
+
- exitCode
|
|
17954
|
+
- output
|
|
17955
|
+
- completedAt
|
|
17956
|
+
additionalProperties: false
|
|
17763
17957
|
slackMessage:
|
|
17764
17958
|
type: object
|
|
17765
17959
|
properties:
|
package/package.json
CHANGED
|
@@ -189,6 +189,10 @@ describe("bash tool background mode", () => {
|
|
|
189
189
|
// Command stdout is fenced as untrusted output, not inlined in the hint.
|
|
190
190
|
expect(wakeCall.untrustedOutput?.content).toContain("bg_output_12345");
|
|
191
191
|
expect(wakeCall.untrustedOutput?.source).toBe("tool_result");
|
|
192
|
+
// Durable completion record stamped onto the persisted wake.
|
|
193
|
+
expect(wakeCall.backgroundToolCompletion?.id).toBe("bg-test1234");
|
|
194
|
+
expect(wakeCall.backgroundToolCompletion?.status).toBe("completed");
|
|
195
|
+
expect(wakeCall.backgroundToolCompletion?.exitCode).toBe(0);
|
|
192
196
|
});
|
|
193
197
|
|
|
194
198
|
test("failing background process delivers an error hint via wake", async () => {
|
|
@@ -210,6 +214,9 @@ describe("bash tool background mode", () => {
|
|
|
210
214
|
expect(wakeCall.hint).toContain("bg-test1234");
|
|
211
215
|
// The command fails with exit code 1, so the hint should reflect failure
|
|
212
216
|
expect(wakeCall.hint).toContain("exit=1");
|
|
217
|
+
expect(wakeCall.backgroundToolCompletion?.id).toBe("bg-test1234");
|
|
218
|
+
expect(wakeCall.backgroundToolCompletion?.status).toBe("failed");
|
|
219
|
+
expect(wakeCall.backgroundToolCompletion?.exitCode).toBe(1);
|
|
213
220
|
});
|
|
214
221
|
|
|
215
222
|
test("cancelled background process wakes with the cancellation, not a completed result", async () => {
|
|
@@ -234,6 +241,8 @@ describe("bash tool background mode", () => {
|
|
|
234
241
|
expect(wakeCall.hint).toContain("cancelled");
|
|
235
242
|
expect(wakeCall.hint).not.toContain("completed");
|
|
236
243
|
expect(wakeCall.untrustedOutput?.content).toContain("cancelled");
|
|
244
|
+
expect(wakeCall.backgroundToolCompletion?.id).toBe("bg-test1234");
|
|
245
|
+
expect(wakeCall.backgroundToolCompletion?.status).toBe("cancelled");
|
|
237
246
|
});
|
|
238
247
|
|
|
239
248
|
test("foreground mode still works when background is not set", async () => {
|
|
@@ -118,6 +118,7 @@ mock.module("../daemon/host-bash-proxy.js", () => ({
|
|
|
118
118
|
// Import under test — MUST come after mock.module calls.
|
|
119
119
|
// ---------------------------------------------------------------------------
|
|
120
120
|
|
|
121
|
+
import type { CompletedBackgroundTool } from "../tools/background-tool-registry.js";
|
|
121
122
|
import { hostShellTool } from "../tools/host-terminal/host-shell.js";
|
|
122
123
|
import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
123
124
|
|
|
@@ -258,6 +259,11 @@ describe("host_bash background mode — proxy path", () => {
|
|
|
258
259
|
maxChars: 40_000,
|
|
259
260
|
});
|
|
260
261
|
expect(wakeCall.source).toBe("background-tool");
|
|
262
|
+
const completion =
|
|
263
|
+
wakeCall.backgroundToolCompletion as CompletedBackgroundTool;
|
|
264
|
+
expect(completion.id).toBe("bg-test-0001");
|
|
265
|
+
expect(completion.status).toBe("completed");
|
|
266
|
+
expect(completion.exitCode).toBeNull();
|
|
261
267
|
});
|
|
262
268
|
|
|
263
269
|
test("calls wakeAgentForOpportunity on proxy error result", async () => {
|
|
@@ -398,6 +404,11 @@ describe("host_bash background mode — direct execution path", () => {
|
|
|
398
404
|
expect((wakeCall.untrustedOutput as { content: string }).content).toContain(
|
|
399
405
|
"hello world",
|
|
400
406
|
);
|
|
407
|
+
const completion =
|
|
408
|
+
wakeCall.backgroundToolCompletion as CompletedBackgroundTool;
|
|
409
|
+
expect(completion.id).toBe("bg-test-0001");
|
|
410
|
+
expect(completion.status).toBe("completed");
|
|
411
|
+
expect(completion.exitCode).toBe(0);
|
|
401
412
|
});
|
|
402
413
|
|
|
403
414
|
test("calls wakeAgentForOpportunity with error hint on non-zero exit", async () => {
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handleListMessages background-tool completion projection.
|
|
3
|
+
*
|
|
4
|
+
* A backgrounded bash/host_bash run posts a `<background_event
|
|
5
|
+
* source="background-tool">` wake on completion, stamped with
|
|
6
|
+
* `metadata.backgroundToolCompletion`. The history projection must surface
|
|
7
|
+
* that structured record (and the `backgroundToolNotification` flag) so the
|
|
8
|
+
* web can rebuild a terminal inline card after a daemon restart.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
mock.module("../util/logger.js", () => ({
|
|
14
|
+
getLogger: () =>
|
|
15
|
+
new Proxy({} as Record<string, unknown>, {
|
|
16
|
+
get: () => () => {},
|
|
17
|
+
}),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
mock.module("../config/loader.js", () => ({
|
|
21
|
+
getConfig: () => ({
|
|
22
|
+
ui: {},
|
|
23
|
+
model: "test",
|
|
24
|
+
provider: "test",
|
|
25
|
+
memory: { enabled: false },
|
|
26
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
27
|
+
}),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
import { ConversationMessageSchema } from "../api/responses/conversation-message.js";
|
|
31
|
+
import {
|
|
32
|
+
addMessage,
|
|
33
|
+
createConversation,
|
|
34
|
+
} from "../persistence/conversation-crud.js";
|
|
35
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
36
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
37
|
+
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
38
|
+
|
|
39
|
+
await initializeDb();
|
|
40
|
+
|
|
41
|
+
function resetTables() {
|
|
42
|
+
const db = getDb();
|
|
43
|
+
db.run("DELETE FROM message_attachments");
|
|
44
|
+
db.run("DELETE FROM attachments");
|
|
45
|
+
db.run("DELETE FROM messages");
|
|
46
|
+
db.run("DELETE FROM conversations");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ProjectedMessage {
|
|
50
|
+
role: string;
|
|
51
|
+
backgroundToolNotification?: boolean;
|
|
52
|
+
backgroundToolCompletion?: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("handleListMessages background-tool completion projection", () => {
|
|
56
|
+
beforeEach(resetTables);
|
|
57
|
+
|
|
58
|
+
test("projects backgroundToolCompletion from the wake row metadata", async () => {
|
|
59
|
+
const conv = createConversation();
|
|
60
|
+
await addMessage(
|
|
61
|
+
conv.id,
|
|
62
|
+
"user",
|
|
63
|
+
JSON.stringify([{ type: "text", text: "run a long command" }]),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const completion = {
|
|
67
|
+
id: "bg-1",
|
|
68
|
+
toolName: "bash",
|
|
69
|
+
conversationId: conv.id,
|
|
70
|
+
command: "sleep 5 && echo done",
|
|
71
|
+
startedAt: 1000,
|
|
72
|
+
status: "completed" as const,
|
|
73
|
+
exitCode: 0,
|
|
74
|
+
output: "done\n",
|
|
75
|
+
completedAt: 2000,
|
|
76
|
+
};
|
|
77
|
+
// Mirror persistWakeTriggerMessage: a user-role
|
|
78
|
+
// `<background_event source="background-tool">` row carrying the structured
|
|
79
|
+
// completion. Not flagged `hidden`; the client suppresses it from the
|
|
80
|
+
// transcript via `backgroundToolNotification`.
|
|
81
|
+
await addMessage(
|
|
82
|
+
conv.id,
|
|
83
|
+
"user",
|
|
84
|
+
JSON.stringify([
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: '<background_event source="background-tool">Background command completed (id=bg-1, exit=0):</background_event>',
|
|
88
|
+
},
|
|
89
|
+
]),
|
|
90
|
+
{
|
|
91
|
+
metadata: {
|
|
92
|
+
kind: "background-event",
|
|
93
|
+
backgroundEventSource: "background-tool",
|
|
94
|
+
automated: true,
|
|
95
|
+
backgroundToolCompletion: completion,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const response = handleListMessages({
|
|
101
|
+
queryParams: { conversationId: conv.id },
|
|
102
|
+
}) as { messages: ProjectedMessage[] };
|
|
103
|
+
|
|
104
|
+
// Every projected message validates against the wire schema.
|
|
105
|
+
for (const message of response.messages) {
|
|
106
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const wakeRow = response.messages.find(
|
|
110
|
+
(m) => m.backgroundToolCompletion !== undefined,
|
|
111
|
+
);
|
|
112
|
+
expect(wakeRow).toBeDefined();
|
|
113
|
+
expect(wakeRow?.backgroundToolNotification).toBe(true);
|
|
114
|
+
expect(wakeRow?.backgroundToolCompletion).toEqual(completion);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("omits backgroundToolCompletion when the row carries no completion metadata", async () => {
|
|
118
|
+
const conv = createConversation();
|
|
119
|
+
await addMessage(
|
|
120
|
+
conv.id,
|
|
121
|
+
"user",
|
|
122
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
123
|
+
);
|
|
124
|
+
await addMessage(
|
|
125
|
+
conv.id,
|
|
126
|
+
"assistant",
|
|
127
|
+
JSON.stringify([{ type: "text", text: "hi there" }]),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const response = handleListMessages({
|
|
131
|
+
queryParams: { conversationId: conv.id },
|
|
132
|
+
}) as { messages: ProjectedMessage[] };
|
|
133
|
+
|
|
134
|
+
expect(response.messages).toHaveLength(2);
|
|
135
|
+
for (const message of response.messages) {
|
|
136
|
+
expect(message.backgroundToolCompletion).toBeUndefined();
|
|
137
|
+
expect(message.backgroundToolNotification).toBeUndefined();
|
|
138
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -16,69 +16,53 @@ describe("onboarding template contracts", () => {
|
|
|
16
16
|
expect(bootstrap).toMatch(/^_ Lines starting with _/);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
test("
|
|
19
|
+
test("anchors identity in the pre-chat personality and demands staying in character", () => {
|
|
20
20
|
const lower = bootstrap.toLowerCase();
|
|
21
|
-
expect(lower).toContain("
|
|
22
|
-
expect(lower).toContain("
|
|
21
|
+
expect(lower).toContain("pre-chat");
|
|
22
|
+
expect(lower).toContain("in character");
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test("
|
|
25
|
+
test("opens in character with a move drawn from workspace files", () => {
|
|
26
26
|
const lower = bootstrap.toLowerCase();
|
|
27
|
-
expect(lower).toContain("
|
|
28
|
-
expect(lower).toContain("
|
|
29
|
-
expect(lower).toContain("tools");
|
|
27
|
+
expect(lower).toContain("you start the conversation");
|
|
28
|
+
expect(lower).toContain("workspace files");
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
test("
|
|
31
|
+
test("is engaging, inquisitive, and brief", () => {
|
|
33
32
|
const lower = bootstrap.toLowerCase();
|
|
34
|
-
expect(lower).toContain("
|
|
35
|
-
expect(lower).toContain("
|
|
36
|
-
expect(lower).toContain("bootstrap.md");
|
|
33
|
+
expect(lower).toContain("follow-up question");
|
|
34
|
+
expect(lower).toContain("short");
|
|
37
35
|
});
|
|
38
36
|
|
|
39
|
-
test("
|
|
37
|
+
test("does the task then keeps learning about the user", () => {
|
|
40
38
|
const lower = bootstrap.toLowerCase();
|
|
41
|
-
expect(lower).toContain("
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
test("instructs saving to IDENTITY.md, SOUL.md, and user persona file via file_edit", () => {
|
|
45
|
-
expect(bootstrap).toContain("IDENTITY.md");
|
|
46
|
-
expect(bootstrap).toContain("SOUL.md");
|
|
47
|
-
expect(bootstrap).toContain("{{userSlug}}.md");
|
|
48
|
-
expect(bootstrap).toContain("file_edit");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("contains core principle", () => {
|
|
52
|
-
expect(bootstrap).toContain("earns its keep");
|
|
39
|
+
expect(lower).toContain("just do it");
|
|
40
|
+
expect(lower).toContain("keep learning about them");
|
|
53
41
|
});
|
|
54
42
|
|
|
55
|
-
test("contains
|
|
43
|
+
test("contains cleanup instructions with deletion", () => {
|
|
56
44
|
const lower = bootstrap.toLowerCase();
|
|
57
|
-
expect(lower).toContain("
|
|
58
|
-
expect(lower).toContain("
|
|
59
|
-
expect(lower).toContain("context");
|
|
45
|
+
expect(lower).toContain("delete");
|
|
46
|
+
expect(lower).toContain("bootstrap.md");
|
|
60
47
|
});
|
|
61
48
|
|
|
62
|
-
test("
|
|
49
|
+
test("offers assistant migration from an existing ChatGPT/Claude", () => {
|
|
63
50
|
const lower = bootstrap.toLowerCase();
|
|
64
|
-
expect(lower).toContain("
|
|
65
|
-
expect(lower).toContain("
|
|
51
|
+
expect(lower).toContain("chatgpt");
|
|
52
|
+
expect(lower).toContain("claude");
|
|
53
|
+
expect(bootstrap).toContain("assistant-migration");
|
|
66
54
|
});
|
|
67
55
|
|
|
68
|
-
test("
|
|
69
|
-
expect(bootstrap).toContain("
|
|
56
|
+
test("does not instruct workspace identity-file writes", () => {
|
|
57
|
+
expect(bootstrap).not.toContain("file_edit");
|
|
58
|
+
expect(bootstrap).not.toContain("file_write");
|
|
59
|
+
expect(bootstrap).not.toMatch(/write .*to SOUL\.md/i);
|
|
60
|
+
expect(bootstrap).not.toMatch(/\{\{userSlug\}\}\.md/);
|
|
70
61
|
});
|
|
71
62
|
|
|
72
|
-
test("
|
|
73
|
-
expect(bootstrap).
|
|
74
|
-
expect(bootstrap).
|
|
75
|
-
expect(bootstrap).toContain("treat it as the real first user turn");
|
|
76
|
-
expect(bootstrap).toContain(
|
|
77
|
-
"If the first real user turn is only a greeting",
|
|
78
|
-
);
|
|
79
|
-
expect(bootstrap).toContain("that first response is the natural opening");
|
|
80
|
-
expect(bootstrap).toContain("assistant-migration");
|
|
81
|
-
expect(bootstrap).toContain("do not load or activate");
|
|
63
|
+
test("does not split users into conversation-first and task-first paths", () => {
|
|
64
|
+
expect(bootstrap).not.toMatch(/Path A/);
|
|
65
|
+
expect(bootstrap).not.toMatch(/Path B/);
|
|
82
66
|
});
|
|
83
67
|
|
|
84
68
|
test("does not contain personality quiz references", () => {
|
|
@@ -161,6 +161,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
161
161
|
"../../../config/assistant-feature-flags.js",
|
|
162
162
|
"../../../config/loader.js",
|
|
163
163
|
"../../../config/memory-v3-gate.js",
|
|
164
|
+
"../../../config/schema.js",
|
|
164
165
|
"../../../config/schemas/memory-v2.js",
|
|
165
166
|
"../../../config/types.js",
|
|
166
167
|
"../../../contacts/guardian-delivery-reader.js",
|
|
@@ -169,10 +170,13 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
169
170
|
"../../../context/token-estimator.js",
|
|
170
171
|
"../../../daemon/date-context.js",
|
|
171
172
|
"../../../daemon/disk-pressure-background-gate.js",
|
|
173
|
+
"../../../daemon/embedding-reconcile.js",
|
|
172
174
|
"../../../daemon/pkb-context-tracker.js",
|
|
173
175
|
"../../../daemon/pkb-reminder-builder.js",
|
|
176
|
+
"../../../daemon/skill-memory-refresh.js",
|
|
174
177
|
"../../../daemon/tool-setup-types.js",
|
|
175
178
|
"../../../daemon/trust-context.js",
|
|
179
|
+
"../../../jobs/register-job-handlers.js",
|
|
176
180
|
"../../../permissions/types.js",
|
|
177
181
|
"../../../persistence/checkpoints.js",
|
|
178
182
|
"../../../persistence/conversation-crud.js",
|
|
@@ -180,7 +184,10 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
180
184
|
"../../../persistence/embeddings/embedding-backend.js",
|
|
181
185
|
"../../../persistence/embeddings/embedding-runtime-manager.js",
|
|
182
186
|
"../../../persistence/embeddings/embedding-types.js",
|
|
187
|
+
"../../../persistence/embeddings/qdrant-client.js",
|
|
188
|
+
"../../../persistence/embeddings/qdrant-manager.js",
|
|
183
189
|
"../../../persistence/jobs-store.js",
|
|
190
|
+
"../../../persistence/jobs-worker.js",
|
|
184
191
|
"../../../persistence/memory-lifecycle-hooks.js",
|
|
185
192
|
"../../../persistence/message-content.js",
|
|
186
193
|
"../../../persistence/raw-query.js",
|
|
@@ -457,9 +457,10 @@ describe("buildSystemPrompt", () => {
|
|
|
457
457
|
expect(result).not.toContain("{{userSlug}}");
|
|
458
458
|
});
|
|
459
459
|
|
|
460
|
-
test("
|
|
461
|
-
//
|
|
462
|
-
//
|
|
460
|
+
test("leaves no unresolved placeholders in the bundled BOOTSTRAP.md template", () => {
|
|
461
|
+
// Render the real bundled BOOTSTRAP.md the daemon ships and verify
|
|
462
|
+
// substitution leaves no leftover {{userSlug}} placeholder, whether or
|
|
463
|
+
// not the current template happens to reference it.
|
|
463
464
|
mockPersona.userSlug = "alice";
|
|
464
465
|
const bundled = readFileSync(
|
|
465
466
|
join(import.meta.dirname, "..", "prompts", "templates", "BOOTSTRAP.md"),
|
|
@@ -467,7 +468,6 @@ describe("buildSystemPrompt", () => {
|
|
|
467
468
|
);
|
|
468
469
|
writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), bundled);
|
|
469
470
|
const result = buildSystemPrompt();
|
|
470
|
-
expect(result).toContain("users/alice.md");
|
|
471
471
|
expect(result).not.toContain("{{userSlug}}");
|
|
472
472
|
});
|
|
473
473
|
});
|
package/src/api/index.ts
CHANGED
|
@@ -296,6 +296,29 @@ export type ConversationAcpNotification = z.infer<
|
|
|
296
296
|
typeof ConversationAcpNotificationSchema
|
|
297
297
|
>;
|
|
298
298
|
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Background-tool completion
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
/** Structured terminal record of a backgrounded bash/host_bash run, carrying
|
|
304
|
+
* everything a web `BackgroundTaskEntry` needs to rebuild a completed inline
|
|
305
|
+
* card from history. Mirrors the `background_tool_completed` SSE event plus
|
|
306
|
+
* the registry fields (`toolName`, `command`, `startedAt`). */
|
|
307
|
+
export const BackgroundToolCompletionSchema = z.object({
|
|
308
|
+
id: z.string(),
|
|
309
|
+
toolName: z.string(),
|
|
310
|
+
conversationId: z.string(),
|
|
311
|
+
command: z.string(),
|
|
312
|
+
startedAt: z.number(),
|
|
313
|
+
status: z.enum(["completed", "failed", "cancelled"]),
|
|
314
|
+
exitCode: z.number().nullable(),
|
|
315
|
+
output: z.string(),
|
|
316
|
+
completedAt: z.number(),
|
|
317
|
+
});
|
|
318
|
+
export type BackgroundToolCompletion = z.infer<
|
|
319
|
+
typeof BackgroundToolCompletionSchema
|
|
320
|
+
>;
|
|
321
|
+
|
|
299
322
|
// ---------------------------------------------------------------------------
|
|
300
323
|
// Slack message envelope
|
|
301
324
|
// ---------------------------------------------------------------------------
|
|
@@ -549,6 +572,12 @@ export const ConversationMessageSchema = z.object({
|
|
|
549
572
|
* notifications, the row stays in state (the LLM reads it) but is filtered
|
|
550
573
|
* from the rendered transcript — the inline card carries the status. */
|
|
551
574
|
backgroundToolNotification: z.boolean().optional(),
|
|
575
|
+
/** Structured completion of a backgrounded bash/host_bash run, stamped on the
|
|
576
|
+
* same persisted background-event wake row as `backgroundToolNotification`.
|
|
577
|
+
* Lets the web reconstruct a terminal inline card after a daemon restart
|
|
578
|
+
* (the in-memory completed ring does not survive restarts). `id` equals the
|
|
579
|
+
* spawning tool call's `{backgrounded,id}` id. */
|
|
580
|
+
backgroundToolCompletion: BackgroundToolCompletionSchema.optional(),
|
|
552
581
|
slackMessage: ConversationSlackMessageSchema.optional(),
|
|
553
582
|
/**
|
|
554
583
|
* Queue state for a user message that is still waiting in the daemon's
|
|
@@ -22,7 +22,7 @@ describe("MemoryV2ConfigSchema", () => {
|
|
|
22
22
|
sparse_weight: 0.15,
|
|
23
23
|
bm25_k1: 1.2,
|
|
24
24
|
bm25_b: 0.4,
|
|
25
|
-
consolidation_interval_hours:
|
|
25
|
+
consolidation_interval_hours: 8,
|
|
26
26
|
consolidation_max_buffer_lines: 100,
|
|
27
27
|
consolidation_max_entries_per_run: 150,
|
|
28
28
|
max_page_chars: 5000,
|
|
@@ -222,7 +222,7 @@ describe("MemoryConfigSchema integration with v2 block", () => {
|
|
|
222
222
|
expect(parsed.v2.d).toBe(0.3);
|
|
223
223
|
expect(parsed.v2.dense_weight).toBe(0.85);
|
|
224
224
|
expect(parsed.v2.sparse_weight).toBe(0.15);
|
|
225
|
-
expect(parsed.v2.consolidation_interval_hours).toBe(
|
|
225
|
+
expect(parsed.v2.consolidation_interval_hours).toBe(8);
|
|
226
226
|
expect(parsed.v2.max_page_chars).toBe(5000);
|
|
227
227
|
});
|
|
228
228
|
|
|
@@ -190,7 +190,7 @@ export const MemoryV2ConfigSchema = z
|
|
|
190
190
|
.positive(
|
|
191
191
|
"memory.v2.consolidation_interval_hours must be a positive integer",
|
|
192
192
|
)
|
|
193
|
-
.default(
|
|
193
|
+
.default(8)
|
|
194
194
|
.describe(
|
|
195
195
|
"Hours between scheduled consolidation runs that synthesize buffered memories into concept pages",
|
|
196
196
|
),
|