@vellumai/assistant 0.8.11 → 0.8.12-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +126 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +301 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +219 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +2 -1
- package/src/providers/openai/responses-provider.ts +5 -1
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/plugins-routes.ts +21 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -1,37 +1,29 @@
|
|
|
1
|
-
import { readdirSync
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { describe, expect, test } from "bun:test";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ label: "rollout flag", pattern: /\brollout flag\b/i },
|
|
26
|
-
{ label: "behind ... flag", pattern: /\bbehind\b[^\n.]{0,120}\bflag\b/i },
|
|
27
|
-
{ label: "gated on", pattern: /\bgated on\b/i },
|
|
28
|
-
{ label: "when enabled", pattern: /\bwhen enabled\b/i },
|
|
5
|
+
/**
|
|
6
|
+
* The historical release-note workspace migrations. These appended bulletins
|
|
7
|
+
* to `<workspace>/UPDATES.md`, which a background job processed at daemon
|
|
8
|
+
* startup. That job has been removed — nothing consumes UPDATES.md release
|
|
9
|
+
* notes anymore — so the set below is frozen. Migration files themselves are
|
|
10
|
+
* append-only and must never be deleted (see the migrations AGENTS.md), but
|
|
11
|
+
* no new ones of this shape may be added.
|
|
12
|
+
*/
|
|
13
|
+
const FROZEN_RELEASE_NOTE_MIGRATIONS = [
|
|
14
|
+
"043-release-notes-latex-rendering.ts",
|
|
15
|
+
"045-release-notes-meet-avatar.ts",
|
|
16
|
+
"049-release-notes-default-sonnet.ts",
|
|
17
|
+
"053-release-notes-acp-codex.ts",
|
|
18
|
+
"055-release-notes-agentic-recall.ts",
|
|
19
|
+
"056-release-notes-inference-profile-reordering.ts",
|
|
20
|
+
"058-release-notes-acp-sessions-ui.ts",
|
|
21
|
+
"063-release-notes-dynamic-model-context.ts",
|
|
22
|
+
"067-release-notes-safe-storage-limits.ts",
|
|
23
|
+
"068-release-notes-local-timezone.ts",
|
|
24
|
+
"078-release-notes-tavily-web-search.ts",
|
|
29
25
|
];
|
|
30
26
|
|
|
31
|
-
function getRepoRoot(): string {
|
|
32
|
-
return join(process.cwd(), "..");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
27
|
function getReleaseNoteMigrationFiles(): string[] {
|
|
36
28
|
const migrationsDir = join(process.cwd(), "src", "workspace", "migrations");
|
|
37
29
|
return readdirSync(migrationsDir)
|
|
@@ -39,77 +31,35 @@ function getReleaseNoteMigrationFiles(): string[] {
|
|
|
39
31
|
.sort();
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
49
|
-
const registry = JSON.parse(readFileSync(registryPath, "utf-8")) as Registry;
|
|
50
|
-
return registry.flags
|
|
51
|
-
.filter((flag) => flag.scope === "assistant" && !flag.defaultEnabled)
|
|
52
|
-
.map((flag) => flag.key)
|
|
53
|
-
.sort();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function escapeRegExp(value: string): string {
|
|
57
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function featureFlagKeyPattern(key: string): RegExp {
|
|
61
|
-
const escaped = escapeRegExp(key);
|
|
62
|
-
if (key.includes("-")) {
|
|
63
|
-
return new RegExp(`(?:^|[^a-z0-9-])${escaped}(?:$|[^a-z0-9-])`, "i");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return new RegExp("[`'\"]" + escaped + "[`'\"]", "i");
|
|
67
|
-
}
|
|
34
|
+
describe("workspace release-note migrations guard", () => {
|
|
35
|
+
test("no new release-note migrations are added", () => {
|
|
36
|
+
const found = getReleaseNoteMigrationFiles();
|
|
37
|
+
const newMigrations = found.filter(
|
|
38
|
+
(fileName) => !FROZEN_RELEASE_NOTE_MIGRATIONS.includes(fileName),
|
|
39
|
+
);
|
|
68
40
|
|
|
69
|
-
|
|
70
|
-
test("new release-note migrations do not announce default-disabled feature-flagged work", () => {
|
|
71
|
-
const migrationsDir = join(process.cwd(), "src", "workspace", "migrations");
|
|
72
|
-
const defaultDisabledFlagKeys = loadDefaultDisabledAssistantFlagKeys();
|
|
73
|
-
const violations: string[] = [];
|
|
74
|
-
|
|
75
|
-
for (const fileName of getReleaseNoteMigrationFiles()) {
|
|
76
|
-
if (LEGACY_FLAGGED_RELEASE_NOTE_ALLOWLIST.has(fileName)) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const content = readFileSync(join(migrationsDir, fileName), "utf-8");
|
|
81
|
-
|
|
82
|
-
for (const { label, pattern } of FLAGGED_FEATURE_LANGUAGE_PATTERNS) {
|
|
83
|
-
if (pattern.test(content)) {
|
|
84
|
-
violations.push(
|
|
85
|
-
`${fileName}: contains flagged-feature language "${label}"`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const key of defaultDisabledFlagKeys) {
|
|
91
|
-
if (featureFlagKeyPattern(key).test(content)) {
|
|
92
|
-
violations.push(
|
|
93
|
-
`${fileName}: references default-disabled assistant feature flag "${key}"`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (violations.length > 0) {
|
|
41
|
+
if (newMigrations.length > 0) {
|
|
100
42
|
const message = [
|
|
101
|
-
"Release-note migrations
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"",
|
|
105
|
-
"Violations:",
|
|
106
|
-
...violations.map((violation) => ` - ${violation}`),
|
|
43
|
+
"Release-note migrations append to <workspace>/UPDATES.md, but the",
|
|
44
|
+
"background job that processed that file has been removed — nothing",
|
|
45
|
+
"consumes release notes written there. Do not add new release-note",
|
|
46
|
+
"migrations until a replacement surfacing mechanism is designed.",
|
|
107
47
|
"",
|
|
108
|
-
"
|
|
109
|
-
|
|
48
|
+
"New release-note migrations found:",
|
|
49
|
+
...newMigrations.map((fileName) => ` - ${fileName}`),
|
|
110
50
|
].join("\n");
|
|
111
51
|
|
|
112
|
-
expect(
|
|
52
|
+
expect(newMigrations, message).toEqual([]);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("frozen release-note migrations are never deleted", () => {
|
|
57
|
+
// Migrations are append-only: even though their bulletins are no longer
|
|
58
|
+
// processed, deleting an entry breaks the sequential migration chain on
|
|
59
|
+
// existing installs.
|
|
60
|
+
const found = getReleaseNoteMigrationFiles();
|
|
61
|
+
for (const fileName of FROZEN_RELEASE_NOTE_MIGRATIONS) {
|
|
62
|
+
expect(found).toContain(fileName);
|
|
113
63
|
}
|
|
114
64
|
});
|
|
115
65
|
});
|
package/src/agent/loop.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
estimateToolsTokens,
|
|
12
12
|
getCalibrationProviderKey,
|
|
13
13
|
} from "../context/token-estimator.js";
|
|
14
|
+
import { spoolAndStubOversizedToolResults } from "../context/tool-result-spool.js";
|
|
14
15
|
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
15
16
|
import { parseActualTokensFromError } from "../daemon/parse-actual-tokens-from-error.js";
|
|
16
17
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
@@ -298,25 +299,27 @@ export type AgentEvent =
|
|
|
298
299
|
/** The running history before injection stripping and compaction. */
|
|
299
300
|
messages: Message[];
|
|
300
301
|
}
|
|
301
|
-
| {
|
|
302
|
+
| ({
|
|
302
303
|
/**
|
|
303
304
|
* Emitted after the loop's inline mid-loop compaction pipeline runs,
|
|
304
305
|
* immediately before re-injection — whether or not the pipeline actually
|
|
305
|
-
* compacted.
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
306
|
+
* compacted. Carries the pipeline's `ContextWindowResult` unnested into
|
|
307
|
+
* the event, so `messages` here is the pipeline's output history. The
|
|
308
|
+
* pre-compaction history lives on the paired `context_compacting` start
|
|
309
|
+
* event (correlated by `compactionId`); consumers that need the
|
|
310
|
+
* stripped pre-compaction base re-derive it from the start event via
|
|
311
|
+
* `stripInjectionsForCompaction`.
|
|
312
|
+
*
|
|
313
|
+
* The daemon's event dispatcher commits the stripped pre-compaction
|
|
314
|
+
* base as the conversation's durable message state, so re-injection
|
|
315
|
+
* (the post-compaction hook) re-applies injections onto the stripped
|
|
316
|
+
* base rather than stacking on top of the still-injected messages.
|
|
317
|
+
* When `compacted` is set it additionally commits the durable
|
|
318
|
+
* compaction result (DB-record fields, graph-memory side effects, SSE)
|
|
319
|
+
* and projects Slack provenance from the pre-compaction base.
|
|
313
320
|
*
|
|
314
321
|
* Treated as a critical event: a failed durable commit re-throws so the
|
|
315
322
|
* turn aborts rather than re-injecting against half-applied state.
|
|
316
|
-
*
|
|
317
|
-
* `messages` is the stripped pre-compaction history the summary was
|
|
318
|
-
* built from; the dispatcher uses it to project Slack provenance onto
|
|
319
|
-
* the compacted result.
|
|
320
323
|
*/
|
|
321
324
|
type: "compaction_completed";
|
|
322
325
|
/** Correlates this end event with its `context_compacting` pair. */
|
|
@@ -330,9 +333,7 @@ export type AgentEvent =
|
|
|
330
333
|
startedAt: number;
|
|
331
334
|
/** Epoch ms when the compaction pipeline returned. */
|
|
332
335
|
finishedAt: number;
|
|
333
|
-
|
|
334
|
-
messages: Message[];
|
|
335
|
-
}
|
|
336
|
+
} & ContextWindowResult)
|
|
336
337
|
| {
|
|
337
338
|
/**
|
|
338
339
|
* Emitted right after the loop strips runtime injections from the
|
|
@@ -595,6 +596,16 @@ export interface AgentLoopConstructorOptions {
|
|
|
595
596
|
* post-compaction re-injection resolve the live conversation through.
|
|
596
597
|
*/
|
|
597
598
|
conversationId: string;
|
|
599
|
+
/**
|
|
600
|
+
* Resolve the conversation's on-disk directory, used to spool oversized
|
|
601
|
+
* tool results to `.tool-results/` and swap the inline copy for the
|
|
602
|
+
* post-turn pass's stub at result time — before the result joins history,
|
|
603
|
+
* so the provider-bound prefix stays append-only for prompt caching.
|
|
604
|
+
* Returns `null` while the directory cannot be resolved (e.g. the
|
|
605
|
+
* conversation row is not yet persisted); the loop then skips the
|
|
606
|
+
* result-time pass and the post-turn truncation covers the turn instead.
|
|
607
|
+
*/
|
|
608
|
+
resolveConversationDir?: () => string | null;
|
|
598
609
|
}
|
|
599
610
|
|
|
600
611
|
export class AgentLoop {
|
|
@@ -616,6 +627,9 @@ export class AgentLoop {
|
|
|
616
627
|
*/
|
|
617
628
|
private readonly conversationId: string;
|
|
618
629
|
|
|
630
|
+
/** See {@link AgentLoopConstructorOptions.resolveConversationDir}. */
|
|
631
|
+
private readonly resolveConversationDir: (() => string | null) | null;
|
|
632
|
+
|
|
619
633
|
/**
|
|
620
634
|
* Loop-held compaction circuit breaker. The loop has a 1:1 lifetime with its
|
|
621
635
|
* conversation, so it is the source of truth for the cross-turn failure
|
|
@@ -635,6 +649,7 @@ export class AgentLoop {
|
|
|
635
649
|
resolveTools,
|
|
636
650
|
resolveSystemPrompt,
|
|
637
651
|
conversationId,
|
|
652
|
+
resolveConversationDir,
|
|
638
653
|
} = options;
|
|
639
654
|
this.provider = provider;
|
|
640
655
|
this.systemPrompt = systemPrompt;
|
|
@@ -644,6 +659,7 @@ export class AgentLoop {
|
|
|
644
659
|
this.resolveSystemPrompt = resolveSystemPrompt ?? null;
|
|
645
660
|
this.toolExecutor = toolExecutor ?? null;
|
|
646
661
|
this.conversationId = conversationId;
|
|
662
|
+
this.resolveConversationDir = resolveConversationDir ?? null;
|
|
647
663
|
this.compactionCircuit = new CompactionCircuit(this.conversationId);
|
|
648
664
|
}
|
|
649
665
|
|
|
@@ -782,10 +798,10 @@ export class AgentLoop {
|
|
|
782
798
|
onEvent,
|
|
783
799
|
);
|
|
784
800
|
}
|
|
785
|
-
// Emit unconditionally: the dispatcher commits the stripped
|
|
786
|
-
//
|
|
787
|
-
// reads it), and runs
|
|
788
|
-
// `
|
|
801
|
+
// Emit unconditionally: the dispatcher commits the stripped pre-compaction
|
|
802
|
+
// base (re-derived from the start event) as the durable message base
|
|
803
|
+
// whether or not the pipeline compacted (re-injection reads it), and runs
|
|
804
|
+
// the durable compaction commit only when `compacted`.
|
|
789
805
|
await onEvent({
|
|
790
806
|
type: "compaction_completed",
|
|
791
807
|
compactionId,
|
|
@@ -793,8 +809,7 @@ export class AgentLoop {
|
|
|
793
809
|
trigger,
|
|
794
810
|
startedAt,
|
|
795
811
|
finishedAt: Date.now(),
|
|
796
|
-
|
|
797
|
-
messages: rawHistory,
|
|
812
|
+
...compactResult,
|
|
798
813
|
});
|
|
799
814
|
const exhausted = compactResult.exhausted ?? false;
|
|
800
815
|
const autoCompressApplied = compactResult.autoCompressApplied ?? false;
|
|
@@ -886,6 +901,19 @@ export class AgentLoop {
|
|
|
886
901
|
let overflowAutoCompressApplied = false;
|
|
887
902
|
const rlog = log.child({ requestId });
|
|
888
903
|
|
|
904
|
+
// Conversation directory for the result-time tool-result spool/stub pass.
|
|
905
|
+
// Resolved once per run; `null` disables the pass for this run and the
|
|
906
|
+
// post-turn truncation covers the turn instead.
|
|
907
|
+
let conversationDir: string | null = null;
|
|
908
|
+
try {
|
|
909
|
+
conversationDir = this.resolveConversationDir?.() ?? null;
|
|
910
|
+
} catch (err) {
|
|
911
|
+
rlog.warn(
|
|
912
|
+
{ err },
|
|
913
|
+
"Resolving conversation dir for tool-result spooling failed (non-fatal)",
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
889
917
|
// Resolve the inference-profile override that applies right now. The
|
|
890
918
|
// optional resolver lets a turn observe a confirmed mid-turn profile switch
|
|
891
919
|
// before the next model call; absent a resolver the turn-start value holds.
|
|
@@ -1724,11 +1752,39 @@ export class AgentLoop {
|
|
|
1724
1752
|
}),
|
|
1725
1753
|
);
|
|
1726
1754
|
|
|
1755
|
+
// Spool oversized results to `.tool-results/` and swap the inline
|
|
1756
|
+
// copy for the post-turn pass's stub now — on the raw blocks, before
|
|
1757
|
+
// the post-tool-use hooks, event emission, and history append. Running
|
|
1758
|
+
// ahead of the hooks means the spooled file holds the tool's full
|
|
1759
|
+
// output rather than the truncate plugin's tail-dropped copy, and the
|
|
1760
|
+
// hooks then see the stub. Stubbing before the first send keeps the
|
|
1761
|
+
// provider-bound history strictly append-only (rewriting an earlier
|
|
1762
|
+
// message between calls would invalidate the prompt-cache prefix on
|
|
1763
|
+
// every iteration).
|
|
1764
|
+
if (conversationDir) {
|
|
1765
|
+
const toolNameByUseId = new Map(
|
|
1766
|
+
toolUseBlocks.map((tu) => [tu.id, tu.name]),
|
|
1767
|
+
);
|
|
1768
|
+
try {
|
|
1769
|
+
spoolAndStubOversizedToolResults(rawResultBlocks, {
|
|
1770
|
+
conversationDir,
|
|
1771
|
+
toolNameById: (id) => toolNameByUseId.get(id),
|
|
1772
|
+
});
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
rlog.warn(
|
|
1775
|
+
{ err, turn: toolUseTurns },
|
|
1776
|
+
"Spooling oversized tool results to disk failed (non-fatal)",
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1727
1781
|
// Run the `post-tool-use` hook once per tool result, after the tool
|
|
1728
1782
|
// returns and before the result joins the provider-bound history.
|
|
1729
1783
|
// The default tool-result-truncate plugin tail-drops oversized output
|
|
1730
|
-
// to fit the context window
|
|
1731
|
-
//
|
|
1784
|
+
// to fit the context window (spool-stubbed results are already tiny;
|
|
1785
|
+
// spool-exempt ones still rely on it); user hooks can swap in a
|
|
1786
|
+
// smarter strategy (e.g. a summariser) or observe results for side
|
|
1787
|
+
// effects.
|
|
1732
1788
|
const contextWindowTokens =
|
|
1733
1789
|
resolveContextWindow?.().maxInputTokens ??
|
|
1734
1790
|
this.config.maxInputTokens ??
|
|
@@ -1746,6 +1802,7 @@ export class AgentLoop {
|
|
|
1746
1802
|
toolResponse: block as ToolResultContent,
|
|
1747
1803
|
messages: history,
|
|
1748
1804
|
additionalContext: null,
|
|
1805
|
+
model: response.model,
|
|
1749
1806
|
maxInputTokens: contextWindowTokens,
|
|
1750
1807
|
logger: rlog,
|
|
1751
1808
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `usage_progress` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Emitted after each LLM call with per-call token deltas and estimated
|
|
5
|
+
* cost. Clients accumulate these additively for live-updating usage
|
|
6
|
+
* metrics (e.g. inline subagent token counters). A UI-only hint — it
|
|
7
|
+
* does not persist to the DB or affect billing.
|
|
8
|
+
*
|
|
9
|
+
* Unlike `usage_update`, which carries the conversation's running
|
|
10
|
+
* totals, this event carries only the deltas for a single LLM call.
|
|
11
|
+
*
|
|
12
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
13
|
+
* directly from this file; external consumers import via
|
|
14
|
+
* `@vellumai/assistant-api`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
export const UsageProgressEventSchema = z.object({
|
|
20
|
+
type: z.literal("usage_progress"),
|
|
21
|
+
conversationId: z.string(),
|
|
22
|
+
inputTokens: z.number(),
|
|
23
|
+
outputTokens: z.number(),
|
|
24
|
+
estimatedCost: z.number(),
|
|
25
|
+
model: z.string(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type UsageProgressEvent = z.infer<typeof UsageProgressEventSchema>;
|
package/src/api/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { UISurfaceCompleteEventSchema } from "./events/ui-surface-complete.js";
|
|
|
49
49
|
import { UISurfaceDismissEventSchema } from "./events/ui-surface-dismiss.js";
|
|
50
50
|
import { UISurfaceShowEventSchema } from "./events/ui-surface-show.js";
|
|
51
51
|
import { UISurfaceUpdateEventSchema } from "./events/ui-surface-update.js";
|
|
52
|
+
import { UsageProgressEventSchema } from "./events/usage-progress.js";
|
|
52
53
|
import { UsageUpdateEventSchema } from "./events/usage-update.js";
|
|
53
54
|
import { UserMessageEchoEventSchema } from "./events/user-message-echo.js";
|
|
54
55
|
|
|
@@ -310,6 +311,10 @@ export {
|
|
|
310
311
|
type UISurfaceUpdateEvent,
|
|
311
312
|
UISurfaceUpdateEventSchema,
|
|
312
313
|
} from "./events/ui-surface-update.js";
|
|
314
|
+
export {
|
|
315
|
+
type UsageProgressEvent,
|
|
316
|
+
UsageProgressEventSchema,
|
|
317
|
+
} from "./events/usage-progress.js";
|
|
313
318
|
export {
|
|
314
319
|
type UsageUpdateEvent,
|
|
315
320
|
UsageUpdateEventSchema,
|
|
@@ -497,6 +502,7 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
|
497
502
|
UISurfaceDismissEventSchema,
|
|
498
503
|
UISurfaceShowEventSchema,
|
|
499
504
|
UISurfaceUpdateEventSchema,
|
|
505
|
+
UsageProgressEventSchema,
|
|
500
506
|
UsageUpdateEventSchema,
|
|
501
507
|
UserMessageEchoEventSchema,
|
|
502
508
|
]);
|
|
@@ -52,6 +52,8 @@ mock.module("../util/platform.js", () => ({
|
|
|
52
52
|
getWorkspaceRoutesDir: () => workspacePath("routes"),
|
|
53
53
|
vellumRoot: () => testWorkspaceDir,
|
|
54
54
|
getDataDir: () => workspacePath("data"),
|
|
55
|
+
getConfigQuarantineNoticePath: () =>
|
|
56
|
+
workspacePath("data", "config-quarantine-notice.json"),
|
|
55
57
|
getDbPath: () => workspacePath("data", "db", "assistant.db"),
|
|
56
58
|
ensureDataDir: () => {
|
|
57
59
|
mkdirSync(workspacePath("data", "db"), { recursive: true });
|
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
10
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
|
|
14
|
-
import archiver from "archiver";
|
|
15
14
|
import JSZip from "jszip";
|
|
16
15
|
|
|
17
16
|
import { getApp, getAppDirPath, isMultifileApp } from "../memory/app-store.js";
|
|
@@ -37,6 +36,14 @@ export interface BundleResult {
|
|
|
37
36
|
iconImageBase64?: string;
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
function generateZipBuffer(zip: JSZip): Promise<Buffer> {
|
|
40
|
+
return zip.generateAsync({
|
|
41
|
+
type: "nodebuffer",
|
|
42
|
+
compression: "DEFLATE",
|
|
43
|
+
compressionOptions: { level: 9 },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
function isDefaultMainScaffold(source: string): boolean {
|
|
41
48
|
const normalized = source.replace(/\s+/g, " ").trim();
|
|
42
49
|
return (
|
|
@@ -177,54 +184,31 @@ export async function packageApp(
|
|
|
177
184
|
const bundleFilename = `${safeName}-${uniqueSuffix}.vellum`;
|
|
178
185
|
const bundlePath = join(tmpdir(), bundleFilename);
|
|
179
186
|
|
|
180
|
-
|
|
181
|
-
const output = createWriteStream(bundlePath);
|
|
182
|
-
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
183
|
-
|
|
184
|
-
output.on("close", () => resolve());
|
|
185
|
-
output.on("error", (err: Error) => reject(err));
|
|
186
|
-
archive.on("error", (err: Error) => reject(err));
|
|
187
|
-
archive.on("warning", (err: Error) => {
|
|
188
|
-
// Only reject on fatal warnings
|
|
189
|
-
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
190
|
-
reject(err);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
archive.pipe(output);
|
|
187
|
+
const zip = new JSZip();
|
|
195
188
|
|
|
196
|
-
|
|
197
|
-
|
|
189
|
+
// Add manifest.json at root level
|
|
190
|
+
zip.file("manifest.json", serializeManifest(manifest));
|
|
198
191
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
192
|
+
// Add compiled dist/ files
|
|
193
|
+
for (const file of compiledFiles) {
|
|
194
|
+
zip.file(file.name, file.data);
|
|
195
|
+
}
|
|
203
196
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
// Include app icon if one was generated
|
|
198
|
+
const iconPath = join(appDir, "icon.png");
|
|
199
|
+
if (existsSync(iconPath)) {
|
|
200
|
+
zip.file("icon.png", readFileSync(iconPath));
|
|
201
|
+
}
|
|
209
202
|
|
|
210
|
-
|
|
211
|
-
});
|
|
203
|
+
await writeFile(bundlePath, await generateZipBuffer(zip));
|
|
212
204
|
|
|
213
205
|
// Sign the bundle if a signing callback is provided
|
|
214
206
|
if (requestSignature) {
|
|
215
207
|
try {
|
|
216
208
|
const signatureJson = await signBundle(bundlePath, requestSignature);
|
|
217
209
|
|
|
218
|
-
// Re-open the zip and add signature.json
|
|
219
|
-
const zipBuffer = await readFile(bundlePath);
|
|
220
|
-
const zip = await JSZip.loadAsync(zipBuffer);
|
|
221
210
|
zip.file("signature.json", JSON.stringify(signatureJson, null, 2));
|
|
222
|
-
|
|
223
|
-
type: "nodebuffer",
|
|
224
|
-
compression: "DEFLATE",
|
|
225
|
-
compressionOptions: { level: 9 },
|
|
226
|
-
});
|
|
227
|
-
await writeFile(bundlePath, signedBuffer);
|
|
211
|
+
await writeFile(bundlePath, await generateZipBuffer(zip));
|
|
228
212
|
|
|
229
213
|
bundlerLog.info({ appId }, "Bundle signed successfully");
|
|
230
214
|
} catch (err) {
|
|
@@ -250,9 +234,8 @@ export async function packageApp(
|
|
|
250
234
|
|
|
251
235
|
// Read icon for inclusion in the response
|
|
252
236
|
let iconImageBase64: string | undefined;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
iconImageBase64 = readFileSync(iconFilePath).toString("base64");
|
|
237
|
+
if (existsSync(iconPath)) {
|
|
238
|
+
iconImageBase64 = readFileSync(iconPath).toString("base64");
|
|
256
239
|
}
|
|
257
240
|
|
|
258
241
|
return { bundlePath, manifest, iconImageBase64 };
|
|
@@ -1312,7 +1312,7 @@ export class CallController {
|
|
|
1312
1312
|
|
|
1313
1313
|
// Mark the linked guardian action request as timed out and
|
|
1314
1314
|
// send expiry notices to guardian destinations. Deliveries
|
|
1315
|
-
// must be captured before
|
|
1315
|
+
// must be captured before expiring the request changes
|
|
1316
1316
|
// their status.
|
|
1317
1317
|
const pendingActionRequest = getPendingCanonicalRequestByCallSessionId(
|
|
1318
1318
|
this.callSessionId,
|