@vellumai/assistant 0.11.5 → 0.11.6-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/AGENTS.md +5 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +70 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +16 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +6 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +121 -61
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/openapi.yaml +421 -15
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/app-pin-store.test.ts +149 -0
- package/src/__tests__/channel-availability-routes.test.ts +23 -1
- package/src/__tests__/channel-readiness-discord.test.ts +231 -0
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +141 -0
- package/src/__tests__/channel-reply-delivery.test.ts +4 -4
- package/src/__tests__/client-os-metadata-persistence.test.ts +23 -10
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-seed-composer.test.ts +8 -0
- package/src/__tests__/conversation-slash-commands.test.ts +8 -0
- package/src/__tests__/disk-pressure-policy.test.ts +6 -0
- package/src/__tests__/gemini-provider.test.ts +138 -0
- package/src/__tests__/history-repair.test.ts +105 -3
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +45 -0
- package/src/__tests__/migration-import-from-path.test.ts +349 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +102 -0
- package/src/__tests__/oauth-commands-routes.test.ts +89 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +7 -6
- package/src/__tests__/openai-provider.test.ts +18 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/platform-callback-registration.test.ts +184 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +71 -3
- package/src/__tests__/pricing.test.ts +2 -2
- package/src/__tests__/public-ingress-urls.test.ts +36 -0
- package/src/__tests__/resolve-trust-class.test.ts +0 -48
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/agent/history-repair/history-repair.ts +45 -14
- package/src/agent/loop.ts +4 -1
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +11 -0
- package/src/apps/app-pin-reconciler.ts +92 -0
- package/src/apps/app-pin-store.ts +125 -0
- package/src/channels/gateway-channel-socket-health.ts +32 -0
- package/src/channels/gateway-discord-admission.ts +32 -0
- package/src/channels/types.ts +20 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +67 -2
- package/src/cli/commands/channels/__tests__/channels.test.ts +85 -0
- package/src/cli/commands/channels/index.ts +45 -31
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +28 -2
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/request.test.ts +290 -0
- package/src/cli/commands/oauth/request.ts +57 -41
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/conversation-error.ts +24 -2
- package/src/daemon/conversation-slash.ts +6 -15
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/disk-pressure-policy.ts +7 -1
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +26 -0
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/trust-context.ts +0 -37
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/platform-callback-registration.ts +28 -2
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +25 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +8 -2
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +212 -10
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +5 -2
- package/src/live-voice/live-voice-connection.ts +46 -8
- package/src/live-voice/live-voice-manager.ts +25 -0
- package/src/live-voice/live-voice-session-manager.ts +318 -2
- package/src/live-voice/live-voice-session.ts +52 -2
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +126 -68
- package/src/messaging/providers/channel-transport.ts +64 -47
- package/src/messaging/providers/discord/send.test.ts +46 -1
- package/src/messaging/providers/discord/send.ts +51 -0
- package/src/messaging/providers/discord/transport.ts +26 -3
- package/src/messaging/providers/index.ts +22 -47
- package/src/messaging/providers/slack/send.test.ts +83 -26
- package/src/messaging/providers/slack/send.ts +120 -51
- package/src/messaging/providers/slack/stream-tasks.test.ts +26 -0
- package/src/messaging/providers/slack/stream-tasks.ts +39 -0
- package/src/messaging/providers/slack/transport.ts +24 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +109 -12
- package/src/messaging/providers/telegram-bot/send.ts +43 -0
- package/src/messaging/providers/telegram-bot/transport.ts +25 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +30 -7
- package/src/notifications/adapters/telegram.ts +48 -1
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/conversation-seed-composer.ts +7 -2
- package/src/oauth/byo-connection.test.ts +63 -0
- package/src/oauth/byo-connection.ts +16 -15
- package/src/oauth/connection.test.ts +111 -0
- package/src/oauth/connection.ts +142 -1
- package/src/oauth/platform-connection.test.ts +34 -0
- package/src/oauth/platform-connection.ts +28 -5
- package/src/oauth/seed-providers.ts +15 -1
- package/src/permissions/types.ts +3 -1
- package/src/persistence/conversation-crud.ts +46 -0
- package/src/persistence/conversation-types.ts +11 -9
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/369-create-app-pins.ts +37 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +17 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +6 -0
- package/src/plugins/mtime-cache.ts +11 -0
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/connection-resolution.ts +23 -1
- package/src/providers/content-blocks.ts +9 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +13 -5
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +0 -9
- package/src/providers/inference/credential-rotation.ts +61 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/model-catalog.ts +170 -125
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +42 -60
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/orphan-tool-result-guard.test.ts +34 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +75 -29
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/openrouter/client.ts +0 -1
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/providers/vercel-ai-gateway/client.ts +0 -1
- package/src/runtime/AGENTS.md +1 -0
- package/src/runtime/__tests__/desktop-presence.test.ts +27 -4
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/channel-readiness-service.ts +214 -14
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +2 -2
- package/src/runtime/desktop-presence.ts +24 -21
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-server.ts +181 -1
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +104 -0
- package/src/runtime/migrations/staged-import-path.ts +116 -0
- package/src/runtime/routes/__tests__/app-pin-routes.test.ts +383 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -56
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/app-management-routes.ts +140 -29
- package/src/runtime/routes/channel-availability-routes.ts +1 -0
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/guardian-approval-interception.ts +24 -33
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +2 -0
- package/src/runtime/routes/inbound-message-handler.ts +10 -7
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +166 -308
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +158 -335
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +24 -4
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/live-voice-routes.test.ts +40 -1
- package/src/runtime/routes/live-voice-routes.ts +34 -0
- package/src/runtime/routes/migration-routes.ts +218 -10
- package/src/runtime/routes/oauth-commands-routes.ts +23 -16
- package/src/runtime/routes/plugins-routes.ts +12 -28
- package/src/runtime/routes/question-routes.ts +6 -0
- package/src/runtime/routes/secret-routes.ts +7 -27
- package/src/runtime/routes/settings-routes.ts +9 -6
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +230 -121
- package/src/runtime/slack-reply-session.ts +113 -81
- package/src/runtime/{slack-task-progress.test.ts → task-progress.test.ts} +1 -28
- package/src/runtime/{slack-task-progress.ts → task-progress.ts} +30 -51
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +108 -0
- package/src/telemetry/live-voice-funnel.ts +75 -8
- package/src/tools/credentials/store.ts +18 -6
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +97 -2
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +141 -55
- package/src/tools/types.ts +2 -1
- package/src/util/oauth-request-body.test.ts +74 -0
- package/src/util/oauth-request-body.ts +60 -0
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
|
@@ -19,6 +19,12 @@ import { join } from "node:path";
|
|
|
19
19
|
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
|
|
22
|
+
import {
|
|
23
|
+
type AppPin,
|
|
24
|
+
listAppPins,
|
|
25
|
+
removeAppPin,
|
|
26
|
+
updateAppPin,
|
|
27
|
+
} from "../../apps/app-pin-store.js";
|
|
22
28
|
import {
|
|
23
29
|
type AppDefinition,
|
|
24
30
|
type AppOrigin,
|
|
@@ -58,6 +64,7 @@ import {
|
|
|
58
64
|
NotFoundError,
|
|
59
65
|
PayloadTooLargeError,
|
|
60
66
|
} from "./errors.js";
|
|
67
|
+
import { parseBody } from "./parse-body.js";
|
|
61
68
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
62
69
|
|
|
63
70
|
const log = getLogger("app-management-routes");
|
|
@@ -80,17 +87,66 @@ function getSharedAppsDir(): string {
|
|
|
80
87
|
// Extracted business logic
|
|
81
88
|
// ---------------------------------------------------------------------------
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
/**
|
|
91
|
+
* One entry in the app list. Declared as the schema the route publishes, so the
|
|
92
|
+
* wire contract and the type the handlers build against cannot drift apart.
|
|
93
|
+
*/
|
|
94
|
+
const appListItemSchema = z.object({
|
|
95
|
+
id: z.string(),
|
|
96
|
+
name: z.string(),
|
|
97
|
+
description: z.string().optional(),
|
|
98
|
+
icon: z.string().optional(),
|
|
99
|
+
createdAt: z.number(),
|
|
100
|
+
updatedAt: z.number(),
|
|
101
|
+
version: z.string(),
|
|
102
|
+
contentId: z.string(),
|
|
103
|
+
origin: z
|
|
104
|
+
.string()
|
|
105
|
+
.describe('"workspace" or "plugin:<name>": where the app comes from'),
|
|
106
|
+
pinSortPosition: z
|
|
107
|
+
.number()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Sidebar order, ascending. Absent when the app is not pinned"),
|
|
110
|
+
pinColor: z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Pin colour id; absent when no colour is set"),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
type AppListItem = z.infer<typeof appListItemSchema>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The pin route's body. `RouteDefinition.requestBody` is a codegen signal and
|
|
120
|
+
* does not validate, so the handler parses against this too; declaring it once
|
|
121
|
+
* keeps what is advertised and what is enforced from drifting apart.
|
|
122
|
+
*/
|
|
123
|
+
const appPinBodySchema = z
|
|
124
|
+
.object({
|
|
125
|
+
pinned: z
|
|
126
|
+
.boolean()
|
|
127
|
+
.optional()
|
|
128
|
+
.describe("Pin (true) or unpin (false). Omit to leave unchanged."),
|
|
129
|
+
color: z
|
|
130
|
+
.string()
|
|
131
|
+
.nullable()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Colour id, or null to clear. Omit to leave unchanged."),
|
|
134
|
+
})
|
|
135
|
+
.refine(
|
|
136
|
+
(value) => value.pinned !== undefined || value.color !== undefined,
|
|
137
|
+
"pinned or color is required",
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
/** Merge an app's pin, if it has one, onto its list entry. */
|
|
141
|
+
function withPin(item: AppListItem, pin: AppPin | undefined): AppListItem {
|
|
142
|
+
if (!pin) {
|
|
143
|
+
return item;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...item,
|
|
147
|
+
pinSortPosition: pin.sortPosition,
|
|
148
|
+
...(pin.color !== undefined ? { pinColor: pin.color } : {}),
|
|
149
|
+
};
|
|
94
150
|
}
|
|
95
151
|
|
|
96
152
|
function workspaceAppItem(a: AppDefinition): AppListItem {
|
|
@@ -578,17 +634,23 @@ async function importBundle(
|
|
|
578
634
|
// ---------------------------------------------------------------------------
|
|
579
635
|
|
|
580
636
|
function handleListApps({ queryParams }: RouteHandlerArgs) {
|
|
637
|
+
const pinsById = new Map(listAppPins().map((pin) => [pin.appId, pin]));
|
|
638
|
+
const pinned = (item: AppListItem): AppListItem =>
|
|
639
|
+
withPin(item, pinsById.get(item.id));
|
|
640
|
+
|
|
581
641
|
const conversationId = queryParams?.conversationId;
|
|
582
642
|
if (conversationId) {
|
|
583
643
|
// Conversation scoping is a workspace-app concept; plugin apps are not
|
|
584
644
|
// associated with conversations, so they are omitted from this view.
|
|
585
645
|
return {
|
|
586
|
-
apps: listAppsByConversation(conversationId).map(
|
|
646
|
+
apps: listAppsByConversation(conversationId).map((app) =>
|
|
647
|
+
pinned(workspaceAppItem(app)),
|
|
648
|
+
),
|
|
587
649
|
};
|
|
588
650
|
}
|
|
589
651
|
const apps: AppListItem[] = [
|
|
590
|
-
...listApps().map(workspaceAppItem),
|
|
591
|
-
...listPluginApps().map(pluginAppItem),
|
|
652
|
+
...listApps().map((app) => pinned(workspaceAppItem(app))),
|
|
653
|
+
...listPluginApps().map((app) => pinned(pluginAppItem(app))),
|
|
592
654
|
];
|
|
593
655
|
return { apps };
|
|
594
656
|
}
|
|
@@ -818,10 +880,50 @@ function handleDeleteApp({ pathParams, headers }: RouteHandlerArgs) {
|
|
|
818
880
|
const appId = pathParams?.id as string;
|
|
819
881
|
assertNotPluginApp(appId, "delete a plugin app");
|
|
820
882
|
deleteApp(appId);
|
|
883
|
+
/* `deleteApp` cannot do this itself: it is filesystem-level and runs where no
|
|
884
|
+
migrated database exists. Deletions that skip this, and plugin apps that
|
|
885
|
+
leave with their plugin, are converged by `apps/app-pin-reconciler.ts`. */
|
|
886
|
+
removeAppPin(appId);
|
|
821
887
|
publishAppsChanged(getOriginClientId(headers));
|
|
822
888
|
return { success: true };
|
|
823
889
|
}
|
|
824
890
|
|
|
891
|
+
/**
|
|
892
|
+
* Pin, unpin, or recolour an app. Plugin apps are pinnable: a pin says nothing
|
|
893
|
+
* about the app's content, so `assertNotPluginApp` does not apply here.
|
|
894
|
+
*/
|
|
895
|
+
/** Every app id this workspace has, plugin apps included. */
|
|
896
|
+
function existingAppIds(): string[] {
|
|
897
|
+
return [
|
|
898
|
+
...listApps().map((app) => app.id),
|
|
899
|
+
...listPluginApps().map((app) => app.id),
|
|
900
|
+
];
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function handlePinApp({ pathParams, body = {}, headers }: RouteHandlerArgs) {
|
|
904
|
+
const appId = pathParams?.id as string;
|
|
905
|
+
const { pinned, color } = parseBody(appPinBodySchema, body);
|
|
906
|
+
const existing = existingAppIds();
|
|
907
|
+
/* A pin only means anything against an app that exists: the list starts from
|
|
908
|
+
the real apps, so a pin for anything else could never be read back.
|
|
909
|
+
Unpinning is exempt, since that is how a client clears state left over from
|
|
910
|
+
an app that has since gone. */
|
|
911
|
+
if (pinned === true && !existing.includes(appId)) {
|
|
912
|
+
throw new NotFoundError(`App not found: ${appId}`);
|
|
913
|
+
}
|
|
914
|
+
const pin = updateAppPin(appId, {
|
|
915
|
+
...(pinned === undefined ? {} : { pinned }),
|
|
916
|
+
...(color === undefined ? {} : { color }),
|
|
917
|
+
});
|
|
918
|
+
publishAppsChanged(getOriginClientId(headers));
|
|
919
|
+
return {
|
|
920
|
+
success: true,
|
|
921
|
+
appId,
|
|
922
|
+
pinSortPosition: pin?.sortPosition ?? null,
|
|
923
|
+
pinColor: pin?.color ?? null,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
825
927
|
function handleGetPreview({ pathParams }: RouteHandlerArgs) {
|
|
826
928
|
const appId = pathParams?.id as string;
|
|
827
929
|
const preview = getAppPreview(appId);
|
|
@@ -887,21 +989,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
887
989
|
description: "Filter apps by conversation ID",
|
|
888
990
|
},
|
|
889
991
|
],
|
|
890
|
-
responseBody: z.object({
|
|
891
|
-
apps: z.array(
|
|
892
|
-
z.object({
|
|
893
|
-
id: z.string(),
|
|
894
|
-
name: z.string(),
|
|
895
|
-
description: z.string().optional(),
|
|
896
|
-
icon: z.string().optional(),
|
|
897
|
-
createdAt: z.number(),
|
|
898
|
-
updatedAt: z.number(),
|
|
899
|
-
version: z.string(),
|
|
900
|
-
contentId: z.string(),
|
|
901
|
-
origin: z.string(),
|
|
902
|
-
}),
|
|
903
|
-
),
|
|
904
|
-
}),
|
|
992
|
+
responseBody: z.object({ apps: z.array(appListItemSchema) }),
|
|
905
993
|
},
|
|
906
994
|
{
|
|
907
995
|
operationId: "apps_open_bundle",
|
|
@@ -1146,6 +1234,29 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
1146
1234
|
tags: ["apps"],
|
|
1147
1235
|
responseBody: z.object({ success: z.boolean() }),
|
|
1148
1236
|
},
|
|
1237
|
+
{
|
|
1238
|
+
operationId: "apps_pin",
|
|
1239
|
+
endpoint: "apps/:id/pin",
|
|
1240
|
+
method: "POST",
|
|
1241
|
+
policy: {
|
|
1242
|
+
requiredScopes: ["settings.write"],
|
|
1243
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
1244
|
+
},
|
|
1245
|
+
handler: handlePinApp,
|
|
1246
|
+
summary: "Pin or unpin an app",
|
|
1247
|
+
description:
|
|
1248
|
+
"Pin an app to the sidebar, unpin it, or set the colour it is tinted " +
|
|
1249
|
+
"with. Pin state is read back from the app list as `pinSortPosition` " +
|
|
1250
|
+
"and `pinColor`.",
|
|
1251
|
+
tags: ["apps"],
|
|
1252
|
+
requestBody: appPinBodySchema,
|
|
1253
|
+
responseBody: z.object({
|
|
1254
|
+
success: z.boolean(),
|
|
1255
|
+
appId: z.string(),
|
|
1256
|
+
pinSortPosition: z.number().nullable(),
|
|
1257
|
+
pinColor: z.string().nullable(),
|
|
1258
|
+
}),
|
|
1259
|
+
},
|
|
1149
1260
|
{
|
|
1150
1261
|
operationId: "apps_preview_get",
|
|
1151
1262
|
endpoint: "apps/:id/preview",
|
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
getInviteAdapterRegistry,
|
|
15
15
|
resolveAdapterHandle,
|
|
16
16
|
} from "../channel-invite-transport.js";
|
|
17
|
+
import {
|
|
18
|
+
CHANNEL_HEALTHS,
|
|
19
|
+
CHECK_KINDS,
|
|
20
|
+
SETUP_STATUSES,
|
|
21
|
+
} from "../channel-readiness-types.js";
|
|
17
22
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
18
23
|
|
|
19
24
|
async function enrichSnapshots(
|
|
@@ -32,6 +37,7 @@ async function enrichSnapshots(
|
|
|
32
37
|
channel: s.channel,
|
|
33
38
|
ready: s.ready,
|
|
34
39
|
setupStatus: s.setupStatus,
|
|
40
|
+
health: s.health,
|
|
35
41
|
checkedAt: s.checkedAt,
|
|
36
42
|
stale: s.stale,
|
|
37
43
|
reasons: s.reasons,
|
|
@@ -89,15 +95,21 @@ const readinessCheckSchema = z.object({
|
|
|
89
95
|
name: z.string(),
|
|
90
96
|
passed: z.boolean(),
|
|
91
97
|
message: z.string().nullable().optional(),
|
|
98
|
+
// `passed` alone cannot say "I could not tell", and dropping this at the
|
|
99
|
+
// boundary is why a client cannot distinguish a channel that is broken from
|
|
100
|
+
// one whose state could not be established.
|
|
101
|
+
indeterminate: z.boolean().optional(),
|
|
102
|
+
kind: z.enum(CHECK_KINDS).optional(),
|
|
92
103
|
});
|
|
93
104
|
|
|
94
105
|
const readinessSnapshotSchema = z.object({
|
|
95
106
|
channel: z.enum(CHANNEL_IDS),
|
|
96
107
|
ready: z.boolean(),
|
|
97
|
-
setupStatus: z.
|
|
108
|
+
setupStatus: z.enum(SETUP_STATUSES).nullable().optional(),
|
|
109
|
+
health: z.enum(CHANNEL_HEALTHS).optional(),
|
|
98
110
|
checkedAt: z.number().nullable().optional(),
|
|
99
111
|
stale: z.boolean().optional(),
|
|
100
|
-
reasons: z.array(z.string()).optional(),
|
|
112
|
+
reasons: z.array(z.object({ code: z.string(), text: z.string() })).optional(),
|
|
101
113
|
localChecks: z.array(readinessCheckSchema).optional(),
|
|
102
114
|
remoteChecks: z.array(readinessCheckSchema).optional(),
|
|
103
115
|
channelHandle: z.string().nullable().optional(),
|
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
getEmbeddingConfigInfo,
|
|
67
67
|
setEmbeddingConfig,
|
|
68
68
|
} from "../../daemon/handlers/config-embeddings.js";
|
|
69
|
+
import { dropTunnelRecordsForNewUrl } from "../../daemon/handlers/config-ingress.js";
|
|
69
70
|
import {
|
|
70
71
|
getModelInfo,
|
|
71
72
|
type ModelSetContext,
|
|
@@ -1525,6 +1526,10 @@ async function handlePatchConfig({ body }: RouteHandlerArgs) {
|
|
|
1525
1526
|
|
|
1526
1527
|
const raw = loadRawConfig();
|
|
1527
1528
|
const patch = body as Record<string, unknown>;
|
|
1529
|
+
const patchedIngressUrl = readPlainObject(patch.ingress)?.publicBaseUrl;
|
|
1530
|
+
if (patchedIngressUrl !== undefined) {
|
|
1531
|
+
dropTunnelRecordsForNewUrl(readPlainObject(raw.ingress), patchedIngressUrl);
|
|
1532
|
+
}
|
|
1528
1533
|
deepMergeOverwrite(raw, patch);
|
|
1529
1534
|
scrubRemovedServiceModes(raw);
|
|
1530
1535
|
seedSttProviderForSparseBlock(raw);
|
|
@@ -1615,6 +1620,11 @@ async function handleSetConfig({ body }: RouteHandlerArgs) {
|
|
|
1615
1620
|
readPlainObject(readPlainObject(raw.llm)?.profiles)?.[managedEntryName],
|
|
1616
1621
|
)
|
|
1617
1622
|
: undefined;
|
|
1623
|
+
// `assistant config set ingress.publicBaseUrl <url>`, what the webhook
|
|
1624
|
+
// diagnostics tell users to run, retargets ingress like the settings route.
|
|
1625
|
+
if (path === "ingress.publicBaseUrl") {
|
|
1626
|
+
dropTunnelRecordsForNewUrl(readPlainObject(raw.ingress), value);
|
|
1627
|
+
}
|
|
1618
1628
|
setNestedValue(raw, path, value);
|
|
1619
1629
|
if (
|
|
1620
1630
|
managedEntryName &&
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* This module is the top-level dispatcher. It delegates plain-text messages to
|
|
6
6
|
* the conversational engine in guardian-text-engine-strategy.ts.
|
|
7
7
|
*/
|
|
8
|
-
import type { KnownBlock } from "@slack/types";
|
|
9
|
-
|
|
10
8
|
import { audienceForReader } from "../../channels/message-audience.js";
|
|
11
9
|
import type { ChannelId } from "../../channels/types.js";
|
|
12
10
|
import type { TrustContext } from "../../daemon/trust-context-types.js";
|
|
@@ -48,7 +46,7 @@ export interface ApprovalInterceptionParams {
|
|
|
48
46
|
approvalCopyGenerator?: ApprovalCopyGenerator;
|
|
49
47
|
approvalConversationGenerator?: ApprovalConversationGenerator;
|
|
50
48
|
/** Original approval message timestamp (Slack ts) for editing after resolution. */
|
|
51
|
-
|
|
49
|
+
approvalMessageId?: string;
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
import type { ApprovalInterceptionResult } from "./approval-interception-types.js";
|
|
@@ -79,7 +77,7 @@ export async function handleApprovalInterception(
|
|
|
79
77
|
assistantId,
|
|
80
78
|
approvalCopyGenerator,
|
|
81
79
|
approvalConversationGenerator,
|
|
82
|
-
|
|
80
|
+
approvalMessageId,
|
|
83
81
|
} = params;
|
|
84
82
|
|
|
85
83
|
// Slack emoji reactions are handled by the guardian decision
|
|
@@ -245,11 +243,11 @@ export async function handleApprovalInterception(
|
|
|
245
243
|
);
|
|
246
244
|
|
|
247
245
|
// Edit the original Slack approval message to remove stale buttons
|
|
248
|
-
if (
|
|
249
|
-
|
|
246
|
+
if (approvalMessageId) {
|
|
247
|
+
editStaleApprovalMessage({
|
|
250
248
|
replyCallbackUrl,
|
|
251
249
|
chatId: conversationExternalId,
|
|
252
|
-
|
|
250
|
+
messageId: approvalMessageId,
|
|
253
251
|
assistantId,
|
|
254
252
|
conversationId,
|
|
255
253
|
});
|
|
@@ -264,7 +262,7 @@ export async function handleApprovalInterception(
|
|
|
264
262
|
if (result.applied) {
|
|
265
263
|
// Edit the original Slack approval message to show the decision
|
|
266
264
|
// and remove stale action buttons.
|
|
267
|
-
if (
|
|
265
|
+
if (approvalMessageId) {
|
|
268
266
|
const decisionOutcome: "approved" | "denied" =
|
|
269
267
|
cbDecision.action === "reject" ? "denied" : "approved";
|
|
270
268
|
const statusEmoji =
|
|
@@ -273,11 +271,11 @@ export async function handleApprovalInterception(
|
|
|
273
271
|
decisionOutcome === "approved" ? "Approved" : "Denied";
|
|
274
272
|
editChannelMessage(replyCallbackUrl, {
|
|
275
273
|
chatId: conversationExternalId,
|
|
276
|
-
messageId:
|
|
274
|
+
messageId: approvalMessageId,
|
|
277
275
|
text: `${statusEmoji} ${statusLabel}`,
|
|
278
276
|
}).catch((err) => {
|
|
279
277
|
log.error(
|
|
280
|
-
{ err, conversationId, messageTs:
|
|
278
|
+
{ err, conversationId, messageTs: approvalMessageId },
|
|
281
279
|
"Failed to edit Slack approval message after decision",
|
|
282
280
|
);
|
|
283
281
|
});
|
|
@@ -291,11 +289,11 @@ export async function handleApprovalInterception(
|
|
|
291
289
|
// Race condition: request was already resolved between the stale check
|
|
292
290
|
// above and the decision attempt.
|
|
293
291
|
// Edit the original Slack approval message to remove stale buttons
|
|
294
|
-
if (
|
|
295
|
-
|
|
292
|
+
if (approvalMessageId) {
|
|
293
|
+
editStaleApprovalMessage({
|
|
296
294
|
replyCallbackUrl,
|
|
297
295
|
chatId: conversationExternalId,
|
|
298
|
-
|
|
296
|
+
messageId: approvalMessageId,
|
|
299
297
|
assistantId,
|
|
300
298
|
conversationId,
|
|
301
299
|
});
|
|
@@ -374,39 +372,32 @@ export async function handleApprovalInterception(
|
|
|
374
372
|
// ---------------------------------------------------------------------------
|
|
375
373
|
|
|
376
374
|
/**
|
|
377
|
-
* Fire-and-forget:
|
|
378
|
-
*
|
|
379
|
-
*
|
|
375
|
+
* Fire-and-forget: mark a stale approval message as resolved and drop its
|
|
376
|
+
* action buttons. Used when a button click arrives for an approval that was
|
|
377
|
+
* already decided.
|
|
378
|
+
*
|
|
379
|
+
* Says the message is settled and leaves each channel to render that. Slack
|
|
380
|
+
* reads it as a context block, and a channel with no equivalent shows the
|
|
381
|
+
* text plainly.
|
|
380
382
|
*/
|
|
381
|
-
function
|
|
383
|
+
function editStaleApprovalMessage(params: {
|
|
382
384
|
replyCallbackUrl: string;
|
|
383
385
|
chatId: string;
|
|
384
|
-
|
|
386
|
+
messageId: string;
|
|
385
387
|
assistantId: string;
|
|
386
388
|
conversationId: string;
|
|
387
389
|
}): void {
|
|
388
|
-
const statusText = "This approval request has been resolved.";
|
|
389
|
-
const blocks: KnownBlock[] = [
|
|
390
|
-
{
|
|
391
|
-
type: "section",
|
|
392
|
-
text: { type: "mrkdwn", text: statusText },
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
type: "context",
|
|
396
|
-
elements: [{ type: "mrkdwn", text: statusText }],
|
|
397
|
-
},
|
|
398
|
-
];
|
|
399
390
|
editChannelMessage(params.replyCallbackUrl, {
|
|
400
391
|
chatId: params.chatId,
|
|
401
|
-
messageId: params.
|
|
402
|
-
text:
|
|
403
|
-
|
|
392
|
+
messageId: params.messageId,
|
|
393
|
+
text: "This approval request has been resolved.",
|
|
394
|
+
emphasis: "muted",
|
|
404
395
|
}).catch((err) => {
|
|
405
396
|
log.error(
|
|
406
397
|
{
|
|
407
398
|
err,
|
|
408
399
|
conversationId: params.conversationId,
|
|
409
|
-
|
|
400
|
+
messageId: params.messageId,
|
|
410
401
|
},
|
|
411
402
|
"Failed to edit stale Slack approval message",
|
|
412
403
|
);
|
|
@@ -112,6 +112,24 @@ async function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Conversation-agnostic observation (see `runtime/host-observe.ts`): the
|
|
116
|
+
// request was raised outside any turn, so there is no conversation and no CU
|
|
117
|
+
// proxy to format the observation. Hand the raw fields to the waiting caller.
|
|
118
|
+
if (peeked.conversationId === undefined) {
|
|
119
|
+
const interaction = pendingInteractions.resolve(requestId, "answered");
|
|
120
|
+
interaction?.rpcResolve?.({
|
|
121
|
+
axTree,
|
|
122
|
+
axDiff,
|
|
123
|
+
screenshot,
|
|
124
|
+
screenshotWidthPx,
|
|
125
|
+
screenshotHeightPx,
|
|
126
|
+
screenWidthPt,
|
|
127
|
+
screenHeightPt,
|
|
128
|
+
executionError,
|
|
129
|
+
});
|
|
130
|
+
return { accepted: true };
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
const conversation = findConversation(peeked.conversationId);
|
|
116
134
|
if (!conversation) {
|
|
117
135
|
pendingInteractions.resolve(requestId, "cancelled");
|
|
@@ -158,6 +158,7 @@ async function getDetailedHealth() {
|
|
|
158
158
|
capabilities: {
|
|
159
159
|
memoryOptOut: true,
|
|
160
160
|
retryLastTurn: true,
|
|
161
|
+
appPins: true,
|
|
161
162
|
},
|
|
162
163
|
...(profiler ? { profiler } : {}),
|
|
163
164
|
...migrationHealthFields,
|
|
@@ -282,6 +283,7 @@ const cesHealthSchema = z.object({
|
|
|
282
283
|
const healthCapabilitiesSchema = z.object({
|
|
283
284
|
memoryOptOut: z.boolean(),
|
|
284
285
|
retryLastTurn: z.boolean(),
|
|
286
|
+
appPins: z.boolean(),
|
|
285
287
|
});
|
|
286
288
|
|
|
287
289
|
const healthDiskSchema = z.object({
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
import { getDiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
|
|
40
40
|
import { classifyDiskPressureTurnPolicy } from "../../daemon/disk-pressure-policy.js";
|
|
41
41
|
import { processMessage } from "../../daemon/process-message.js";
|
|
42
|
-
import { mapChatTypeToConversationType } from "../../daemon/trust-context.js";
|
|
43
42
|
import type { TrustContext } from "../../daemon/trust-context-types.js";
|
|
44
43
|
import { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
45
44
|
import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
|
|
@@ -1030,7 +1029,11 @@ export async function handleChannelInbound({
|
|
|
1030
1029
|
sourceMetadata.chatType.trim().length > 0
|
|
1031
1030
|
? sourceMetadata.chatType.trim()
|
|
1032
1031
|
: undefined;
|
|
1033
|
-
|
|
1032
|
+
// Decided by the sending channel, which is the only side that knows what its
|
|
1033
|
+
// own surfaces mean. The daemon reads the answer rather than re-deriving it
|
|
1034
|
+
// from a vocabulary where `channel` means a public room on Slack and a
|
|
1035
|
+
// broadcast feed on Telegram.
|
|
1036
|
+
trustCtx.conversationType = sourceMetadata?.conversationType;
|
|
1034
1037
|
|
|
1035
1038
|
// Preserve locale from sourceMetadata so the model can greet in the user's language
|
|
1036
1039
|
const sourceLanguageCode =
|
|
@@ -1092,8 +1095,8 @@ export async function handleChannelInbound({
|
|
|
1092
1095
|
// cleanup. When a Slack block_actions payload is forwarded, the gateway
|
|
1093
1096
|
// sets sourceMetadata.messageId to the ts of the message containing
|
|
1094
1097
|
// the button. This lets us edit the message after resolution.
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1098
|
+
const approvalMessageId =
|
|
1099
|
+
typeof sourceMetadata?.messageId === "string"
|
|
1097
1100
|
? sourceMetadata.messageId
|
|
1098
1101
|
: undefined;
|
|
1099
1102
|
|
|
@@ -1109,7 +1112,7 @@ export async function handleChannelInbound({
|
|
|
1109
1112
|
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1110
1113
|
approvalCopyGenerator,
|
|
1111
1114
|
approvalConversationGenerator,
|
|
1112
|
-
|
|
1115
|
+
approvalMessageId,
|
|
1113
1116
|
});
|
|
1114
1117
|
|
|
1115
1118
|
if (approvalResult.handled) {
|
|
@@ -1199,10 +1202,10 @@ export async function handleChannelInbound({
|
|
|
1199
1202
|
// instead of a silent no-op (JARVIS-299).
|
|
1200
1203
|
// No channel check: a transport without `edit` declines the call, so a
|
|
1201
1204
|
// channel gains this the moment it can revise a sent message.
|
|
1202
|
-
if (replyCallbackUrl &&
|
|
1205
|
+
if (replyCallbackUrl && approvalMessageId) {
|
|
1203
1206
|
editChannelMessage(replyCallbackUrl, {
|
|
1204
1207
|
chatId: conversationExternalId,
|
|
1205
|
-
messageId:
|
|
1208
|
+
messageId: approvalMessageId,
|
|
1206
1209
|
text: "This approval request has been resolved.",
|
|
1207
1210
|
}).catch((err) => {
|
|
1208
1211
|
log.error(
|