@vellumai/assistant 0.10.3-staging.2 → 0.10.4-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/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The gateway resolves a per-actor verdict from its ACL DB and stamps it onto
|
|
6
6
|
* inbound `sourceMetadata`. These pure mappers turn that verdict into the same
|
|
7
|
-
* {@link TrustContext}
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* locally by contactId.
|
|
7
|
+
* {@link TrustContext} the local resolver would have produced — ACL + identity
|
|
8
|
+
* only. INFO fields (notes, userFile, contactType, interactionCount) are never
|
|
9
|
+
* carried on the wire; the consumer re-joins them locally by contactId.
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
import type { TrustVerdict } from "@vellumai/gateway-client";
|
|
@@ -18,12 +17,12 @@ import type {
|
|
|
18
17
|
ChannelPolicy,
|
|
19
18
|
ChannelStatus,
|
|
20
19
|
ContactChannel,
|
|
20
|
+
ContactRole,
|
|
21
21
|
ContactWithChannels,
|
|
22
22
|
} from "../contacts/types.js";
|
|
23
23
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
24
24
|
import type { ActorTrustContext } from "./actor-trust-resolver.js";
|
|
25
25
|
import { toTrustContext } from "./actor-trust-resolver.js";
|
|
26
|
-
import type { ResolvedMember } from "./routes/inbound-stages/acl-enforcement.js";
|
|
27
26
|
|
|
28
27
|
export interface TrustVerdictTransport {
|
|
29
28
|
sourceChannel: ChannelId;
|
|
@@ -66,7 +65,7 @@ export function actorTrustContextFromVerdict(
|
|
|
66
65
|
// deny/escalate. Null for memberless verdicts. Text path is unaffected:
|
|
67
66
|
// toTrustContext derives the same member fields trustContextFromVerdict
|
|
68
67
|
// already stamps.
|
|
69
|
-
memberRecord:
|
|
68
|
+
memberRecord: memberRecordFromVerdict(verdict),
|
|
70
69
|
trustClass: verdict.trustClass,
|
|
71
70
|
actorMetadata: {
|
|
72
71
|
identifier,
|
|
@@ -99,11 +98,11 @@ export function trustContextFromVerdict(
|
|
|
99
98
|
// Stamp the verdict's ACL member fields onto the context so downstream turn
|
|
100
99
|
// assembly reads member status/policy from the verdict rather than a local
|
|
101
100
|
// re-resolution. The contact ID anchors the local info-only join.
|
|
102
|
-
const member =
|
|
101
|
+
const member = verdictMemberFromVerdict(verdict);
|
|
103
102
|
if (member) {
|
|
104
|
-
context.requesterContactId = member.
|
|
105
|
-
context.memberStatus = channelStatusToMemberStatus(member.
|
|
106
|
-
context.memberPolicy = member.
|
|
103
|
+
context.requesterContactId = member.contactId;
|
|
104
|
+
context.memberStatus = channelStatusToMemberStatus(member.status);
|
|
105
|
+
context.memberPolicy = member.policy;
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
return context;
|
|
@@ -111,7 +110,7 @@ export function trustContextFromVerdict(
|
|
|
111
110
|
|
|
112
111
|
/**
|
|
113
112
|
* True when the verdict carries a member identity (contactId or channelId),
|
|
114
|
-
* regardless of whether that member resolves to a usable {@link
|
|
113
|
+
* regardless of whether that member resolves to a usable {@link VerdictMember}.
|
|
115
114
|
*/
|
|
116
115
|
export function verdictHasMemberIdentity(verdict: TrustVerdict): boolean {
|
|
117
116
|
return !!(verdict.contactId || verdict.channelId);
|
|
@@ -119,13 +118,13 @@ export function verdictHasMemberIdentity(verdict: TrustVerdict): boolean {
|
|
|
119
118
|
|
|
120
119
|
/**
|
|
121
120
|
* True when the verdict claims a member identity but that member can't be
|
|
122
|
-
*
|
|
121
|
+
* resolved (partial/mixed-version verdict). Such a verdict is unusable —
|
|
123
122
|
* callers fall back to local resolution.
|
|
124
123
|
*/
|
|
125
124
|
export function verdictMemberUnresolvable(verdict: TrustVerdict): boolean {
|
|
126
125
|
return (
|
|
127
126
|
verdictHasMemberIdentity(verdict) &&
|
|
128
|
-
|
|
127
|
+
verdictMemberFromVerdict(verdict) === null
|
|
129
128
|
);
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -167,8 +166,8 @@ export interface VerdictMember {
|
|
|
167
166
|
/**
|
|
168
167
|
* Extract the narrow {@link VerdictMember} ACL view from a gateway verdict.
|
|
169
168
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
169
|
+
* Guards on contactId/channelId presence + known status/policy enums, failing
|
|
170
|
+
* closed to null otherwise.
|
|
172
171
|
*/
|
|
173
172
|
export function verdictMemberFromVerdict(
|
|
174
173
|
verdict: TrustVerdict,
|
|
@@ -190,38 +189,26 @@ export function verdictMemberFromVerdict(
|
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
/**
|
|
193
|
-
* Build
|
|
192
|
+
* Build the voice-path {@link ActorTrustContext.memberRecord} from a gateway
|
|
193
|
+
* verdict's narrow ACL view.
|
|
194
194
|
*
|
|
195
195
|
* ACL + identity only; info fields are placeholders, re-joined locally by
|
|
196
|
-
* contactId. Returns null for memberless verdicts.
|
|
196
|
+
* contactId. Returns null for memberless/unresolvable verdicts.
|
|
197
197
|
*/
|
|
198
|
-
|
|
198
|
+
function memberRecordFromVerdict(
|
|
199
199
|
verdict: TrustVerdict,
|
|
200
|
-
):
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// (fail-closed): a partial/mixed-version verdict (absent OR
|
|
204
|
-
// present-but-unknown ACL value) must not synthesize an active/allow channel
|
|
205
|
-
// that would skip ingress ACL gates.
|
|
206
|
-
if (!verdict.status || !verdict.policy) return null;
|
|
207
|
-
if (!isChannelStatus(verdict.status) || !isChannelPolicy(verdict.policy)) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
200
|
+
): ActorTrustContext["memberRecord"] {
|
|
201
|
+
const member = verdictMemberFromVerdict(verdict);
|
|
202
|
+
if (!member) return null;
|
|
210
203
|
|
|
211
204
|
const channel: ContactChannel = {
|
|
212
|
-
id:
|
|
213
|
-
contactId:
|
|
205
|
+
id: member.channelId,
|
|
206
|
+
contactId: member.contactId,
|
|
214
207
|
type: verdict.type ?? "",
|
|
215
208
|
address: verdict.address ?? "",
|
|
216
209
|
isPrimary: false,
|
|
217
210
|
externalChatId: verdict.externalChatId ?? null,
|
|
218
|
-
status: verdict.status,
|
|
219
|
-
policy: verdict.policy,
|
|
220
|
-
verifiedAt: verdict.verifiedAt ?? null,
|
|
221
|
-
verifiedVia: verdict.verifiedVia ?? null,
|
|
222
211
|
inviteId: null,
|
|
223
|
-
revokedReason: null,
|
|
224
|
-
blockedReason: null,
|
|
225
212
|
lastSeenAt: null,
|
|
226
213
|
interactionCount: 0,
|
|
227
214
|
lastInteraction: null,
|
|
@@ -229,20 +216,28 @@ export function resolvedMemberFromVerdict(
|
|
|
229
216
|
createdAt: 0,
|
|
230
217
|
};
|
|
231
218
|
|
|
219
|
+
const role: ContactRole =
|
|
220
|
+
verdict.trustClass === "guardian" ? "guardian" : "contact";
|
|
221
|
+
|
|
232
222
|
const contact: ContactWithChannels = {
|
|
233
|
-
id:
|
|
234
|
-
displayName:
|
|
223
|
+
id: member.contactId,
|
|
224
|
+
displayName: member.displayName ?? "",
|
|
235
225
|
notes: null,
|
|
226
|
+
role,
|
|
236
227
|
lastInteraction: null,
|
|
237
228
|
interactionCount: 0,
|
|
238
229
|
createdAt: 0,
|
|
239
230
|
updatedAt: 0,
|
|
240
|
-
role: verdict.trustClass === "guardian" ? "guardian" : "contact",
|
|
241
231
|
contactType: "human",
|
|
242
|
-
principalId: verdict.guardianPrincipalId ?? null,
|
|
243
232
|
userFile: null,
|
|
244
233
|
channels: [channel],
|
|
245
234
|
};
|
|
246
235
|
|
|
247
|
-
return {
|
|
236
|
+
return {
|
|
237
|
+
contact,
|
|
238
|
+
channel,
|
|
239
|
+
status: member.status,
|
|
240
|
+
policy: member.policy,
|
|
241
|
+
role,
|
|
242
|
+
};
|
|
248
243
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { advisorRequestText, buildAdvisorSystem } from "../consult-prompt.js";
|
|
4
|
+
|
|
5
|
+
describe("buildAdvisorSystem", () => {
|
|
6
|
+
test("includes the senior-advisor framing", () => {
|
|
7
|
+
const prompt = buildAdvisorSystem(null);
|
|
8
|
+
expect(prompt).toContain("senior advisor");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("embeds the parent prompt inside <agent_system_prompt> when provided", () => {
|
|
12
|
+
const prompt = buildAdvisorSystem("You are a coding agent.");
|
|
13
|
+
expect(prompt).toContain(
|
|
14
|
+
"<agent_system_prompt>\nYou are a coding agent.\n</agent_system_prompt>",
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("omits the <agent_system_prompt> block when no parent prompt is given", () => {
|
|
19
|
+
const prompt = buildAdvisorSystem(null);
|
|
20
|
+
expect(prompt).not.toContain("<agent_system_prompt>");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("advisorRequestText", () => {
|
|
25
|
+
test("is non-empty and asks for focused strategic guidance", () => {
|
|
26
|
+
const text = advisorRequestText();
|
|
27
|
+
expect(text.length).toBeGreaterThan(0);
|
|
28
|
+
expect(text).toContain("focused strategic guidance");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("imposes no length cap", () => {
|
|
32
|
+
// The request must not constrain how much the advisor writes.
|
|
33
|
+
expect(advisorRequestText()).not.toContain("words");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import type { ContentBlock, Message } from "
|
|
4
|
-
import {
|
|
3
|
+
import type { ContentBlock, Message } from "../../providers/types.js";
|
|
4
|
+
import { sanitizeConsultTranscript } from "../consult-transcript.js";
|
|
5
5
|
|
|
6
6
|
const text = (t: string): ContentBlock => ({ type: "text", text: t });
|
|
7
7
|
|
|
8
|
-
describe("
|
|
8
|
+
describe("sanitizeConsultTranscript", () => {
|
|
9
9
|
test("drops thinking and redacted-thinking blocks", () => {
|
|
10
10
|
const messages: Message[] = [
|
|
11
11
|
{ role: "user", content: [text("do the task")] },
|
|
@@ -18,12 +18,34 @@ describe("toAdvisorMessages", () => {
|
|
|
18
18
|
],
|
|
19
19
|
},
|
|
20
20
|
];
|
|
21
|
-
const out =
|
|
21
|
+
const out = sanitizeConsultTranscript(messages);
|
|
22
22
|
expect(out).toHaveLength(2);
|
|
23
23
|
expect(out[1].content).toEqual([text("here is my answer")]);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test("
|
|
26
|
+
test("drops a file block", () => {
|
|
27
|
+
const messages: Message[] = [
|
|
28
|
+
{
|
|
29
|
+
role: "user",
|
|
30
|
+
content: [
|
|
31
|
+
text("read this"),
|
|
32
|
+
{
|
|
33
|
+
type: "file",
|
|
34
|
+
source: {
|
|
35
|
+
type: "base64",
|
|
36
|
+
media_type: "application/pdf",
|
|
37
|
+
data: "z",
|
|
38
|
+
filename: "f.pdf",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const out = sanitizeConsultTranscript(messages);
|
|
45
|
+
expect(out[0].content).toEqual([text("read this")]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("strips the pending tool_use from the final assistant turn", () => {
|
|
27
49
|
const messages: Message[] = [
|
|
28
50
|
{ role: "user", content: [text("task")] },
|
|
29
51
|
{
|
|
@@ -34,7 +56,7 @@ describe("toAdvisorMessages", () => {
|
|
|
34
56
|
],
|
|
35
57
|
},
|
|
36
58
|
];
|
|
37
|
-
const out =
|
|
59
|
+
const out = sanitizeConsultTranscript(messages);
|
|
38
60
|
expect(out[1].content).toEqual([text("let me consult the advisor")]);
|
|
39
61
|
});
|
|
40
62
|
|
|
@@ -51,7 +73,7 @@ describe("toAdvisorMessages", () => {
|
|
|
51
73
|
},
|
|
52
74
|
{ role: "assistant", content: [text("done")] },
|
|
53
75
|
];
|
|
54
|
-
const out =
|
|
76
|
+
const out = sanitizeConsultTranscript(messages);
|
|
55
77
|
expect(out[1].content[0]).toEqual({
|
|
56
78
|
type: "tool_use",
|
|
57
79
|
id: "a",
|
|
@@ -87,7 +109,7 @@ describe("toAdvisorMessages", () => {
|
|
|
87
109
|
},
|
|
88
110
|
{ role: "assistant", content: [text("found it; here is the answer")] },
|
|
89
111
|
];
|
|
90
|
-
const out =
|
|
112
|
+
const out = sanitizeConsultTranscript(messages);
|
|
91
113
|
const flat = out.flatMap((m) => m.content);
|
|
92
114
|
expect(flat.some((b) => b.type === "server_tool_use")).toBe(false);
|
|
93
115
|
expect(flat.some((b) => b.type === "web_search_tool_result")).toBe(false);
|
|
@@ -103,7 +125,7 @@ describe("toAdvisorMessages", () => {
|
|
|
103
125
|
const messages: Message[] = [
|
|
104
126
|
{ role: "user", content: [text("look at this"), img] },
|
|
105
127
|
];
|
|
106
|
-
const out =
|
|
128
|
+
const out = sanitizeConsultTranscript(messages);
|
|
107
129
|
expect(out[0].content).toEqual([text("look at this"), img]);
|
|
108
130
|
});
|
|
109
131
|
|
|
@@ -136,7 +158,7 @@ describe("toAdvisorMessages", () => {
|
|
|
136
158
|
],
|
|
137
159
|
},
|
|
138
160
|
];
|
|
139
|
-
const out =
|
|
161
|
+
const out = sanitizeConsultTranscript(messages);
|
|
140
162
|
expect(out[0].content[0]).toEqual({
|
|
141
163
|
type: "tool_result",
|
|
142
164
|
tool_use_id: "a",
|
|
@@ -144,4 +166,19 @@ describe("toAdvisorMessages", () => {
|
|
|
144
166
|
contentBlocks: [img],
|
|
145
167
|
});
|
|
146
168
|
});
|
|
169
|
+
|
|
170
|
+
test("drops messages that are empty after sanitization", () => {
|
|
171
|
+
const messages: Message[] = [
|
|
172
|
+
{ role: "user", content: [text("task")] },
|
|
173
|
+
{
|
|
174
|
+
role: "assistant",
|
|
175
|
+
content: [{ type: "thinking", thinking: "secret", signature: "sig" }],
|
|
176
|
+
},
|
|
177
|
+
{ role: "assistant", content: [text("answer")] },
|
|
178
|
+
];
|
|
179
|
+
const out = sanitizeConsultTranscript(messages);
|
|
180
|
+
expect(out).toHaveLength(2);
|
|
181
|
+
expect(out[0].content).toEqual([text("task")]);
|
|
182
|
+
expect(out[1].content).toEqual([text("answer")]);
|
|
183
|
+
});
|
|
147
184
|
});
|
|
@@ -1,47 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* - `
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the consult itself.
|
|
2
|
+
* Advice-framing prompt fragments for the advisor consult:
|
|
3
|
+
* - `buildAdvisorSystem` — the advisor-facing system prompt; frames the role and,
|
|
4
|
+
* for context, embeds the executor's own system prompt.
|
|
5
|
+
* - `advisorRequestText` — the final user turn appended to the transcript asking
|
|
6
|
+
* for guidance.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
/** Idempotency marker; the steering block is appended at the end of the prompt. */
|
|
11
|
-
export const STEERING_MARKER = "<!-- advisor:steering -->";
|
|
12
|
-
|
|
13
|
-
const STEERING_BODY = `You have an \`advisor\` tool backed by a stronger reviewer model. It takes NO parameters — calling it forwards your entire conversation automatically (the task, every tool call, every result). Call advisor BEFORE you start building or implementing: once you understand what's being asked, consult it to shape the plan — it can lay out the plan when you don't have one yet, or pressure-test and sharpen a plan you've already drafted. Orient yourself first (read the relevant files, understand the task), then call advisor before you commit to an approach and start producing work. Also call it when you get stuck, when you're weighing a change in direction, and once before you declare the task done. Give its guidance serious weight; only override it when primary-source evidence contradicts a specific claim, and say so when you do.`;
|
|
14
|
-
|
|
15
|
-
const ADVISOR_STEERING = `${STEERING_MARKER}\n${STEERING_BODY}`;
|
|
16
|
-
|
|
17
|
-
/** Append the steering block to the executor's system prompt (idempotent). */
|
|
18
|
-
export function appendSteering(systemPrompt: string | null): string | null {
|
|
19
|
-
if (systemPrompt === null) return null;
|
|
20
|
-
if (systemPrompt.includes(STEERING_MARKER)) return systemPrompt;
|
|
21
|
-
return `${systemPrompt}\n\n${ADVISOR_STEERING}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Remove a previously-appended steering block, recovering the original prompt. */
|
|
25
|
-
export function stripSteering(systemPrompt: string | null): string | null {
|
|
26
|
-
if (systemPrompt === null) return null;
|
|
27
|
-
const idx = systemPrompt.indexOf(STEERING_MARKER);
|
|
28
|
-
if (idx === -1) return systemPrompt;
|
|
29
|
-
return systemPrompt.slice(0, idx).trimEnd();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
9
|
/**
|
|
33
10
|
* System prompt for the advisor sub-call. Frames the advisor's role and, for
|
|
34
11
|
* context, quotes the executor's own system prompt (as the advisor tool does —
|
|
35
12
|
* the advisor sees the system prompt as context about the executor's task).
|
|
36
|
-
*
|
|
37
|
-
* `runtimeContext`, when present, carries the agent's situational context that
|
|
38
|
-
* lives outside its system prompt — available tools and skills, workspace /
|
|
39
|
-
* project context, and recalled memory (see `buildAdvisorContext`) — so the
|
|
40
|
-
* advisor can ground its recommendations in what the agent can actually do.
|
|
41
13
|
*/
|
|
42
14
|
export function buildAdvisorSystem(
|
|
43
15
|
originalSystemPrompt: string | null,
|
|
44
|
-
runtimeContext?: string | null,
|
|
45
16
|
): string {
|
|
46
17
|
const base = `You are a senior advisor consulted by another AI agent working on a task — most often at the planning stage, before it starts building, but sometimes partway through. The entire conversation above is the agent's working context: its task or goal, every tool call it has made, and every result it has seen. The agent has paused to consult you because you bring a second, independent perspective it cannot get from inside its own reasoning loop. Your job is to maximize its odds of completing the task correctly and efficiently.
|
|
47
18
|
|
|
@@ -64,16 +35,23 @@ Write as much as the guidance genuinely needs, and no more.`;
|
|
|
64
35
|
if (originalSystemPrompt) {
|
|
65
36
|
prompt += `\n\nFor context, the agent is operating under this system prompt:\n<agent_system_prompt>\n${originalSystemPrompt}\n</agent_system_prompt>`;
|
|
66
37
|
}
|
|
67
|
-
if (runtimeContext) {
|
|
68
|
-
prompt += `\n\nThe agent's runtime context — the tools and skills available to it, the loaded workspace/project context, and relevant memory — follows. Ground your recommendations in what the agent can actually do and what is around it; reference specific tools, skills, files, or memory where relevant.\n<agent_runtime_context>\n${runtimeContext}\n</agent_runtime_context>`;
|
|
69
|
-
}
|
|
70
38
|
return prompt;
|
|
71
39
|
}
|
|
72
40
|
|
|
73
41
|
/**
|
|
74
42
|
* The final user turn appended to the transcript for the advisor sub-call. Asks
|
|
75
43
|
* for guidance; imposes no length limit — the advisor decides how much to say.
|
|
44
|
+
*
|
|
45
|
+
* `agentRequest` is the executing agent's own `objective` from the
|
|
46
|
+
* `subagent_spawn` call — the agent's framing of what it wants weighed in on.
|
|
47
|
+
* It is included verbatim because (a) the agent naturally states the task there,
|
|
48
|
+
* and (b) the inherited transcript can be thin (e.g. a wake turn whose task
|
|
49
|
+
* lives in memory rather than a user message), so the request text is often the
|
|
50
|
+
* advisor's clearest signal of what is actually being asked.
|
|
76
51
|
*/
|
|
77
|
-
export function advisorRequestText(): string {
|
|
78
|
-
|
|
52
|
+
export function advisorRequestText(agentRequest?: string): string {
|
|
53
|
+
const base = `Review the conversation above — the task, the tool calls, and their results — and give focused strategic guidance on how to proceed.`;
|
|
54
|
+
const trimmed = agentRequest?.trim();
|
|
55
|
+
if (!trimmed) return base;
|
|
56
|
+
return `${base}\n\nThe agent described what it wants your input on:\n<agent_request>\n${trimmed}\n</agent_request>\nTreat this as the agent's framing of the task. If it conflicts with the transcript above, say so; if the transcript is sparse, rely on it.`;
|
|
79
57
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* advisor
|
|
2
|
+
* Sanitize an inherited parent transcript before it is injected into a
|
|
3
|
+
* tool-less advisor consult subagent.
|
|
4
|
+
*
|
|
5
|
+
* `Conversation.injectInheritedContext` injects the parent's messages VERBATIM
|
|
6
|
+
* with no sanitization, so the advisor path must run this over the parent
|
|
7
|
+
* messages before they are injected.
|
|
4
8
|
*
|
|
5
9
|
* Strips blocks the advisor shouldn't (or can't) replay:
|
|
6
10
|
* - thinking / redacted-thinking (the advisor tool drops thinking),
|
|
@@ -16,13 +20,17 @@
|
|
|
16
20
|
* Visual tasks depend on it; the advisor profile is expected to be vision-capable.
|
|
17
21
|
*
|
|
18
22
|
* It also strips the *pending* client tool calls from the final assistant turn:
|
|
19
|
-
* at capture time
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* at capture time the last assistant message can carry a `tool_use` with no
|
|
24
|
+
* matching `tool_result` yet, so sending it would be a dangling call. Earlier,
|
|
25
|
+
* completed `tool_use` / `tool_result` pairs are preserved intact.
|
|
26
|
+
*
|
|
27
|
+
* In-flight-turn parity — ensuring the assistant's just-written plan from the
|
|
28
|
+
* current turn is included while its dangling tool_use is stripped — is handled
|
|
29
|
+
* by the consumer (the wiring that calls this before injection); this function
|
|
30
|
+
* only strips a dangling `tool_use` IF it appears on the final assistant turn.
|
|
23
31
|
*/
|
|
24
32
|
|
|
25
|
-
import type { ContentBlock, Message } from "
|
|
33
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
26
34
|
|
|
27
35
|
/** Drop disallowed blocks; recursively sanitize tool_result content. `null` = drop. */
|
|
28
36
|
function sanitize(block: ContentBlock): ContentBlock | null {
|
|
@@ -51,7 +59,9 @@ function sanitize(block: ContentBlock): ContentBlock | null {
|
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
export function
|
|
62
|
+
export function sanitizeConsultTranscript(
|
|
63
|
+
messages: ReadonlyArray<Message>,
|
|
64
|
+
): Message[] {
|
|
55
65
|
const out: Message[] = [];
|
|
56
66
|
const lastIndex = messages.length - 1;
|
|
57
67
|
|
package/src/subagent/index.ts
CHANGED