@vellumai/assistant 0.12.3 → 0.12.4-dev.202609231620.a15f321
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/Dockerfile +7 -0
- package/README.md +12 -12
- package/docker/mousepad/90-vellum.gschema.override +4 -0
- 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/openapi.yaml +97 -4
- package/package.json +1 -1
- package/src/__tests__/approval-routes-http.test.ts +4 -2
- package/src/__tests__/call-routes-http.test.ts +4 -2
- package/src/__tests__/channel-readiness-discord.test.ts +8 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +8 -0
- package/src/__tests__/config-loader-backfill.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-resurface-done.test.ts +289 -0
- package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
- package/src/__tests__/gemini-provider.test.ts +31 -0
- package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
- package/src/__tests__/job-handler-registration-guard.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +43 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +3 -1
- package/src/__tests__/platform-callback-registration.test.ts +46 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/pricing.test.ts +33 -0
- package/src/__tests__/provider-error-scenarios.test.ts +46 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +45 -15
- package/src/__tests__/retry-fallback-escalation.test.ts +22 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +116 -66
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -2
- package/src/__tests__/skill-load-inline-command.test.ts +35 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -11
- package/src/__tests__/skill-tool-factory.test.ts +50 -2
- package/src/__tests__/steer-on-enqueue-question.test.ts +265 -34
- package/src/__tests__/validate-input.test.ts +73 -0
- package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
- package/src/api/ask-question-tool.ts +50 -0
- package/src/api/events/tool-result.ts +11 -0
- package/src/api/index.ts +7 -0
- package/src/api/requests/dictation.ts +2 -2
- package/src/api/responses/home.ts +27 -0
- package/src/avatar/notification-avatar.test.ts +42 -34
- package/src/avatar/notification-avatar.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +63 -1
- package/src/calls/voice-escalation-judge.ts +6 -5
- package/src/calls/voice-screen-annotation.ts +44 -0
- package/src/calls/voice-session-bridge.ts +22 -0
- package/src/cli/commands/__tests__/plugins.test.ts +29 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +12 -2
- package/src/cli/commands/platform/index.help.ts +2 -1
- package/src/cli/commands/platform/index.ts +8 -2
- package/src/cli/commands/plugins.ts +16 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +29 -0
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +92 -2
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +15 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +66 -2
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +21 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +65 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +43 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +26 -0
- package/src/cli/lib/bundled-marketplace.json +4 -4
- package/src/cli/lib/inspect-plugin.ts +5 -0
- package/src/cli/lib/plugin-catalog-cache.ts +41 -4
- package/src/cli/lib/plugin-catalog-local.ts +9 -0
- package/src/cli/lib/plugin-catalog-platform.ts +48 -9
- package/src/cli/lib/plugin-catalog-visibility.ts +41 -0
- package/src/cli/lib/plugin-details.ts +10 -7
- package/src/cli/lib/search-plugins.ts +2 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +61 -1
- package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +71 -0
- package/src/config/bundled-skills/screen-annotation/SKILL.md +6 -3
- package/src/config/call-site-defaults.ts +17 -11
- package/src/config/default-profile-catalog.ts +118 -19
- package/src/config/default-profile-names.ts +69 -0
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/inference-profile-validation.ts +7 -6
- package/src/config/llm-resolver.ts +16 -0
- package/src/config/profile-text-generation.test.ts +9 -0
- package/src/config/schemas/call-site-catalog.ts +8 -1
- package/src/config/schemas/llm.ts +70 -9
- package/src/config/sidebar-done-gate.ts +27 -0
- package/src/config/sync-gated-profiles.ts +96 -44
- package/src/daemon/__tests__/auto-profile-router.test.ts +228 -0
- package/src/daemon/auto-profile-router.ts +220 -0
- package/src/daemon/conversation-agent-loop.ts +77 -4
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-turn-finalize.ts +12 -0
- package/src/daemon/dictation-text-processing.ts +4 -4
- package/src/daemon/handlers/__tests__/conversation-history-search.test.ts +61 -0
- package/src/daemon/handlers/config-channels.ts +3 -1
- package/src/daemon/handlers/conversation-history.ts +11 -1
- package/src/daemon/handlers/conversations.ts +118 -10
- package/src/daemon/pkb-reminder-builder.test.ts +10 -3
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/desktop/desktop-browser-endpoint.ts +1 -0
- package/src/home/feed-types.ts +2 -0
- package/src/inbound/platform-callback-registration.ts +19 -6
- package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +49 -1
- package/src/ipc/routes/conversation-sync-ipc-routes.ts +38 -2
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +27 -0
- 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 +7 -6
- package/src/live-voice/protocol.ts +16 -5
- package/src/messaging/providers/gmail/client.ts +4 -0
- package/src/notifications/README.md +11 -3
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +87 -3
- package/src/notifications/conversation-pairing.ts +23 -8
- package/src/notifications/home-feed-mirror.ts +39 -0
- package/src/notifications/home-feed-side-effect.ts +46 -19
- package/src/oauth/AGENTS.md +7 -1
- package/src/permissions/question-prompter.ts +14 -4
- package/src/persistence/conversation-crud.ts +114 -3
- package/src/persistence/conversation-queries.ts +48 -3
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +228 -32
- package/src/persistence/embeddings/embedding-backend-shutdown.test.ts +47 -0
- package/src/persistence/embeddings/embedding-backend.ts +23 -41
- package/src/persistence/embeddings/embedding-gemini.test.ts +17 -0
- package/src/persistence/embeddings/embedding-gemini.ts +3 -1
- package/src/persistence/embeddings/embedding-local.ts +72 -17
- package/src/persistence/embeddings/embedding-types.ts +0 -19
- 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/plugin-api/model-profiles.ts +5 -3
- package/src/plugins/defaults/memory/AGENTS.md +5 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +2 -0
- 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/graph/tools.ts +9 -5
- package/src/plugins/defaults/memory/hooks/conversation-deleted.ts +4 -0
- package/src/plugins/defaults/memory/job-handlers.ts +8 -2
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +5 -0
- package/src/plugins/defaults/memory/skill-update-receipt-job.ts +334 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/tools.test.ts +17 -0
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +3 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +2 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +2 -2
- package/src/providers/gemini/client.ts +17 -1
- package/src/providers/inference/auth.ts +25 -0
- package/src/providers/jev/ask.test.ts +66 -0
- package/src/{calls/typesafe-noul.ts → providers/jev/ask.ts} +75 -36
- package/src/providers/malformed-tool-call.ts +55 -0
- package/src/providers/model-catalog.ts +257 -4
- package/src/providers/openai/__tests__/chat-completions-malformed-tool-call.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +30 -0
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/retry.ts +68 -36
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/vellum-model-routing.test.ts +1 -0
- package/src/runtime/__tests__/agent-wake-archived-gate.test.ts +90 -0
- package/src/runtime/__tests__/question-resolution.test.ts +32 -0
- package/src/runtime/agent-wake.ts +9 -2
- package/src/runtime/auth/__tests__/middleware.test.ts +138 -0
- package/src/runtime/auth/middleware.ts +15 -11
- package/src/runtime/middleware/auth.ts +7 -1
- package/src/runtime/pending-interactions.ts +8 -8
- package/src/runtime/question-resolution.ts +54 -8
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +146 -0
- package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +74 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +60 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +8 -0
- package/src/runtime/routes/conversation-list-routes.ts +16 -14
- package/src/runtime/routes/conversation-query-routes.ts +4 -0
- package/src/runtime/routes/conversation-routes.ts +4 -2
- package/src/runtime/routes/diagnostics-routes.ts +66 -229
- package/src/runtime/routes/global-search-routes.ts +22 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +25 -8
- package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
- package/src/runtime/routes/platform-routes.ts +6 -1
- package/src/runtime/routes/plugins-routes.ts +5 -2
- package/src/runtime/sync/documents-sidecar-publish.test.ts +50 -4
- package/src/runtime/sync/resource-sync-events.ts +11 -0
- package/src/runtime/sync/worker-daemon-notify.test.ts +30 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -0
- package/src/runtime/verification-outbound-actions.ts +2 -2
- package/src/skills/validate-input.ts +75 -11
- package/src/telegram/__tests__/webhook-health.test.ts +8 -0
- package/src/tools/ask-question/ask-question-tool.test.ts +38 -0
- package/src/tools/ask-question/ask-question-tool.ts +12 -1
- 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/execute.ts +1 -1
- package/src/tools/skills/format-input-schema.ts +15 -0
- package/src/tools/skills/load.ts +15 -43
- package/src/tools/skills/scaffold-managed.ts +86 -71
- package/src/tools/skills/skill-tool-factory.ts +2 -1
- package/src/tools/workflows/manage-workflows.ts +4 -2
- package/src/util/worker-pipe.test.ts +146 -0
- package/src/util/worker-pipe.ts +49 -0
- package/src/workspace/migrations/158-repoint-opencode-host-provider-fragments.ts +163 -0
- package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
- package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts +188 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
|
@@ -191,6 +191,7 @@ const MANAGED_FALLBACK_PROVIDERS: string[] = [
|
|
|
191
191
|
"openai",
|
|
192
192
|
"fireworks",
|
|
193
193
|
"together",
|
|
194
|
+
"typesafe",
|
|
194
195
|
];
|
|
195
196
|
|
|
196
197
|
function enableManagedProxy() {
|
|
@@ -314,28 +315,57 @@ describe("managed proxy integration — credential precedence", () => {
|
|
|
314
315
|
},
|
|
315
316
|
);
|
|
316
317
|
|
|
317
|
-
test("managed bootstrap registers
|
|
318
|
+
test("managed bootstrap registers every managed-routable catalog provider", async () => {
|
|
318
319
|
enableManagedProxy();
|
|
319
320
|
mockProviderKeys = {};
|
|
320
321
|
await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
|
|
321
|
-
expect(listProviders()).toEqual(
|
|
322
|
-
|
|
323
|
-
"anthropic",
|
|
324
|
-
"openai",
|
|
325
|
-
"gemini",
|
|
326
|
-
"fireworks",
|
|
327
|
-
"together",
|
|
328
|
-
]),
|
|
322
|
+
expect([...listProviders()].sort()).toEqual(
|
|
323
|
+
[...MANAGED_FALLBACK_PROVIDERS].sort(),
|
|
329
324
|
);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
|
|
334
|
-
expect(getProviderRoutingSource("fireworks")).toBe("managed-proxy");
|
|
335
|
-
expect(getProviderRoutingSource("together")).toBe("managed-proxy");
|
|
325
|
+
for (const provider of MANAGED_FALLBACK_PROVIDERS) {
|
|
326
|
+
expect(getProviderRoutingSource(provider)).toBe("managed-proxy");
|
|
327
|
+
}
|
|
336
328
|
expect(getProviderRoutingSource("openrouter")).toBeUndefined();
|
|
337
329
|
});
|
|
338
330
|
|
|
331
|
+
test("managed typesafe sends System One through the typesafe proxy path with the assistant key", async () => {
|
|
332
|
+
enableManagedProxy();
|
|
333
|
+
mockProviderKeys = {};
|
|
334
|
+
await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
|
|
335
|
+
|
|
336
|
+
const provider = getProvider("typesafe");
|
|
337
|
+
const originalFetch = globalThis.fetch;
|
|
338
|
+
let capturedUrl = "";
|
|
339
|
+
let capturedHeaders: Record<string, string> = {};
|
|
340
|
+
globalThis.fetch = (async (
|
|
341
|
+
input: string | URL | Request,
|
|
342
|
+
init?: RequestInit,
|
|
343
|
+
) => {
|
|
344
|
+
capturedUrl = String(input);
|
|
345
|
+
capturedHeaders = (init?.headers ?? {}) as Record<string, string>;
|
|
346
|
+
return new Response(
|
|
347
|
+
JSON.stringify({
|
|
348
|
+
model: "jev-latest",
|
|
349
|
+
answers: {},
|
|
350
|
+
usage: { input_tokens: 1, output_tokens: 0 },
|
|
351
|
+
}),
|
|
352
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
353
|
+
);
|
|
354
|
+
}) as typeof fetch;
|
|
355
|
+
try {
|
|
356
|
+
await provider.sendMessage([userMsg("hello")], {
|
|
357
|
+
config: { model: "jev-latest" },
|
|
358
|
+
});
|
|
359
|
+
} finally {
|
|
360
|
+
globalThis.fetch = originalFetch;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
expect(capturedUrl).toBe(
|
|
364
|
+
`${PLATFORM_BASE}/v1/runtime-proxy/typesafe/v1/systemone`,
|
|
365
|
+
);
|
|
366
|
+
expect(capturedHeaders.Authorization).toBe(`Bearer ${MANAGED_API_KEY}`);
|
|
367
|
+
});
|
|
368
|
+
|
|
339
369
|
test("managed anthropic uses anthropic proxy path", async () => {
|
|
340
370
|
enableManagedProxy();
|
|
341
371
|
mockProviderKeys = {};
|
|
@@ -12,7 +12,9 @@ mock.module("../util/retry.js", () => ({
|
|
|
12
12
|
import {
|
|
13
13
|
recordFallbackServed,
|
|
14
14
|
resetFallbackBreaker,
|
|
15
|
+
shouldSkipPrimary,
|
|
15
16
|
} from "../providers/fallback-breaker.js";
|
|
17
|
+
import { malformedToolCallError } from "../providers/malformed-tool-call.js";
|
|
16
18
|
import type {
|
|
17
19
|
Message,
|
|
18
20
|
Provider,
|
|
@@ -666,6 +668,26 @@ describe("RetryProvider fallback-route escalation", () => {
|
|
|
666
668
|
expect(backup.calls()).toBe(1);
|
|
667
669
|
});
|
|
668
670
|
|
|
671
|
+
test("malformed tool call exhausts retries → backup serves, breaker stays closed", async () => {
|
|
672
|
+
const primary = failingProvider("openai", () =>
|
|
673
|
+
malformedToolCallError("openai", "MALFORMED_FUNCTION_CALL"),
|
|
674
|
+
);
|
|
675
|
+
const backup = backupProvider();
|
|
676
|
+
const route = makeRoute(backup.provider);
|
|
677
|
+
const wrapped = new RetryProvider(primary.provider, {
|
|
678
|
+
resolveFallbackRoute: route.resolveFallbackRoute,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const result = await wrapped.sendMessage(MESSAGES, {
|
|
682
|
+
config: { callSite: "mainAgent" },
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
expect(primary.calls()).toBe(1 + DEFAULT_MAX_RETRIES);
|
|
686
|
+
expect(backup.calls()).toBe(1);
|
|
687
|
+
expect(result.model).toBe("backup-model");
|
|
688
|
+
expect(shouldSkipPrimary({ upstream: "openai" })).toBe(false);
|
|
689
|
+
});
|
|
690
|
+
|
|
669
691
|
test("resolveFallbackRoute unset → behavior unchanged (original error rethrown)", async () => {
|
|
670
692
|
const finalError = new ProviderError("Service Unavailable", "openai", 503);
|
|
671
693
|
const primary = failingProvider("openai", () => finalError);
|
|
@@ -55,8 +55,10 @@ afterAll(() => {
|
|
|
55
55
|
resetDbForTesting();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// This suite mocks isHttpAuthDisabled() to true, so a request carrying no
|
|
59
|
+
// bearer authenticates through the dev bypass. A bearer that is present is
|
|
60
|
+
// verified against the signing key, which these tests do not set up.
|
|
61
|
+
const AUTH_HEADERS: Record<string, string> = {};
|
|
60
62
|
|
|
61
63
|
describe("Runtime attachment metadata", () => {
|
|
62
64
|
let server: RuntimeHttpServer;
|
|
@@ -32,25 +32,24 @@ mock.module("../daemon/skill-memory-refresh.js", () => ({
|
|
|
32
32
|
refreshSkillCapabilityMemories: mockRefreshSkillCapabilityMemories,
|
|
33
33
|
}));
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
// the
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}));
|
|
35
|
+
// Receipt recorder: the executor's one side effect for a background
|
|
36
|
+
// overwrite, captured at the job module's boundary so the tests read what
|
|
37
|
+
// was recorded rather than standing up the memory job queue.
|
|
38
|
+
import type * as ReceiptJob from "../plugins/defaults/memory/skill-update-receipt-job.js";
|
|
39
|
+
|
|
40
|
+
let recordedUpdates: Parameters<typeof ReceiptJob.recordSkillUpdate>[0][] = [];
|
|
41
|
+
let recordUpdateFails = false;
|
|
42
|
+
mock.module(
|
|
43
|
+
"../plugins/defaults/memory/skill-update-receipt-job.js",
|
|
44
|
+
(): Partial<typeof ReceiptJob> => ({
|
|
45
|
+
recordSkillUpdate: (input) => {
|
|
46
|
+
recordedUpdates.push(input);
|
|
47
|
+
if (recordUpdateFails) {
|
|
48
|
+
throw new Error("memory database unavailable");
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
54
53
|
|
|
55
54
|
// Skill-card enqueue recorder. Snapshot + override (rather than a full module
|
|
56
55
|
// replacement) because other modules in this import graph (e.g. the managed
|
|
@@ -152,7 +151,7 @@ beforeEach(() => {
|
|
|
152
151
|
skillCardJobUpserts = [];
|
|
153
152
|
skillCardUpsertThrows = false;
|
|
154
153
|
watchdogEvents.length = 0;
|
|
155
|
-
|
|
154
|
+
recordedUpdates = [];
|
|
156
155
|
});
|
|
157
156
|
|
|
158
157
|
afterEach(() => {
|
|
@@ -1771,7 +1770,8 @@ describe("background skill update notification", () => {
|
|
|
1771
1770
|
});
|
|
1772
1771
|
watchdogEvents.length = 0;
|
|
1773
1772
|
skillCardJobUpserts = [];
|
|
1774
|
-
|
|
1773
|
+
recordedUpdates = [];
|
|
1774
|
+
recordUpdateFails = false;
|
|
1775
1775
|
}
|
|
1776
1776
|
|
|
1777
1777
|
const lineage = () => ({
|
|
@@ -1781,9 +1781,73 @@ describe("background skill update notification", () => {
|
|
|
1781
1781
|
: null,
|
|
1782
1782
|
});
|
|
1783
1783
|
|
|
1784
|
-
test("a background overwrite of an existing skill
|
|
1784
|
+
test("a background overwrite of an existing skill records a receipt entry keyed to the tool call", async () => {
|
|
1785
1785
|
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1786
1786
|
|
|
1787
|
+
const result = await executeScaffoldManagedSkill(
|
|
1788
|
+
{
|
|
1789
|
+
skill_id: "weekly-export",
|
|
1790
|
+
name: "Weekly Report Export",
|
|
1791
|
+
description: "export the weekly usage report",
|
|
1792
|
+
body_markdown: "1. Refined steps.",
|
|
1793
|
+
activation_hints: HINTS,
|
|
1794
|
+
overwrite: true,
|
|
1795
|
+
change_summary: "Refined the steps.",
|
|
1796
|
+
},
|
|
1797
|
+
makeRetrospectiveContext({
|
|
1798
|
+
conversationId: "retro-run-conv",
|
|
1799
|
+
toolUseId: "toolu_1",
|
|
1800
|
+
}),
|
|
1801
|
+
lineage(),
|
|
1802
|
+
);
|
|
1803
|
+
|
|
1804
|
+
expect(result.isError).toBe(false);
|
|
1805
|
+
// Nothing is announced here: the entry joins the pending receipt job,
|
|
1806
|
+
// which announces the burst once it has settled.
|
|
1807
|
+
expect(recordedUpdates).toEqual([
|
|
1808
|
+
{
|
|
1809
|
+
// The producing tool call, so a re-executed call records nothing new.
|
|
1810
|
+
entryId: "retro-run-conv:toolu_1",
|
|
1811
|
+
skillId: "weekly-export",
|
|
1812
|
+
name: "Weekly Report Export",
|
|
1813
|
+
changeSummary: "Refined the steps.",
|
|
1814
|
+
runConversationId: "retro-run-conv",
|
|
1815
|
+
// The receipt's link for this entry resolves through this id, so it
|
|
1816
|
+
// must be the conversation the work came from, not the hidden fork.
|
|
1817
|
+
sourceConversationId: "source-conv",
|
|
1818
|
+
},
|
|
1819
|
+
]);
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
test("an entry without a tool call id gets a fresh key, so a second rewrite of the same skill is still recorded", async () => {
|
|
1823
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1824
|
+
|
|
1825
|
+
for (const body of ["1. Refined steps.", "1. Refined again."]) {
|
|
1826
|
+
await executeScaffoldManagedSkill(
|
|
1827
|
+
{
|
|
1828
|
+
skill_id: "weekly-export",
|
|
1829
|
+
name: "Weekly Report Export",
|
|
1830
|
+
description: "export the weekly usage report",
|
|
1831
|
+
body_markdown: body,
|
|
1832
|
+
activation_hints: HINTS,
|
|
1833
|
+
overwrite: true,
|
|
1834
|
+
change_summary: `Refined: ${body}`,
|
|
1835
|
+
},
|
|
1836
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
expect(recordedUpdates).toHaveLength(2);
|
|
1841
|
+
const [first, second] = recordedUpdates;
|
|
1842
|
+
expect(first?.entryId.startsWith("retro-run-conv:")).toBe(true);
|
|
1843
|
+
expect(second?.entryId.startsWith("retro-run-conv:")).toBe(true);
|
|
1844
|
+
expect(first?.entryId).not.toBe(second?.entryId);
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
test("a receipt that cannot be recorded is logged and counted, and the skill write stands", async () => {
|
|
1848
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1849
|
+
recordUpdateFails = true;
|
|
1850
|
+
|
|
1787
1851
|
const result = await executeScaffoldManagedSkill(
|
|
1788
1852
|
{
|
|
1789
1853
|
skill_id: "weekly-export",
|
|
@@ -1799,29 +1863,20 @@ describe("background skill update notification", () => {
|
|
|
1799
1863
|
);
|
|
1800
1864
|
|
|
1801
1865
|
expect(result.isError).toBe(false);
|
|
1802
|
-
expect(
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
expect(
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
);
|
|
1817
|
-
expect(signal.contextPayload?.body).toBe("Refined the steps.");
|
|
1818
|
-
// Deduped per skill per day so repeated refinements cannot flood the feed.
|
|
1819
|
-
expect(signal.dedupeKey).toBe(
|
|
1820
|
-
`skill-updated:weekly-export:${new Date().toISOString().slice(0, 10)}`,
|
|
1821
|
-
);
|
|
1822
|
-
});
|
|
1823
|
-
|
|
1824
|
-
test("the notice body is the pass's change_summary, so the feed says what changed", async () => {
|
|
1866
|
+
expect(
|
|
1867
|
+
readFileSync(
|
|
1868
|
+
join(TEST_DIR, "skills", "weekly-export", "SKILL.md"),
|
|
1869
|
+
"utf-8",
|
|
1870
|
+
),
|
|
1871
|
+
).toContain("Refined steps.");
|
|
1872
|
+
expect(watchdogEvents).toContainEqual(
|
|
1873
|
+
expect.objectContaining({
|
|
1874
|
+
checkName: "skill_update_receipt_record_failed",
|
|
1875
|
+
}),
|
|
1876
|
+
);
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
test("the entry body is the pass's change_summary, so the receipt says what changed", async () => {
|
|
1825
1880
|
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1826
1881
|
|
|
1827
1882
|
const result = await executeScaffoldManagedSkill(
|
|
@@ -1840,14 +1895,9 @@ describe("background skill update notification", () => {
|
|
|
1840
1895
|
);
|
|
1841
1896
|
|
|
1842
1897
|
expect(result.isError).toBe(false);
|
|
1843
|
-
expect(
|
|
1844
|
-
const payload = emittedSignals[0]!.contextPayload;
|
|
1845
|
-
// The title still names the skill; the body is the change itself.
|
|
1846
|
-
expect(payload?.title).toBe("Skill updated: Weekly Report Export");
|
|
1847
|
-
expect(payload?.body).toBe(
|
|
1898
|
+
expect(recordedUpdates[0]?.changeSummary).toBe(
|
|
1848
1899
|
"Added the retry after an expired session and the export endpoint that held steady.",
|
|
1849
1900
|
);
|
|
1850
|
-
expect(payload?.summary).toBe(payload?.body);
|
|
1851
1901
|
});
|
|
1852
1902
|
|
|
1853
1903
|
test("a background overwrite without a change_summary is refused before the write", async () => {
|
|
@@ -1881,7 +1931,7 @@ describe("background skill update notification", () => {
|
|
|
1881
1931
|
'Error: change_summary is required when updating an existing skill: pass one or two short sentences (under 200 characters) for the person who reads the "Skill updated" notice, naming what you changed and what in the trace prompted it (for example "Added the retry after an expired session and the export endpoint that held steady.").',
|
|
1882
1932
|
);
|
|
1883
1933
|
expect(readFileSync(skillFile, "utf-8")).toBe(before);
|
|
1884
|
-
expect(
|
|
1934
|
+
expect(recordedUpdates).toHaveLength(0);
|
|
1885
1935
|
}
|
|
1886
1936
|
});
|
|
1887
1937
|
|
|
@@ -1905,7 +1955,7 @@ describe("background skill update notification", () => {
|
|
|
1905
1955
|
expect(result.isError).toBe(true);
|
|
1906
1956
|
expect(result.content).toContain("Set overwrite=true to replace it.");
|
|
1907
1957
|
expect(result.content).not.toContain("change_summary");
|
|
1908
|
-
expect(
|
|
1958
|
+
expect(recordedUpdates).toHaveLength(0);
|
|
1909
1959
|
});
|
|
1910
1960
|
|
|
1911
1961
|
test("the ownership backstop speaks before the change_summary requirement", async () => {
|
|
@@ -1937,7 +1987,7 @@ describe("background skill update notification", () => {
|
|
|
1937
1987
|
expect(result.content).toContain("not verifiably assistant-authored");
|
|
1938
1988
|
});
|
|
1939
1989
|
|
|
1940
|
-
test("a user-directed overwrite needs no change_summary: it
|
|
1990
|
+
test("a user-directed overwrite needs no change_summary: it records no entry", async () => {
|
|
1941
1991
|
await executeScaffoldManagedSkill(
|
|
1942
1992
|
{
|
|
1943
1993
|
skill_id: "user-skill",
|
|
@@ -1964,7 +2014,7 @@ describe("background skill update notification", () => {
|
|
|
1964
2014
|
expect(result.isError).toBe(false);
|
|
1965
2015
|
});
|
|
1966
2016
|
|
|
1967
|
-
test("change_summary is sanitized like any
|
|
2017
|
+
test("change_summary is sanitized like any notification body", async () => {
|
|
1968
2018
|
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1969
2019
|
|
|
1970
2020
|
await executeScaffoldManagedSkill(
|
|
@@ -1981,7 +2031,7 @@ describe("background skill update notification", () => {
|
|
|
1981
2031
|
lineage(),
|
|
1982
2032
|
);
|
|
1983
2033
|
|
|
1984
|
-
const body = String(
|
|
2034
|
+
const body = String(recordedUpdates[0]?.changeSummary);
|
|
1985
2035
|
// Control characters go, horizontal whitespace collapses, blank-line runs
|
|
1986
2036
|
// collapse to one paragraph break, and the whole thing is clamped to the
|
|
1987
2037
|
// shared notification preview budget.
|
|
@@ -2012,7 +2062,7 @@ describe("background skill update notification", () => {
|
|
|
2012
2062
|
|
|
2013
2063
|
expect(result.isError).toBe(true);
|
|
2014
2064
|
expect(result.content).toBe("Error: change_summary must be a string");
|
|
2015
|
-
expect(
|
|
2065
|
+
expect(recordedUpdates).toHaveLength(0);
|
|
2016
2066
|
});
|
|
2017
2067
|
|
|
2018
2068
|
test("the registered tool's schema declares change_summary as an optional string", () => {
|
|
@@ -2044,7 +2094,7 @@ describe("background skill update notification", () => {
|
|
|
2044
2094
|
expect(result.ok).toBe(true);
|
|
2045
2095
|
});
|
|
2046
2096
|
|
|
2047
|
-
test("
|
|
2097
|
+
test("an entry whose fork lineage does not resolve carries no source, never the run", async () => {
|
|
2048
2098
|
await seedAssistantSkill("weekly-export", "Old body.");
|
|
2049
2099
|
|
|
2050
2100
|
await executeScaffoldManagedSkill(
|
|
@@ -2061,10 +2111,10 @@ describe("background skill update notification", () => {
|
|
|
2061
2111
|
{ getConversation: () => null },
|
|
2062
2112
|
);
|
|
2063
2113
|
|
|
2064
|
-
//
|
|
2065
|
-
//
|
|
2066
|
-
expect(
|
|
2067
|
-
expect(
|
|
2114
|
+
// The run is an ephemeral fork; naming it as the source would have the
|
|
2115
|
+
// receipt job drop the entry once the fork is garbage collected.
|
|
2116
|
+
expect(recordedUpdates).toHaveLength(1);
|
|
2117
|
+
expect(recordedUpdates[0]).not.toHaveProperty("sourceConversationId");
|
|
2068
2118
|
});
|
|
2069
2119
|
|
|
2070
2120
|
test("a background CREATE gets the skill card instead, with no duplicate signal", async () => {
|
|
@@ -2082,10 +2132,10 @@ describe("background skill update notification", () => {
|
|
|
2082
2132
|
|
|
2083
2133
|
expect(result.isError).toBe(false);
|
|
2084
2134
|
expect(skillCardJobUpserts).toHaveLength(1);
|
|
2085
|
-
expect(
|
|
2135
|
+
expect(recordedUpdates).toHaveLength(0);
|
|
2086
2136
|
});
|
|
2087
2137
|
|
|
2088
|
-
test("a user-directed overwrite
|
|
2138
|
+
test("a user-directed overwrite records no receipt entry: it is not unattended work", async () => {
|
|
2089
2139
|
await executeScaffoldManagedSkill(
|
|
2090
2140
|
{
|
|
2091
2141
|
skill_id: "user-skill",
|
|
@@ -2096,7 +2146,7 @@ describe("background skill update notification", () => {
|
|
|
2096
2146
|
},
|
|
2097
2147
|
makeContext(),
|
|
2098
2148
|
);
|
|
2099
|
-
|
|
2149
|
+
recordedUpdates = [];
|
|
2100
2150
|
|
|
2101
2151
|
await executeScaffoldManagedSkill(
|
|
2102
2152
|
{
|
|
@@ -2113,6 +2163,6 @@ describe("background skill update notification", () => {
|
|
|
2113
2163
|
expect(
|
|
2114
2164
|
readFileSync(join(TEST_DIR, "skills", "user-skill", "SKILL.md"), "utf-8"),
|
|
2115
2165
|
).toContain("V2.");
|
|
2116
|
-
expect(
|
|
2166
|
+
expect(recordedUpdates).toHaveLength(0);
|
|
2117
2167
|
});
|
|
2118
2168
|
});
|
|
@@ -302,8 +302,10 @@ function makePendingApprovalConversation(
|
|
|
302
302
|
// Tests
|
|
303
303
|
// ---------------------------------------------------------------------------
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
// This suite mocks isHttpAuthDisabled() to true, so a request carrying no
|
|
306
|
+
// bearer authenticates through the dev bypass. A bearer that is present is
|
|
307
|
+
// verified against the signing key, which these tests do not set up.
|
|
308
|
+
const AUTH_HEADERS: Record<string, string> = {};
|
|
307
309
|
|
|
308
310
|
describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
309
311
|
let server: RuntimeHttpServer;
|
|
@@ -109,6 +109,41 @@ async function executeSkillLoad(
|
|
|
109
109
|
// ── Tests ─────────────────────────────────────────────────────────────────
|
|
110
110
|
|
|
111
111
|
describe("skill_load inline command expansion", () => {
|
|
112
|
+
test("read-only preloading returns instructions without running inline commands", async () => {
|
|
113
|
+
writeSkill(
|
|
114
|
+
"readonly-example",
|
|
115
|
+
"readonly-example",
|
|
116
|
+
"Example skill",
|
|
117
|
+
"Context: !`echo example`",
|
|
118
|
+
);
|
|
119
|
+
const result = await skillLoadTool.execute(
|
|
120
|
+
{ skill: "readonly-example" },
|
|
121
|
+
{
|
|
122
|
+
conversationId: "conversation-1",
|
|
123
|
+
workingDir: TEST_DIR,
|
|
124
|
+
trustClass: "guardian",
|
|
125
|
+
},
|
|
126
|
+
{ readOnly: true },
|
|
127
|
+
);
|
|
128
|
+
expect(result.isError).toBe(false);
|
|
129
|
+
expect(result.content).toContain("[inline command not executed]");
|
|
130
|
+
expect(runInlineCommandCalls).toHaveLength(0);
|
|
131
|
+
expect(mockAutoInstall).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("read-only preloading does not install missing skills", async () => {
|
|
135
|
+
const result = await skillLoadTool.execute(
|
|
136
|
+
{ skill: "missing-readonly-skill" },
|
|
137
|
+
{
|
|
138
|
+
conversationId: "conversation-1",
|
|
139
|
+
workingDir: TEST_DIR,
|
|
140
|
+
trustClass: "guardian",
|
|
141
|
+
},
|
|
142
|
+
{ readOnly: true },
|
|
143
|
+
);
|
|
144
|
+
expect(result.isError).toBe(true);
|
|
145
|
+
expect(mockAutoInstall).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
112
147
|
beforeEach(() => {
|
|
113
148
|
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
114
149
|
runInlineCommandCalls.length = 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, symlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
4
|
|
|
@@ -781,16 +781,48 @@ describe("skill_load tool", () => {
|
|
|
781
781
|
expect(result.content).toContain("Deploy the application to production");
|
|
782
782
|
expect(result.content).toContain("Rollback to previous version");
|
|
783
783
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
784
|
+
const schemas = [
|
|
785
|
+
...result.content.matchAll(/```json\n([\s\S]*?)\n```/g),
|
|
786
|
+
].map((match) => JSON.parse(match[1]));
|
|
787
|
+
expect(schemas[0].required).toEqual(["environment"]);
|
|
788
|
+
expect(schemas[0].properties.environment).toEqual({
|
|
789
|
+
type: "string",
|
|
790
|
+
description: "Target environment",
|
|
791
|
+
});
|
|
792
|
+
expect(schemas[0].properties.force.type).toBe("boolean");
|
|
793
|
+
expect(schemas[1].required).toEqual(["version"]);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("discloses the complete annotation mark schema before execution", async () => {
|
|
797
|
+
const manifest = JSON.parse(
|
|
798
|
+
readFileSync(
|
|
799
|
+
join(
|
|
800
|
+
import.meta.dir,
|
|
801
|
+
"../config/bundled-skills/screen-annotation/TOOLS.json",
|
|
802
|
+
),
|
|
803
|
+
"utf-8",
|
|
804
|
+
),
|
|
787
805
|
);
|
|
788
|
-
|
|
789
|
-
"-
|
|
806
|
+
writeSkill(
|
|
807
|
+
"annotation-schema",
|
|
808
|
+
"Annotation Schema",
|
|
809
|
+
"Point at a control",
|
|
810
|
+
"Name the control.",
|
|
790
811
|
);
|
|
791
|
-
|
|
792
|
-
|
|
812
|
+
writeToolsJson("annotation-schema", manifest.tools);
|
|
813
|
+
|
|
814
|
+
const result = await executeSkillLoad(
|
|
815
|
+
{ skill: "annotation-schema" },
|
|
816
|
+
"macos",
|
|
793
817
|
);
|
|
818
|
+
expect(result.isError).toBe(false);
|
|
819
|
+
const schemaMatch = result.content.match(/```json\n([\s\S]*?)\n```/);
|
|
820
|
+
expect(schemaMatch).not.toBeNull();
|
|
821
|
+
const schema = JSON.parse(schemaMatch![1]);
|
|
822
|
+
expect(schema).toEqual(manifest.tools[0].input_schema);
|
|
823
|
+
expect(schema.properties.marks.items.oneOf).toHaveLength(2);
|
|
824
|
+
expect(schema.properties.marks.items.properties.target.minLength).toBe(1);
|
|
825
|
+
expect(schema.properties.marks.items.properties.caption.maxLength).toBe(80);
|
|
794
826
|
});
|
|
795
827
|
|
|
796
828
|
test("skill without TOOLS.json does not include tool schemas section", async () => {
|
|
@@ -862,9 +894,8 @@ describe("skill_load tool", () => {
|
|
|
862
894
|
// The child skill's tool schemas should appear (#### level under ### Tools from …)
|
|
863
895
|
expect(result.content).toContain("#### child_action");
|
|
864
896
|
expect(result.content).toContain("A child tool action");
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
);
|
|
897
|
+
const schemaMatch = result.content.match(/```json\n([\s\S]*?)\n```/);
|
|
898
|
+
expect(JSON.parse(schemaMatch![1]).required).toEqual(["target"]);
|
|
868
899
|
expect(result.content).toContain(
|
|
869
900
|
"Use `skill_execute` to call these tools.",
|
|
870
901
|
);
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { getBundledSkillsDir, type SkillToolEntry } from "../config/skills.js";
|
|
8
8
|
import { RiskLevel } from "../permissions/types.js";
|
|
9
9
|
import { computeSkillVersionHash } from "../skills/version-hash.js";
|
|
10
10
|
import { resolveSubagentRole } from "../subagent/role-resolution.js";
|
|
@@ -261,6 +261,54 @@ describe("createSkillTool — unknown parameter validation", () => {
|
|
|
261
261
|
expect(result.content).toContain('Unknown parameter "bar"');
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
+
test.each([
|
|
265
|
+
{ description: "Filters" },
|
|
266
|
+
{ marks: [{ description: "Filters" }] },
|
|
267
|
+
{ marks: [{}] },
|
|
268
|
+
{ marks: [{ target: 42 }] },
|
|
269
|
+
{ marks: [{ x: 0.1, y: 0.2 }] },
|
|
270
|
+
{ marks: [{ target: "Filters", x: 0.1, y: 0.2, width: 0.3, height: 0.4 }] },
|
|
271
|
+
])(
|
|
272
|
+
"an annotation validation error provides enough schema for one corrected retry: %j",
|
|
273
|
+
async (invalid) => {
|
|
274
|
+
const skillDir = join(getBundledSkillsDir(), "screen-annotation");
|
|
275
|
+
const manifest = JSON.parse(
|
|
276
|
+
readFileSync(join(skillDir, "TOOLS.json"), "utf-8"),
|
|
277
|
+
);
|
|
278
|
+
const entry = manifest.tools.find(
|
|
279
|
+
(tool: SkillToolEntry) => tool.name === "screen_point_at",
|
|
280
|
+
);
|
|
281
|
+
const tool = createSkillTool(
|
|
282
|
+
entry,
|
|
283
|
+
skillDir,
|
|
284
|
+
computeSkillVersionHash(skillDir),
|
|
285
|
+
BUNDLED,
|
|
286
|
+
);
|
|
287
|
+
const calls: unknown[] = [];
|
|
288
|
+
const context = makeContext({
|
|
289
|
+
proxyToolResolver: async (name, input) => {
|
|
290
|
+
calls.push({ name, input });
|
|
291
|
+
return { content: "Drew 1 mark", isError: false };
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const rejected = await tool.execute(invalid, context);
|
|
296
|
+
expect(rejected.isError).toBe(true);
|
|
297
|
+
expect(calls).toHaveLength(0);
|
|
298
|
+
expect(rejected.content).toContain("skill_load");
|
|
299
|
+
const schemaMatch = rejected.content.match(/```json\n([\s\S]*?)\n```/);
|
|
300
|
+
expect(schemaMatch).not.toBeNull();
|
|
301
|
+
expect(JSON.parse(schemaMatch![1])).toEqual(entry.input_schema);
|
|
302
|
+
|
|
303
|
+
const input = {
|
|
304
|
+
marks: [{ target: "Filters", caption: "Click Filters" }],
|
|
305
|
+
};
|
|
306
|
+
const corrected = await tool.execute(input, context);
|
|
307
|
+
expect(corrected.isError).toBe(false);
|
|
308
|
+
expect(calls).toEqual([{ name: "computer_use_point_at", input }]);
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
264
312
|
test("allows input with only known parameters", async () => {
|
|
265
313
|
const hash = computeSkillVersionHash(tempDir);
|
|
266
314
|
const tool = createSkillTool(
|
|
@@ -471,7 +519,7 @@ describe("createSkillTool — required/type/enum validation", () => {
|
|
|
471
519
|
|
|
472
520
|
expect(result.isError).toBe(true);
|
|
473
521
|
expect(result.content).toContain(
|
|
474
|
-
"auto_open must be a boolean
|
|
522
|
+
"auto_open must be a boolean: pass true or false as a JSON boolean, not a string",
|
|
475
523
|
);
|
|
476
524
|
});
|
|
477
525
|
|