@vellumai/assistant 0.9.0 → 0.9.1-staging.1
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +4 -4
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +135 -59
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
- package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/config-loader-backfill.test.ts +103 -6
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +3 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -13
- package/src/__tests__/dynamic-page-surface.test.ts +101 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/invite-redemption-service.test.ts +4 -4
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +0 -22
- package/src/__tests__/llm-resolver.test.ts +49 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +11 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/skill-execute-input.test.ts +46 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/subagent-tools.test.ts +116 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +6 -6
- package/src/__tests__/voice-invite-redemption.test.ts +2 -2
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/agent/loop.ts +25 -5
- package/src/api/README.md +6 -6
- package/src/api/responses/conversation-message.ts +2 -4
- package/src/api/responses/home.ts +0 -4
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/plugins.ts +143 -2
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +228 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/upgrade-plugin.ts +270 -10
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +0 -1
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +12 -2
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/loader.ts +4 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/seed-inference-profiles.ts +25 -20
- package/src/contacts/contact-store.ts +87 -96
- package/src/contacts/contacts-write.ts +5 -21
- package/src/context/compactor.ts +2 -2
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop.ts +15 -4
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-tool-setup.ts +11 -3
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/handlers/config-channels.ts +20 -16
- package/src/daemon/handlers/config-slack-channel.ts +2 -3
- package/src/daemon/lifecycle.ts +0 -7
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/home/relationship-state.ts +2 -4
- package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/db-init.ts +7 -17
- package/src/memory/embedding-backend.ts +23 -0
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +52 -0
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-job.ts +19 -227
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/schema/contacts.ts +0 -4
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +5 -32
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/broadcaster.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +4 -3
- package/src/notifications/notification-utils.ts +17 -19
- package/src/notifications/types.ts +7 -0
- package/src/oauth/AGENTS.md +5 -24
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +6 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
- package/src/prompts/persona-resolver.ts +2 -2
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/actor-trust-resolver.ts +12 -44
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/channel-approval-types.ts +18 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-verification-service.ts +4 -3
- package/src/runtime/invite-redemption-service.ts +3 -3
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +171 -5
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/workflow-routes.test.ts +4 -43
- package/src/runtime/routes/workflow-routes.ts +0 -28
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/schedule/inference-profile.ts +2 -14
- package/src/subagent/manager.ts +6 -0
- package/src/subagent/types.ts +6 -0
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/schedule/create.ts +3 -9
- package/src/tools/schedule/update.ts +2 -10
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +34 -0
- package/src/tools/subagent/spawn.ts +34 -9
- package/src/tools/tool-approval-handler.ts +1 -3
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/util/platform.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/run-manager.test.ts +0 -25
- package/src/workflows/run-manager.ts +2 -24
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the default `surface-completion-nudge` plugin's hooks.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - The `post-model-call` hook nudges (continue + canonical text appended as a
|
|
6
|
+
* `user` message) when a turn ends with a progress surface left open: a
|
|
7
|
+
* `task_progress` card shown and never advanced to a terminal status, and a
|
|
8
|
+
* `work_result` shown `in_progress`.
|
|
9
|
+
* - It does NOT nudge when the surface was completed via `ui_update`, dismissed
|
|
10
|
+
* via `ui_dismiss`, or was never a progress surface (a plain card / a form).
|
|
11
|
+
* - Outcomes it does not own are ignored: a provider rejection, a tool-bearing
|
|
12
|
+
* turn, and a non-main-agent call site.
|
|
13
|
+
* - The signal is scoped to the current response cycle — a surface left open in
|
|
14
|
+
* a prior cycle (before the last genuine user prompt) does not trigger it.
|
|
15
|
+
* - The one-shot bound is split across the two hooks: `post-model-call` marks it
|
|
16
|
+
* (nudging at most once per run) and `stop` clears it so the next run nudges
|
|
17
|
+
* afresh.
|
|
18
|
+
*
|
|
19
|
+
* The loop's actual continuation side-effects live in `agent/loop.ts` and are
|
|
20
|
+
* covered by integration tests. This file isolates the hook.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
PluginLogger,
|
|
27
|
+
PostModelCallContext,
|
|
28
|
+
} from "../plugin-api/types.js";
|
|
29
|
+
import postModelCall, {
|
|
30
|
+
SURFACE_COMPLETION_NUDGE_TEXT,
|
|
31
|
+
} from "../plugins/defaults/surface-completion-nudge/hooks/post-model-call.js";
|
|
32
|
+
import stop from "../plugins/defaults/surface-completion-nudge/hooks/stop.js";
|
|
33
|
+
import {
|
|
34
|
+
isSurfaceCompletionNudged,
|
|
35
|
+
resetSurfaceCompletionNudgeStoreForTests,
|
|
36
|
+
} from "../plugins/defaults/surface-completion-nudge/nudge-state-store.js";
|
|
37
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
38
|
+
|
|
39
|
+
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const noopLogger: PluginLogger = {
|
|
42
|
+
info: () => {},
|
|
43
|
+
warn: () => {},
|
|
44
|
+
error: () => {},
|
|
45
|
+
debug: () => {},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const finalText: ContentBlock = { type: "text", text: "All set." };
|
|
49
|
+
|
|
50
|
+
let surfaceCounter = 0;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An assistant `ui_show` turn paired with its `{ surfaceId }` tool result.
|
|
54
|
+
* Returns both messages plus the assigned surface id.
|
|
55
|
+
*/
|
|
56
|
+
function showSurface(input: Record<string, unknown>): {
|
|
57
|
+
messages: Message[];
|
|
58
|
+
surfaceId: string;
|
|
59
|
+
} {
|
|
60
|
+
surfaceCounter += 1;
|
|
61
|
+
const toolUseId = `tu_show_${surfaceCounter}`;
|
|
62
|
+
const surfaceId = `surface-${surfaceCounter}`;
|
|
63
|
+
return {
|
|
64
|
+
surfaceId,
|
|
65
|
+
messages: [
|
|
66
|
+
{
|
|
67
|
+
role: "assistant",
|
|
68
|
+
content: [{ type: "tool_use", id: toolUseId, name: "ui_show", input }],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
role: "user",
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "tool_result",
|
|
75
|
+
tool_use_id: toolUseId,
|
|
76
|
+
content: JSON.stringify({ surfaceId }),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function updateSurface(
|
|
85
|
+
surfaceId: string,
|
|
86
|
+
data: Record<string, unknown>,
|
|
87
|
+
): Message {
|
|
88
|
+
return {
|
|
89
|
+
role: "assistant",
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "tool_use",
|
|
93
|
+
id: `tu_update_${surfaceId}`,
|
|
94
|
+
name: "ui_update",
|
|
95
|
+
input: { surface_id: surfaceId, data },
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function dismissSurface(surfaceId: string): Message {
|
|
102
|
+
return {
|
|
103
|
+
role: "assistant",
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "tool_use",
|
|
107
|
+
id: `tu_dismiss_${surfaceId}`,
|
|
108
|
+
name: "ui_dismiss",
|
|
109
|
+
input: { surface_id: surfaceId },
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function userPrompt(text: string): Message {
|
|
116
|
+
return { role: "user", content: [{ type: "text", text }] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const taskProgressShow = (status?: string): Record<string, unknown> => ({
|
|
120
|
+
surface_type: "card",
|
|
121
|
+
title: "Working on X",
|
|
122
|
+
data: {
|
|
123
|
+
template: "task_progress",
|
|
124
|
+
templateData: {
|
|
125
|
+
title: "Working on X",
|
|
126
|
+
...(status ? { status } : {}),
|
|
127
|
+
steps: [{ label: "Step 1", status: status ?? "in_progress" }],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
function makeCtx(
|
|
133
|
+
overrides: Partial<PostModelCallContext> = {},
|
|
134
|
+
): PostModelCallContext {
|
|
135
|
+
return {
|
|
136
|
+
conversationId: "conv-scn",
|
|
137
|
+
callSite: "mainAgent",
|
|
138
|
+
content: [finalText],
|
|
139
|
+
messages: [],
|
|
140
|
+
stopReason: null,
|
|
141
|
+
decision: "stop",
|
|
142
|
+
logger: noopLogger,
|
|
143
|
+
...overrides,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
resetSurfaceCompletionNudgeStoreForTests();
|
|
149
|
+
surfaceCounter = 0;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ─── Nudges when a progress surface is left open ──────────────────────────────
|
|
153
|
+
|
|
154
|
+
describe("surface-completion-nudge — nudges on a dangling progress surface", () => {
|
|
155
|
+
test("task_progress card shown and never completed → continue with nudge", async () => {
|
|
156
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
157
|
+
const ctx = makeCtx({
|
|
158
|
+
messages: [userPrompt("do the thing"), ...shown.messages],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await postModelCall(ctx);
|
|
162
|
+
|
|
163
|
+
expect(ctx.decision).toBe("continue");
|
|
164
|
+
const last = ctx.messages[ctx.messages.length - 1];
|
|
165
|
+
expect(last.role).toBe("user");
|
|
166
|
+
expect(last.content[0]).toEqual({
|
|
167
|
+
type: "text",
|
|
168
|
+
text: SURFACE_COMPLETION_NUDGE_TEXT,
|
|
169
|
+
});
|
|
170
|
+
expect(isSurfaceCompletionNudged("conv-scn")).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("task_progress card shown with no explicit status → continue with nudge", async () => {
|
|
174
|
+
const shown = showSurface(taskProgressShow());
|
|
175
|
+
const ctx = makeCtx({
|
|
176
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await postModelCall(ctx);
|
|
180
|
+
|
|
181
|
+
expect(ctx.decision).toBe("continue");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("work_result shown in_progress → continue with nudge", async () => {
|
|
185
|
+
const shown = showSurface({
|
|
186
|
+
surface_type: "work_result",
|
|
187
|
+
data: { status: "in_progress", summary: "Crunching" },
|
|
188
|
+
});
|
|
189
|
+
const ctx = makeCtx({
|
|
190
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await postModelCall(ctx);
|
|
194
|
+
|
|
195
|
+
expect(ctx.decision).toBe("continue");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// ─── Stays quiet when there is nothing to close ───────────────────────────────
|
|
200
|
+
|
|
201
|
+
describe("surface-completion-nudge — no nudge when surface is closed or absent", () => {
|
|
202
|
+
test("task_progress completed via ui_update → stop", async () => {
|
|
203
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
204
|
+
const ctx = makeCtx({
|
|
205
|
+
messages: [
|
|
206
|
+
userPrompt("go"),
|
|
207
|
+
...shown.messages,
|
|
208
|
+
updateSurface(shown.surfaceId, {
|
|
209
|
+
templateData: { status: "completed" },
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await postModelCall(ctx);
|
|
215
|
+
|
|
216
|
+
expect(ctx.decision).toBe("stop");
|
|
217
|
+
expect(isSurfaceCompletionNudged("conv-scn")).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("progress surface dismissed via ui_dismiss → stop", async () => {
|
|
221
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
222
|
+
const ctx = makeCtx({
|
|
223
|
+
messages: [
|
|
224
|
+
userPrompt("go"),
|
|
225
|
+
...shown.messages,
|
|
226
|
+
dismissSurface(shown.surfaceId),
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await postModelCall(ctx);
|
|
231
|
+
|
|
232
|
+
expect(ctx.decision).toBe("stop");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("work_result shown completed → stop", async () => {
|
|
236
|
+
const shown = showSurface({
|
|
237
|
+
surface_type: "work_result",
|
|
238
|
+
data: { status: "completed", summary: "Done" },
|
|
239
|
+
});
|
|
240
|
+
const ctx = makeCtx({
|
|
241
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
await postModelCall(ctx);
|
|
245
|
+
|
|
246
|
+
expect(ctx.decision).toBe("stop");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("plain card (not a progress surface) → stop", async () => {
|
|
250
|
+
const shown = showSurface({
|
|
251
|
+
surface_type: "card",
|
|
252
|
+
title: "Weather",
|
|
253
|
+
data: { template: "weather_forecast", body: "Sunny" },
|
|
254
|
+
});
|
|
255
|
+
const ctx = makeCtx({
|
|
256
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await postModelCall(ctx);
|
|
260
|
+
|
|
261
|
+
expect(ctx.decision).toBe("stop");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("no surfaces shown at all → stop", async () => {
|
|
265
|
+
const ctx = makeCtx({
|
|
266
|
+
messages: [userPrompt("go")],
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await postModelCall(ctx);
|
|
270
|
+
|
|
271
|
+
expect(ctx.decision).toBe("stop");
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ─── Outcomes the hook does not own ───────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
describe("surface-completion-nudge — ignores outcomes it does not own", () => {
|
|
278
|
+
test("provider rejection (error present) → stop", async () => {
|
|
279
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
280
|
+
const ctx = makeCtx({
|
|
281
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
282
|
+
error: new Error("provider exploded"),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await postModelCall(ctx);
|
|
286
|
+
|
|
287
|
+
expect(ctx.decision).toBe("stop");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("tool-bearing turn (model still working) → stop", async () => {
|
|
291
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
292
|
+
const ctx = makeCtx({
|
|
293
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
294
|
+
content: [
|
|
295
|
+
{ type: "tool_use", id: "tu_next", name: "read_file", input: {} },
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
await postModelCall(ctx);
|
|
300
|
+
|
|
301
|
+
expect(ctx.decision).toBe("stop");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("non-main-agent call site → stop", async () => {
|
|
305
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
306
|
+
const ctx = makeCtx({
|
|
307
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
308
|
+
callSite: "heartbeatAgent",
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await postModelCall(ctx);
|
|
312
|
+
|
|
313
|
+
expect(ctx.decision).toBe("stop");
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ─── Cycle scoping and the one-shot bound ─────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
describe("surface-completion-nudge — cycle scoping and one-shot bound", () => {
|
|
320
|
+
test("a surface left open in a prior cycle does not trigger this cycle", async () => {
|
|
321
|
+
const priorOpen = showSurface(taskProgressShow("in_progress"));
|
|
322
|
+
const ctx = makeCtx({
|
|
323
|
+
messages: [
|
|
324
|
+
userPrompt("first task"),
|
|
325
|
+
...priorOpen.messages,
|
|
326
|
+
// New genuine user prompt opens a fresh cycle with no open surface.
|
|
327
|
+
userPrompt("second task"),
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
await postModelCall(ctx);
|
|
332
|
+
|
|
333
|
+
expect(ctx.decision).toBe("stop");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("nudges at most once per run; stop clears the bound", async () => {
|
|
337
|
+
const shown = showSurface(taskProgressShow("in_progress"));
|
|
338
|
+
const firstCtx = makeCtx({
|
|
339
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await postModelCall(firstCtx);
|
|
343
|
+
expect(firstCtx.decision).toBe("continue");
|
|
344
|
+
|
|
345
|
+
// Same run, surface still open: the one-shot bound suppresses a second nudge.
|
|
346
|
+
const secondCtx = makeCtx({
|
|
347
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
348
|
+
});
|
|
349
|
+
await postModelCall(secondCtx);
|
|
350
|
+
expect(secondCtx.decision).toBe("stop");
|
|
351
|
+
|
|
352
|
+
// Terminal stop clears the bound so the next run nudges afresh.
|
|
353
|
+
await stop({
|
|
354
|
+
conversationId: "conv-scn",
|
|
355
|
+
messages: [],
|
|
356
|
+
exitReason: "no_tool_calls",
|
|
357
|
+
logger: noopLogger,
|
|
358
|
+
});
|
|
359
|
+
expect(isSurfaceCompletionNudged("conv-scn")).toBe(false);
|
|
360
|
+
|
|
361
|
+
const thirdCtx = makeCtx({
|
|
362
|
+
messages: [userPrompt("go"), ...shown.messages],
|
|
363
|
+
});
|
|
364
|
+
await postModelCall(thirdCtx);
|
|
365
|
+
expect(thirdCtx.decision).toBe("continue");
|
|
366
|
+
});
|
|
367
|
+
});
|
|
@@ -128,7 +128,6 @@ function makeSystemPrompt(size: "small" | "production" = "small"): string {
|
|
|
128
128
|
"- `file_write` — Creating or overwriting files",
|
|
129
129
|
"- `file_edit` — Modifying existing files",
|
|
130
130
|
"- `file_delete` — Deleting files",
|
|
131
|
-
"- `credential_store set` — Storing credentials",
|
|
132
131
|
"",
|
|
133
132
|
"### Medium-Risk Tools (require approval on first use per session)",
|
|
134
133
|
"- `web_fetch` — Fetching external URLs",
|
|
@@ -349,7 +348,7 @@ function makeToolDefinitions(): Array<{
|
|
|
349
348
|
input_schema: object;
|
|
350
349
|
}> = [];
|
|
351
350
|
|
|
352
|
-
// Core tools (
|
|
351
|
+
// Core tools (10)
|
|
353
352
|
tools.push(
|
|
354
353
|
{
|
|
355
354
|
name: "bash",
|
|
@@ -566,33 +565,6 @@ function makeToolDefinitions(): Array<{
|
|
|
566
565
|
required: ["query"],
|
|
567
566
|
},
|
|
568
567
|
},
|
|
569
|
-
{
|
|
570
|
-
name: "credential_store",
|
|
571
|
-
description:
|
|
572
|
-
"Securely store or retrieve credentials for external services. Credentials are encrypted at rest.",
|
|
573
|
-
input_schema: {
|
|
574
|
-
type: "object",
|
|
575
|
-
properties: {
|
|
576
|
-
action: {
|
|
577
|
-
type: "string",
|
|
578
|
-
enum: ["get", "set", "delete", "list"],
|
|
579
|
-
},
|
|
580
|
-
service: {
|
|
581
|
-
type: "string",
|
|
582
|
-
description: "The service name (e.g., 'github', 'slack')",
|
|
583
|
-
},
|
|
584
|
-
key: {
|
|
585
|
-
type: "string",
|
|
586
|
-
description: "Credential key within the service",
|
|
587
|
-
},
|
|
588
|
-
value: {
|
|
589
|
-
type: "string",
|
|
590
|
-
description: "Credential value (required for 'set')",
|
|
591
|
-
},
|
|
592
|
-
},
|
|
593
|
-
required: ["action", "service"],
|
|
594
|
-
},
|
|
595
|
-
},
|
|
596
568
|
);
|
|
597
569
|
|
|
598
570
|
// Computer-use proxy tools (11)
|