@vellumai/assistant 0.4.48 → 0.4.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -60,10 +60,16 @@ mock.module("../util/logger.js", () => ({
|
|
|
60
60
|
new Proxy({} as Record<string, unknown>, {
|
|
61
61
|
get: () => () => {},
|
|
62
62
|
}),
|
|
63
|
-
isDebug: () => false,
|
|
64
63
|
truncateForLog: (s: unknown) => String(s),
|
|
65
64
|
}));
|
|
66
65
|
|
|
66
|
+
// Mock autoInstallFromCatalog — default returns false (not found in catalog).
|
|
67
|
+
// Tests can override via `mockAutoInstall.mockImplementation(...)`.
|
|
68
|
+
const mockAutoInstall = mock((_skillId: string) => Promise.resolve(false));
|
|
69
|
+
mock.module("../skills/catalog-install.js", () => ({
|
|
70
|
+
autoInstallFromCatalog: (skillId: string) => mockAutoInstall(skillId),
|
|
71
|
+
}));
|
|
72
|
+
|
|
67
73
|
await import("../tools/skills/load.js");
|
|
68
74
|
const { getTool } = await import("../tools/registry.js");
|
|
69
75
|
|
|
@@ -145,6 +151,10 @@ async function executeSkillLoad(
|
|
|
145
151
|
describe("skill_load tool", () => {
|
|
146
152
|
beforeEach(() => {
|
|
147
153
|
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
154
|
+
mockAutoInstall.mockReset();
|
|
155
|
+
mockAutoInstall.mockImplementation((_skillId: string) =>
|
|
156
|
+
Promise.resolve(false),
|
|
157
|
+
);
|
|
148
158
|
});
|
|
149
159
|
|
|
150
160
|
afterEach(() => {
|
|
@@ -850,4 +860,142 @@ describe("skill_load tool", () => {
|
|
|
850
860
|
"Use `skill_execute` to call these tools.",
|
|
851
861
|
);
|
|
852
862
|
});
|
|
863
|
+
|
|
864
|
+
test("auto-installs missing includes from catalog", async () => {
|
|
865
|
+
// Parent includes "dep-a" which is not initially in the catalog
|
|
866
|
+
writeSkillWithIncludes(
|
|
867
|
+
"auto-parent",
|
|
868
|
+
"Auto Parent",
|
|
869
|
+
"Has auto-installable dep",
|
|
870
|
+
"Parent body",
|
|
871
|
+
["dep-a"],
|
|
872
|
+
);
|
|
873
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- auto-parent\n");
|
|
874
|
+
|
|
875
|
+
// Mock autoInstallFromCatalog to succeed and write the skill to disk
|
|
876
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
877
|
+
if (skillId === "dep-a") {
|
|
878
|
+
writeSkill("dep-a", "Dep A", "A dependency", "Dep A body");
|
|
879
|
+
// Add to SKILLS.md so catalog reload finds it
|
|
880
|
+
writeFileSync(
|
|
881
|
+
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
882
|
+
"- auto-parent\n- dep-a\n",
|
|
883
|
+
);
|
|
884
|
+
return Promise.resolve(true);
|
|
885
|
+
}
|
|
886
|
+
return Promise.resolve(false);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
const result = await executeSkillLoad({ skill: "auto-parent" });
|
|
890
|
+
expect(result.isError).toBe(false);
|
|
891
|
+
expect(result.content).toContain("Skill: Auto Parent");
|
|
892
|
+
expect(result.content).toContain("<loaded_skill");
|
|
893
|
+
expect(mockAutoInstall).toHaveBeenCalledWith("dep-a");
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test("auto-installs transitive missing includes across rounds", async () => {
|
|
897
|
+
// Skill A includes B, B includes C. Neither B nor C in initial catalog.
|
|
898
|
+
writeSkillWithIncludes("trans-a", "Trans A", "Top level", "Body A", [
|
|
899
|
+
"trans-b",
|
|
900
|
+
]);
|
|
901
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- trans-a\n");
|
|
902
|
+
|
|
903
|
+
let round = 0;
|
|
904
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
905
|
+
if (skillId === "trans-b" && round === 0) {
|
|
906
|
+
// First round: install B (which includes C)
|
|
907
|
+
writeSkillWithIncludes("trans-b", "Trans B", "Mid level", "Body B", [
|
|
908
|
+
"trans-c",
|
|
909
|
+
]);
|
|
910
|
+
writeFileSync(
|
|
911
|
+
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
912
|
+
"- trans-a\n- trans-b\n",
|
|
913
|
+
);
|
|
914
|
+
round++;
|
|
915
|
+
return Promise.resolve(true);
|
|
916
|
+
}
|
|
917
|
+
if (skillId === "trans-c") {
|
|
918
|
+
// Second round: install C
|
|
919
|
+
writeSkill("trans-c", "Trans C", "Leaf", "Body C");
|
|
920
|
+
writeFileSync(
|
|
921
|
+
join(TEST_DIR, "skills", "SKILLS.md"),
|
|
922
|
+
"- trans-a\n- trans-b\n- trans-c\n",
|
|
923
|
+
);
|
|
924
|
+
return Promise.resolve(true);
|
|
925
|
+
}
|
|
926
|
+
return Promise.resolve(false);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const result = await executeSkillLoad({ skill: "trans-a" });
|
|
930
|
+
expect(result.isError).toBe(false);
|
|
931
|
+
expect(result.content).toContain("Skill: Trans A");
|
|
932
|
+
expect(result.content).toContain("<loaded_skill");
|
|
933
|
+
expect(mockAutoInstall).toHaveBeenCalledWith("trans-b");
|
|
934
|
+
expect(mockAutoInstall).toHaveBeenCalledWith("trans-c");
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test("returns error when auto-install of missing include fails", async () => {
|
|
938
|
+
writeSkillWithIncludes(
|
|
939
|
+
"fail-parent",
|
|
940
|
+
"Fail Parent",
|
|
941
|
+
"Has failing dep",
|
|
942
|
+
"Body",
|
|
943
|
+
["dep-x"],
|
|
944
|
+
);
|
|
945
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- fail-parent\n");
|
|
946
|
+
|
|
947
|
+
// autoInstallFromCatalog throws an error
|
|
948
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
949
|
+
if (skillId === "dep-x") {
|
|
950
|
+
return Promise.reject(new Error("Network error"));
|
|
951
|
+
}
|
|
952
|
+
return Promise.resolve(false);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
const result = await executeSkillLoad({ skill: "fail-parent" });
|
|
956
|
+
expect(result.isError).toBe(true);
|
|
957
|
+
expect(result.content).toContain("dep-x");
|
|
958
|
+
expect(result.content).toContain("not found");
|
|
959
|
+
expect(result.content).not.toContain("<loaded_skill");
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
test("stops after MAX_INSTALL_ROUNDS", async () => {
|
|
963
|
+
// Pathological case: each install round reveals a new missing dep
|
|
964
|
+
writeSkillWithIncludes("loop-root", "Loop Root", "Infinite deps", "Body", [
|
|
965
|
+
"loop-dep-0",
|
|
966
|
+
]);
|
|
967
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- loop-root\n");
|
|
968
|
+
|
|
969
|
+
let installCount = 0;
|
|
970
|
+
mockAutoInstall.mockImplementation((skillId: string) => {
|
|
971
|
+
const id = skillId;
|
|
972
|
+
if (id.startsWith("loop-dep-")) {
|
|
973
|
+
installCount++;
|
|
974
|
+
const nextDepId = `loop-dep-${installCount}`;
|
|
975
|
+
// Install the requested dep, but it includes yet another missing dep
|
|
976
|
+
writeSkillWithIncludes(
|
|
977
|
+
id,
|
|
978
|
+
`Loop Dep ${installCount}`,
|
|
979
|
+
"Generated dep",
|
|
980
|
+
"Body",
|
|
981
|
+
[nextDepId],
|
|
982
|
+
);
|
|
983
|
+
// Update SKILLS.md to include all installed deps so far
|
|
984
|
+
const entries = ["- loop-root\n"];
|
|
985
|
+
for (let i = 0; i < installCount; i++) {
|
|
986
|
+
entries.push(`- loop-dep-${i}\n`);
|
|
987
|
+
}
|
|
988
|
+
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), entries.join(""));
|
|
989
|
+
return Promise.resolve(true);
|
|
990
|
+
}
|
|
991
|
+
return Promise.resolve(false);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
const result = await executeSkillLoad({ skill: "loop-root" });
|
|
995
|
+
// Should terminate with an error (the final dep is still missing)
|
|
996
|
+
expect(result.isError).toBe(true);
|
|
997
|
+
expect(result.content).toContain("not found");
|
|
998
|
+
// Should have terminated — installCount should be bounded by MAX_INSTALL_ROUNDS (5)
|
|
999
|
+
expect(installCount).toBeLessThanOrEqual(5);
|
|
1000
|
+
});
|
|
853
1001
|
});
|
|
@@ -9,7 +9,7 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
11
|
|
|
12
|
-
import { uninstallSkillLocally } from "../
|
|
12
|
+
import { uninstallSkillLocally } from "../skills/catalog-install.js";
|
|
13
13
|
|
|
14
14
|
let tempDir: string;
|
|
15
15
|
let originalBaseDataDir: string | undefined;
|
|
@@ -60,7 +60,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
60
60
|
...realLogger,
|
|
61
61
|
getLogger: () => noopLogger,
|
|
62
62
|
getCliLogger: () => noopLogger,
|
|
63
|
-
isDebug: () => false,
|
|
64
63
|
truncateForLog: (v: string) => v,
|
|
65
64
|
initLogger: () => {},
|
|
66
65
|
pruneOldLogFiles: () => 0,
|
|
@@ -719,15 +718,16 @@ describe("bundled computer-use skill", () => {
|
|
|
719
718
|
expect(cuSkill!.disableModelInvocation).toBe(true);
|
|
720
719
|
});
|
|
721
720
|
|
|
722
|
-
test("computer-use skill has a valid tool manifest with
|
|
721
|
+
test("computer-use skill has a valid tool manifest with 11 tools", () => {
|
|
723
722
|
const catalog = loadSkillCatalog();
|
|
724
723
|
const cuSkill = catalog.find((s) => s.id === "computer-use");
|
|
725
724
|
expect(cuSkill).toBeDefined();
|
|
726
725
|
expect(cuSkill!.toolManifest).toBeDefined();
|
|
727
726
|
expect(cuSkill!.toolManifest!.present).toBe(true);
|
|
728
727
|
expect(cuSkill!.toolManifest!.valid).toBe(true);
|
|
729
|
-
expect(cuSkill!.toolManifest!.toolCount).toBe(
|
|
728
|
+
expect(cuSkill!.toolManifest!.toolCount).toBe(11);
|
|
730
729
|
expect(cuSkill!.toolManifest!.toolNames).toEqual([
|
|
730
|
+
"computer_use_observe",
|
|
731
731
|
"computer_use_click",
|
|
732
732
|
"computer_use_type_text",
|
|
733
733
|
"computer_use_key",
|
|
@@ -75,13 +75,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
75
75
|
debug: () => {},
|
|
76
76
|
trace: () => {},
|
|
77
77
|
fatal: () => {},
|
|
78
|
-
isDebug: () => false,
|
|
79
78
|
child: () => ({
|
|
80
79
|
info: () => {},
|
|
81
80
|
warn: () => {},
|
|
82
81
|
error: () => {},
|
|
83
82
|
debug: () => {},
|
|
84
|
-
isDebug: () => false,
|
|
85
83
|
}),
|
|
86
84
|
}),
|
|
87
85
|
}));
|
|
@@ -116,6 +114,46 @@ mock.module("../security/secure-keys.js", () => {
|
|
|
116
114
|
};
|
|
117
115
|
});
|
|
118
116
|
|
|
117
|
+
// Mock oauth-store (getConnectionByProvider)
|
|
118
|
+
let oauthConnectionStore: Record<
|
|
119
|
+
string,
|
|
120
|
+
{ id: string; status: string; accountInfo?: string | null }
|
|
121
|
+
> = {};
|
|
122
|
+
|
|
123
|
+
mock.module("../oauth/oauth-store.js", () => ({
|
|
124
|
+
getConnectionByProvider: (providerKey: string) =>
|
|
125
|
+
oauthConnectionStore[providerKey] ?? undefined,
|
|
126
|
+
createConnection: () => ({ id: "test-conn-id" }),
|
|
127
|
+
updateConnection: () => true,
|
|
128
|
+
deleteConnection: (id: string) => {
|
|
129
|
+
for (const [key, conn] of Object.entries(oauthConnectionStore)) {
|
|
130
|
+
if (conn.id === id) {
|
|
131
|
+
delete oauthConnectionStore[key];
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
},
|
|
137
|
+
upsertApp: async () => ({ id: "test-app-id" }),
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
// Mock manual-token-connection
|
|
141
|
+
mock.module("../oauth/manual-token-connection.js", () => ({
|
|
142
|
+
ensureManualTokenConnection: async (
|
|
143
|
+
providerKey: string,
|
|
144
|
+
accountInfo?: string,
|
|
145
|
+
) => {
|
|
146
|
+
oauthConnectionStore[providerKey] = {
|
|
147
|
+
id: `conn-${providerKey}`,
|
|
148
|
+
status: "active",
|
|
149
|
+
accountInfo: accountInfo ?? null,
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
removeManualTokenConnection: (providerKey: string) => {
|
|
153
|
+
delete oauthConnectionStore[providerKey];
|
|
154
|
+
},
|
|
155
|
+
}));
|
|
156
|
+
|
|
119
157
|
// Mock credential metadata store
|
|
120
158
|
let credentialMetadataStore: Array<{
|
|
121
159
|
service: string;
|
|
@@ -187,6 +225,7 @@ describe("Slack channel config handler", () => {
|
|
|
187
225
|
beforeEach(() => {
|
|
188
226
|
secureKeyStore = {};
|
|
189
227
|
credentialMetadataStore = [];
|
|
228
|
+
oauthConnectionStore = {};
|
|
190
229
|
configStore = {};
|
|
191
230
|
globalThis.fetch = originalFetch;
|
|
192
231
|
});
|
|
@@ -199,7 +238,11 @@ describe("Slack channel config handler", () => {
|
|
|
199
238
|
expect(result.connected).toBe(false);
|
|
200
239
|
});
|
|
201
240
|
|
|
202
|
-
test("GET returns connected: true when both
|
|
241
|
+
test("GET returns connected: true when oauth_connection is active and both keys exist", () => {
|
|
242
|
+
oauthConnectionStore["slack_channel"] = {
|
|
243
|
+
id: "conn-slack",
|
|
244
|
+
status: "active",
|
|
245
|
+
};
|
|
203
246
|
secureKeyStore[credentialKey("slack_channel", "bot_token")] = "xoxb-test";
|
|
204
247
|
secureKeyStore[credentialKey("slack_channel", "app_token")] = "xapp-test";
|
|
205
248
|
|
|
@@ -210,8 +253,29 @@ describe("Slack channel config handler", () => {
|
|
|
210
253
|
expect(result.connected).toBe(true);
|
|
211
254
|
});
|
|
212
255
|
|
|
256
|
+
test("GET reports per-field token presence independently of connection row", () => {
|
|
257
|
+
// Only bot_token in keychain, no app_token, but connection row exists
|
|
258
|
+
oauthConnectionStore["slack_channel"] = {
|
|
259
|
+
id: "conn-slack",
|
|
260
|
+
status: "active",
|
|
261
|
+
};
|
|
262
|
+
secureKeyStore[credentialKey("slack_channel", "bot_token")] = "xoxb-test";
|
|
263
|
+
|
|
264
|
+
const result = getSlackChannelConfig();
|
|
265
|
+
expect(result.success).toBe(true);
|
|
266
|
+
expect(result.hasBotToken).toBe(true);
|
|
267
|
+
expect(result.hasAppToken).toBe(false);
|
|
268
|
+
// connected requires both keys AND connection row
|
|
269
|
+
expect(result.connected).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
|
|
213
272
|
test("GET returns metadata from config when available", () => {
|
|
273
|
+
oauthConnectionStore["slack_channel"] = {
|
|
274
|
+
id: "conn-slack",
|
|
275
|
+
status: "active",
|
|
276
|
+
};
|
|
214
277
|
secureKeyStore[credentialKey("slack_channel", "bot_token")] = "xoxb-test";
|
|
278
|
+
secureKeyStore[credentialKey("slack_channel", "app_token")] = "xapp-test";
|
|
215
279
|
configStore = {
|
|
216
280
|
slack: {
|
|
217
281
|
teamId: "T123",
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import { credentialKey } from "../security/credential-key.js";
|
|
4
|
-
|
|
5
3
|
// ---------------------------------------------------------------------------
|
|
6
4
|
// Mocks — must be declared before any imports that pull in mocked modules
|
|
7
5
|
// ---------------------------------------------------------------------------
|
|
@@ -12,6 +10,12 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
12
10
|
setSecureKeyAsync: async () => {},
|
|
13
11
|
}));
|
|
14
12
|
|
|
13
|
+
let connectionByProvider: Record<string, unknown> = {};
|
|
14
|
+
mock.module("../oauth/oauth-store.js", () => ({
|
|
15
|
+
getConnectionByProvider: (key: string) =>
|
|
16
|
+
connectionByProvider[key] ?? undefined,
|
|
17
|
+
}));
|
|
18
|
+
|
|
15
19
|
let listConversationsResult: unknown = { ok: true, channels: [] };
|
|
16
20
|
let postMessageResult: unknown = {
|
|
17
21
|
ok: true,
|
|
@@ -86,6 +90,7 @@ function makeRequest(body: unknown): Request {
|
|
|
86
90
|
|
|
87
91
|
beforeEach(() => {
|
|
88
92
|
secureKeyValues.clear();
|
|
93
|
+
connectionByProvider = {};
|
|
89
94
|
listConversationsResult = { ok: true, channels: [] };
|
|
90
95
|
userInfoResults = new Map();
|
|
91
96
|
appStoreResult = null;
|
|
@@ -106,8 +111,9 @@ describe("handleListSlackChannels", () => {
|
|
|
106
111
|
});
|
|
107
112
|
|
|
108
113
|
test("returns channels sorted by type then name", async () => {
|
|
114
|
+
connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
|
|
109
115
|
secureKeyValues.set(
|
|
110
|
-
|
|
116
|
+
"oauth_connection/conn-slack-1/access_token",
|
|
111
117
|
"xoxb-test",
|
|
112
118
|
);
|
|
113
119
|
|
|
@@ -176,18 +182,6 @@ describe("handleListSlackChannels", () => {
|
|
|
176
182
|
isPrivate: true,
|
|
177
183
|
});
|
|
178
184
|
});
|
|
179
|
-
|
|
180
|
-
test("falls back to legacy bot token", async () => {
|
|
181
|
-
secureKeyValues.set(
|
|
182
|
-
credentialKey("slack_channel", "bot_token"),
|
|
183
|
-
"xoxb-legacy",
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
listConversationsResult = { ok: true, channels: [] };
|
|
187
|
-
|
|
188
|
-
const res = await handleListSlackChannels();
|
|
189
|
-
expect(res.status).toBe(200);
|
|
190
|
-
});
|
|
191
185
|
});
|
|
192
186
|
|
|
193
187
|
describe("handleShareToSlackChannel", () => {
|
|
@@ -198,8 +192,9 @@ describe("handleShareToSlackChannel", () => {
|
|
|
198
192
|
});
|
|
199
193
|
|
|
200
194
|
test("returns 400 for malformed JSON", async () => {
|
|
195
|
+
connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
|
|
201
196
|
secureKeyValues.set(
|
|
202
|
-
|
|
197
|
+
"oauth_connection/conn-slack-1/access_token",
|
|
203
198
|
"xoxb-test",
|
|
204
199
|
);
|
|
205
200
|
const req = new Request("http://localhost/v1/slack/share", {
|
|
@@ -212,8 +207,9 @@ describe("handleShareToSlackChannel", () => {
|
|
|
212
207
|
});
|
|
213
208
|
|
|
214
209
|
test("returns 400 when missing required fields", async () => {
|
|
210
|
+
connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
|
|
215
211
|
secureKeyValues.set(
|
|
216
|
-
|
|
212
|
+
"oauth_connection/conn-slack-1/access_token",
|
|
217
213
|
"xoxb-test",
|
|
218
214
|
);
|
|
219
215
|
const req = makeRequest({ appId: "app1" });
|
|
@@ -224,8 +220,9 @@ describe("handleShareToSlackChannel", () => {
|
|
|
224
220
|
});
|
|
225
221
|
|
|
226
222
|
test("returns 404 when app not found", async () => {
|
|
223
|
+
connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
|
|
227
224
|
secureKeyValues.set(
|
|
228
|
-
|
|
225
|
+
"oauth_connection/conn-slack-1/access_token",
|
|
229
226
|
"xoxb-test",
|
|
230
227
|
);
|
|
231
228
|
appStoreResult = null;
|
|
@@ -235,8 +232,9 @@ describe("handleShareToSlackChannel", () => {
|
|
|
235
232
|
});
|
|
236
233
|
|
|
237
234
|
test("posts message and returns success", async () => {
|
|
235
|
+
connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
|
|
238
236
|
secureKeyValues.set(
|
|
239
|
-
|
|
237
|
+
"oauth_connection/conn-slack-1/access_token",
|
|
240
238
|
"xoxb-test",
|
|
241
239
|
);
|
|
242
240
|
appStoreResult = {
|
|
@@ -7,44 +7,42 @@
|
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
8
|
|
|
9
9
|
import type { ChannelId } from "../channels/types.js";
|
|
10
|
-
import { telegramInviteAdapter } from "../runtime/channel-invite-transports/telegram.js";
|
|
11
10
|
|
|
12
11
|
// Mock credential metadata so tests don't depend on local persisted state.
|
|
13
12
|
mock.module("../tools/credentials/metadata-store.js", () => ({
|
|
14
13
|
getCredentialMetadata: () => undefined,
|
|
15
14
|
}));
|
|
16
15
|
|
|
16
|
+
// Mock getTelegramBotUsername — the env var fallback was removed so we
|
|
17
|
+
// control the return value directly via a mutable variable.
|
|
18
|
+
let mockBotUsername: string | undefined;
|
|
19
|
+
mock.module("../telegram/bot-username.js", () => ({
|
|
20
|
+
getTelegramBotUsername: () => mockBotUsername,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
import { telegramInviteAdapter } from "../runtime/channel-invite-transports/telegram.js";
|
|
24
|
+
|
|
17
25
|
// ---------------------------------------------------------------------------
|
|
18
26
|
// Helpers
|
|
19
27
|
// ---------------------------------------------------------------------------
|
|
20
28
|
|
|
21
29
|
const CHANNEL: ChannelId = "telegram" as ChannelId;
|
|
22
30
|
|
|
23
|
-
function setEnv(key: string, value: string | undefined) {
|
|
24
|
-
if (value === undefined) {
|
|
25
|
-
delete process.env[key];
|
|
26
|
-
} else {
|
|
27
|
-
process.env[key] = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
// buildShareLink
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
35
|
describe("telegramInviteAdapter.buildShareLink", () => {
|
|
36
|
-
let originalBotUsername: string | undefined;
|
|
37
|
-
|
|
38
36
|
beforeEach(() => {
|
|
39
|
-
|
|
37
|
+
mockBotUsername = undefined;
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
afterEach(() => {
|
|
43
|
-
|
|
41
|
+
mockBotUsername = undefined;
|
|
44
42
|
});
|
|
45
43
|
|
|
46
44
|
test("builds a deep link with iv_ prefix", () => {
|
|
47
|
-
|
|
45
|
+
mockBotUsername = "TestBot";
|
|
48
46
|
|
|
49
47
|
const link = telegramInviteAdapter.buildShareLink!({
|
|
50
48
|
rawToken: "abc123",
|
|
@@ -56,7 +54,7 @@ describe("telegramInviteAdapter.buildShareLink", () => {
|
|
|
56
54
|
});
|
|
57
55
|
|
|
58
56
|
test("throws when bot username is not configured", () => {
|
|
59
|
-
|
|
57
|
+
mockBotUsername = undefined;
|
|
60
58
|
|
|
61
59
|
expect(() =>
|
|
62
60
|
telegramInviteAdapter.buildShareLink!({
|
|
@@ -138,25 +136,23 @@ describe("telegramInviteAdapter.extractInboundToken", () => {
|
|
|
138
136
|
// ---------------------------------------------------------------------------
|
|
139
137
|
|
|
140
138
|
describe("telegramInviteAdapter.resolveChannelHandle", () => {
|
|
141
|
-
let originalBotUsername: string | undefined;
|
|
142
|
-
|
|
143
139
|
beforeEach(() => {
|
|
144
|
-
|
|
140
|
+
mockBotUsername = undefined;
|
|
145
141
|
});
|
|
146
142
|
|
|
147
143
|
afterEach(() => {
|
|
148
|
-
|
|
144
|
+
mockBotUsername = undefined;
|
|
149
145
|
});
|
|
150
146
|
|
|
151
|
-
test("returns @-prefixed bot username from
|
|
152
|
-
|
|
147
|
+
test("returns @-prefixed bot username from config", () => {
|
|
148
|
+
mockBotUsername = "MyBot";
|
|
153
149
|
|
|
154
150
|
const handle = telegramInviteAdapter.resolveChannelHandle!();
|
|
155
151
|
expect(handle).toBe("@MyBot");
|
|
156
152
|
});
|
|
157
153
|
|
|
158
154
|
test("returns undefined when bot username is not configured", () => {
|
|
159
|
-
|
|
155
|
+
mockBotUsername = undefined;
|
|
160
156
|
|
|
161
157
|
const handle = telegramInviteAdapter.resolveChannelHandle!();
|
|
162
158
|
expect(handle).toBeUndefined();
|
|
@@ -457,10 +457,10 @@ describe("buildSanitizedEnv", () => {
|
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
test("injects INTERNAL_GATEWAY_BASE_URL from gateway config", () => {
|
|
460
|
-
process.env.
|
|
460
|
+
process.env.GATEWAY_PORT = "9000";
|
|
461
461
|
const env = buildSanitizedEnv();
|
|
462
|
-
expect(env.INTERNAL_GATEWAY_BASE_URL).toBe("http://
|
|
463
|
-
delete process.env.
|
|
462
|
+
expect(env.INTERNAL_GATEWAY_BASE_URL).toBe("http://127.0.0.1:9000");
|
|
463
|
+
delete process.env.GATEWAY_PORT;
|
|
464
464
|
});
|
|
465
465
|
|
|
466
466
|
test("result is a plain object with no prototype-inherited secrets", () => {
|
|
@@ -485,6 +485,7 @@ describe("buildSanitizedEnv", () => {
|
|
|
485
485
|
"SSH_AGENT_PID",
|
|
486
486
|
"GPG_TTY",
|
|
487
487
|
"GNUPGHOME",
|
|
488
|
+
"VELLUM_DEV",
|
|
488
489
|
"INTERNAL_GATEWAY_BASE_URL",
|
|
489
490
|
"VELLUM_DATA_DIR",
|
|
490
491
|
];
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Reusable constants and helpers for the computer-use skill migration test suite.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
/** The
|
|
5
|
+
/** The 11 computer_use_* action tool names provided by the bundled computer-use skill. */
|
|
6
6
|
export const COMPUTER_USE_TOOL_NAMES = [
|
|
7
|
+
"computer_use_observe",
|
|
7
8
|
"computer_use_click",
|
|
8
9
|
"computer_use_type_text",
|
|
9
10
|
"computer_use_key",
|
|
@@ -20,7 +21,7 @@ export const COMPUTER_USE_TOOL_NAMES = [
|
|
|
20
21
|
export const COMPUTER_USE_SKILL_ID = "computer-use";
|
|
21
22
|
|
|
22
23
|
/** Number of computer_use_* tools. */
|
|
23
|
-
export const COMPUTER_USE_TOOL_COUNT = COMPUTER_USE_TOOL_NAMES.length; //
|
|
24
|
+
export const COMPUTER_USE_TOOL_COUNT = COMPUTER_USE_TOOL_NAMES.length; // 11
|
|
24
25
|
|
|
25
26
|
import { expect } from "bun:test";
|
|
26
27
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { patternMatchesCandidate } from "../permissions/trust-store.js";
|
|
4
|
+
|
|
5
|
+
describe("patternMatchesCandidate", () => {
|
|
6
|
+
test("exact match", () => {
|
|
7
|
+
expect(patternMatchesCandidate("bash:git commit", "bash:git commit")).toBe(
|
|
8
|
+
true,
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("glob match", () => {
|
|
13
|
+
expect(patternMatchesCandidate("bash:git *", "bash:git commit")).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("no match", () => {
|
|
17
|
+
expect(patternMatchesCandidate("bash:git *", "file_write:/foo")).toBe(
|
|
18
|
+
false,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("globstar matches anything", () => {
|
|
23
|
+
expect(patternMatchesCandidate("**", "bash:anything")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("invalid pattern returns false", () => {
|
|
27
|
+
expect(patternMatchesCandidate("[", "bash:anything")).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|