@vellumai/assistant 0.12.1-dev.202609141913.5e16607 → 0.12.1-dev.202609142113.d1590bc
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 +9 -9
- package/openapi.yaml +35 -2
- package/package.json +2 -1
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -2
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/host-cu-proxy.test.ts +539 -0
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/oauth-commands-routes.test.ts +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/acp/agent-process.ts +2 -1
- package/src/agent/loop.ts +1 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +4 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-runtime-assembly.ts +2 -1
- package/src/daemon/conversation-surfaces.ts +5 -1
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-cu-proxy.ts +186 -19
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/byo-connection.test.ts +60 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +39 -25
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +2 -1
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/startup.ts +22 -1
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +39 -3
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/chat-completions-provider.ts +20 -1
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +24 -3
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +1 -1
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/diagnostics-routes.ts +6 -1
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/oauth-commands-routes.ts +36 -0
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/security/untrusted-content.ts +3 -1
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/computer-use/definitions.ts +117 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-service.ts +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -1406,15 +1406,15 @@ skills/<skill-id>/
|
|
|
1406
1406
|
|
|
1407
1407
|
The following capabilities ship as bundled skills in `assistant/src/config/bundled-skills/`:
|
|
1408
1408
|
|
|
1409
|
-
| Skill ID | Tools
|
|
1410
|
-
| --------------- |
|
|
1411
|
-
| `browser` | `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_close`, `browser_click`, `browser_type`, `browser_press_key`, `browser_wait_for`, `browser_extract`, `browser_fill_credential`
|
|
1412
|
-
| `gmail` | Gmail search, archive, send, etc.
|
|
1413
|
-
| `computer-use` | `computer_use_observe`, `computer_use_click`, `computer_use_type_text`, `computer_use_key`, `computer_use_scroll`, `computer_use_drag`, `computer_use_wait`, `computer_use_open_app`, `computer_use_run_applescript`, `computer_use_done`, `computer_use_respond` | Computer-use proxy tools preactivated via `preactivatedSkillIds` in desktop sessions. Each tool forwards actions to a compatible connected desktop host via `HostCuProxy`, which handles request/resolve proxying, step counting, loop detection, and observation formatting in the same agent loop. |
|
|
1414
|
-
| `weather` | `get-weather`
|
|
1415
|
-
| `app-builder` | `app_create`, `app_delete`, `app_refresh`, `app_generate_icon`
|
|
1416
|
-
| `self-upgrade` | (instruction-only)
|
|
1417
|
-
| `start-the-day` | (instruction-only)
|
|
1409
|
+
| Skill ID | Tools | Purpose |
|
|
1410
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1411
|
+
| `browser` | `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_close`, `browser_click`, `browser_type`, `browser_press_key`, `browser_wait_for`, `browser_extract`, `browser_fill_credential` | Headless browser automation: web scraping, form filling, interaction (previously core-registered as `headless-browser`; now skill-provided with default allow rules) |
|
|
1412
|
+
| `gmail` | Gmail search, archive, send, etc. | Email management via OAuth2 integration |
|
|
1413
|
+
| `computer-use` | `computer_use_observe`, `computer_use_click`, `computer_use_type_text`, `computer_use_key`, `computer_use_scroll`, `computer_use_drag`, `computer_use_wait`, `computer_use_open_app`, `computer_use_run_applescript`, `computer_use_sequence`, `computer_use_done`, `computer_use_respond` | Computer-use proxy tools preactivated via `preactivatedSkillIds` in desktop sessions. Each tool forwards actions to a compatible connected desktop host via `HostCuProxy`, which handles request/resolve proxying, step counting, loop detection, and observation formatting in the same agent loop. |
|
|
1414
|
+
| `weather` | `get-weather` | Fetch current weather data |
|
|
1415
|
+
| `app-builder` | `app_create`, `app_delete`, `app_refresh`, `app_generate_icon` | Dynamic app authoring: create and manage persistent apps; file editing uses generic file tools plus `app_refresh` (activated via `skill_load app-builder`; `app_open` remains a core proxy tool) |
|
|
1416
|
+
| `self-upgrade` | (instruction-only) | Self-improvement workflow |
|
|
1417
|
+
| `start-the-day` | (instruction-only) | Morning briefing routine |
|
|
1418
1418
|
|
|
1419
1419
|
### Activation and Projection Flow
|
|
1420
1420
|
|
package/openapi.yaml
CHANGED
|
@@ -15981,6 +15981,13 @@ paths:
|
|
|
15981
15981
|
type: string
|
|
15982
15982
|
userGuidance:
|
|
15983
15983
|
type: string
|
|
15984
|
+
timings:
|
|
15985
|
+
type: object
|
|
15986
|
+
propertyNames:
|
|
15987
|
+
type: string
|
|
15988
|
+
additionalProperties:
|
|
15989
|
+
type: number
|
|
15990
|
+
description: Per-phase helper timings in milliseconds
|
|
15984
15991
|
required:
|
|
15985
15992
|
- requestId
|
|
15986
15993
|
responses:
|
|
@@ -22068,6 +22075,9 @@ paths:
|
|
|
22068
22075
|
type: boolean
|
|
22069
22076
|
noResponse:
|
|
22070
22077
|
type: boolean
|
|
22078
|
+
cameraFrame:
|
|
22079
|
+
type: boolean
|
|
22080
|
+
const: true
|
|
22071
22081
|
assistantTextVisibility:
|
|
22072
22082
|
type: string
|
|
22073
22083
|
enum:
|
|
@@ -37047,8 +37057,18 @@ paths:
|
|
|
37047
37057
|
schema:
|
|
37048
37058
|
type: string
|
|
37049
37059
|
description:
|
|
37050
|
-
"Compute recursive byte size for each directory entry (true/false). Budget-bounded
|
|
37051
|
-
return size: null."
|
|
37060
|
+
"Compute recursive byte size for each directory entry (true/false). Budget-bounded: large subtrees may
|
|
37061
|
+
return size: null. Not applied to a recursive listing, which carries every file for the client to sum."
|
|
37062
|
+
- name: recursive
|
|
37063
|
+
in: query
|
|
37064
|
+
required: false
|
|
37065
|
+
schema:
|
|
37066
|
+
type: string
|
|
37067
|
+
description:
|
|
37068
|
+
"List the whole subtree in one response (true/false). Depth-first, each directory's entries in listing
|
|
37069
|
+
order, so grouping by parent path recovers per-directory listings. Bounded: sets truncated when the entry
|
|
37070
|
+
cap or deadline stops the walk, and does not enter directories the workspace gitignore rules exclude, which
|
|
37071
|
+
are named in skipped."
|
|
37052
37072
|
responses:
|
|
37053
37073
|
"200":
|
|
37054
37074
|
description: Successful response
|
|
@@ -37092,6 +37112,19 @@ paths:
|
|
|
37092
37112
|
- modifiedAt
|
|
37093
37113
|
additionalProperties: false
|
|
37094
37114
|
description: Directory entry objects
|
|
37115
|
+
truncated:
|
|
37116
|
+
description:
|
|
37117
|
+
A recursive walk stopped at its entry cap or deadline, so deeper entries are missing. Absent from
|
|
37118
|
+
assistants that predate recursive listings.
|
|
37119
|
+
type: boolean
|
|
37120
|
+
skipped:
|
|
37121
|
+
description:
|
|
37122
|
+
Directories a recursive walk listed but did not enter, whether because a workspace gitignore rule excludes
|
|
37123
|
+
them, they are symlinks, or they could not be read. Each can still be listed on its own. Absent
|
|
37124
|
+
from assistants that predate recursive listings.
|
|
37125
|
+
type: array
|
|
37126
|
+
items:
|
|
37127
|
+
type: string
|
|
37095
37128
|
required:
|
|
37096
37129
|
- path
|
|
37097
37130
|
- entries
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/assistant",
|
|
3
|
-
"version": "0.12.1-dev.
|
|
3
|
+
"version": "0.12.1-dev.202609142113.d1590bc",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"dotenv": "17.3.1",
|
|
63
63
|
"drizzle-orm": "0.45.2",
|
|
64
64
|
"hast-util-to-html": "9.0.5",
|
|
65
|
+
"ignore": "7.0.9",
|
|
65
66
|
"jszip": "3.10.1",
|
|
66
67
|
"marked": "18.0.0",
|
|
67
68
|
"mdast-util-to-hast": "13.2.1",
|
|
@@ -3029,69 +3029,6 @@ describe("AnthropicProvider - internal attachment id", () => {
|
|
|
3029
3029
|
});
|
|
3030
3030
|
});
|
|
3031
3031
|
|
|
3032
|
-
// ---------------------------------------------------------------------------
|
|
3033
|
-
// Tests — Orphaned UTF-16 surrogate sanitization
|
|
3034
|
-
// ---------------------------------------------------------------------------
|
|
3035
|
-
|
|
3036
|
-
describe("AnthropicProvider — surrogate sanitization", () => {
|
|
3037
|
-
let provider: AnthropicProvider;
|
|
3038
|
-
|
|
3039
|
-
beforeEach(() => {
|
|
3040
|
-
lastStreamParams = null;
|
|
3041
|
-
provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
|
|
3042
|
-
});
|
|
3043
|
-
|
|
3044
|
-
test("strips orphaned high surrogate from a tool result before sending", async () => {
|
|
3045
|
-
// An orphaned high surrogate — the exact shape that triggers Anthropic's
|
|
3046
|
-
// "no low surrogate in string" 400. The mock's JSON.parse(JSON.stringify)
|
|
3047
|
-
// on line ~44 would throw if sanitization didn't happen.
|
|
3048
|
-
const LONE_HIGH = "\uD83C";
|
|
3049
|
-
const messages: Message[] = [
|
|
3050
|
-
toolUseMsg("tu1", "bash"),
|
|
3051
|
-
toolResultMsg("tu1", `shell output ${LONE_HIGH} more output`),
|
|
3052
|
-
userMsg("what happened?"),
|
|
3053
|
-
];
|
|
3054
|
-
|
|
3055
|
-
await provider.sendMessage(messages);
|
|
3056
|
-
|
|
3057
|
-
const sent = lastStreamParams!.messages as Array<{
|
|
3058
|
-
role: string;
|
|
3059
|
-
content: Array<{ type: string; content?: string; text?: string }>;
|
|
3060
|
-
}>;
|
|
3061
|
-
// Find the tool_result block in the captured payload and assert no orphans.
|
|
3062
|
-
const toolResult = sent
|
|
3063
|
-
.flatMap((m) => m.content)
|
|
3064
|
-
.find((b) => b.type === "tool_result");
|
|
3065
|
-
expect(toolResult).toBeDefined();
|
|
3066
|
-
expect(toolResult!.content).toBeDefined();
|
|
3067
|
-
const content = toolResult!.content as string;
|
|
3068
|
-
for (let i = 0; i < content.length; i++) {
|
|
3069
|
-
const code = content.charCodeAt(i);
|
|
3070
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
3071
|
-
const next = i + 1 < content.length ? content.charCodeAt(i + 1) : 0;
|
|
3072
|
-
expect(next >= 0xdc00 && next <= 0xdfff).toBe(true);
|
|
3073
|
-
i++;
|
|
3074
|
-
} else {
|
|
3075
|
-
expect(code < 0xdc00 || code > 0xdfff).toBe(true);
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
});
|
|
3079
|
-
|
|
3080
|
-
test("clean payloads are not copied unnecessarily", async () => {
|
|
3081
|
-
// When there are no orphans, the sanitizer should be a no-op. We can't
|
|
3082
|
-
// easily assert reference equality through the mock boundary (the mock
|
|
3083
|
-
// JSON-round-trips params for capture), but we can at least confirm the
|
|
3084
|
-
// call succeeds without error on ordinary payloads containing valid
|
|
3085
|
-
// surrogate pairs (emoji).
|
|
3086
|
-
const EMOJI = "\uD83C\uDF89";
|
|
3087
|
-
await provider.sendMessage([userMsg(`hello ${EMOJI} world`)]);
|
|
3088
|
-
const sent = lastStreamParams!.messages as Array<{
|
|
3089
|
-
content: Array<{ text?: string }>;
|
|
3090
|
-
}>;
|
|
3091
|
-
expect(sent[0].content[0].text).toContain(EMOJI);
|
|
3092
|
-
});
|
|
3093
|
-
});
|
|
3094
|
-
|
|
3095
3032
|
// ---------------------------------------------------------------------------
|
|
3096
3033
|
// Haiku model gating
|
|
3097
3034
|
// ---------------------------------------------------------------------------
|
|
@@ -125,7 +125,10 @@ describe("AssistantEventHub — machineName", () => {
|
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
describe(
|
|
128
|
+
describe.each([
|
|
129
|
+
["x-vellum-cu-window-capture", "host_cu_window_capture"],
|
|
130
|
+
["x-vellum-cu-sequence", "host_cu_sequence"],
|
|
131
|
+
] as const)("%s negotiation", (header, capability) => {
|
|
129
132
|
test.each([
|
|
130
133
|
["macos", undefined, false],
|
|
131
134
|
["macos", "0", false],
|
|
@@ -145,18 +148,14 @@ describe("window capture negotiation", () => {
|
|
|
145
148
|
headers: {
|
|
146
149
|
"x-vellum-client-id": "client-1",
|
|
147
150
|
"x-vellum-interface-id": interfaceId,
|
|
148
|
-
...(advertised
|
|
149
|
-
? { "x-vellum-cu-window-capture": advertised }
|
|
150
|
-
: {}),
|
|
151
|
+
...(advertised ? { [header]: advertised } : {}),
|
|
151
152
|
},
|
|
152
153
|
abortSignal: ac.signal,
|
|
153
154
|
},
|
|
154
155
|
{ hub },
|
|
155
156
|
);
|
|
156
157
|
expect(
|
|
157
|
-
hub
|
|
158
|
-
.getClientById("client-1")
|
|
159
|
-
?.capabilities.includes("host_cu_window_capture"),
|
|
158
|
+
hub.getClientById("client-1")?.capabilities.includes(capability),
|
|
160
159
|
).toBe(supported);
|
|
161
160
|
} finally {
|
|
162
161
|
ac.abort();
|
|
@@ -690,3 +690,47 @@ test("window requests never reach an older overlapping connection with the same
|
|
|
690
690
|
supported.dispose();
|
|
691
691
|
}
|
|
692
692
|
});
|
|
693
|
+
|
|
694
|
+
test("batched action requests never reach an older overlapping connection with the same device ID", async () => {
|
|
695
|
+
const receivedLegacy: unknown[] = [];
|
|
696
|
+
const receivedSupported: unknown[] = [];
|
|
697
|
+
const legacy = assistantEventHub.subscribe({
|
|
698
|
+
type: "client",
|
|
699
|
+
clientId: "sequence-client",
|
|
700
|
+
interfaceId: "macos",
|
|
701
|
+
capabilities: ["host_cu"],
|
|
702
|
+
callback: (event) => {
|
|
703
|
+
receivedLegacy.push(event);
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
const supported = assistantEventHub.subscribe({
|
|
707
|
+
type: "client",
|
|
708
|
+
clientId: "sequence-client",
|
|
709
|
+
interfaceId: "macos",
|
|
710
|
+
capabilities: ["host_cu", "host_cu_sequence"],
|
|
711
|
+
callback: (event) => {
|
|
712
|
+
receivedSupported.push(event);
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
try {
|
|
716
|
+
broadcastMessage(
|
|
717
|
+
{
|
|
718
|
+
type: "host_cu_request",
|
|
719
|
+
requestId: "sequence-request",
|
|
720
|
+
conversationId: "sequence-session",
|
|
721
|
+
toolName: "computer_use_sequence",
|
|
722
|
+
input: { actions: [{ action: "key", key: "cmd+n" }] },
|
|
723
|
+
stepNumber: 1,
|
|
724
|
+
targetClientId: "sequence-client",
|
|
725
|
+
},
|
|
726
|
+
"sequence-session",
|
|
727
|
+
{ targetClientId: "sequence-client" },
|
|
728
|
+
);
|
|
729
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
730
|
+
expect(receivedLegacy).toHaveLength(0);
|
|
731
|
+
expect(receivedSupported).toHaveLength(1);
|
|
732
|
+
} finally {
|
|
733
|
+
legacy.dispose();
|
|
734
|
+
supported.dispose();
|
|
735
|
+
}
|
|
736
|
+
});
|
|
@@ -29,7 +29,7 @@ const manifestPath = resolve(
|
|
|
29
29
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
30
30
|
|
|
31
31
|
describe("computer-use skill manifest regression", () => {
|
|
32
|
-
test("manifest has exactly
|
|
32
|
+
test("manifest has exactly 12 tools", () => {
|
|
33
33
|
expect(manifest.tools).toHaveLength(COMPUTER_USE_TOOL_COUNT);
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -37,8 +37,8 @@ const ctx: ToolContext = {
|
|
|
37
37
|
// ── Tool definitions ────────────────────────────────────────────────
|
|
38
38
|
|
|
39
39
|
describe("computer-use tool definitions", () => {
|
|
40
|
-
test("allComputerUseTools contains
|
|
41
|
-
expect(allComputerUseTools.length).toBe(
|
|
40
|
+
test("allComputerUseTools contains 12 tools", () => {
|
|
41
|
+
expect(allComputerUseTools.length).toBe(12);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test("all tools belong to computer-use category", () => {
|
|
@@ -557,52 +557,52 @@ describe("HeartbeatService", () => {
|
|
|
557
557
|
expect(processMessageCalls).toHaveLength(1);
|
|
558
558
|
});
|
|
559
559
|
|
|
560
|
-
|
|
561
|
-
|
|
560
|
+
test("active hours handles overnight window", async () => {
|
|
561
|
+
setHeartbeatConfig({ activeHoursStart: 22, activeHoursEnd: 6 });
|
|
562
562
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
563
|
+
// 23:00 should be within the window
|
|
564
|
+
const service = createService({ getCurrentHour: () => 23 });
|
|
565
|
+
await service.runOnce();
|
|
566
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
567
567
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
568
|
+
// 10:00 should be outside the window
|
|
569
|
+
processMessageCalls.length = 0;
|
|
570
|
+
createdConversations.length = 0;
|
|
571
|
+
const service2 = createService({ getCurrentHour: () => 10 });
|
|
572
|
+
await service2.runOnce();
|
|
573
|
+
expect(processMessageCalls).toHaveLength(0);
|
|
574
|
+
});
|
|
575
575
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
576
|
+
test("active hours guard uses user timezone in interval mode", async () => {
|
|
577
|
+
setConfig("ui", { detectedTimezone: "America/Los_Angeles" });
|
|
578
|
+
setHeartbeatConfig({
|
|
579
|
+
cronExpression: null,
|
|
580
|
+
timezone: null,
|
|
581
|
+
activeHoursStart: 8,
|
|
582
|
+
activeHoursEnd: 22,
|
|
583
|
+
});
|
|
584
584
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
585
|
+
// 23:00 UTC is 16:00 Pacific (inside 8-22) and 23:00 host-local
|
|
586
|
+
// (outside 8-22). If the guard still used the host clock this would skip.
|
|
587
|
+
const inside = createService({
|
|
588
|
+
now: () => new Date("2026-09-08T23:00:00Z"),
|
|
589
|
+
getCurrentHour: () => 23,
|
|
590
|
+
});
|
|
591
|
+
expect(await inside.runOnce()).toBe(true);
|
|
592
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
593
593
|
|
|
594
|
-
|
|
595
|
-
|
|
594
|
+
processMessageCalls.length = 0;
|
|
595
|
+
createdConversations.length = 0;
|
|
596
596
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
});
|
|
603
|
-
expect(await outside.runOnce()).toBe(false);
|
|
604
|
-
expect(processMessageCalls).toHaveLength(0);
|
|
597
|
+
// 06:00 UTC is 23:00 Pacific, outside 8-22, even though 6 is inside
|
|
598
|
+
// a naive UTC 8-22 window.
|
|
599
|
+
const outside = createService({
|
|
600
|
+
now: () => new Date("2026-09-09T06:00:00Z"),
|
|
601
|
+
getCurrentHour: () => 6,
|
|
605
602
|
});
|
|
603
|
+
expect(await outside.runOnce()).toBe(false);
|
|
604
|
+
expect(processMessageCalls).toHaveLength(0);
|
|
605
|
+
});
|
|
606
606
|
|
|
607
607
|
test("overlap prevention works", async () => {
|
|
608
608
|
let resolveFirst: () => void;
|
|
@@ -1004,7 +1004,23 @@ describe("HeartbeatService", () => {
|
|
|
1004
1004
|
"slack",
|
|
1005
1005
|
]);
|
|
1006
1006
|
|
|
1007
|
-
expect(prompt).toContain(
|
|
1007
|
+
expect(prompt).toContain(
|
|
1008
|
+
"google (integration, acts as the connected person), slack (integration, acts as the connected person)",
|
|
1009
|
+
);
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
test("prompt tells an integration apart from the channel bot of the same name", () => {
|
|
1013
|
+
const service = createService();
|
|
1014
|
+
const { prompt } = service.buildPrompt("- Check things", ["slack"]);
|
|
1015
|
+
|
|
1016
|
+
expect(prompt).toContain(
|
|
1017
|
+
"slack (integration, acts as the connected person)",
|
|
1018
|
+
);
|
|
1019
|
+
expect(prompt).not.toContain("slack_channel");
|
|
1020
|
+
expect(prompt).toContain("A channel bot is the assistant's own identity");
|
|
1021
|
+
|
|
1022
|
+
const bot = service.buildPrompt("- Check things", ["slack_channel"]);
|
|
1023
|
+
expect(bot.prompt).toContain("slack_channel (the slack channel bot)");
|
|
1008
1024
|
});
|
|
1009
1025
|
});
|
|
1010
1026
|
|