@vellumai/assistant 0.4.49 → 0.4.50
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 +24 -33
- package/README.md +3 -3
- package/docs/architecture/memory.md +180 -119
- package/package.json +2 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -0
- package/src/__tests__/credential-vault.test.ts +13 -1
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +572 -5
- package/src/__tests__/oauth-store.test.ts +120 -6
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +132 -0
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +43 -5
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +31 -2
- package/src/cli/commands/oauth/connections.ts +431 -97
- package/src/cli/commands/oauth/providers.ts +15 -1
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +4 -10
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +31 -3
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +12 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/instrument.ts +61 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/oauth/byo-connection.test.ts +8 -1
- package/src/oauth/oauth-store.ts +113 -27
- package/src/oauth/seed-providers.ts +6 -0
- package/src/oauth/token-persistence.ts +11 -3
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/conversation-routes.ts +9 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +2 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +36 -36
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +1 -3
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -690,7 +690,7 @@ function buildMemoryRecallSection(): string {
|
|
|
690
690
|
"- The auto-injected memory context doesn't contain what you need",
|
|
691
691
|
"- The user references something from a previous session",
|
|
692
692
|
"",
|
|
693
|
-
"The tool
|
|
693
|
+
"The tool uses hybrid search (dense and sparse vectors) supplemented by recency. Be specific in your query for best results.",
|
|
694
694
|
].join("\n");
|
|
695
695
|
}
|
|
696
696
|
|
|
@@ -845,7 +845,7 @@ export function buildCliReferenceSection(): string {
|
|
|
845
845
|
return [
|
|
846
846
|
"## Assistant CLI",
|
|
847
847
|
"",
|
|
848
|
-
"The `assistant` CLI is
|
|
848
|
+
"The `assistant` CLI is available in the sandbox. Always use the `bash` tool (never `host_bash`) when running `assistant` commands.",
|
|
849
849
|
"For account and authentication work, prefer real `assistant` CLI workflows over any legacy account-record abstraction.",
|
|
850
850
|
"- Use `assistant credentials ...` for stored secrets and credential metadata.",
|
|
851
851
|
"- Use `assistant oauth connections token <provider-key>` for connected integration tokens.",
|
|
@@ -960,6 +960,22 @@ function buildDynamicSkillWorkflowSection(
|
|
|
960
960
|
);
|
|
961
961
|
}
|
|
962
962
|
|
|
963
|
+
lines.push(
|
|
964
|
+
"",
|
|
965
|
+
"### Community Skills Discovery",
|
|
966
|
+
"",
|
|
967
|
+
"When no built-in skill satisfies a request, search the community skills.sh registry:",
|
|
968
|
+
"1. Run `assistant skills search <query>` to find community skills. Results include install counts and security audit badges (ATH, Socket, Snyk).",
|
|
969
|
+
"2. Present the search results to the user, highlighting the security audit status. ATH is Gen Agent Trust Hub. Audits show PASS (safe/low risk), WARN (medium risk), or FAIL (high/critical risk) for each provider.",
|
|
970
|
+
"3. Check the skill's **source owner** to determine the trust level:",
|
|
971
|
+
" - **Vellum-owned** (source starts with `vellum-ai/`): These are first-party skills published by the Vellum team. Install them directly without prompting — they are vetted and trusted.",
|
|
972
|
+
" - **Third-party** (any other owner): Ask the user for permission before installing. Say something like: \"I found a community skill that could help with this, but it's published by a third party — we haven't vetted it. Want to install it anyway?\" Share the skill name, source, audit results, and install count.",
|
|
973
|
+
"4. Install with `assistant skills add <owner>/<repo>@<skill-name>` (e.g., `assistant skills add vercel-labs/skills@find-skills`).",
|
|
974
|
+
"5. After installation, load the skill with `skill_load` as usual.",
|
|
975
|
+
"",
|
|
976
|
+
"**Never install third-party community skills without explicit user confirmation.** Vellum-owned skills (`vellum-ai/*`) can be installed automatically.",
|
|
977
|
+
);
|
|
978
|
+
|
|
963
979
|
return lines.join("\n");
|
|
964
980
|
}
|
|
965
981
|
|
|
@@ -63,20 +63,6 @@ function isToolUseBlock(block: unknown): block is Anthropic.ToolUseBlockParam {
|
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/** Type-guard for server_tool_use blocks (e.g. native web search). */
|
|
67
|
-
function isServerToolUseBlock(block: unknown): block is {
|
|
68
|
-
type: "server_tool_use";
|
|
69
|
-
id: string;
|
|
70
|
-
name: string;
|
|
71
|
-
input: unknown;
|
|
72
|
-
} {
|
|
73
|
-
return (
|
|
74
|
-
typeof block === "object" &&
|
|
75
|
-
block != null &&
|
|
76
|
-
(block as { type: string }).type === "server_tool_use"
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
66
|
/** Type-guard for tool_result blocks in Anthropic-formatted content. */
|
|
81
67
|
function isToolResultBlock(
|
|
82
68
|
block: unknown,
|
|
@@ -88,19 +74,6 @@ function isToolResultBlock(
|
|
|
88
74
|
);
|
|
89
75
|
}
|
|
90
76
|
|
|
91
|
-
/** Type-guard for web_search_tool_result blocks. */
|
|
92
|
-
function isWebSearchToolResultBlock(block: unknown): block is {
|
|
93
|
-
type: "web_search_tool_result";
|
|
94
|
-
tool_use_id: string;
|
|
95
|
-
content: unknown;
|
|
96
|
-
} {
|
|
97
|
-
return (
|
|
98
|
-
typeof block === "object" &&
|
|
99
|
-
block != null &&
|
|
100
|
-
(block as { type: string }).type === "web_search_tool_result"
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
77
|
/**
|
|
105
78
|
* Build a short diagnostic summary of a message array for error logging.
|
|
106
79
|
* Shows role + block types (with tool_use/tool_result IDs) for each message.
|
|
@@ -134,79 +107,55 @@ function buildSyntheticToolResult(
|
|
|
134
107
|
};
|
|
135
108
|
}
|
|
136
109
|
|
|
137
|
-
function buildSyntheticWebSearchToolResult(
|
|
138
|
-
toolUseId: string,
|
|
139
|
-
): Anthropic.ContentBlockParam {
|
|
140
|
-
return {
|
|
141
|
-
type: "web_search_tool_result",
|
|
142
|
-
tool_use_id: toolUseId,
|
|
143
|
-
content: {
|
|
144
|
-
type: "web_search_tool_result_error",
|
|
145
|
-
error_code: "unavailable",
|
|
146
|
-
},
|
|
147
|
-
} as unknown as Anthropic.ContentBlockParam;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Build the appropriate synthetic result block based on whether the ID is for a server tool or regular tool. */
|
|
151
|
-
function buildSyntheticResult(
|
|
152
|
-
toolUseId: string,
|
|
153
|
-
serverToolIds: ReadonlySet<string>,
|
|
154
|
-
): Anthropic.ContentBlockParam {
|
|
155
|
-
if (serverToolIds.has(toolUseId)) {
|
|
156
|
-
return buildSyntheticWebSearchToolResult(toolUseId);
|
|
157
|
-
}
|
|
158
|
-
return buildSyntheticToolResult(toolUseId);
|
|
159
|
-
}
|
|
160
110
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Collect ordered IDs of client-side tool_use blocks only.
|
|
113
|
+
* Server-side tools (server_tool_use / web_search_tool_result) are self-paired
|
|
114
|
+
* within the assistant message and do not need cross-message pairing.
|
|
115
|
+
*/
|
|
116
|
+
function getOrderedToolUseIds(
|
|
117
|
+
content: Anthropic.ContentBlockParam[],
|
|
118
|
+
): string[] {
|
|
165
119
|
const ids: string[] = [];
|
|
166
120
|
const seen = new Set<string>();
|
|
167
|
-
const serverToolIds = new Set<string>();
|
|
168
121
|
for (const block of content) {
|
|
169
122
|
if (isToolUseBlock(block)) {
|
|
170
123
|
if (!seen.has(block.id)) {
|
|
171
124
|
seen.add(block.id);
|
|
172
125
|
ids.push(block.id);
|
|
173
126
|
}
|
|
174
|
-
} else if (isServerToolUseBlock(block)) {
|
|
175
|
-
if (!seen.has(block.id)) {
|
|
176
|
-
seen.add(block.id);
|
|
177
|
-
ids.push(block.id);
|
|
178
|
-
serverToolIds.add(block.id);
|
|
179
|
-
}
|
|
180
127
|
}
|
|
181
128
|
}
|
|
182
|
-
return
|
|
129
|
+
return ids;
|
|
183
130
|
}
|
|
184
131
|
|
|
185
132
|
function hasOrderedToolResultPrefix(
|
|
186
133
|
content: Anthropic.ContentBlockParam[],
|
|
187
134
|
orderedToolUseIds: string[],
|
|
188
|
-
serverToolIds: ReadonlySet<string>,
|
|
189
135
|
): boolean {
|
|
190
136
|
if (content.length < orderedToolUseIds.length) return false;
|
|
191
137
|
for (let idx = 0; idx < orderedToolUseIds.length; idx++) {
|
|
192
138
|
const block = content[idx];
|
|
193
139
|
const expectedId = orderedToolUseIds[idx];
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
if (block.tool_use_id !== expectedId) return false;
|
|
197
|
-
} else {
|
|
198
|
-
if (!isToolResultBlock(block)) return false;
|
|
199
|
-
if (block.tool_use_id !== expectedId) return false;
|
|
200
|
-
}
|
|
140
|
+
if (!isToolResultBlock(block)) return false;
|
|
141
|
+
if (block.tool_use_id !== expectedId) return false;
|
|
201
142
|
}
|
|
202
143
|
return true;
|
|
203
144
|
}
|
|
204
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Split an assistant message into:
|
|
148
|
+
* - pairedContent: everything up to and including client-side tool_use blocks
|
|
149
|
+
* - carryoverContent: trailing non-tool blocks after the last tool_use
|
|
150
|
+
*
|
|
151
|
+
* Server-side tools (server_tool_use / web_search_tool_result) are treated as
|
|
152
|
+
* regular content — they are self-paired within the assistant message and must
|
|
153
|
+
* not be separated by the cross-message pairing logic.
|
|
154
|
+
*/
|
|
205
155
|
function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
|
|
206
156
|
pairedContent: Anthropic.ContentBlockParam[];
|
|
207
157
|
carryoverContent: Anthropic.ContentBlockParam[];
|
|
208
158
|
toolUseIds: string[];
|
|
209
|
-
serverToolIds: Set<string>;
|
|
210
159
|
} {
|
|
211
160
|
const leading: Anthropic.ContentBlockParam[] = [];
|
|
212
161
|
const toolUseBlocks: Anthropic.ContentBlockParam[] = [];
|
|
@@ -214,7 +163,7 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
|
|
|
214
163
|
let seenToolUse = false;
|
|
215
164
|
|
|
216
165
|
for (const block of content) {
|
|
217
|
-
if (isToolUseBlock(block)
|
|
166
|
+
if (isToolUseBlock(block)) {
|
|
218
167
|
seenToolUse = true;
|
|
219
168
|
toolUseBlocks.push(block);
|
|
220
169
|
continue;
|
|
@@ -231,7 +180,6 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
|
|
|
231
180
|
pairedContent: content,
|
|
232
181
|
carryoverContent: [],
|
|
233
182
|
toolUseIds: [],
|
|
234
|
-
serverToolIds: new Set(),
|
|
235
183
|
};
|
|
236
184
|
}
|
|
237
185
|
|
|
@@ -239,19 +187,16 @@ function splitAssistantForToolPairing(content: Anthropic.ContentBlockParam[]): {
|
|
|
239
187
|
...leading,
|
|
240
188
|
...toolUseBlocks,
|
|
241
189
|
];
|
|
242
|
-
const { ids, serverToolIds } = getOrderedToolUseIds(pairedContent);
|
|
243
190
|
return {
|
|
244
191
|
pairedContent,
|
|
245
192
|
carryoverContent: carryover,
|
|
246
|
-
toolUseIds:
|
|
247
|
-
serverToolIds,
|
|
193
|
+
toolUseIds: getOrderedToolUseIds(pairedContent),
|
|
248
194
|
};
|
|
249
195
|
}
|
|
250
196
|
|
|
251
197
|
function normalizeFollowingUserContent(
|
|
252
198
|
nextContent: Anthropic.ContentBlockParam[],
|
|
253
199
|
orderedToolUseIds: string[],
|
|
254
|
-
serverToolIds: ReadonlySet<string>,
|
|
255
200
|
): {
|
|
256
201
|
toolResultPrefix: Anthropic.ContentBlockParam[];
|
|
257
202
|
remainingContent: Anthropic.ContentBlockParam[];
|
|
@@ -266,41 +211,24 @@ function normalizeFollowingUserContent(
|
|
|
266
211
|
if (
|
|
267
212
|
isToolResultBlock(block) &&
|
|
268
213
|
pendingIds.has(block.tool_use_id) &&
|
|
269
|
-
!matchedById.has(block.tool_use_id)
|
|
270
|
-
!serverToolIds.has(block.tool_use_id)
|
|
214
|
+
!matchedById.has(block.tool_use_id)
|
|
271
215
|
) {
|
|
272
216
|
matchedById.set(block.tool_use_id, block);
|
|
273
217
|
continue;
|
|
274
218
|
}
|
|
275
|
-
if (
|
|
276
|
-
isWebSearchToolResultBlock(block) &&
|
|
277
|
-
pendingIds.has(block.tool_use_id) &&
|
|
278
|
-
!matchedById.has(block.tool_use_id) &&
|
|
279
|
-
serverToolIds.has(block.tool_use_id)
|
|
280
|
-
) {
|
|
281
|
-
matchedById.set(
|
|
282
|
-
block.tool_use_id,
|
|
283
|
-
block as unknown as Anthropic.ContentBlockParam,
|
|
284
|
-
);
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
219
|
remaining.push(block);
|
|
288
220
|
}
|
|
289
221
|
|
|
290
222
|
const missingIds = orderedToolUseIds.filter((id) => !matchedById.has(id));
|
|
291
223
|
const orderedResults = orderedToolUseIds.map(
|
|
292
|
-
(id) => matchedById.get(id) ??
|
|
224
|
+
(id) => matchedById.get(id) ?? buildSyntheticToolResult(id),
|
|
293
225
|
);
|
|
294
226
|
|
|
295
227
|
return {
|
|
296
228
|
toolResultPrefix: orderedResults,
|
|
297
229
|
remainingContent: remaining,
|
|
298
230
|
missingIds,
|
|
299
|
-
hadOrderedPrefix: hasOrderedToolResultPrefix(
|
|
300
|
-
nextContent,
|
|
301
|
-
orderedToolUseIds,
|
|
302
|
-
serverToolIds,
|
|
303
|
-
),
|
|
231
|
+
hadOrderedPrefix: hasOrderedToolResultPrefix(nextContent, orderedToolUseIds),
|
|
304
232
|
};
|
|
305
233
|
}
|
|
306
234
|
|
|
@@ -328,7 +256,7 @@ function ensureToolPairing(
|
|
|
328
256
|
}
|
|
329
257
|
|
|
330
258
|
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
331
|
-
const { pairedContent, carryoverContent, toolUseIds
|
|
259
|
+
const { pairedContent, carryoverContent, toolUseIds } =
|
|
332
260
|
splitAssistantForToolPairing(content);
|
|
333
261
|
|
|
334
262
|
if (toolUseIds.length === 0) {
|
|
@@ -337,7 +265,7 @@ function ensureToolPairing(
|
|
|
337
265
|
continue;
|
|
338
266
|
}
|
|
339
267
|
|
|
340
|
-
// Assistant message — push the paired portion (pre-tool text + tool_use
|
|
268
|
+
// Assistant message — push the paired portion (pre-tool text + tool_use blocks)
|
|
341
269
|
result.push({
|
|
342
270
|
role: "assistant" as const,
|
|
343
271
|
content: pairedContent,
|
|
@@ -358,11 +286,7 @@ function ensureToolPairing(
|
|
|
358
286
|
const next = messages[i + 1];
|
|
359
287
|
if (next && next.role === "user") {
|
|
360
288
|
const nextContent = Array.isArray(next.content) ? next.content : [];
|
|
361
|
-
const normalized = normalizeFollowingUserContent(
|
|
362
|
-
nextContent,
|
|
363
|
-
toolUseIds,
|
|
364
|
-
serverToolIds,
|
|
365
|
-
);
|
|
289
|
+
const normalized = normalizeFollowingUserContent(nextContent, toolUseIds);
|
|
366
290
|
if (normalized.missingIds.length > 0) {
|
|
367
291
|
log.warn(
|
|
368
292
|
{
|
|
@@ -427,9 +351,7 @@ function ensureToolPairing(
|
|
|
427
351
|
);
|
|
428
352
|
result.push({
|
|
429
353
|
role: "user" as const,
|
|
430
|
-
content: toolUseIds.map((id) =>
|
|
431
|
-
buildSyntheticResult(id, serverToolIds),
|
|
432
|
-
),
|
|
354
|
+
content: toolUseIds.map((id) => buildSyntheticToolResult(id)),
|
|
433
355
|
});
|
|
434
356
|
|
|
435
357
|
// If the assistant contained collapsed post-tool text, preserve it as a
|
|
@@ -445,13 +367,14 @@ function ensureToolPairing(
|
|
|
445
367
|
}
|
|
446
368
|
}
|
|
447
369
|
|
|
448
|
-
// Self-validation: verify no tool_use/tool_result mismatches remain
|
|
370
|
+
// Self-validation: verify no client-side tool_use/tool_result mismatches remain.
|
|
371
|
+
// Server-side tools (server_tool_use / web_search_tool_result) are self-paired
|
|
372
|
+
// within assistant messages and are not validated here.
|
|
449
373
|
for (let j = 0; j < result.length; j++) {
|
|
450
374
|
const m = result[j];
|
|
451
375
|
if (m.role !== "assistant") continue;
|
|
452
376
|
const c = Array.isArray(m.content) ? m.content : [];
|
|
453
|
-
const
|
|
454
|
-
getOrderedToolUseIds(c);
|
|
377
|
+
const validationIds = getOrderedToolUseIds(c);
|
|
455
378
|
if (validationIds.length === 0) continue;
|
|
456
379
|
|
|
457
380
|
const nxt = result[j + 1];
|
|
@@ -459,20 +382,9 @@ function ensureToolPairing(
|
|
|
459
382
|
nxt && nxt.role === "user" && Array.isArray(nxt.content)
|
|
460
383
|
? nxt.content
|
|
461
384
|
: [];
|
|
462
|
-
if (
|
|
463
|
-
!hasOrderedToolResultPrefix(
|
|
464
|
-
nxtContent,
|
|
465
|
-
validationIds,
|
|
466
|
-
validationServerToolIds,
|
|
467
|
-
)
|
|
468
|
-
) {
|
|
385
|
+
if (!hasOrderedToolResultPrefix(nxtContent, validationIds)) {
|
|
469
386
|
const unmatchedIds = validationIds.filter((id, idx) => {
|
|
470
387
|
const block = nxtContent[idx];
|
|
471
|
-
if (validationServerToolIds.has(id)) {
|
|
472
|
-
return !(
|
|
473
|
-
isWebSearchToolResultBlock(block) && block.tool_use_id === id
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
388
|
return !(isToolResultBlock(block) && block.tool_use_id === id);
|
|
477
389
|
});
|
|
478
390
|
log.error(
|
|
@@ -768,10 +680,14 @@ export class AnthropicProvider implements Provider {
|
|
|
768
680
|
onEvent?.({ type: "text_delta", text: " " });
|
|
769
681
|
}
|
|
770
682
|
hasSeenTextBlock = true;
|
|
771
|
-
} else if (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
683
|
+
} else if (
|
|
684
|
+
event.type === "content_block_start" &&
|
|
685
|
+
event.content_block.type === "tool_use"
|
|
686
|
+
) {
|
|
687
|
+
// Reset only for client-side tool_use blocks, which create visual
|
|
688
|
+
// separators in the UI. Server-side tool blocks (server_tool_use,
|
|
689
|
+
// web_search_tool_result) are transparent in the text stream and
|
|
690
|
+
// need the space preserved between surrounding text blocks.
|
|
775
691
|
hasSeenTextBlock = false;
|
|
776
692
|
}
|
|
777
693
|
if (
|
|
@@ -796,6 +712,20 @@ export class AnthropicProvider implements Provider {
|
|
|
796
712
|
type: "server_tool_start",
|
|
797
713
|
name: event.content_block.name,
|
|
798
714
|
toolUseId: event.content_block.id,
|
|
715
|
+
input: (
|
|
716
|
+
event.content_block as { input?: Record<string, unknown> }
|
|
717
|
+
).input ?? {},
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (
|
|
721
|
+
event.type === "content_block_start" &&
|
|
722
|
+
event.content_block.type === "web_search_tool_result"
|
|
723
|
+
) {
|
|
724
|
+
onEvent?.({
|
|
725
|
+
type: "server_tool_complete",
|
|
726
|
+
toolUseId: (
|
|
727
|
+
event.content_block as { tool_use_id: string }
|
|
728
|
+
).tool_use_id,
|
|
799
729
|
});
|
|
800
730
|
}
|
|
801
731
|
if (event.type === "content_block_stop") {
|
package/src/providers/types.ts
CHANGED
|
@@ -117,7 +117,13 @@ export type ProviderEvent =
|
|
|
117
117
|
toolUseId: string;
|
|
118
118
|
accumulatedJson: string;
|
|
119
119
|
}
|
|
120
|
-
| {
|
|
120
|
+
| {
|
|
121
|
+
type: "server_tool_start";
|
|
122
|
+
name: string;
|
|
123
|
+
toolUseId: string;
|
|
124
|
+
input: Record<string, unknown>;
|
|
125
|
+
}
|
|
126
|
+
| { type: "server_tool_complete"; toolUseId: string };
|
|
121
127
|
|
|
122
128
|
export interface SendMessageConfig {
|
|
123
129
|
model?: string;
|
package/src/runtime/AGENTS.md
CHANGED
|
@@ -43,6 +43,15 @@ Host file allows the assistant to perform file operations (read, write, edit) on
|
|
|
43
43
|
- `POST /v1/host-file-result` — `{ requestId, content, isError }`
|
|
44
44
|
- **Tracking**: Uses the same `pending-interactions` tracker as approvals and host bash, with `kind: "host_file"`. The endpoint validates the interaction kind before resolving.
|
|
45
45
|
|
|
46
|
+
### Host CU (desktop proxy computer-use execution)
|
|
47
|
+
|
|
48
|
+
Host CU allows the assistant to proxy computer-use actions (screenshots, mouse/keyboard input) to the desktop host via the client, following the same pattern as host bash and host file.
|
|
49
|
+
|
|
50
|
+
- **Discovery**: Clients discover pending host CU requests via SSE events (`host_cu_request`) which include a `requestId`.
|
|
51
|
+
- **Resolution**: Clients execute the CU action on the host and respond via:
|
|
52
|
+
- `POST /v1/host-cu-result` — `{ requestId, axTree?, axDiff?, screenshot?, screenshotWidthPx?, screenshotHeightPx?, screenWidthPt?, screenHeightPt?, executionResult?, executionError?, secondaryWindows?, userGuidance? }`
|
|
53
|
+
- **Tracking**: Uses the same `pending-interactions` tracker as the other host proxy types, with `kind: "host_cu"`. Registration happens in `conversation-routes.ts` and the route handler is in `host-cu-routes.ts`.
|
|
54
|
+
|
|
46
55
|
### Channel approvals (Telegram, Slack)
|
|
47
56
|
|
|
48
57
|
Channel approval flows use `requestId` (not `runId`) as the primary identifier:
|
|
@@ -347,6 +347,12 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
347
347
|
{ endpoint: "skills:DELETE", scopes: ["settings.write"] },
|
|
348
348
|
{ endpoint: "skills:PATCH", scopes: ["settings.write"] },
|
|
349
349
|
|
|
350
|
+
// Memory items
|
|
351
|
+
{ endpoint: "memory-items:GET", scopes: ["settings.read"] },
|
|
352
|
+
{ endpoint: "memory-items:POST", scopes: ["settings.write"] },
|
|
353
|
+
{ endpoint: "memory-items:PATCH", scopes: ["settings.write"] },
|
|
354
|
+
{ endpoint: "memory-items:DELETE", scopes: ["settings.write"] },
|
|
355
|
+
|
|
350
356
|
// Trust rule CRUD management
|
|
351
357
|
{ endpoint: "trust-rules/manage:GET", scopes: ["settings.read"] },
|
|
352
358
|
{ endpoint: "trust-rules/manage:POST", scopes: ["settings.write"] },
|
|
@@ -378,9 +384,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
378
384
|
// Delivery ack
|
|
379
385
|
{ endpoint: "channels/delivery-ack", scopes: ["internal.write"] },
|
|
380
386
|
|
|
381
|
-
// MCP
|
|
382
|
-
{ endpoint: "mcp/reload", scopes: ["settings.write"] },
|
|
383
|
-
|
|
384
387
|
// Migrations
|
|
385
388
|
{ endpoint: "migrations/validate", scopes: ["settings.write"] },
|
|
386
389
|
{ endpoint: "migrations/export", scopes: ["settings.write"] },
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type CanonicalGuardianRequest,
|
|
31
31
|
getCanonicalGuardianRequest,
|
|
32
32
|
getCanonicalGuardianRequestByCode,
|
|
33
|
+
isRequestExpired,
|
|
33
34
|
listCanonicalGuardianRequests,
|
|
34
35
|
} from "../memory/canonical-guardian-store.js";
|
|
35
36
|
import {
|
|
@@ -198,49 +199,50 @@ function findPendingCanonicalRequests(
|
|
|
198
199
|
pendingRequestIds?: string[],
|
|
199
200
|
conversationId?: string,
|
|
200
201
|
): CanonicalGuardianRequest[] {
|
|
202
|
+
let results: CanonicalGuardianRequest[];
|
|
203
|
+
|
|
201
204
|
// When explicit IDs are provided, look them up directly
|
|
202
205
|
if (pendingRequestIds) {
|
|
203
206
|
if (pendingRequestIds.length === 0) {
|
|
204
207
|
return [];
|
|
205
208
|
}
|
|
206
|
-
|
|
209
|
+
results = pendingRequestIds
|
|
207
210
|
.map(getCanonicalGuardianRequest)
|
|
208
211
|
.filter((r): r is CanonicalGuardianRequest => r?.status === "pending");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (actor.actorExternalUserId) {
|
|
213
|
-
return listCanonicalGuardianRequests({
|
|
212
|
+
} else if (actor.actorExternalUserId) {
|
|
213
|
+
// Query by guardian identity when available
|
|
214
|
+
results = listCanonicalGuardianRequests({
|
|
214
215
|
status: "pending",
|
|
215
216
|
guardianExternalUserId: actor.actorExternalUserId,
|
|
216
217
|
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (conversationId) {
|
|
224
|
-
return listCanonicalGuardianRequests({
|
|
218
|
+
} else if (conversationId) {
|
|
219
|
+
// Actors without an actorExternalUserId: scope by conversationId so the NL
|
|
220
|
+
// path can discover pending requests bound to this conversation.
|
|
221
|
+
// Include guardianPrincipalId filter when available so the guardian only
|
|
222
|
+
// sees requests they are authorized to act on.
|
|
223
|
+
results = listCanonicalGuardianRequests({
|
|
225
224
|
status: "pending",
|
|
226
225
|
conversationId,
|
|
227
226
|
...(actor.guardianPrincipalId
|
|
228
227
|
? { guardianPrincipalId: actor.guardianPrincipalId }
|
|
229
228
|
: {}),
|
|
230
229
|
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (actor.guardianPrincipalId) {
|
|
237
|
-
return listCanonicalGuardianRequests({
|
|
230
|
+
} else if (actor.guardianPrincipalId) {
|
|
231
|
+
// Actors with a guardianPrincipalId but no actorExternalUserId or
|
|
232
|
+
// conversationId: query by principal so desktop sessions can still
|
|
233
|
+
// discover pending guardian work via their bound principal.
|
|
234
|
+
results = listCanonicalGuardianRequests({
|
|
238
235
|
status: "pending",
|
|
239
236
|
guardianPrincipalId: actor.guardianPrincipalId,
|
|
240
237
|
});
|
|
238
|
+
} else {
|
|
239
|
+
return [];
|
|
241
240
|
}
|
|
242
241
|
|
|
243
|
-
|
|
242
|
+
// Exclude requests that have passed their expiresAt deadline — they can
|
|
243
|
+
// no longer be resolved and should not trigger disambiguation or NL
|
|
244
|
+
// classification.
|
|
245
|
+
return results.filter((r) => !isRequestExpired(r));
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
/** Map an approval action string to the NL engine's allowed actions for guardians. */
|
|
@@ -135,7 +135,7 @@ import { telegramRouteDefinitions } from "./routes/integrations/telegram.js";
|
|
|
135
135
|
import { twilioRouteDefinitions } from "./routes/integrations/twilio.js";
|
|
136
136
|
import { inviteRouteDefinitions } from "./routes/invite-routes.js";
|
|
137
137
|
import { logExportRouteDefinitions } from "./routes/log-export-routes.js";
|
|
138
|
-
import {
|
|
138
|
+
import { memoryItemRouteDefinitions } from "./routes/memory-item-routes.js";
|
|
139
139
|
import { migrationRouteDefinitions } from "./routes/migration-routes.js";
|
|
140
140
|
import type { PairingHandlerContext } from "./routes/pairing-routes.js";
|
|
141
141
|
import {
|
|
@@ -723,9 +723,9 @@ export class RuntimeHttpServer {
|
|
|
723
723
|
...secretRouteDefinitions(),
|
|
724
724
|
...identityRouteDefinitions(),
|
|
725
725
|
...debugRouteDefinitions(),
|
|
726
|
-
...mcpRouteDefinitions(),
|
|
727
726
|
...usageRouteDefinitions(),
|
|
728
727
|
...workspaceRouteDefinitions(),
|
|
728
|
+
...memoryItemRouteDefinitions(),
|
|
729
729
|
...settingsRouteDefinitions(),
|
|
730
730
|
...scheduleRouteDefinitions({
|
|
731
731
|
sendMessageDeps: this.sendMessageDeps,
|
|
@@ -146,6 +146,12 @@ export function redeemInvite(params: {
|
|
|
146
146
|
return { ok: false, reason: "invalid_token" };
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// Guardian channels must not be reactivated via regular invite redemption —
|
|
150
|
+
// their lifecycle is managed exclusively through the guardian binding flow.
|
|
151
|
+
if (existingContact && existingContact.role === "guardian") {
|
|
152
|
+
return { ok: false, reason: "invalid_token" };
|
|
153
|
+
}
|
|
154
|
+
|
|
149
155
|
// Inactive member reactivation: when the user already has a member record
|
|
150
156
|
// in a non-active state (revoked/pending), reactivate it via upsertContactChannel
|
|
151
157
|
// and consume an invite use atomically. The fresh-member path below also
|
|
@@ -338,6 +344,7 @@ export function redeemVoiceInviteCode(params: {
|
|
|
338
344
|
externalUserId: canonicalCallerId,
|
|
339
345
|
});
|
|
340
346
|
const existingVoiceChannel = voiceContactResult?.channel ?? null;
|
|
347
|
+
const voiceContact = voiceContactResult?.contact ?? null;
|
|
341
348
|
|
|
342
349
|
if (existingVoiceChannel && existingVoiceChannel.status === "active") {
|
|
343
350
|
return {
|
|
@@ -352,13 +359,18 @@ export function redeemVoiceInviteCode(params: {
|
|
|
352
359
|
return { ok: false, reason: "invalid_or_expired" };
|
|
353
360
|
}
|
|
354
361
|
|
|
362
|
+
// Guardian channels must not be reactivated via regular invite redemption —
|
|
363
|
+
// their lifecycle is managed exclusively through the guardian binding flow.
|
|
364
|
+
if (voiceContact && voiceContact.role === "guardian") {
|
|
365
|
+
return { ok: false, reason: "invalid_or_expired" };
|
|
366
|
+
}
|
|
367
|
+
|
|
355
368
|
// Atomic redemption: upsert member + consume invite use in a transaction
|
|
356
369
|
const STALE_INVITE = Symbol("stale_invite");
|
|
357
370
|
let memberId: string | undefined;
|
|
358
371
|
|
|
359
372
|
// Reactivation should not overwrite a guardian-managed nickname (same
|
|
360
373
|
// protection as the token-based redemption path above).
|
|
361
|
-
const voiceContact = voiceContactResult?.contact ?? null;
|
|
362
374
|
const preservedDisplayName = voiceContact?.displayName?.trim().length
|
|
363
375
|
? voiceContact.displayName
|
|
364
376
|
: (invite.friendName ?? undefined);
|
|
@@ -487,6 +499,12 @@ export function redeemInviteByCode(params: {
|
|
|
487
499
|
return { ok: false, reason: "invalid_token" };
|
|
488
500
|
}
|
|
489
501
|
|
|
502
|
+
// Guardian channels must not be reactivated via regular invite redemption —
|
|
503
|
+
// their lifecycle is managed exclusively through the guardian binding flow.
|
|
504
|
+
if (existingContact && existingContact.role === "guardian") {
|
|
505
|
+
return { ok: false, reason: "invalid_token" };
|
|
506
|
+
}
|
|
507
|
+
|
|
490
508
|
// Inactive member reactivation: reactivate via upsertContactChannel and consume
|
|
491
509
|
// an invite use atomically.
|
|
492
510
|
if (existingChannel) {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* /v1/contacts/channels endpoints.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { startInviteCall } from "../calls/call-domain.js";
|
|
11
12
|
import { isChannelId } from "../channels/types.js";
|
|
12
13
|
import {
|
|
13
14
|
createInvite,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
DEFAULT_USER_REFERENCE,
|
|
24
25
|
resolveGuardianName,
|
|
25
26
|
} from "../prompts/user-reference.js";
|
|
27
|
+
import { getLogger } from "../util/logger.js";
|
|
26
28
|
import { isValidE164 } from "../util/phone.js";
|
|
27
29
|
import { generateVoiceCode, hashVoiceCode } from "../util/voice-code.js";
|
|
28
30
|
import {
|
|
@@ -37,6 +39,8 @@ import {
|
|
|
37
39
|
type VoiceRedemptionOutcome,
|
|
38
40
|
} from "./invite-redemption-service.js";
|
|
39
41
|
|
|
42
|
+
const log = getLogger("invite-service");
|
|
43
|
+
|
|
40
44
|
// ---------------------------------------------------------------------------
|
|
41
45
|
// Response shapes — used by both HTTP routes and message handlers
|
|
42
46
|
// ---------------------------------------------------------------------------
|
|
@@ -250,6 +254,27 @@ export async function createIngressInvite(params: {
|
|
|
250
254
|
});
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
// For voice invites with a known phone number, initiate an outbound call
|
|
258
|
+
// so the contact is prompted to enter their code immediately.
|
|
259
|
+
if (
|
|
260
|
+
params.sourceChannel === "phone" &&
|
|
261
|
+
params.expectedExternalUserId &&
|
|
262
|
+
params.friendName &&
|
|
263
|
+
effectiveGuardianName
|
|
264
|
+
) {
|
|
265
|
+
// Fire-and-forget: don't block invite creation on call initiation
|
|
266
|
+
startInviteCall({
|
|
267
|
+
phoneNumber: params.expectedExternalUserId,
|
|
268
|
+
friendName: params.friendName,
|
|
269
|
+
guardianName: effectiveGuardianName,
|
|
270
|
+
}).catch((err) => {
|
|
271
|
+
log.warn(
|
|
272
|
+
{ err, inviteId: invite.id },
|
|
273
|
+
"Failed to initiate outbound invite call",
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
253
278
|
// Voice invites must not expose the token — callers must redeem via the
|
|
254
279
|
// identity-bound voice code flow, not the generic token redemption path.
|
|
255
280
|
return {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* host_bash_request, host_file_request, or host_cu_request, the onEvent
|
|
7
7
|
* callback registers the interaction here. Standalone HTTP endpoints
|
|
8
8
|
* (/v1/confirm, /v1/secret, /v1/trust-rules, /v1/host-bash-result,
|
|
9
|
-
* /v1/host-file-result, /v1/host-cu-result) look up the session from
|
|
10
|
-
*
|
|
9
|
+
* /v1/host-file-result, /v1/host-cu-result) look up the session from this
|
|
10
|
+
* tracker to resolve the interaction.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { Session } from "../daemon/session.js";
|