@vellumai/assistant 0.12.4 → 0.12.5-dev.202609251017.e467a6c
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/docs/architecture/memory.md +9 -1
- package/docs/architecture/scheduling.md +13 -5
- package/docs/desktop-browser-cli.md +3 -2
- package/docs/guardian-request-flow.md +1 -1
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/notification-avatar.test.ts +48 -4
- package/node_modules/@vellumai/avatar-manifest/src/notification-avatar.ts +12 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/guardian-requests.test.ts +26 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +77 -3
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/guardian-requests.test.ts +26 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +77 -3
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +2 -4
- package/node_modules/@vellumai/service-contracts/src/__tests__/guardian-requests.test.ts +26 -0
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +77 -3
- package/openapi.yaml +152 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +76 -0
- package/src/__tests__/anthropic-error-formatting.test.ts +9 -0
- package/src/__tests__/background-shell-bash.test.ts +34 -0
- package/src/__tests__/background-shell-host-bash.test.ts +98 -1
- package/src/__tests__/background-tool-registry.test.ts +39 -0
- package/src/__tests__/channel-readiness-discord.test.ts +8 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +8 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +4 -20
- package/src/__tests__/conversation-agent-loop.test.ts +529 -1
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-pairing.test.ts +69 -0
- package/src/__tests__/conversation-queue.test.ts +257 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +62 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +21 -0
- package/src/__tests__/gemini-provider.test.ts +9 -0
- package/src/__tests__/host-cu-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
- package/src/__tests__/introduction-card-resolver.test.ts +53 -8
- package/src/__tests__/job-handler-registration-guard.test.ts +1 -0
- package/src/__tests__/non-member-access-request.test.ts +48 -0
- package/src/__tests__/notification-events-source-context-probe.test.ts +48 -1
- package/src/__tests__/notification-platform-adapter.test.ts +97 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +103 -3
- package/src/__tests__/platform-callback-registration.test.ts +46 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +116 -66
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -0
- package/src/__tests__/scheduler-result-notification.test.ts +521 -7
- package/src/__tests__/scheduler-wake.test.ts +1 -0
- package/src/__tests__/skill-load-inline-command.test.ts +35 -0
- package/src/__tests__/subagent-cron-run-attribution.test.ts +41 -2
- package/src/__tests__/subagent-manager-notify.test.ts +48 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +6 -6
- package/src/__tests__/usage-attribution.test.ts +31 -0
- package/src/__tests__/wake-schedule-escalation.test.ts +1 -0
- package/src/agent/loop.ts +23 -0
- package/src/api/ask-question-tool.ts +50 -0
- package/src/api/events/generation-handoff.ts +4 -0
- package/src/api/events/message-complete.ts +4 -0
- package/src/api/events/tool-result.ts +11 -0
- package/src/api/index.ts +11 -0
- package/src/api/requests/guardian-actions.test.ts +45 -0
- package/src/api/requests/guardian-actions.ts +20 -0
- package/src/api/responses/conversation-message.ts +4 -0
- package/src/api/responses/home.ts +27 -0
- package/src/approvals/guardian-decision-primitive.ts +6 -4
- package/src/approvals/guardian-request-resolvers.ts +35 -30
- package/src/avatar/notification-avatar.test.ts +42 -34
- package/src/avatar/notification-avatar.ts +1 -0
- package/src/calls/__tests__/call-controller.test.ts +18 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +6 -1
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +70 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +116 -4
- package/src/calls/__tests__/voice-triage-escalate.test.ts +77 -11
- package/src/calls/call-controller.ts +3 -1
- package/src/calls/voice-control-protocol.ts +12 -4
- package/src/calls/voice-escalation-judge.ts +2 -2
- package/src/calls/voice-leg-coordinator.ts +23 -5
- package/src/calls/voice-screen-annotation.ts +44 -0
- package/src/calls/voice-session-bridge.ts +63 -20
- package/src/calls/voice-triage-escalate.ts +82 -27
- package/src/cli/commands/__tests__/conversations-clear.test.ts +156 -0
- package/src/cli/commands/conversations.help.ts +7 -2
- package/src/cli/commands/conversations.ts +36 -30
- package/src/cli/commands/oauth/index.help.ts +8 -2
- package/src/cli/commands/oauth/request.test.ts +32 -0
- package/src/cli/commands/platform/index.help.ts +2 -1
- package/src/cli/commands/platform/index.ts +8 -2
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +60 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +66 -2
- package/src/cli/lib/bundled-marketplace.json +35 -0
- package/src/cli/lib/bundled-plugin-packages.json +21 -0
- package/src/cli/lib/plugin-catalog-cache.ts +17 -2
- package/src/cli/lib/plugin-catalog-platform.ts +48 -9
- package/src/config/__tests__/default-profile-catalog.test.ts +28 -0
- package/src/config/__tests__/platform-identity.test.ts +59 -0
- package/src/config/__tests__/profile-order.test.ts +19 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +71 -0
- 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/bundled-skills/screen-annotation/SKILL.md +41 -29
- package/src/config/bundled-skills/screen-annotation/TOOLS.json +5 -5
- package/src/config/call-site-defaults.ts +6 -0
- package/src/config/default-profile-catalog.ts +30 -1
- package/src/config/default-profile-names.ts +36 -2
- package/src/config/feature-flag-registry.json +9 -1
- package/src/config/platform-identity.ts +22 -5
- package/src/config/profile-order.ts +7 -1
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/llm.ts +21 -5
- package/src/config/sync-gated-profiles.ts +96 -44
- package/src/daemon/__tests__/abort-null-controller.test.ts +135 -1
- package/src/daemon/__tests__/auto-profile-router.test.ts +380 -0
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +16 -0
- package/src/daemon/activity-trail.test.ts +206 -0
- package/src/daemon/activity-trail.ts +190 -0
- package/src/daemon/auto-profile-router.ts +431 -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-handlers.ts +11 -0
- package/src/daemon/conversation-agent-loop.ts +157 -32
- package/src/daemon/conversation-error.ts +19 -0
- package/src/daemon/conversation-lifecycle.ts +70 -7
- package/src/daemon/conversation-media-retry.ts +1 -2
- package/src/daemon/conversation-messaging.ts +31 -2
- package/src/daemon/conversation-process.ts +107 -16
- package/src/daemon/conversation-queue-manager.ts +7 -0
- package/src/daemon/conversation-runtime-assembly.ts +1 -0
- package/src/daemon/conversation-turn-finalize.ts +14 -0
- package/src/daemon/conversation.ts +25 -6
- package/src/daemon/event-loop-watchdog.test.ts +201 -2
- package/src/daemon/event-loop-watchdog.ts +182 -6
- package/src/daemon/host-cu-proxy.ts +34 -3
- package/src/daemon/pkb-reminder-builder.test.ts +10 -3
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/wake-conversation-ops.ts +4 -2
- package/src/desktop/desktop-computer-use.test.ts +14 -4
- package/src/home/feed-types.ts +2 -0
- package/src/inbound/platform-callback-registration.ts +19 -6
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +27 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +48 -1
- package/src/live-voice/__tests__/live-voice-vad.test.ts +34 -2
- package/src/live-voice/__tests__/protocol.test.ts +22 -0
- package/src/live-voice/continuation-judge.ts +1 -1
- package/src/live-voice/live-voice-session.ts +25 -19
- package/src/live-voice/protocol.ts +16 -5
- package/src/messaging/providers/discord/withdraw.ts +4 -5
- package/src/messaging/providers/gmail/client.ts +4 -0
- package/src/messaging/providers/slack/withdraw.ts +5 -5
- package/src/messaging/providers/telegram-bot/withdraw.ts +4 -5
- package/src/monitoring/__tests__/proc-fixtures.ts +28 -0
- package/src/monitoring/__tests__/proc-wait-state.test.ts +232 -0
- package/src/monitoring/__tests__/process-usage.test.ts +243 -0
- package/src/monitoring/proc-wait-state.ts +366 -0
- package/src/monitoring/process-memory.ts +4 -3
- package/src/monitoring/process-usage.ts +215 -0
- package/src/monitoring/resource-sample-types.ts +8 -0
- package/src/monitoring/resource-sampler.ts +17 -2
- package/src/monitoring/stall-capture.ts +12 -0
- package/src/notifications/README.md +88 -9
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +390 -119
- package/src/notifications/__tests__/background-result-producer.test.ts +1272 -0
- package/src/notifications/__tests__/broadcaster.test.ts +292 -1
- package/src/notifications/__tests__/completion-policy.test.ts +243 -0
- package/src/notifications/__tests__/decision-engine.test.ts +51 -4
- package/src/notifications/__tests__/has-pending-background-work.test.ts +281 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +161 -3
- package/src/notifications/__tests__/resolve-visible-in-source.test.ts +110 -1
- package/src/notifications/__tests__/schedule-result-producer.test.ts +11 -4
- package/src/notifications/adapters/macos.ts +24 -12
- package/src/notifications/adapters/platform.ts +16 -5
- package/src/notifications/assistant-reply-producer.ts +96 -71
- package/src/notifications/background-result-producer.ts +369 -0
- package/src/notifications/broadcaster.ts +28 -8
- package/src/notifications/completion-policy.ts +115 -0
- package/src/notifications/completion-work.ts +46 -0
- package/src/notifications/conversation-pairing.ts +32 -13
- package/src/notifications/decision-engine.ts +18 -24
- package/src/notifications/emit-signal.ts +16 -1
- package/src/notifications/events-store.ts +4 -0
- package/src/notifications/has-pending-background-work.ts +91 -0
- package/src/notifications/home-feed-mirror.ts +44 -0
- package/src/notifications/home-feed-side-effect.ts +54 -28
- package/src/notifications/resolve-visible-in-source.ts +56 -2
- package/src/notifications/result-output.ts +154 -0
- package/src/notifications/schedule-result-producer.ts +6 -187
- package/src/notifications/types.ts +2 -0
- package/src/oauth/AGENTS.md +9 -1
- package/src/oauth/byo-connection.test.ts +28 -0
- package/src/persistence/__tests__/recent-conversation-messages.test.ts +41 -0
- package/src/persistence/conversation-crud.ts +26 -0
- package/src/persistence/conversation-types.ts +15 -37
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +85 -14
- package/src/persistence/jobs-store.ts +229 -0
- package/src/persistence/migrations/383-normalize-opencode-host-connections.ts +44 -0
- package/src/persistence/migrations/__tests__/383-normalize-opencode-host-connections.test.ts +102 -0
- package/src/persistence/steps.ts +12 -0
- package/src/platform/sync-avatar.test.ts +29 -0
- package/src/plugins/defaults/memory/AGENTS.md +5 -3
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-boundary-guard.test.ts +45 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-job.test.ts +580 -0
- package/src/plugins/defaults/memory/__tests__/turn-retrieval.test.ts +21 -0
- package/src/plugins/defaults/memory/graph/tools.ts +9 -5
- package/src/plugins/defaults/memory/hooks/conversation-deleted.ts +4 -0
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +7 -3
- package/src/plugins/defaults/memory/job-handlers.ts +8 -2
- package/src/plugins/defaults/memory/skill-update-receipt-job.ts +334 -0
- package/src/plugins/defaults/memory/tools.test.ts +17 -0
- package/src/plugins/defaults/memory/turn-retrieval.ts +8 -0
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/injector.ts +4 -4
- package/src/plugins/types.ts +2 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +4 -0
- package/src/providers/gemini/client.ts +7 -1
- package/src/providers/inference/auth.ts +25 -0
- package/src/providers/inference/profile-probe.ts +1 -0
- package/src/{calls/__tests__/typesafe-noul.test.ts → providers/jev/ask.test.ts} +4 -4
- package/src/{calls/typesafe-noul.ts → providers/jev/ask.ts} +67 -34
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +12 -0
- package/src/providers/openai/api-error-normalization.ts +4 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +7 -0
- package/src/providers/usage-tracking.ts +5 -3
- package/src/runtime/__tests__/agent-wake.test.ts +308 -0
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake-queue.ts +52 -0
- package/src/runtime/agent-wake.ts +111 -18
- package/src/runtime/channel-approval-types.ts +6 -38
- package/src/runtime/channel-approvals.ts +4 -5
- package/src/runtime/http-server.ts +4 -1
- package/src/runtime/introduction-policy.test.ts +17 -1
- package/src/runtime/introduction-policy.ts +25 -7
- package/src/runtime/migrations/vbundle-builder.ts +9 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +21 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +60 -0
- package/src/runtime/routes/__tests__/oauth-proxy-routes.test.ts +61 -3
- package/src/runtime/routes/auto-profile-routes.ts +83 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -3
- package/src/runtime/routes/conversation-routes.ts +9 -0
- package/src/runtime/routes/guardian-action-routes.ts +8 -23
- package/src/runtime/routes/host-cu-routes.ts +3 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profiles-routes.ts +5 -2
- package/src/runtime/routes/inference-provider-connection-routes.ts +25 -8
- package/src/runtime/routes/migration-routes.ts +8 -2
- package/src/runtime/routes/oauth-proxy-passthrough.test.ts +32 -0
- package/src/runtime/routes/platform-routes.ts +6 -1
- package/src/runtime/routes/user-route-dispatcher.ts +1 -2
- package/src/runtime/web-presence.ts +3 -4
- package/src/schedule/schedule-store.ts +10 -0
- package/src/schedule/scheduler.ts +92 -24
- package/src/subagent/manager.ts +32 -12
- package/src/telegram/__tests__/webhook-health.test.ts +8 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/tools/background-tool-registry.ts +30 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +1 -2
- package/src/tools/host-terminal/host-shell.ts +7 -0
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +28 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +34 -0
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +35 -1
- package/src/tools/network/__tests__/web-search.test.ts +96 -0
- package/src/tools/network/web-fetch.ts +30 -9
- package/src/tools/network/web-search.ts +235 -4
- package/src/tools/skills/load.ts +13 -17
- package/src/tools/skills/scaffold-managed.ts +86 -71
- package/src/tools/terminal/shell-launch.test.ts +27 -1
- package/src/tools/terminal/shell.ts +4 -0
- package/src/tools/workspace-tools/loader.ts +1 -2
- package/src/usage/attribution.ts +18 -1
- package/src/util/abort-reasons.ts +2 -0
- package/src/util/errors.ts +1 -0
- package/src/util/oauth-request-body.test.ts +5 -1
- package/src/util/provider-error-patterns.ts +11 -0
- package/src/util/single-flight.test.ts +6 -0
- package/src/util/single-flight.ts +2 -0
- package/src/util/worker-pipe.test.ts +56 -10
- package/src/util/worker-pipe.ts +9 -4
- package/src/workspace/git-service.ts +6 -1
- package/src/workspace/migrations/158-repoint-opencode-host-provider-fragments.ts +163 -0
- package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts +188 -0
- package/src/workspace/migrations/registry.ts +2 -0
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
* Repoint legacy-shape LLM fragments (`provider: "openai-compatible"` plus a
|
|
9
|
+
* `provider_connection` binding) whose bound row's endpoint host is
|
|
10
|
+
* opencode.ai to `provider: "opencode"`.
|
|
11
|
+
*
|
|
12
|
+
* DB migration 383 restamps such rows as provider "opencode", and dispatch
|
|
13
|
+
* requires a bound row's provider to equal the fragment's declared provider,
|
|
14
|
+
* so a fragment left declaring "openai-compatible" would either mismatch or
|
|
15
|
+
* auto-resolve to an unrelated generic row. Entry-shape fragments (provider
|
|
16
|
+
* holds the row name) derive their kind from the row and need no rewrite.
|
|
17
|
+
* Rows are judged by their base_url host, not their provider column, so the
|
|
18
|
+
* result is the same whichever migration runs first.
|
|
19
|
+
*
|
|
20
|
+
* Swept in `llm.default` and `llm.profiles.*`; call-site fragments carry no
|
|
21
|
+
* binding (the schema strips a raw `provider_connection`).
|
|
22
|
+
*/
|
|
23
|
+
export const repointOpencodeHostProviderFragmentsMigration: WorkspaceMigration =
|
|
24
|
+
{
|
|
25
|
+
id: "158-repoint-opencode-host-provider-fragments",
|
|
26
|
+
description:
|
|
27
|
+
"Repoint openai-compatible LLM fragments bound to opencode.ai connections to provider opencode",
|
|
28
|
+
run(workspaceDir: string): void {
|
|
29
|
+
const configPath = join(workspaceDir, "config.json");
|
|
30
|
+
if (!existsSync(configPath)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Read outside the parse catch: a transient filesystem error must reach
|
|
35
|
+
// the runner so the migration retries, while malformed JSON is a
|
|
36
|
+
// permanent state this migration cannot repair.
|
|
37
|
+
const rawText = readFileSync(configPath, "utf-8");
|
|
38
|
+
|
|
39
|
+
let config: Record<string, unknown>;
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(rawText);
|
|
42
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
config = raw as Record<string, unknown>;
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const llm = readObject(config.llm);
|
|
51
|
+
if (llm === null) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Rows load lazily: only a legacy-shape openai-compatible fragment
|
|
56
|
+
// needs them. An unreadable DB then fails the run (retried next boot)
|
|
57
|
+
// rather than checkpointing a pass that skipped bound fragments.
|
|
58
|
+
let openCodeRows: Set<string> | null | undefined;
|
|
59
|
+
const isOpenCodeRow = (name: unknown): boolean => {
|
|
60
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (openCodeRows === undefined) {
|
|
64
|
+
openCodeRows = readOpenCodeRowNames(workspaceDir);
|
|
65
|
+
}
|
|
66
|
+
if (openCodeRows === null) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"provider_connections is not readable; retrying the opencode repoint on the next run",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return openCodeRows.has(name);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const repoint = (fragment: Record<string, unknown> | null): boolean => {
|
|
75
|
+
if (
|
|
76
|
+
fragment === null ||
|
|
77
|
+
fragment.provider !== "openai-compatible" ||
|
|
78
|
+
!isOpenCodeRow(fragment.provider_connection)
|
|
79
|
+
) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
fragment.provider = "opencode";
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let changed = repoint(readObject(llm.default));
|
|
87
|
+
const profiles = readObject(llm.profiles);
|
|
88
|
+
if (profiles !== null) {
|
|
89
|
+
for (const rawProfile of Object.values(profiles)) {
|
|
90
|
+
changed = repoint(readObject(rawProfile)) || changed;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!changed) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Write-then-rename so an interrupted write cannot leave config.json
|
|
98
|
+
// truncated for the retry to misread as "nothing to do".
|
|
99
|
+
const tmpPath = `${configPath}.migration-158.tmp`;
|
|
100
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
101
|
+
renameSync(tmpPath, configPath);
|
|
102
|
+
},
|
|
103
|
+
// The exact-match rewrite is idempotent, so a transient failure is safe
|
|
104
|
+
// to retry on later startups.
|
|
105
|
+
retryFailedCheckpoint: true,
|
|
106
|
+
down(_workspaceDir: string): void {
|
|
107
|
+
// Forward-only: the rows these fragments bind now carry provider
|
|
108
|
+
// "opencode", so reverting the fragments would recreate the mismatch.
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Helpers: self-contained per workspace migrations AGENTS.md
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Names of `provider_connections` rows whose base_url host is opencode.ai or
|
|
118
|
+
* a subdomain, or null when the DB or table is not readable. An absent DB
|
|
119
|
+
* file is a real state (no rows, so no fragment is bound to one).
|
|
120
|
+
*/
|
|
121
|
+
function readOpenCodeRowNames(workspaceDir: string): Set<string> | null {
|
|
122
|
+
const dbPath = join(workspaceDir, "data", "db", "assistant.db");
|
|
123
|
+
if (!existsSync(dbPath)) {
|
|
124
|
+
return new Set();
|
|
125
|
+
}
|
|
126
|
+
let db: Database;
|
|
127
|
+
try {
|
|
128
|
+
db = new Database(dbPath, { readonly: true });
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const rows = db
|
|
134
|
+
.query(
|
|
135
|
+
`SELECT name, base_url FROM provider_connections WHERE base_url IS NOT NULL`,
|
|
136
|
+
)
|
|
137
|
+
.all() as Array<{ name: string; base_url: string }>;
|
|
138
|
+
return new Set(
|
|
139
|
+
rows.filter((row) => isOpenCodeHost(row.base_url)).map((row) => row.name),
|
|
140
|
+
);
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
} finally {
|
|
144
|
+
db.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isOpenCodeHost(baseUrl: string): boolean {
|
|
149
|
+
let host: string;
|
|
150
|
+
try {
|
|
151
|
+
host = new URL(baseUrl).hostname.toLowerCase();
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return host === "opencode.ai" || host.endsWith(".opencode.ai");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
159
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return value as Record<string, unknown>;
|
|
163
|
+
}
|
package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdirSync,
|
|
3
|
+
mkdtempSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Database } from "bun:sqlite";
|
|
11
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { WORKSPACE_MIGRATIONS } from "../registry.js";
|
|
14
|
+
|
|
15
|
+
const migration = WORKSPACE_MIGRATIONS.find(
|
|
16
|
+
(entry) => entry.id === "158-repoint-opencode-host-provider-fragments",
|
|
17
|
+
)!;
|
|
18
|
+
|
|
19
|
+
const workspaces: string[] = [];
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
for (const workspace of workspaces.splice(0)) {
|
|
22
|
+
rmSync(workspace, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function setup(): string {
|
|
27
|
+
const workspace = mkdtempSync(join(tmpdir(), "opencode-repoint-migration-"));
|
|
28
|
+
workspaces.push(workspace);
|
|
29
|
+
return workspace;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeConfig(workspace: string, data: Record<string, unknown>): void {
|
|
33
|
+
writeFileSync(
|
|
34
|
+
join(workspace, "config.json"),
|
|
35
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readLlm(workspace: string): Record<string, any> {
|
|
40
|
+
return JSON.parse(readFileSync(join(workspace, "config.json"), "utf-8")).llm;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function seedRows(
|
|
44
|
+
workspace: string,
|
|
45
|
+
rows: Array<{ name: string; provider: string; baseUrl: string | null }>,
|
|
46
|
+
): void {
|
|
47
|
+
mkdirSync(join(workspace, "data", "db"), { recursive: true });
|
|
48
|
+
const db = new Database(join(workspace, "data", "db", "assistant.db"));
|
|
49
|
+
db.run(`CREATE TABLE provider_connections (
|
|
50
|
+
name TEXT PRIMARY KEY,
|
|
51
|
+
provider TEXT NOT NULL,
|
|
52
|
+
auth TEXT NOT NULL,
|
|
53
|
+
base_url TEXT,
|
|
54
|
+
created_at INTEGER NOT NULL,
|
|
55
|
+
updated_at INTEGER NOT NULL
|
|
56
|
+
)`);
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
db.query(
|
|
59
|
+
`INSERT INTO provider_connections (name, provider, auth, base_url, created_at, updated_at)
|
|
60
|
+
VALUES (?, ?, '{"type":"api_key","credential":"cred"}', ?, 1, 1)`,
|
|
61
|
+
).run(row.name, row.provider, row.baseUrl);
|
|
62
|
+
}
|
|
63
|
+
db.close();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe("migration 158: repoint opencode-host provider fragments", () => {
|
|
67
|
+
test("repoints legacy-shape fragments bound to an opencode.ai row, whichever provider the row stores", () => {
|
|
68
|
+
const workspace = setup();
|
|
69
|
+
seedRows(workspace, [
|
|
70
|
+
{
|
|
71
|
+
name: "go",
|
|
72
|
+
provider: "openai-compatible",
|
|
73
|
+
baseUrl: "https://opencode.ai/zen/go/v1",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "zen",
|
|
77
|
+
provider: "opencode",
|
|
78
|
+
baseUrl: "https://opencode.ai/zen/v1",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "vllm",
|
|
82
|
+
provider: "openai-compatible",
|
|
83
|
+
baseUrl: "http://localhost:8080/v1",
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
writeConfig(workspace, {
|
|
87
|
+
llm: {
|
|
88
|
+
default: {
|
|
89
|
+
provider: "openai-compatible",
|
|
90
|
+
provider_connection: "go",
|
|
91
|
+
model: "m",
|
|
92
|
+
},
|
|
93
|
+
profiles: {
|
|
94
|
+
a: {
|
|
95
|
+
provider: "openai-compatible",
|
|
96
|
+
provider_connection: "go",
|
|
97
|
+
model: "m",
|
|
98
|
+
},
|
|
99
|
+
b: {
|
|
100
|
+
provider: "openai-compatible",
|
|
101
|
+
provider_connection: "zen",
|
|
102
|
+
model: "m",
|
|
103
|
+
},
|
|
104
|
+
c: {
|
|
105
|
+
provider: "openai-compatible",
|
|
106
|
+
provider_connection: "vllm",
|
|
107
|
+
model: "m",
|
|
108
|
+
},
|
|
109
|
+
entry: { provider: "go", model: "m" },
|
|
110
|
+
unbound: { provider: "openai-compatible", model: "m" },
|
|
111
|
+
dangling: {
|
|
112
|
+
provider: "openai-compatible",
|
|
113
|
+
provider_connection: "gone",
|
|
114
|
+
model: "m",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
migration.run(workspace);
|
|
121
|
+
|
|
122
|
+
const llm = readLlm(workspace);
|
|
123
|
+
expect(llm.default.provider).toBe("opencode");
|
|
124
|
+
expect(llm.default.provider_connection).toBe("go");
|
|
125
|
+
expect(llm.profiles.a.provider).toBe("opencode");
|
|
126
|
+
expect(llm.profiles.b.provider).toBe("opencode");
|
|
127
|
+
expect(llm.profiles.c.provider).toBe("openai-compatible");
|
|
128
|
+
expect(llm.profiles.entry.provider).toBe("go");
|
|
129
|
+
expect(llm.profiles.unbound.provider).toBe("openai-compatible");
|
|
130
|
+
expect(llm.profiles.dangling.provider).toBe("openai-compatible");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("no-ops without config, without a db, or with nothing bound", () => {
|
|
134
|
+
const noConfig = setup();
|
|
135
|
+
expect(() => migration.run(noConfig)).not.toThrow();
|
|
136
|
+
|
|
137
|
+
const noDb = setup();
|
|
138
|
+
writeConfig(noDb, {
|
|
139
|
+
llm: {
|
|
140
|
+
profiles: {
|
|
141
|
+
a: { provider: "openai-compatible", provider_connection: "go" },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
migration.run(noDb);
|
|
146
|
+
expect(readLlm(noDb).profiles.a.provider).toBe("openai-compatible");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("fails the run instead of checkpointing when the db is unreadable", () => {
|
|
150
|
+
const workspace = setup();
|
|
151
|
+
seedRows(workspace, []);
|
|
152
|
+
writeFileSync(
|
|
153
|
+
join(workspace, "data", "db", "assistant.db"),
|
|
154
|
+
"not a database",
|
|
155
|
+
);
|
|
156
|
+
writeConfig(workspace, {
|
|
157
|
+
llm: {
|
|
158
|
+
profiles: {
|
|
159
|
+
a: { provider: "openai-compatible", provider_connection: "go" },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
expect(() => migration.run(workspace)).toThrow(/not readable/);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("is idempotent", () => {
|
|
167
|
+
const workspace = setup();
|
|
168
|
+
seedRows(workspace, [
|
|
169
|
+
{
|
|
170
|
+
name: "go",
|
|
171
|
+
provider: "openai-compatible",
|
|
172
|
+
baseUrl: "https://opencode.ai/zen/go/v1",
|
|
173
|
+
},
|
|
174
|
+
]);
|
|
175
|
+
writeConfig(workspace, {
|
|
176
|
+
llm: {
|
|
177
|
+
profiles: {
|
|
178
|
+
a: { provider: "openai-compatible", provider_connection: "go" },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
migration.run(workspace);
|
|
183
|
+
const once = readFileSync(join(workspace, "config.json"), "utf-8");
|
|
184
|
+
migration.run(workspace);
|
|
185
|
+
expect(readFileSync(join(workspace, "config.json"), "utf-8")).toBe(once);
|
|
186
|
+
expect(readLlm(workspace).profiles.a.provider).toBe("opencode");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -155,6 +155,7 @@ import { repairRetiredCodexGpt54ModelIdsMigration } from "./154-repair-retired-c
|
|
|
155
155
|
import { moveFrontModelConfigToVoiceMigration } from "./155-move-front-model-config-to-voice.js";
|
|
156
156
|
import { extractWorkspaceMcpJsonMigration } from "./156-extract-workspace-mcp-json.js";
|
|
157
157
|
import { addDesktopFilesLauncherMigration } from "./157-add-desktop-files-launcher.js";
|
|
158
|
+
import { repointOpencodeHostProviderFragmentsMigration } from "./158-repoint-opencode-host-provider-fragments.js";
|
|
158
159
|
import { renameCollidingJevProfileNameMigration } from "./159-rename-colliding-jev-profile-name.js";
|
|
159
160
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
160
161
|
import type { WorkspaceMigration } from "./types.js";
|
|
@@ -326,5 +327,6 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
326
327
|
moveFrontModelConfigToVoiceMigration,
|
|
327
328
|
extractWorkspaceMcpJsonMigration,
|
|
328
329
|
addDesktopFilesLauncherMigration,
|
|
330
|
+
repointOpencodeHostProviderFragmentsMigration,
|
|
329
331
|
renameCollidingJevProfileNameMigration,
|
|
330
332
|
];
|