@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292019.a6f6d85
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/.env.example +2 -0
- package/ARCHITECTURE.md +23 -0
- package/docs/automatic-done.md +32 -0
- package/docs/desktop-browser-cli.md +3 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
- package/node_modules/@vellumai/environments/src/shell.ts +22 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/openapi.yaml +270 -10
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +76 -0
- package/src/__tests__/anthropic-provider.test.ts +2 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
- package/src/__tests__/config-get-vision-flag.test.ts +1 -1
- package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
- package/src/__tests__/conversation-evictor.test.ts +54 -0
- package/src/__tests__/conversation-pairing.test.ts +112 -0
- package/src/__tests__/document-append-idempotency.test.ts +13 -0
- package/src/__tests__/document-create-dedupe.test.ts +13 -0
- package/src/__tests__/document-find-replace.test.ts +13 -0
- package/src/__tests__/document-revision-history.test.ts +390 -0
- package/src/__tests__/document-revision.test.ts +425 -0
- package/src/__tests__/document-tool-security.test.ts +13 -0
- package/src/__tests__/document-update-default-surface.test.ts +13 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
- package/src/__tests__/gateway-flag-listener.test.ts +59 -6
- package/src/__tests__/headless-browser-navigate.test.ts +20 -30
- package/src/__tests__/host-cu-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -2
- package/src/__tests__/identity-routes.test.ts +4 -3
- package/src/__tests__/llm-catalog-parity.test.ts +4 -4
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/notification-platform-adapter.test.ts +83 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/openai-provider.test.ts +11 -11
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/public-ingress-urls.test.ts +26 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
- package/src/__tests__/usage-attribution.test.ts +31 -0
- package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
- package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
- package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
- package/src/acp/agent-process.ts +6 -1
- package/src/acp/client-handler.ts +6 -1
- package/src/agent/loop.ts +29 -6
- package/src/api/events/document-editor-show.ts +5 -0
- package/src/api/events/document-editor-update.ts +10 -0
- package/src/api/events/tool-result.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
- package/src/calls/channel-admission-reader.ts +15 -9
- package/src/calls/twilio-provider.ts +14 -31
- package/src/calls/voice-session-bridge.ts +18 -1
- package/src/cli/commands/browser.ts +1 -2
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
- package/src/cli/lib/bundled-marketplace.json +41 -2
- package/src/cli/lib/inspect-plugin.ts +15 -15
- package/src/cli/lib/list-installed-plugins.ts +29 -28
- package/src/cli/lib/plugin-artifact.ts +74 -63
- package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
- package/src/cli/lib/plugin-details.ts +27 -42
- package/src/cli/lib/plugin-marketplace.ts +6 -8
- package/src/cli/lib/plugin-surfaces.ts +8 -7
- package/src/cli.ts +1 -3
- package/src/config/__tests__/chat-settings.test.ts +121 -0
- package/src/config/assistant-feature-flags.ts +28 -23
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
- package/src/config/chat-settings.ts +100 -0
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/feature-flag-cache.ts +7 -25
- package/src/config/feature-flag-registry.json +1 -9
- package/src/config/loader.ts +5 -1
- package/src/config/profile-text-generation.ts +25 -21
- package/src/config/schemas/conversations.ts +14 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/notifications.ts +8 -1
- package/src/contacts/gateway-channel-read.ts +1 -1
- package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
- package/src/conversations/__tests__/auto-archive.test.ts +304 -0
- package/src/conversations/auto-archive-activity.ts +61 -0
- package/src/conversations/auto-archive.ts +201 -0
- package/src/daemon/computer-use-observation.test.ts +81 -0
- package/src/daemon/computer-use-observation.ts +23 -0
- package/src/daemon/conversation-agent-loop.ts +11 -6
- package/src/daemon/conversation-evictor.ts +50 -16
- package/src/daemon/doordash-steps.ts +1 -3
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
- package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
- package/src/daemon/handlers/config-a2a.ts +75 -2
- package/src/daemon/host-cu-proxy.ts +34 -3
- package/src/daemon/lifecycle.ts +15 -2
- package/src/daemon/shutdown-handlers.ts +3 -0
- package/src/daemon/turn-finalization.ts +4 -0
- package/src/desktop/desktop-browser-endpoint.ts +11 -2
- package/src/desktop/desktop-chrome-policy.test.ts +1 -0
- package/src/desktop/desktop-chrome-policy.ts +7 -1
- package/src/desktop/desktop-computer-use.test.ts +14 -4
- package/src/desktop/desktop-session-manager.ts +5 -3
- package/src/documents/document-revisions-store.ts +153 -0
- package/src/documents/document-store.ts +471 -133
- package/src/followups/followup-store.ts +0 -4
- package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
- package/src/inbound/oauth-callback-url.ts +30 -1
- package/src/inbound/public-ingress-urls.ts +12 -2
- package/src/ipc/gateway-client.test.ts +40 -0
- package/src/ipc/gateway-client.ts +12 -29
- package/src/ipc/gateway-flag-listener.ts +7 -10
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
- package/src/live-voice/__tests__/protocol.test.ts +77 -0
- package/src/live-voice/__tests__/share-targets.test.ts +163 -0
- package/src/live-voice/live-voice-session.ts +23 -6
- package/src/live-voice/protocol.ts +22 -0
- package/src/live-voice/share-targets.ts +225 -0
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
- package/src/mcp/client.ts +1 -19
- package/src/mcp/mcp-oauth-provider.ts +66 -49
- package/src/media/openrouter-image-service.ts +39 -22
- package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
- package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
- package/src/monitoring/control.ts +2 -1
- package/src/monitoring/oom-inheritance-sweep.ts +96 -0
- package/src/monitoring/oom-kill-reporter.ts +469 -0
- package/src/monitoring/resource-sampler.ts +30 -0
- package/src/notifications/README.md +53 -4
- package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
- package/src/notifications/__tests__/broadcaster.test.ts +558 -6
- package/src/notifications/__tests__/completion-policy.test.ts +37 -0
- package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
- package/src/notifications/adapters/platform.ts +14 -0
- package/src/notifications/assistant-initiated-thread.ts +101 -0
- package/src/notifications/broadcaster.ts +94 -16
- package/src/notifications/chat-reply-policy.ts +11 -0
- package/src/notifications/completion-policy.ts +12 -2
- package/src/notifications/conversation-pairing.ts +15 -70
- package/src/notifications/emit-signal.ts +34 -3
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connect-orchestrator.ts +1 -2
- package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
- package/src/persistence/conversation-auto-archive.ts +170 -0
- package/src/persistence/conversation-crud.ts +2 -1
- package/src/persistence/conversation-queries.ts +15 -8
- package/src/persistence/embeddings/embed.test.ts +96 -0
- package/src/persistence/embeddings/embed.ts +20 -2
- package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
- package/src/persistence/embeddings/embedding-gemini.ts +39 -1
- package/src/persistence/embeddings/embedding-local.ts +6 -0
- package/src/persistence/embeddings/qdrant-manager.ts +5 -0
- package/src/persistence/job-utils.ts +10 -0
- package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
- package/src/persistence/migrations/384-add-document-revision.ts +21 -0
- package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
- package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
- package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
- package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
- package/src/persistence/schema/conversations.ts +1 -0
- package/src/persistence/schema/documents.ts +20 -0
- package/src/persistence/steps.ts +6 -0
- package/src/platform/managed-speech.ts +42 -46
- package/src/plugin-api/vision-support.test.ts +21 -21
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/fireworks/client.test.ts +168 -0
- package/src/providers/fireworks/client.ts +24 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/ionet/client.test.ts +143 -0
- package/src/providers/ionet/client.ts +37 -0
- package/src/providers/model-catalog.ts +141 -84
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +23 -1
- package/src/providers/retry.ts +56 -4
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +10 -2
- package/src/providers/usage-tracking.ts +5 -3
- package/src/runtime/assistant-event-hub.ts +23 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
- package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
- package/src/runtime/channel-retry-sweep.ts +1 -2
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
- package/src/runtime/migrations/vbundle-builder.ts +19 -3
- package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
- package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
- package/src/runtime/migrations/vbundle-validator.ts +54 -1
- package/src/runtime/pending-interactions.ts +20 -2
- package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
- package/src/runtime/routes/contact-routes.ts +2 -2
- package/src/runtime/routes/conversation-list-routes.ts +10 -2
- package/src/runtime/routes/conversation-query-routes.ts +48 -10
- package/src/runtime/routes/documents-routes.ts +185 -47
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -0
- package/src/runtime/routes/host-bash-routes.ts +1 -0
- package/src/runtime/routes/host-browser-routes.ts +15 -0
- package/src/runtime/routes/host-cu-routes.ts +4 -1
- package/src/runtime/routes/host-file-routes.ts +1 -0
- package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
- package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
- package/src/runtime/routes/host-transfer-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +3 -3
- package/src/runtime/routes/integrations/a2a.ts +4 -1
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/notification-routes.ts +1 -2
- package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
- package/src/runtime/routes/user-route-dispatcher.ts +5 -1
- package/src/runtime/verification-templates.ts +1 -2
- package/src/sequence/analytics.ts +0 -4
- package/src/sequence/engine.ts +1 -11
- package/src/sequence/guardrails.ts +1 -6
- package/src/sequence/importer.ts +0 -8
- package/src/sequence/store.ts +0 -10
- package/src/signals/user-message.ts +0 -6
- package/src/skills/catalog-install.ts +52 -23
- package/src/tools/browser/browser-execution.ts +20 -23
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
- package/src/tools/document/document-tool.ts +13 -11
- package/src/tools/executor.ts +1 -2
- package/src/tools/host-terminal/host-shell.ts +4 -2
- package/src/tools/network/__tests__/web-search.test.ts +105 -0
- package/src/tools/network/web-fetch.ts +2 -4
- package/src/tools/network/web-search.ts +239 -12
- package/src/tools/shared/filesystem/format-diff.ts +1 -2
- package/src/tools/shared/shell-output.test.ts +42 -0
- package/src/tools/shared/shell-output.ts +27 -4
- package/src/tools/terminal/shell.ts +3 -3
- package/src/tools/ui-surface/visual-validation.ts +1 -2
- package/src/usage/attribution.ts +18 -1
- package/src/util/__tests__/oom-priority.test.ts +55 -0
- package/src/util/__tests__/worker-memory.test.ts +16 -0
- package/src/util/debounce.ts +0 -4
- package/src/util/oom-priority.ts +79 -0
- package/src/util/platform.ts +0 -2
- package/src/util/plugin-manifest.ts +5 -7
- package/src/util/pricing.ts +25 -0
- package/src/util/worker-memory.ts +9 -3
- package/src/util/worker-process.ts +8 -0
- package/src/workspace/byok-default-profile-ensure.ts +11 -0
- package/src/workspace/hatched-date.ts +11 -4
- package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
- package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
- package/src/workspace/migrations/registry.ts +4 -0
package/src/usage/attribution.ts
CHANGED
|
@@ -10,11 +10,15 @@ import { safeStringSlice } from "../util/unicode.js";
|
|
|
10
10
|
|
|
11
11
|
const MAX_METADATA_VALUE_LENGTH = 128;
|
|
12
12
|
|
|
13
|
+
/** `profileSource` reported for a turn whose profile the Auto router chose. */
|
|
14
|
+
export const AUTO_PROFILE_SOURCE = "auto";
|
|
15
|
+
|
|
13
16
|
export type UsageAttributionProfileSource =
|
|
14
17
|
| "call_site"
|
|
15
18
|
| "conversation"
|
|
16
19
|
| "active"
|
|
17
20
|
| "default"
|
|
21
|
+
| typeof AUTO_PROFILE_SOURCE
|
|
18
22
|
| "unknown";
|
|
19
23
|
|
|
20
24
|
export interface UsageAttributionInput {
|
|
@@ -27,6 +31,12 @@ export interface UsageAttributionInput {
|
|
|
27
31
|
*/
|
|
28
32
|
profileResolutionCallSite?: LLMCallSite | null;
|
|
29
33
|
overrideProfile?: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Who chose `overrideProfile`. `"auto"` marks a profile the Auto profile's
|
|
36
|
+
* router picked for the turn, so usage rows can tell an Auto pick from a
|
|
37
|
+
* user pin that landed on the same profile. Absent for a user pin.
|
|
38
|
+
*/
|
|
39
|
+
overrideProfileOrigin?: "auto" | null;
|
|
30
40
|
/**
|
|
31
41
|
* Mirrors `ResolveCallSiteOpts.forceOverrideProfile`: the override profile
|
|
32
42
|
* was floated above the call-site layers for this request, so attribution
|
|
@@ -227,9 +237,16 @@ function appliedProfileFromWinnerSelection(
|
|
|
227
237
|
call_site: "call_site",
|
|
228
238
|
default: "default",
|
|
229
239
|
} as const;
|
|
240
|
+
// An override the Auto router chose is reported as its own source: the
|
|
241
|
+
// applied profile is the routed arm, so without this a routed turn would
|
|
242
|
+
// be indistinguishable from a user who pinned that arm.
|
|
243
|
+
const profileSource =
|
|
244
|
+
selection.source === "override" && input.overrideProfileOrigin === "auto"
|
|
245
|
+
? AUTO_PROFILE_SOURCE
|
|
246
|
+
: sourceBySelection[selection.source];
|
|
230
247
|
return {
|
|
231
248
|
appliedProfile: selection.profileName,
|
|
232
|
-
profileSource
|
|
249
|
+
profileSource,
|
|
233
250
|
};
|
|
234
251
|
}
|
|
235
252
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import { setOomScoreAdj, withOomScoreAdj } from "../oom-priority.js";
|
|
7
|
+
|
|
8
|
+
function fakeProc(pid: number): string {
|
|
9
|
+
const procRoot = mkdtempSync(join(tmpdir(), "oom-priority-"));
|
|
10
|
+
mkdirSync(join(procRoot, String(pid)));
|
|
11
|
+
return procRoot;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("setOomScoreAdj", () => {
|
|
15
|
+
test("writes the value for a pid on Linux", () => {
|
|
16
|
+
const procRoot = fakeProc(42);
|
|
17
|
+
expect(setOomScoreAdj(1000, 42, { procRoot, platform: "linux" })).toBe(
|
|
18
|
+
true,
|
|
19
|
+
);
|
|
20
|
+
expect(readFileSync(join(procRoot, "42", "oom_score_adj"), "utf-8")).toBe(
|
|
21
|
+
"1000",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("is a no-op off Linux", () => {
|
|
26
|
+
const procRoot = fakeProc(42);
|
|
27
|
+
expect(setOomScoreAdj(1000, 42, { procRoot, platform: "darwin" })).toBe(
|
|
28
|
+
false,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("reports failure for a pid that is gone", () => {
|
|
33
|
+
const procRoot = fakeProc(42);
|
|
34
|
+
expect(setOomScoreAdj(0, 43, { procRoot, platform: "linux" })).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("withOomScoreAdj", () => {
|
|
39
|
+
test("execs the command through a shell that raises its own score on Linux", () => {
|
|
40
|
+
expect(withOomScoreAdj(["chrome", "--flag"], 1000, "linux")).toEqual([
|
|
41
|
+
"sh",
|
|
42
|
+
"-c",
|
|
43
|
+
'echo 1000 2>/dev/null >/proc/self/oom_score_adj; exec "$0" "$@"',
|
|
44
|
+
"chrome",
|
|
45
|
+
"--flag",
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("returns the command unchanged off Linux", () => {
|
|
50
|
+
expect(withOomScoreAdj(["chrome", "--flag"], 1000, "darwin")).toEqual([
|
|
51
|
+
"chrome",
|
|
52
|
+
"--flag",
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -38,6 +38,7 @@ describe("computeWorkerForceRamSizeBytes", () => {
|
|
|
38
38
|
|
|
39
39
|
describe("workerMemoryEnv", () => {
|
|
40
40
|
const savedForceRamSize = process.env.BUN_JSC_forceRAMSize;
|
|
41
|
+
const savedGcTimerDisable = process.env.BUN_GC_TIMER_DISABLE;
|
|
41
42
|
|
|
42
43
|
afterEach(() => {
|
|
43
44
|
if (savedForceRamSize === undefined) {
|
|
@@ -45,6 +46,11 @@ describe("workerMemoryEnv", () => {
|
|
|
45
46
|
} else {
|
|
46
47
|
process.env.BUN_JSC_forceRAMSize = savedForceRamSize;
|
|
47
48
|
}
|
|
49
|
+
if (savedGcTimerDisable === undefined) {
|
|
50
|
+
delete process.env.BUN_GC_TIMER_DISABLE;
|
|
51
|
+
} else {
|
|
52
|
+
process.env.BUN_GC_TIMER_DISABLE = savedGcTimerDisable;
|
|
53
|
+
}
|
|
48
54
|
});
|
|
49
55
|
|
|
50
56
|
test("adds a clamped BUN_JSC_forceRAMSize on top of the parent environment", () => {
|
|
@@ -61,4 +67,14 @@ describe("workerMemoryEnv", () => {
|
|
|
61
67
|
const env = workerMemoryEnv();
|
|
62
68
|
expect(env.BUN_JSC_forceRAMSize).toBe("12345");
|
|
63
69
|
});
|
|
70
|
+
|
|
71
|
+
test("disables the worker GC timer unless the operator set it", () => {
|
|
72
|
+
delete process.env.BUN_GC_TIMER_DISABLE;
|
|
73
|
+
expect(workerMemoryEnv().BUN_GC_TIMER_DISABLE).toBe("1");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("keeps an operator-provided BUN_GC_TIMER_DISABLE", () => {
|
|
77
|
+
process.env.BUN_GC_TIMER_DISABLE = "0";
|
|
78
|
+
expect(workerMemoryEnv().BUN_GC_TIMER_DISABLE).toBe("0");
|
|
79
|
+
});
|
|
64
80
|
});
|
package/src/util/debounce.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single-key debouncer. Delays execution until no new calls arrive
|
|
3
|
-
* within the specified delay period.
|
|
4
|
-
*/
|
|
5
1
|
/**
|
|
6
2
|
* Multi-key debouncer. Each key gets its own independent timer.
|
|
7
3
|
* Includes an optional entry limit with eviction of oldest non-protected entries.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linux OOM-killer priority for the daemon and everything it spawns.
|
|
3
|
+
*
|
|
4
|
+
* When the container hits its memory limit the kernel kills the process with
|
|
5
|
+
* the highest oom_score: its share of memory plus `oom_score_adj`. The daemon
|
|
6
|
+
* is usually the largest process in the container, so by default it is the
|
|
7
|
+
* victim and the assistant goes down with the runaway tool. Setting the daemon
|
|
8
|
+
* strongly negative and every child non-negative makes the kernel take a tool,
|
|
9
|
+
* desktop, or worker process first and leave the assistant running.
|
|
10
|
+
*
|
|
11
|
+
* Children inherit the parent's value at fork, so every spawn path must reset
|
|
12
|
+
* its child: shell children through `buildShellInvocation`, desktop children
|
|
13
|
+
* through the desktop session's spawn wrapper, workers and Qdrant through a
|
|
14
|
+
* parent-side write right after spawn. Raising a value never needs a
|
|
15
|
+
* capability; lowering one below the inherited value needs CAP_SYS_RESOURCE,
|
|
16
|
+
* which the platform grants and self-hosted Docker (non-root) does not. There
|
|
17
|
+
* the daemon stays at 0, which children at +1000 still lose to.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { writeFileSync } from "node:fs";
|
|
21
|
+
|
|
22
|
+
import { CHILD_OOM_SCORE_ADJ } from "@vellumai/environments/shell";
|
|
23
|
+
|
|
24
|
+
export { CHILD_OOM_SCORE_ADJ };
|
|
25
|
+
|
|
26
|
+
/** Strongly avoided but still killable as a last resort, unlike -1000. */
|
|
27
|
+
export const DAEMON_OOM_SCORE_ADJ = -700;
|
|
28
|
+
/** The monitor records the kill, so it outlives everything but the daemon. */
|
|
29
|
+
export const MONITOR_OOM_SCORE_ADJ = -500;
|
|
30
|
+
/** Workers and Qdrant: kernel default, picked by memory share alone. */
|
|
31
|
+
export const WORKER_OOM_SCORE_ADJ = 0;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a command so the child raises its own `oom_score_adj` before it execs.
|
|
35
|
+
* `exec` keeps the pid and process group, so callers that track or kill the
|
|
36
|
+
* child see no difference. Deterministic where a parent-side write after
|
|
37
|
+
* spawn is not: the child may already have forked (Chrome's zygote, a coding
|
|
38
|
+
* agent's tools) before the parent's write lands, and those grandchildren
|
|
39
|
+
* would keep the inherited value. Identity on non-Linux hosts.
|
|
40
|
+
*/
|
|
41
|
+
export function withOomScoreAdj(
|
|
42
|
+
cmd: readonly string[],
|
|
43
|
+
value: number,
|
|
44
|
+
platform: NodeJS.Platform = process.platform,
|
|
45
|
+
): string[] {
|
|
46
|
+
if (platform !== "linux") {
|
|
47
|
+
return [...cmd];
|
|
48
|
+
}
|
|
49
|
+
return [
|
|
50
|
+
"sh",
|
|
51
|
+
"-c",
|
|
52
|
+
`echo ${value} 2>/dev/null >/proc/self/oom_score_adj; exec "$0" "$@"`,
|
|
53
|
+
...cmd,
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Write `oom_score_adj` for `pid` (or this process). Returns false where it
|
|
59
|
+
* cannot apply: non-Linux hosts, a process that already exited, or lowering
|
|
60
|
+
* without CAP_SYS_RESOURCE.
|
|
61
|
+
*/
|
|
62
|
+
export function setOomScoreAdj(
|
|
63
|
+
value: number,
|
|
64
|
+
pid: number | "self" = "self",
|
|
65
|
+
options: { procRoot?: string; platform?: NodeJS.Platform } = {},
|
|
66
|
+
): boolean {
|
|
67
|
+
if ((options.platform ?? process.platform) !== "linux") {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
writeFileSync(
|
|
72
|
+
`${options.procRoot ?? "/proc"}/${pid}/oom_score_adj`,
|
|
73
|
+
String(value),
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/util/platform.ts
CHANGED
|
@@ -297,7 +297,6 @@ export function getSignalsDir(): string {
|
|
|
297
297
|
return join(getWorkspaceDir(), "signals");
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
// --- Root-level runtime path helpers ---
|
|
301
300
|
// These expose specific root-level file paths so callers don't need to
|
|
302
301
|
// import getRootDir() directly. getRootDir() is intentionally unexported.
|
|
303
302
|
|
|
@@ -567,7 +566,6 @@ export function getWorkspaceSystemPromptDir(): string {
|
|
|
567
566
|
return join(getWorkspaceDir(), "prompts", "system");
|
|
568
567
|
}
|
|
569
568
|
|
|
570
|
-
// ── Profiler filesystem layout ──────────────────────────────────────────
|
|
571
569
|
// Managed profiler runs live under <workspace>/data/profiler/. These
|
|
572
570
|
// helpers enforce a single canonical layout so every runtime caller
|
|
573
571
|
// resolves the same paths.
|
|
@@ -37,6 +37,10 @@ const LegacyPluginManifestSchema = z
|
|
|
37
37
|
})
|
|
38
38
|
.passthrough();
|
|
39
39
|
|
|
40
|
+
const StandardPluginManifestDiscriminatorSchema = z.object({
|
|
41
|
+
$schema: z.literal(AGENT_PLUGINS_MANIFEST_SCHEMA_URL),
|
|
42
|
+
});
|
|
43
|
+
|
|
40
44
|
const StandardPluginManifestSchema = z
|
|
41
45
|
.object({
|
|
42
46
|
$schema: z.literal(AGENT_PLUGINS_MANIFEST_SCHEMA_URL),
|
|
@@ -129,13 +133,7 @@ export function getPluginManifestInstallAction(
|
|
|
129
133
|
return "synthesize-legacy";
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
if (
|
|
133
|
-
typeof json !== "object" ||
|
|
134
|
-
json === null ||
|
|
135
|
-
Array.isArray(json) ||
|
|
136
|
-
(json as Record<string, unknown>).$schema !==
|
|
137
|
-
AGENT_PLUGINS_MANIFEST_SCHEMA_URL
|
|
138
|
-
) {
|
|
136
|
+
if (!StandardPluginManifestDiscriminatorSchema.safeParse(json).success) {
|
|
139
137
|
return "synthesize-legacy";
|
|
140
138
|
}
|
|
141
139
|
|
package/src/util/pricing.ts
CHANGED
|
@@ -63,6 +63,31 @@ const LEGACY_PRICING_FALLBACK: Record<string, Record<string, ModelPricing>> = {
|
|
|
63
63
|
gemini: {
|
|
64
64
|
"gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4 },
|
|
65
65
|
},
|
|
66
|
+
// Models Fireworks no longer serves serverless. The catalog does not list
|
|
67
|
+
// them, but stored usage keeps their IDs, so request-log cost estimates
|
|
68
|
+
// still need their rates.
|
|
69
|
+
fireworks: {
|
|
70
|
+
"accounts/fireworks/models/glm-5p2": {
|
|
71
|
+
inputPer1M: 1.4,
|
|
72
|
+
outputPer1M: 4.4,
|
|
73
|
+
cacheReadPer1M: 0.26,
|
|
74
|
+
},
|
|
75
|
+
"accounts/fireworks/models/kimi-k2p6": {
|
|
76
|
+
inputPer1M: 0.95,
|
|
77
|
+
outputPer1M: 4.0,
|
|
78
|
+
cacheReadPer1M: 0.16,
|
|
79
|
+
},
|
|
80
|
+
"accounts/fireworks/models/deepseek-v4-pro-0813": {
|
|
81
|
+
inputPer1M: 1.32,
|
|
82
|
+
outputPer1M: 3.96,
|
|
83
|
+
cacheReadPer1M: 0.044,
|
|
84
|
+
},
|
|
85
|
+
"accounts/fireworks/models/deepseek-v4-flash-0731": {
|
|
86
|
+
inputPer1M: 0.14,
|
|
87
|
+
outputPer1M: 0.28,
|
|
88
|
+
cacheReadPer1M: 0.028,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
66
91
|
};
|
|
67
92
|
|
|
68
93
|
/** Convert a catalog pricing entry to the internal pricing shape. */
|
|
@@ -60,9 +60,14 @@ export function computeWorkerForceRamSizeBytes(
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Environment for spawning a background worker `bun` process: the parent
|
|
63
|
-
* environment plus `BUN_JSC_forceRAMSize`. An
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* environment plus `BUN_JSC_forceRAMSize` and `BUN_GC_TIMER_DISABLE`. An
|
|
64
|
+
* operator-provided value for either in the parent environment wins.
|
|
65
|
+
*
|
|
66
|
+
* `BUN_GC_TIMER_DISABLE=1` turns off Bun's repeating GC timer in the worker.
|
|
67
|
+
* Under a `forceRAMSize` heap that timer, together with the per-tick
|
|
68
|
+
* collection request, keeps a long-lived worker collecting continuously at
|
|
69
|
+
* roughly one full core; the workers' own allocation still triggers
|
|
70
|
+
* collection normally.
|
|
66
71
|
*
|
|
67
72
|
* Bun.spawn replaces (rather than merges) the child environment when `env` is
|
|
68
73
|
* passed, so this spreads the full parent environment.
|
|
@@ -73,5 +78,6 @@ export function workerMemoryEnv(): Record<string, string | undefined> {
|
|
|
73
78
|
BUN_JSC_forceRAMSize:
|
|
74
79
|
process.env.BUN_JSC_forceRAMSize ??
|
|
75
80
|
String(computeWorkerForceRamSizeBytes(getContainerMemoryLimitBytes())),
|
|
81
|
+
BUN_GC_TIMER_DISABLE: process.env.BUN_GC_TIMER_DISABLE ?? "1",
|
|
76
82
|
};
|
|
77
83
|
}
|
|
@@ -20,6 +20,7 @@ import { dirname, join } from "node:path";
|
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
|
|
22
22
|
import { getCurrentLogFilePath, getLogger } from "./logger.js";
|
|
23
|
+
import { setOomScoreAdj, WORKER_OOM_SCORE_ADJ } from "./oom-priority.js";
|
|
23
24
|
import { isProcessAlive } from "./process-liveness.js";
|
|
24
25
|
import {
|
|
25
26
|
listProcessTable,
|
|
@@ -128,6 +129,11 @@ const PID_FILE_WAIT_TIMEOUT_MS = 15_000;
|
|
|
128
129
|
const PID_FILE_POLL_INTERVAL_MS = 100;
|
|
129
130
|
|
|
130
131
|
export interface SpawnWorkerProcessOptions {
|
|
132
|
+
/**
|
|
133
|
+
* `oom_score_adj` for the worker. Defaults to {@link WORKER_OOM_SCORE_ADJ};
|
|
134
|
+
* the child would otherwise inherit the daemon's protected value.
|
|
135
|
+
*/
|
|
136
|
+
oomScoreAdj?: number;
|
|
131
137
|
/**
|
|
132
138
|
* Override how long to wait for the worker's PID file, in ms. Defaults to
|
|
133
139
|
* {@link PID_FILE_WAIT_TIMEOUT_MS}. Primarily a testing seam.
|
|
@@ -626,6 +632,8 @@ export async function spawnWorkerProcess(args: {
|
|
|
626
632
|
windowsHide: true,
|
|
627
633
|
});
|
|
628
634
|
|
|
635
|
+
setOomScoreAdj(opts.oomScoreAdj ?? WORKER_OOM_SCORE_ADJ, child.pid);
|
|
636
|
+
|
|
629
637
|
// Close our copy of the log fd — the child has its own.
|
|
630
638
|
if (typeof stderrFd === "number") {
|
|
631
639
|
closeSync(stderrFd);
|
|
@@ -187,6 +187,8 @@ const HISTORICAL_INTENT_MODELS: Record<
|
|
|
187
187
|
"accounts/fireworks/models/deepseek-v4-flash",
|
|
188
188
|
// the ID migration 146 writes over stale deepseek-v4-flash pins.
|
|
189
189
|
"accounts/fireworks/models/deepseek-v4-flash-0731",
|
|
190
|
+
// the ID migration 160 writes over stale deepseek-v4-flash-0731 pins.
|
|
191
|
+
"accounts/fireworks/models/deepseek-v4p1-flash",
|
|
190
192
|
],
|
|
191
193
|
},
|
|
192
194
|
"quality-optimized": {
|
|
@@ -209,10 +211,15 @@ const HISTORICAL_INTENT_MODELS: Record<
|
|
|
209
211
|
fireworks: [
|
|
210
212
|
// quality intent 2026-05-05 (#29755) to 2026-05-19 (#31068).
|
|
211
213
|
"accounts/fireworks/models/kimi-k2p5",
|
|
214
|
+
// quality intent 2026-05-19 (#31068) to the 2026-09-28 kimi-k3
|
|
215
|
+
// repoint.
|
|
216
|
+
"accounts/fireworks/models/kimi-k2p6",
|
|
212
217
|
// migration 136's in-place rewrite of a kimi-k2p5 pin.
|
|
213
218
|
"accounts/fireworks/models/deepseek-v4-flash",
|
|
214
219
|
// the ID migration 146 writes over stale deepseek-v4-flash pins.
|
|
215
220
|
"accounts/fireworks/models/deepseek-v4-flash-0731",
|
|
221
|
+
// the ID migration 160 writes over stale deepseek-v4-flash-0731 pins.
|
|
222
|
+
"accounts/fireworks/models/deepseek-v4p1-flash",
|
|
216
223
|
],
|
|
217
224
|
},
|
|
218
225
|
"cost-optimized": {
|
|
@@ -234,6 +241,10 @@ const HISTORICAL_INTENT_MODELS: Record<
|
|
|
234
241
|
// 146 rewrites it to the dated ID, so this survives only in pre-146
|
|
235
242
|
// backups.
|
|
236
243
|
"accounts/fireworks/models/deepseek-v4-flash",
|
|
244
|
+
// latency intent 2026-08-17 to the 2026-09-28 deepseek-v4p1-flash
|
|
245
|
+
// repoint. On live configs migration 160 rewrites it, so this
|
|
246
|
+
// survives only in pre-160 backups.
|
|
247
|
+
"accounts/fireworks/models/deepseek-v4-flash-0731",
|
|
237
248
|
],
|
|
238
249
|
},
|
|
239
250
|
};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
4
6
|
import { getDataDir } from "../util/platform.js";
|
|
5
7
|
|
|
6
8
|
const HATCHED_SIDECAR_FILENAME = "hatched.json";
|
|
9
|
+
const HatchedSidecarSchema = z.object({
|
|
10
|
+
hatchedAt: z.unknown().optional(),
|
|
11
|
+
});
|
|
7
12
|
|
|
8
13
|
export function getHatchedSidecarPath(): string {
|
|
9
14
|
return join(getDataDir(), HATCHED_SIDECAR_FILENAME);
|
|
@@ -24,10 +29,12 @@ function normalizeHatchedAt(value: unknown): string | undefined {
|
|
|
24
29
|
|
|
25
30
|
export function readHatchedAtSidecar(): string | undefined {
|
|
26
31
|
try {
|
|
27
|
-
const parsed =
|
|
28
|
-
readFileSync(getHatchedSidecarPath(), "utf-8"),
|
|
29
|
-
)
|
|
30
|
-
return
|
|
32
|
+
const parsed = HatchedSidecarSchema.safeParse(
|
|
33
|
+
JSON.parse(readFileSync(getHatchedSidecarPath(), "utf-8")),
|
|
34
|
+
);
|
|
35
|
+
return parsed.success
|
|
36
|
+
? normalizeHatchedAt(parsed.data.hatchedAt)
|
|
37
|
+
: undefined;
|
|
31
38
|
} catch {
|
|
32
39
|
return undefined;
|
|
33
40
|
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Repair the retired Fireworks model IDs in workspace LLM config.
|
|
9
|
+
*
|
|
10
|
+
* Fireworks has no serverless deployment of DeepSeek V4 Flash 0731,
|
|
11
|
+
* DeepSeek V4 Pro 0813 or Kimi K2.6: chat/completions calls fail with a
|
|
12
|
+
* 404 "Model not found, inaccessible, and/or not deployed". Existing
|
|
13
|
+
* configs can pin any of these IDs in `llm.default`, `llm.callSites.*`, and `llm.profiles.*`.
|
|
14
|
+
*
|
|
15
|
+
* Repair those leaves only on an exact stale match, replacing with the
|
|
16
|
+
* successor Fireworks names (see `REPLACEMENT_BY_STALE_MODEL_ID`). Kimi
|
|
17
|
+
* K2.6 has two named successors; Kimi K3 is the one that accepts image
|
|
18
|
+
* input, which Kimi K2.6 does.
|
|
19
|
+
*
|
|
20
|
+
* A replacement can also be an intent template's model, so a repaired
|
|
21
|
+
* hand-edited `custom-*` profile can read as unedited to
|
|
22
|
+
* `ensureByokDefaultProfiles` and collapse into that default. That is
|
|
23
|
+
* acceptable: the collapsed copy routes to the same model this migration
|
|
24
|
+
* writes.
|
|
25
|
+
*
|
|
26
|
+
* Provider guard: the stale IDs belong to the `fireworks` provider and also
|
|
27
|
+
* appear in managed profiles stamped `provider: "vellum"` (which route
|
|
28
|
+
* Fireworks-account model IDs through the managed proxy). Under the entries
|
|
29
|
+
* model (migration 145) `provider` can also hold a `provider_connections`
|
|
30
|
+
* entry name whose row kind drives dispatch. A fragment is repaired when
|
|
31
|
+
* its `provider` is `"fireworks"`, `"vellum"`, absent, or an entry name
|
|
32
|
+
* whose row kind is one of those. Any other provider is left untouched: an
|
|
33
|
+
* `openai-compatible` endpoint may legitimately serve a model by the stale
|
|
34
|
+
* name.
|
|
35
|
+
*/
|
|
36
|
+
export const repairRetiredFireworksModelIdsMigration: WorkspaceMigration = {
|
|
37
|
+
id: "160-repair-retired-fireworks-model-ids",
|
|
38
|
+
description: "Repair retired Fireworks model IDs in workspace LLM config",
|
|
39
|
+
run(workspaceDir: string): void {
|
|
40
|
+
const configPath = join(workspaceDir, "config.json");
|
|
41
|
+
if (!existsSync(configPath)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Read outside the parse catch: a transient filesystem error (EIO,
|
|
46
|
+
// EACCES) must reach the runner so the migration retries, while
|
|
47
|
+
// malformed JSON is a permanent state this migration cannot repair.
|
|
48
|
+
const rawText = readFileSync(configPath, "utf-8");
|
|
49
|
+
|
|
50
|
+
let config: Record<string, unknown>;
|
|
51
|
+
try {
|
|
52
|
+
const raw = JSON.parse(rawText);
|
|
53
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
config = raw as Record<string, unknown>;
|
|
57
|
+
} catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const llm = readObject(config.llm);
|
|
62
|
+
if (llm === null) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Entry rows load lazily: only a stale fragment whose provider is
|
|
67
|
+
// neither a repairable vendor nor absent needs them. An unreadable DB
|
|
68
|
+
// then fails the run (retried next boot) rather than checkpointing a
|
|
69
|
+
// pass that skips entry-bound profiles.
|
|
70
|
+
let rows: Map<string, string> | null | undefined;
|
|
71
|
+
const isRepairableProvider = (provider: unknown): boolean => {
|
|
72
|
+
if (provider === undefined) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (typeof provider !== "string") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (REPAIRABLE_PROVIDERS.has(provider)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (rows === undefined) {
|
|
82
|
+
rows = readConnectionRows(workspaceDir);
|
|
83
|
+
}
|
|
84
|
+
if (rows === null) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"provider_connections is not readable; retrying the model-ID repair on the next run",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const kind = rows.get(provider);
|
|
90
|
+
return kind !== undefined && REPAIRABLE_PROVIDERS.has(kind);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let changed = false;
|
|
94
|
+
|
|
95
|
+
changed =
|
|
96
|
+
repairFragment(readObject(llm.default), isRepairableProvider) || changed;
|
|
97
|
+
|
|
98
|
+
const callSites = readObject(llm.callSites);
|
|
99
|
+
if (callSites !== null) {
|
|
100
|
+
for (const rawConfig of Object.values(callSites)) {
|
|
101
|
+
changed =
|
|
102
|
+
repairFragment(readObject(rawConfig), isRepairableProvider) ||
|
|
103
|
+
changed;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const profiles = readObject(llm.profiles);
|
|
108
|
+
if (profiles !== null) {
|
|
109
|
+
for (const rawProfile of Object.values(profiles)) {
|
|
110
|
+
changed =
|
|
111
|
+
repairFragment(readObject(rawProfile), isRepairableProvider) ||
|
|
112
|
+
changed;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!changed) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Write-then-rename so an interrupted write cannot leave config.json
|
|
121
|
+
// truncated: a torn in-place write would parse as invalid JSON on the
|
|
122
|
+
// retry, which the catch above treats as "nothing to do", letting the
|
|
123
|
+
// runner checkpoint the migration as completed against a corrupt file.
|
|
124
|
+
const tmpPath = `${configPath}.migration-160.tmp`;
|
|
125
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
126
|
+
renameSync(tmpPath, configPath);
|
|
127
|
+
},
|
|
128
|
+
// The exact-match rewrite is idempotent, so a transient failure (full
|
|
129
|
+
// disk, I/O error) is safe to retry on later startups.
|
|
130
|
+
retryFailedCheckpoint: true,
|
|
131
|
+
down(_workspaceDir: string): void {
|
|
132
|
+
// Forward-only: reintroducing a retired model ID would break
|
|
133
|
+
// Fireworks calls.
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Helpers: self-contained per workspace migrations AGENTS.md
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
const REPLACEMENT_BY_STALE_MODEL_ID = new Map([
|
|
142
|
+
[
|
|
143
|
+
"accounts/fireworks/models/deepseek-v4-flash-0731",
|
|
144
|
+
"accounts/fireworks/models/deepseek-v4p1-flash",
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
"accounts/fireworks/models/deepseek-v4-pro-0813",
|
|
148
|
+
"accounts/fireworks/models/deepseek-v4p1-flash",
|
|
149
|
+
],
|
|
150
|
+
["accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k3"],
|
|
151
|
+
]);
|
|
152
|
+
const REPAIRABLE_PROVIDERS = new Set(["fireworks", "vellum"]);
|
|
153
|
+
|
|
154
|
+
function repairFragment(
|
|
155
|
+
fragment: Record<string, unknown> | null,
|
|
156
|
+
isRepairableProvider: (provider: unknown) => boolean,
|
|
157
|
+
): boolean {
|
|
158
|
+
if (fragment === null) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (typeof fragment.model !== "string") {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const replacement = REPLACEMENT_BY_STALE_MODEL_ID.get(fragment.model);
|
|
165
|
+
if (replacement === undefined) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (!isRepairableProvider(fragment.provider)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
fragment.model = replacement;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Connection name -> provider kind, or null when the DB or table is not
|
|
177
|
+
* readable. The caller fails the run on null: entry-name providers must be
|
|
178
|
+
* judged against real rows, never guessed. An absent DB file is a real
|
|
179
|
+
* state (no rows, so every entry name is dangling and stays untouched).
|
|
180
|
+
*/
|
|
181
|
+
function readConnectionRows(workspaceDir: string): Map<string, string> | null {
|
|
182
|
+
const dbPath = join(workspaceDir, "data", "db", "assistant.db");
|
|
183
|
+
if (!existsSync(dbPath)) {
|
|
184
|
+
return new Map();
|
|
185
|
+
}
|
|
186
|
+
let db: Database;
|
|
187
|
+
try {
|
|
188
|
+
db = new Database(dbPath);
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const rows = db
|
|
194
|
+
.query(`SELECT name, provider FROM provider_connections`)
|
|
195
|
+
.all() as Array<{ name: string; provider: string }>;
|
|
196
|
+
return new Map(rows.map((r) => [r.name, r.provider]));
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
} finally {
|
|
200
|
+
db.close();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
205
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return value as Record<string, unknown>;
|
|
209
|
+
}
|