@vellumai/assistant 0.10.3 → 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
|
@@ -520,12 +520,14 @@ export async function runAgentLoopImpl(
|
|
|
520
520
|
let turnStarted = false;
|
|
521
521
|
const state = createEventHandlerState();
|
|
522
522
|
let persistedErrorAssistantMessage = false;
|
|
523
|
+
let deletedReservedAssistantMessage = false;
|
|
523
524
|
|
|
524
525
|
const publishLoopMessagesChanged = (): void => {
|
|
525
526
|
if (
|
|
526
527
|
state.lastAssistantMessageId ||
|
|
527
528
|
state.persistedToolUseIds.size > 0 ||
|
|
528
|
-
persistedErrorAssistantMessage
|
|
529
|
+
persistedErrorAssistantMessage ||
|
|
530
|
+
deletedReservedAssistantMessage
|
|
529
531
|
) {
|
|
530
532
|
publishConversationMessagesChanged(ctx.conversationId);
|
|
531
533
|
}
|
|
@@ -1151,23 +1153,22 @@ export async function runAgentLoopImpl(
|
|
|
1151
1153
|
!abortController.signal.aborted &&
|
|
1152
1154
|
!yieldedForHandoff
|
|
1153
1155
|
) {
|
|
1154
|
-
// Drop any reservation stranded by the failed LLM call
|
|
1155
|
-
//
|
|
1156
|
-
//
|
|
1157
|
-
//
|
|
1158
|
-
//
|
|
1159
|
-
//
|
|
1160
|
-
//
|
|
1161
|
-
// (`syncLastAssistantMessageToDisk`) would mis-target the empty
|
|
1162
|
-
// row. After delete we set `lastAssistantMessageId` to the new
|
|
1163
|
-
// error row's id so the post-loop emission paths still point at
|
|
1164
|
-
// a real message.
|
|
1156
|
+
// Drop any reservation stranded by the failed LLM call. The B3
|
|
1157
|
+
// pre-allocation path reserves an empty assistant row at
|
|
1158
|
+
// `llm_call_started`; when the call exits through the provider-error
|
|
1159
|
+
// branch (no `message_complete`), `assistantRowAwaitingFinalization`
|
|
1160
|
+
// stays true. Without this delete the transcript would carry an empty
|
|
1161
|
+
// reserved row, and downstream sync (`syncLastAssistantMessageToDisk`)
|
|
1162
|
+
// would target it.
|
|
1165
1163
|
if (
|
|
1166
1164
|
state.assistantRowAwaitingFinalization &&
|
|
1167
1165
|
state.lastAssistantMessageId
|
|
1168
1166
|
) {
|
|
1169
1167
|
try {
|
|
1170
1168
|
deleteMessageById(state.lastAssistantMessageId);
|
|
1169
|
+
deletedReservedAssistantMessage = true;
|
|
1170
|
+
state.lastAssistantMessageId = undefined;
|
|
1171
|
+
state.assistantRowAwaitingFinalization = false;
|
|
1171
1172
|
} catch (err) {
|
|
1172
1173
|
rlog.warn(
|
|
1173
1174
|
{ err, messageId: state.lastAssistantMessageId },
|
|
@@ -1175,57 +1176,63 @@ export async function runAgentLoopImpl(
|
|
|
1175
1176
|
);
|
|
1176
1177
|
}
|
|
1177
1178
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
// message id. The previous reservation (if any) was already deleted
|
|
1201
|
-
// above. Mark finalization complete so the next LLM call in this run
|
|
1202
|
-
// (or a downstream handler) doesn't try to clean up an id that
|
|
1203
|
-
// already corresponds to a finalized row.
|
|
1204
|
-
state.lastAssistantMessageId = errorRow.id;
|
|
1205
|
-
state.assistantRowAwaitingFinalization = false;
|
|
1206
|
-
newMessages.push(errorAssistantMessage);
|
|
1207
|
-
// Pipe the just-assigned message id into any orphaned LLM request log
|
|
1208
|
-
// row(s) for this turn. The success path links rows via
|
|
1209
|
-
// `handleMessageComplete` -> `backfillMessageIdOnLogs`, but provider-
|
|
1210
|
-
// failure turns never fire `message_complete` (the synthetic assistant
|
|
1211
|
-
// message is persisted directly above), so without this call the rows
|
|
1212
|
-
// from `handleProviderError` stay with `message_id IS NULL` and a
|
|
1213
|
-
// later turn's backfill sweep would wrong-attach them to that turn's
|
|
1214
|
-
// assistant message. Scope is per-conversation, so concurrent runs on
|
|
1215
|
-
// other conversations cannot collide. Non-fatal — a DB hiccup must
|
|
1216
|
-
// not escalate a provider rejection into a turn-level throw.
|
|
1217
|
-
try {
|
|
1218
|
-
backfillMessageIdOnLogs(ctx.conversationId, errorRow.id);
|
|
1219
|
-
} catch (err) {
|
|
1220
|
-
rlog.warn(
|
|
1221
|
-
{ err },
|
|
1222
|
-
"Failed to backfill message_id on provider-error LLM request logs (non-fatal)",
|
|
1179
|
+
if (!state.persistProviderErrorAsAssistantMessage) {
|
|
1180
|
+
state.assistantRowAwaitingFinalization = false;
|
|
1181
|
+
state.lastAssistantMessageId = undefined;
|
|
1182
|
+
} else {
|
|
1183
|
+
const errChannelMeta = {
|
|
1184
|
+
...provenanceFromTrustContext(ctx.trustContext),
|
|
1185
|
+
userMessageChannel: capturedTurnChannelContext.userMessageChannel,
|
|
1186
|
+
assistantMessageChannel:
|
|
1187
|
+
capturedTurnChannelContext.assistantMessageChannel,
|
|
1188
|
+
userMessageInterface:
|
|
1189
|
+
capturedTurnInterfaceContext.userMessageInterface,
|
|
1190
|
+
assistantMessageInterface:
|
|
1191
|
+
capturedTurnInterfaceContext.assistantMessageInterface,
|
|
1192
|
+
};
|
|
1193
|
+
const errorAssistantMessage = createAssistantMessage(
|
|
1194
|
+
state.providerErrorUserMessage,
|
|
1195
|
+
);
|
|
1196
|
+
const errorRow = await addMessage(
|
|
1197
|
+
ctx.conversationId,
|
|
1198
|
+
"assistant",
|
|
1199
|
+
JSON.stringify(errorAssistantMessage.content),
|
|
1200
|
+
{ metadata: errChannelMeta },
|
|
1223
1201
|
);
|
|
1202
|
+
persistedErrorAssistantMessage = true;
|
|
1203
|
+
// Repoint `lastAssistantMessageId` at the synthetic error row so the
|
|
1204
|
+
// post-loop sync, attachment resolution, and `message_complete`/
|
|
1205
|
+
// `generation_handoff` emissions all reference a real, persisted
|
|
1206
|
+
// message id. The previous reservation (if any) was already deleted
|
|
1207
|
+
// above. Mark finalization complete so the next LLM call in this run
|
|
1208
|
+
// (or a downstream handler) doesn't try to clean up an id that
|
|
1209
|
+
// already corresponds to a finalized row.
|
|
1210
|
+
state.lastAssistantMessageId = errorRow.id;
|
|
1211
|
+
state.assistantRowAwaitingFinalization = false;
|
|
1212
|
+
newMessages.push(errorAssistantMessage);
|
|
1213
|
+
// Pipe the just-assigned message id into any orphaned LLM request log
|
|
1214
|
+
// row(s) for this turn. The success path links rows via
|
|
1215
|
+
// `handleMessageComplete` -> `backfillMessageIdOnLogs`, but provider-
|
|
1216
|
+
// failure turns never fire `message_complete` (the synthetic assistant
|
|
1217
|
+
// message is persisted directly above), so without this call the rows
|
|
1218
|
+
// from `handleProviderError` stay with `message_id IS NULL` and a
|
|
1219
|
+
// later turn's backfill sweep would wrong-attach them to that turn's
|
|
1220
|
+
// assistant message. Scope is per-conversation, so concurrent runs on
|
|
1221
|
+
// other conversations cannot collide. Non-fatal — a DB hiccup must
|
|
1222
|
+
// not escalate a provider rejection into a turn-level throw.
|
|
1223
|
+
try {
|
|
1224
|
+
backfillMessageIdOnLogs(ctx.conversationId, errorRow.id);
|
|
1225
|
+
} catch (err) {
|
|
1226
|
+
rlog.warn(
|
|
1227
|
+
{ err },
|
|
1228
|
+
"Failed to backfill message_id on provider-error LLM request logs (non-fatal)",
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
// Do NOT send assistant_text_delta here — handleProviderError already
|
|
1232
|
+
// emitted a conversation_error event for this same error text, and the
|
|
1233
|
+
// client renders it as an InlineChatErrorAlert. Sending a text delta
|
|
1234
|
+
// would create a duplicate plain-text bubble below the alert card.
|
|
1224
1235
|
}
|
|
1225
|
-
// Do NOT send assistant_text_delta here — handleProviderError already
|
|
1226
|
-
// emitted a conversation_error event for this same error text, and the
|
|
1227
|
-
// client renders it as an InlineChatErrorAlert. Sending a text delta
|
|
1228
|
-
// would create a duplicate plain-text bubble below the alert card.
|
|
1229
1236
|
}
|
|
1230
1237
|
|
|
1231
1238
|
// Base persisted into `ctx.messages` is the loop's own returned history
|
|
@@ -315,20 +315,17 @@ function classifyCore(
|
|
|
315
315
|
}
|
|
316
316
|
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
317
317
|
// Both managed-proxy and user-key 401/403s reach this branch.
|
|
318
|
-
// Managed-proxy routes through the assistant API key
|
|
319
|
-
//
|
|
320
|
-
// user-set credential that the upstream provider
|
|
321
|
-
// `PROVIDER_INVALID_KEY`
|
|
322
|
-
//
|
|
323
|
-
// which only fires when the key is genuinely missing — see
|
|
324
|
-
// `providerNotConfiguredClassification`).
|
|
318
|
+
// Managed-proxy routes through the assistant API key; if that
|
|
319
|
+
// credential is stale, the user cannot fix it from model settings.
|
|
320
|
+
// Everything else is a user-set credential that the upstream provider
|
|
321
|
+
// rejected, so emit `PROVIDER_INVALID_KEY` and let the chat banner point
|
|
322
|
+
// at Settings.
|
|
325
323
|
const providerName = error.provider;
|
|
326
324
|
if (getProviderRoutingSource(providerName) === "managed-proxy") {
|
|
327
325
|
return {
|
|
328
326
|
code: "MANAGED_KEY_INVALID",
|
|
329
|
-
userMessage:
|
|
330
|
-
|
|
331
|
-
retryable: true,
|
|
327
|
+
userMessage: "Couldn't refresh assistant credentials.",
|
|
328
|
+
retryable: false,
|
|
332
329
|
errorCategory: "managed_key_invalid",
|
|
333
330
|
};
|
|
334
331
|
}
|
|
@@ -17,7 +17,6 @@ import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
|
17
17
|
import { getBindingByConversation } from "../memory/external-conversation-store.js";
|
|
18
18
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
19
19
|
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
20
|
-
import { advisorEnabledForProfile } from "../plugins/defaults/advisor/advisor-gate.js";
|
|
21
20
|
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
22
21
|
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
23
22
|
import { registerConversationSender } from "../tools/browser/browser-screencast.js";
|
|
@@ -575,15 +574,6 @@ export function isToolActiveForContext(
|
|
|
575
574
|
return true;
|
|
576
575
|
}
|
|
577
576
|
}
|
|
578
|
-
if (name === "advisor") {
|
|
579
|
-
// Gated per chat-profile (`ProfileEntry.advisorEnabled`): when the resolved
|
|
580
|
-
// profile disables the advisor, omit the tool from the wire list so the
|
|
581
|
-
// model never sees a tool it can only no-op on. Resolves the profile the
|
|
582
|
-
// same way the advisor's execution-time guard does (the per-turn override,
|
|
583
|
-
// else the active profile). The wire list is fixed before PRE_MODEL_CALL
|
|
584
|
-
// hooks run, so later profile changes from hooks are not reflected here.
|
|
585
|
-
return advisorEnabledForProfile(ctx.currentTurnOverrideProfile ?? null);
|
|
586
|
-
}
|
|
587
577
|
if (UI_SURFACE_TOOL_NAMES.has(name)) {
|
|
588
578
|
if (
|
|
589
579
|
channelCapabilities?.channel === "slack" &&
|
|
@@ -243,6 +243,14 @@ export interface ConversationConstructorOptions {
|
|
|
243
243
|
speedOverride?: Speed;
|
|
244
244
|
cacheTtl?: "5m" | "1h";
|
|
245
245
|
modelOverride?: string;
|
|
246
|
+
/**
|
|
247
|
+
* Give this conversation's LLM calls provider-native (server-side) web
|
|
248
|
+
* search when the resolved provider supports it (see
|
|
249
|
+
* {@link AgentLoopConfig.enableNativeWebSearch}). Set by the subagent manager
|
|
250
|
+
* for the tool-less advisor consult so it can ground guidance with live web
|
|
251
|
+
* access; non-native providers get nothing. Defaults to false.
|
|
252
|
+
*/
|
|
253
|
+
enableNativeWebSearch?: boolean;
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
export class Conversation {
|
|
@@ -592,6 +600,7 @@ export class Conversation {
|
|
|
592
600
|
options?: ConversationConstructorOptions,
|
|
593
601
|
) {
|
|
594
602
|
const { maxTokens, speedOverride, cacheTtl, modelOverride } = options ?? {};
|
|
603
|
+
const enableNativeWebSearch = options?.enableNativeWebSearch ?? false;
|
|
595
604
|
this.conversationId = conversationId;
|
|
596
605
|
this.systemPrompt = systemPrompt;
|
|
597
606
|
this.provider = provider;
|
|
@@ -690,6 +699,7 @@ export class Conversation {
|
|
|
690
699
|
? { speed: resolvedSpeed }
|
|
691
700
|
: {}),
|
|
692
701
|
...(cacheTtl ? { cacheTtl } : {}),
|
|
702
|
+
...(enableNativeWebSearch ? { enableNativeWebSearch: true } : {}),
|
|
693
703
|
};
|
|
694
704
|
if (configuredMaxTokens !== undefined) {
|
|
695
705
|
agentLoopConfig.maxTokens = configuredMaxTokens;
|
|
@@ -247,7 +247,14 @@ export async function bootstrapPlugins(): Promise<void> {
|
|
|
247
247
|
// out-of-band kill switch — the operator creates a directory named
|
|
248
248
|
// after the plugin's manifest name (e.g. `plugins/default-advisor/`)
|
|
249
249
|
// and drops a `.disabled` file inside it. Runs before init so no
|
|
250
|
-
//
|
|
250
|
+
// tools or routes from the disabled plugin are ever wired.
|
|
251
|
+
//
|
|
252
|
+
// Unlike the feature-flag path above, we do NOT call
|
|
253
|
+
// `unregisterPlugin(name)` here. The plugin's hooks stay in the hook
|
|
254
|
+
// registry and are filtered at read time by `isPluginDisabled` in
|
|
255
|
+
// `getHooksFor`. This means `assistant plugins enable <name>` takes
|
|
256
|
+
// effect on the next turn without a restart — the hooks are already
|
|
257
|
+
// registered, they just need the sentinel removed to be included.
|
|
251
258
|
const disabledSentinelPath = join(
|
|
252
259
|
getWorkspacePluginsDir(),
|
|
253
260
|
name,
|
|
@@ -258,7 +265,6 @@ export async function bootstrapPlugins(): Promise<void> {
|
|
|
258
265
|
{ plugin: name, sentinel: disabledSentinelPath },
|
|
259
266
|
`skipping plugin ${name}: disabled via .disabled sentinel`,
|
|
260
267
|
);
|
|
261
|
-
unregisterPlugin(name);
|
|
262
268
|
continue;
|
|
263
269
|
}
|
|
264
270
|
|
|
@@ -174,7 +174,6 @@ describe("acceptA2AInvite", () => {
|
|
|
174
174
|
expect(contact).not.toBeNull();
|
|
175
175
|
expect(contact!.channels).toHaveLength(1);
|
|
176
176
|
expect(contact!.channels[0]!.type).toBe("a2a");
|
|
177
|
-
expect(contact!.channels[0]!.status).toBe("active");
|
|
178
177
|
|
|
179
178
|
// Verify assistant metadata
|
|
180
179
|
const metadata = getAssistantContactMetadata(result.contactId!);
|
|
@@ -100,8 +100,6 @@ describe("completeA2AInvite", () => {
|
|
|
100
100
|
expect(contact!.displayName).toBe("Acceptor Bot");
|
|
101
101
|
expect(contact!.channels).toHaveLength(1);
|
|
102
102
|
expect(contact!.channels[0]!.type).toBe("a2a");
|
|
103
|
-
expect(contact!.channels[0]!.status).toBe("active");
|
|
104
|
-
expect(contact!.channels[0]!.policy).toBe("allow");
|
|
105
103
|
});
|
|
106
104
|
|
|
107
105
|
test("contact channel address is acceptor.assistantId.toLowerCase()", () => {
|
|
@@ -71,8 +71,6 @@ describe("redeemA2AInvite", () => {
|
|
|
71
71
|
expect(contact!.displayName).toBe("Sender Bot");
|
|
72
72
|
expect(contact!.channels).toHaveLength(1);
|
|
73
73
|
expect(contact!.channels[0]!.type).toBe("a2a");
|
|
74
|
-
expect(contact!.channels[0]!.status).toBe("active");
|
|
75
|
-
expect(contact!.channels[0]!.policy).toBe("allow");
|
|
76
74
|
});
|
|
77
75
|
|
|
78
76
|
test("idempotency: already-connected sender returns alreadyConnected", () => {
|
|
@@ -20,11 +20,14 @@ mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
|
20
20
|
input.channelTypes!.includes(g.channelType),
|
|
21
21
|
);
|
|
22
22
|
},
|
|
23
|
+
guardianForChannel: (
|
|
24
|
+
list: GuardianDelivery[],
|
|
25
|
+
channelType: string,
|
|
26
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
23
27
|
}));
|
|
24
28
|
|
|
25
29
|
mock.module("../../../contacts/contact-store.js", () => ({
|
|
26
30
|
findContactChannel: () => mockContactChannel,
|
|
27
|
-
findGuardianForChannel: () => null,
|
|
28
31
|
getChannelById: () => mockChannel,
|
|
29
32
|
getContact: () => ({ id: "contact-1", displayName: "Pat" }),
|
|
30
33
|
}));
|
|
@@ -123,15 +126,7 @@ function channel(overrides: Partial<ContactChannel> = {}): ContactChannel {
|
|
|
123
126
|
address: "user-123",
|
|
124
127
|
isPrimary: true,
|
|
125
128
|
externalChatId: "chat-123",
|
|
126
|
-
// DB columns are intentionally a terminal state to prove the gates ignore
|
|
127
|
-
// them and read from the gateway delivery instead.
|
|
128
|
-
status: "revoked",
|
|
129
|
-
policy: {} as ContactChannel["policy"],
|
|
130
|
-
verifiedAt: null,
|
|
131
|
-
verifiedVia: null,
|
|
132
129
|
inviteId: null,
|
|
133
|
-
revokedReason: null,
|
|
134
|
-
blockedReason: null,
|
|
135
130
|
lastSeenAt: null,
|
|
136
131
|
interactionCount: 0,
|
|
137
132
|
lastInteraction: null,
|
|
@@ -168,9 +163,9 @@ describe("revokeVerificationForChannel", () => {
|
|
|
168
163
|
});
|
|
169
164
|
|
|
170
165
|
test("skips a redundant revoke when the gateway delivery is already revoked", async () => {
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
mockContactChannel = { channel: channel(
|
|
166
|
+
// The gateway (SoT) says revoked — the gate must follow the gateway and
|
|
167
|
+
// not relay regardless of local state.
|
|
168
|
+
mockContactChannel = { channel: channel() };
|
|
174
169
|
mockGuardians = [delivery({ status: "revoked" })];
|
|
175
170
|
await revokeVerificationForChannel("telegram");
|
|
176
171
|
expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
|
|
@@ -208,8 +203,8 @@ describe("verifyTrustedContact already-verified gate", () => {
|
|
|
208
203
|
});
|
|
209
204
|
|
|
210
205
|
test("does not short-circuit when the gateway channel has no verifiedAt", async () => {
|
|
211
|
-
//
|
|
212
|
-
mockChannel = channel(
|
|
206
|
+
// The gateway channel is unverified — proceed regardless of local state.
|
|
207
|
+
mockChannel = channel();
|
|
213
208
|
mockGwContactChannels = [
|
|
214
209
|
{ id: "ch-1", status: "pending", verifiedAt: null },
|
|
215
210
|
];
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
4
|
-
import {
|
|
5
|
-
GetContactIpcResponseSchema,
|
|
6
|
-
MarkChannelRevokedIpcResponseSchema,
|
|
7
|
-
} from "@vellumai/gateway-client/gateway-ipc-contracts";
|
|
4
|
+
import { MarkChannelRevokedIpcResponseSchema } from "@vellumai/gateway-client/gateway-ipc-contracts";
|
|
8
5
|
|
|
9
6
|
import { startVerificationCall } from "../../calls/call-domain.js";
|
|
10
7
|
import type { ChannelId } from "../../channels/types.js";
|
|
11
8
|
import { emitContactChange } from "../../contacts/contact-events.js";
|
|
12
9
|
import {
|
|
13
10
|
findContactChannel,
|
|
14
|
-
findGuardianForChannel,
|
|
15
11
|
getChannelById,
|
|
16
12
|
getContact,
|
|
17
13
|
} from "../../contacts/contact-store.js";
|
|
18
|
-
import {
|
|
14
|
+
import { gatewayContactChannelState } from "../../contacts/gateway-channel-read.js";
|
|
15
|
+
import {
|
|
16
|
+
getGuardianDelivery,
|
|
17
|
+
guardianForChannel,
|
|
18
|
+
} from "../../contacts/guardian-delivery-reader.js";
|
|
19
19
|
import type { ContactChannel } from "../../contacts/types.js";
|
|
20
20
|
import { ipcCallPersistent } from "../../ipc/gateway-client.js";
|
|
21
21
|
import { getBindingByChannelChat } from "../../memory/external-conversation-store.js";
|
|
@@ -105,25 +105,6 @@ async function deliveryForChannel(
|
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
/**
|
|
109
|
-
* Read a contact channel's verified state from the gateway contact-channel read
|
|
110
|
-
* (ACL source of truth). Covers all contacts, not just guardian deliveries.
|
|
111
|
-
* Returns `undefined` when the gateway is unreachable or has no such channel.
|
|
112
|
-
*/
|
|
113
|
-
async function gatewayContactChannelState(
|
|
114
|
-
channel: Pick<ContactChannel, "id" | "contactId">,
|
|
115
|
-
): Promise<{ status: string; verifiedAt: number | null } | undefined> {
|
|
116
|
-
const result = await ipcCallPersistent("contacts_get_rich", {
|
|
117
|
-
contactId: channel.contactId,
|
|
118
|
-
});
|
|
119
|
-
if (!result || (result as { contact?: unknown }).contact == null) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
const { contact } = GetContactIpcResponseSchema.parse(result);
|
|
123
|
-
const ch = contact.channels.find((c) => c.id === channel.id);
|
|
124
|
-
return ch ? { status: ch.status, verifiedAt: ch.verifiedAt } : undefined;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
108
|
// ---------------------------------------------------------------------------
|
|
128
109
|
// Extracted business logic functions
|
|
129
110
|
// ---------------------------------------------------------------------------
|
|
@@ -170,10 +151,14 @@ export async function getVerificationStatus(
|
|
|
170
151
|
|
|
171
152
|
const binding = await getGuardianBinding(resolvedAssistantId, resolvedChannel);
|
|
172
153
|
|
|
173
|
-
// Read the
|
|
174
|
-
// compatibility shim that doesn't carry metadataJson.
|
|
175
|
-
const
|
|
176
|
-
|
|
154
|
+
// Read the guardian displayName from the gateway delivery — getGuardianBinding
|
|
155
|
+
// is a compatibility shim that doesn't carry metadataJson.
|
|
156
|
+
const guardians = await getGuardianDelivery({
|
|
157
|
+
channelTypes: [resolvedChannel],
|
|
158
|
+
});
|
|
159
|
+
const bindingDisplayName = guardians
|
|
160
|
+
? (guardianForChannel(guardians, resolvedChannel)?.displayName ?? undefined)
|
|
161
|
+
: undefined;
|
|
177
162
|
const guardianDisplayName = resolveGuardianName(bindingDisplayName);
|
|
178
163
|
|
|
179
164
|
// Resolve username from external conversation store.
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
resolveSigningKey,
|
|
75
75
|
} from "../runtime/auth/token-service.js";
|
|
76
76
|
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
77
|
+
import { warmLocalGuardianPrincipalCache } from "../runtime/local-actor-identity.js";
|
|
77
78
|
import { recoverInterruptedImport } from "../runtime/migrations/vbundle-streaming-importer.js";
|
|
78
79
|
import { registerSecretsDeps } from "../runtime/routes/secrets-deps.js";
|
|
79
80
|
import {
|
|
@@ -824,6 +825,16 @@ export async function runDaemon(): Promise<void> {
|
|
|
824
825
|
|
|
825
826
|
await server.start();
|
|
826
827
|
log.info("Daemon startup: DaemonServer started");
|
|
828
|
+
|
|
829
|
+
// Warm the gateway guardian-delivery cache so the SSE eager-subscribe path
|
|
830
|
+
// (sync, IO-free) resolves the local actor principal on the FIRST client
|
|
831
|
+
// registration. Without this, a cold cache regresses host-proxy same-user
|
|
832
|
+
// targeting until a later reconnect. Non-blocking: failures aren't cached
|
|
833
|
+
// and the async hot paths re-warm on their next read.
|
|
834
|
+
void warmLocalGuardianPrincipalCache().catch((err) =>
|
|
835
|
+
log.warn({ err }, "Guardian principal cache warm failed — continuing"),
|
|
836
|
+
);
|
|
837
|
+
|
|
827
838
|
startDiskPressureGuardForLifecycle();
|
|
828
839
|
startOrphanReaper();
|
|
829
840
|
startEventLoopWatchdog();
|
|
@@ -966,10 +977,11 @@ export async function runDaemon(): Promise<void> {
|
|
|
966
977
|
}
|
|
967
978
|
}
|
|
968
979
|
|
|
969
|
-
// `startMemoryJobsWorker`
|
|
970
|
-
//
|
|
971
|
-
//
|
|
972
|
-
//
|
|
980
|
+
// `startMemoryJobsWorker` starts the in-process supervisor (which owns
|
|
981
|
+
// the synchronous runner and stands down when an out-of-process worker is
|
|
982
|
+
// live) and spawns the out-of-process worker at boot when
|
|
983
|
+
// `memory.worker.enabled` is set. Shutdown stops whichever worker is
|
|
984
|
+
// actually running — see shutdown-handlers.ts.
|
|
973
985
|
log.info("Daemon startup: starting memory worker");
|
|
974
986
|
bgRefs.memoryWorker = startMemoryJobsWorker();
|
|
975
987
|
|
|
@@ -116,6 +116,8 @@ export interface FormSurfaceData {
|
|
|
116
116
|
submitLabel?: string;
|
|
117
117
|
pages?: FormPage[];
|
|
118
118
|
pageLabels?: { next?: string; back?: string; submit?: string };
|
|
119
|
+
/** Progress indicator style for multi-page forms: segment bar or labeled tabs. */
|
|
120
|
+
progressStyle?: "bar" | "tabs";
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
export interface ListItem {
|
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { getConfig } from "../config/loader.js";
|
|
5
5
|
import type { HeartbeatConfig } from "../config/schemas/heartbeat.js";
|
|
6
|
+
import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
|
|
6
7
|
import {
|
|
7
8
|
checkDiskPressureBackgroundGate,
|
|
8
9
|
diskPressureBackgroundSkipLogFields,
|
|
@@ -761,6 +762,10 @@ export class HeartbeatService {
|
|
|
761
762
|
|
|
762
763
|
const checklist = this.readChecklist();
|
|
763
764
|
const completedRunCount = countCompletedHeartbeatRuns();
|
|
765
|
+
// Warm the vellum guardian-delivery cache so buildPrompt's sync guardian
|
|
766
|
+
// persona read (isShallowProfile → resolveGuardianPersona) hits a fresh key
|
|
767
|
+
// instead of falling back to default.md on a cold/TTL-expired cache.
|
|
768
|
+
await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
764
769
|
const { prompt, includedReengagement } = this.buildPrompt(
|
|
765
770
|
checklist,
|
|
766
771
|
unhealthyProviders,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
|
|
24
|
+
import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
|
|
24
25
|
import { countConversations as countConversationsDb } from "../memory/conversation-queries.js";
|
|
25
26
|
import { listConnections } from "../oauth/oauth-store.js";
|
|
26
27
|
import { resolveGuardianPersonaPath } from "../prompts/persona-resolver.js";
|
|
@@ -162,6 +163,10 @@ export async function computeRelationshipState(): Promise<RelationshipState> {
|
|
|
162
163
|
// old workspaces that never ran migration 031.
|
|
163
164
|
// 3. Empty string → extraction yields [] and `userName` is undefined.
|
|
164
165
|
// Every step is guarded because the writer must never throw.
|
|
166
|
+
// Warm the vellum guardian-delivery cache so the sync persona resolution in
|
|
167
|
+
// resolveGuardianUserContent hits a fresh key instead of falling back to
|
|
168
|
+
// default.md on a cold/TTL-expired cache.
|
|
169
|
+
await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
165
170
|
const userMd = resolveGuardianUserContent();
|
|
166
171
|
const soulMd = safeRead(getWorkspacePromptPath("SOUL.md"));
|
|
167
172
|
const identityPath = getWorkspacePromptPath("IDENTITY.md");
|