@vellumai/assistant 0.12.2-staging.1 → 0.12.2-staging.3
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/docs/desktop-browser-cli.md +33 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +50 -4
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +279 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +5 -5
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-sounds-sync.test.ts +23 -0
- package/src/__tests__/conversation-error.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +33 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/credential-routes.test.ts +38 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/cu-unified-flow.test.ts +6 -2
- package/src/__tests__/external-plugin-loader.test.ts +52 -0
- package/src/__tests__/headless-browser-mode.test.ts +48 -0
- package/src/__tests__/host-cu-proxy.test.ts +185 -30
- package/src/__tests__/list-all-apps.test.ts +40 -8
- package/src/__tests__/mcp-auth-routes.test.ts +99 -0
- package/src/__tests__/mcp-client-auth.test.ts +54 -0
- package/src/__tests__/mcp-health-check.test.ts +32 -32
- package/src/__tests__/mcp-list-plugin-servers.test.ts +77 -32
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +4 -2
- package/src/__tests__/oauth-commands-routes.test.ts +55 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/skills.test.ts +10 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +51 -0
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/user-plugin-loader.test.ts +21 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/apps/app-store.ts +6 -5
- package/src/browser/operations.ts +2 -1
- package/src/browser/types.ts +7 -0
- package/src/browser/virtual-desktop-target.ts +34 -0
- package/src/calls/__tests__/progress-narration.test.ts +23 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +69 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +81 -0
- package/src/calls/progress-narration.ts +15 -2
- package/src/calls/voice-control-protocol.ts +132 -0
- package/src/calls/voice-session-bridge.ts +56 -26
- package/src/calls/voice-triage-escalate.ts +3 -3
- package/src/cli/commands/__tests__/browser.test.ts +84 -3
- package/src/cli/commands/browser.help.ts +43 -0
- package/src/cli/commands/browser.ts +52 -8
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/plugins.ts +16 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +111 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +77 -2
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +45 -3
- package/src/cli/lib/__tests__/plugin-details.test.ts +26 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +122 -5
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/install-from-github.ts +17 -18
- package/src/cli/lib/install-from-platform.ts +14 -9
- package/src/cli/lib/list-installed-plugins.ts +21 -35
- package/src/cli/lib/plugin-details.ts +21 -8
- package/src/cli/lib/uninstall-plugin.ts +59 -1
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +21 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +43 -4
- package/src/config/bundled-skills/computer-use/TOOLS.json +3 -3
- package/src/config/bundled-skills/screen-annotation/SKILL.md +2 -2
- package/src/config/feature-flag-registry.json +11 -3
- package/src/config/loader.ts +1 -0
- package/src/config/schemas/__tests__/voice.test.ts +2 -2
- package/src/config/schemas/mcp.ts +9 -7
- package/src/config/schemas/monitoring.ts +9 -0
- package/src/config/schemas/services.ts +10 -0
- package/src/config/schemas/voice.ts +2 -2
- package/src/config/skills.ts +13 -21
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +47 -0
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -0
- package/src/daemon/conversation-client-surface.ts +26 -0
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-surfaces.ts +6 -1
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/host-cu-proxy.ts +94 -31
- package/src/daemon/host-proxy-preactivation.ts +2 -0
- package/src/daemon/mcp-reload-service.ts +7 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/providers-setup.ts +4 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-automation-lease.test.ts +208 -0
- package/src/desktop/desktop-automation-lease.ts +270 -0
- package/src/desktop/desktop-browser-client.test.ts +413 -0
- package/src/desktop/desktop-browser-client.ts +430 -0
- package/src/desktop/desktop-browser-cursor.ts +40 -0
- package/src/desktop/desktop-browser-endpoint.test.ts +78 -0
- package/src/desktop/desktop-browser-endpoint.ts +145 -0
- package/src/desktop/desktop-browser-operations.ts +130 -0
- package/src/desktop/desktop-chrome-session.test.ts +48 -0
- package/src/desktop/desktop-chrome-session.ts +58 -8
- package/src/desktop/desktop-dependencies.test.ts +29 -0
- package/src/desktop/desktop-dependencies.ts +26 -5
- package/src/desktop/desktop-display.ts +9 -0
- package/src/desktop/desktop-panel-config.ts +14 -1
- package/src/desktop/desktop-session-manager.test.ts +86 -3
- package/src/desktop/desktop-session-manager.ts +144 -24
- package/src/desktop/desktop-stream-bridge.ts +1 -1
- package/src/desktop/virtual-desktop-feature.ts +18 -0
- package/src/desktop/virtual-desktop-platform.test.ts +61 -0
- package/src/i18n/__tests__/i18n.test.ts +9 -5
- package/src/i18n/messages.ts +13 -4
- package/src/ipc/__tests__/browser-ipc.test.ts +230 -2
- package/src/ipc/__tests__/cancel-on-disconnect.test.ts +54 -0
- package/src/ipc/assistant-server.ts +15 -1
- package/src/ipc/cli-client.ts +13 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +143 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +9 -7
- package/src/live-voice/__tests__/live-voice-progress.test.ts +79 -0
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +19 -0
- package/src/live-voice/__tests__/protocol.test.ts +47 -0
- package/src/live-voice/__tests__/session-controls.test.ts +113 -0
- package/src/live-voice/live-voice-metrics.ts +9 -0
- package/src/live-voice/live-voice-session.ts +88 -7
- package/src/live-voice/protocol.ts +69 -0
- package/src/live-voice/session-controls.ts +139 -0
- package/src/mcp/__tests__/credential-target.test.ts +59 -0
- package/src/mcp/__tests__/effective-config.test.ts +11 -0
- package/src/mcp/__tests__/manager-state.test.ts +128 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +20 -6
- package/src/mcp/__tests__/mcp-auth-state-target.test.ts +49 -0
- package/src/mcp/__tests__/plugin-mcp-oauth-cleanup.test.ts +107 -0
- package/src/mcp/__tests__/plugin-mcp-oauth-provider-isolation.test.ts +89 -0
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +63 -11
- package/src/mcp/__tests__/reload-signal-emission.test.ts +39 -2
- package/src/mcp/client.ts +64 -16
- package/src/mcp/credential-target.ts +105 -0
- package/src/mcp/effective-config.ts +42 -15
- package/src/mcp/manager.ts +61 -8
- package/src/mcp/mcp-auth-orchestrator.ts +17 -4
- package/src/mcp/mcp-auth-state.ts +38 -6
- package/src/mcp/mcp-oauth-provider.ts +101 -33
- package/src/monitoring/__tests__/mount-watch.test.ts +236 -0
- package/src/monitoring/mount-watch.ts +348 -0
- package/src/monitoring/worker.ts +9 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +95 -0
- package/src/oauth/connection-resolver.test.ts +27 -0
- package/src/oauth/connection-resolver.ts +25 -1
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/seed-providers.ts +121 -2
- package/src/onboarding/checkin-event.ts +2 -1
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +14 -1
- package/src/plugins/__tests__/mcp-servers.test.ts +38 -8
- package/src/plugins/external-plugin-loader.ts +49 -86
- package/src/plugins/installed-plugin-dirs.ts +6 -4
- package/src/plugins/mcp-servers.ts +9 -15
- package/src/plugins/mtime-cache.ts +2 -1
- package/src/plugins/user-loader.ts +2 -1
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +45 -0
- package/src/providers/inference/endpoint-probe.ts +27 -12
- package/src/providers/openai/responses-provider.ts +9 -2
- package/src/providers/opencode/client.test.ts +167 -0
- package/src/providers/opencode/client.ts +73 -4
- package/src/runtime/AGENTS.md +4 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +45 -2
- package/src/runtime/http-server.ts +6 -6
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +74 -4
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +25 -3
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/app-management-routes.ts +17 -2
- package/src/runtime/routes/browser-context.ts +39 -0
- package/src/runtime/routes/browser-routes.ts +16 -47
- package/src/runtime/routes/browser-tabs-routes.ts +72 -29
- package/src/runtime/routes/credential-routes.ts +1 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +16 -1
- package/src/runtime/routes/desktop-setup-routes.ts +7 -5
- package/src/runtime/routes/mcp-auth-routes.ts +106 -94
- package/src/runtime/routes/oauth-commands-routes.ts +14 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +14 -1
- package/src/runtime/sync/resource-sync-events.ts +4 -0
- package/src/tools/browser/browser-execution.ts +44 -13
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +69 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +5 -0
- package/src/tools/computer-use/definitions.ts +3 -4
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/util/abort-reasons.ts +12 -1
- package/src/util/plugin-manifest.ts +203 -0
- package/src/watch/watch-retro.ts +6 -8
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
|
@@ -1,68 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Conversation-stable wire projections of the UI surface tool definitions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Activation-rail conversations additionally get the `activation_moment`
|
|
9
|
-
* telemetry param, which no other conversation pays schema tokens for (the
|
|
10
|
-
* emit path is gated on `isActivationSession` regardless).
|
|
4
|
+
* `ui_show`, `ui_update`, and `ui_dismiss` keep one definition across every
|
|
5
|
+
* channel and across live vs clientless turns. Channel renderers enforce the
|
|
6
|
+
* surfaces they can display at execution. Clientless calls persist surface
|
|
7
|
+
* content for the next capable client that opens the conversation.
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* `activation_moment` is the remaining schema projection. It applies only to
|
|
10
|
+
* activation-rail conversations, whose marker is written before the first
|
|
11
|
+
* tool resolution, so the extra property is stable for that conversation's
|
|
12
|
+
* lifetime. It must not vary by channel or turn presence.
|
|
16
13
|
*/
|
|
17
14
|
|
|
18
15
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
19
16
|
import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
|
|
20
|
-
import { TASK_PROGRESS_TEMPLATE_SHAPE } from "./surface-shape-docs.js";
|
|
21
|
-
|
|
22
|
-
const SLACK_UI_SHOW_DESCRIPTION = `Show a live task-progress card for the current work — the only surface this channel renders. data: { template: "task_progress", templateData: ${TASK_PROGRESS_TEMPLATE_SHAPE} }. Show it early on multi-step turns and advance step statuses via ui_update as work progresses.`;
|
|
23
|
-
|
|
24
|
-
const SLACK_UI_SHOW_INPUT_SCHEMA = {
|
|
25
|
-
type: "object",
|
|
26
|
-
properties: {
|
|
27
|
-
surface_type: {
|
|
28
|
-
type: "string",
|
|
29
|
-
enum: ["card"],
|
|
30
|
-
description: "Always card on this channel",
|
|
31
|
-
},
|
|
32
|
-
title: {
|
|
33
|
-
type: "string",
|
|
34
|
-
description: "Optional surface title",
|
|
35
|
-
},
|
|
36
|
-
data: {
|
|
37
|
-
type: "object",
|
|
38
|
-
description:
|
|
39
|
-
'Card data: { template: "task_progress", templateData: { title, status, steps } }',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
required: ["surface_type", "data"],
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Swap UI surface tool definitions for channel-appropriate variants. Returns
|
|
47
|
-
* the input array unchanged (same identity) for channels without a variant.
|
|
48
|
-
*/
|
|
49
|
-
export function projectUiToolsForChannel(
|
|
50
|
-
definitions: ToolDefinition[],
|
|
51
|
-
channel: string | undefined,
|
|
52
|
-
): ToolDefinition[] {
|
|
53
|
-
if (channel !== "slack") {
|
|
54
|
-
return definitions;
|
|
55
|
-
}
|
|
56
|
-
return definitions.map((def) =>
|
|
57
|
-
def.name === "ui_show"
|
|
58
|
-
? {
|
|
59
|
-
...def,
|
|
60
|
-
description: SLACK_UI_SHOW_DESCRIPTION,
|
|
61
|
-
input_schema: SLACK_UI_SHOW_INPUT_SCHEMA,
|
|
62
|
-
}
|
|
63
|
-
: def,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
17
|
|
|
67
18
|
const ACTIVATION_MOMENT_PROPERTY = {
|
|
68
19
|
type: "string",
|
|
@@ -73,7 +24,7 @@ const ACTIVATION_MOMENT_PROPERTY = {
|
|
|
73
24
|
|
|
74
25
|
/**
|
|
75
26
|
* Add the optional `activation_moment` param to ui_show's schema. Applied
|
|
76
|
-
* only for activation-rail conversations (`isActivationSession`)
|
|
27
|
+
* only for activation-rail conversations (`isActivationSession`). The rail
|
|
77
28
|
* bootstrap prompt instructs the tagging, and the daemon's emit path reads
|
|
78
29
|
* the tag from tool input independently of this schema.
|
|
79
30
|
*/
|
|
@@ -21,7 +21,18 @@ export type AbortReasonKind =
|
|
|
21
21
|
/** A signal-file cancel was written by an out-of-process caller (CLI, hook). */
|
|
22
22
|
| "signal_cancel"
|
|
23
23
|
/** Voice session bridge aborted the conversation (turn supersession, call end). */
|
|
24
|
-
| "voice_session_aborted"
|
|
24
|
+
| "voice_session_aborted"
|
|
25
|
+
/**
|
|
26
|
+
* One spoken progress update ran out of its generation budget.
|
|
27
|
+
*
|
|
28
|
+
* Distinct from {@link "voice_session_aborted"} because nothing was aborted
|
|
29
|
+
* but the update itself: the call is still up, the turn is still running,
|
|
30
|
+
* and the only consequence is that this beat goes unspoken. Sharing the
|
|
31
|
+
* session-abort kind made a narrator that had gone fail-open read in the
|
|
32
|
+
* logs as a session dying over and over, which is the opposite of what the
|
|
33
|
+
* line means.
|
|
34
|
+
*/
|
|
35
|
+
| "voice_progress_narration_timeout";
|
|
25
36
|
|
|
26
37
|
const ABORT_REASON_TAG = "__vellumAbortReason" as const;
|
|
27
38
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the manifest that identifies an installed plugin.
|
|
3
|
+
*
|
|
4
|
+
* `package.json` is the primary manifest for Vellum plugins. A root
|
|
5
|
+
* `plugin.json` is selected only when `package.json` is absent, so a legacy
|
|
6
|
+
* plugin can carry another ecosystem's `plugin.json` without being
|
|
7
|
+
* reclassified.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
export const LEGACY_PLUGIN_MANIFEST = "package.json";
|
|
16
|
+
export const STANDARD_PLUGIN_MANIFEST = "plugin.json";
|
|
17
|
+
export const AGENT_PLUGINS_MANIFEST_SCHEMA_URL =
|
|
18
|
+
"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
|
|
19
|
+
|
|
20
|
+
const PluginNameSchema = z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(64)
|
|
24
|
+
.regex(/^[a-z0-9](?!.*(?:--|\.\.))[a-z0-9.-]*[a-z0-9]$|^[a-z0-9]$/);
|
|
25
|
+
|
|
26
|
+
const LegacyPluginManifestSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
name: z.string().min(1),
|
|
29
|
+
version: z.string().optional(),
|
|
30
|
+
description: z.string().min(1).optional(),
|
|
31
|
+
homepage: z.string().optional(),
|
|
32
|
+
license: z.unknown().optional(),
|
|
33
|
+
peerDependencies: z.record(z.string(), z.string()).optional(),
|
|
34
|
+
credentialKeyPatterns: z.unknown().optional(),
|
|
35
|
+
displayName: z.string().min(1).optional(),
|
|
36
|
+
icon: z.string().min(1).optional(),
|
|
37
|
+
})
|
|
38
|
+
.passthrough();
|
|
39
|
+
|
|
40
|
+
const StandardPluginManifestSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
$schema: z.literal(AGENT_PLUGINS_MANIFEST_SCHEMA_URL),
|
|
43
|
+
name: PluginNameSchema,
|
|
44
|
+
version: z.string().optional(),
|
|
45
|
+
description: z.string().optional(),
|
|
46
|
+
author: z
|
|
47
|
+
.object({
|
|
48
|
+
name: z.string().optional(),
|
|
49
|
+
email: z.string().optional(),
|
|
50
|
+
url: z.string().optional(),
|
|
51
|
+
})
|
|
52
|
+
.strict()
|
|
53
|
+
.optional(),
|
|
54
|
+
homepage: z.string().optional(),
|
|
55
|
+
repository: z.string().optional(),
|
|
56
|
+
license: z.string().optional(),
|
|
57
|
+
keywords: z.array(z.string()).optional(),
|
|
58
|
+
// The core specification says a non-object extensions value is reported
|
|
59
|
+
// and ignored rather than rejecting the plugin. This host does not consume
|
|
60
|
+
// a standard extension namespace here, so keep it opaque.
|
|
61
|
+
extensions: z.unknown().optional(),
|
|
62
|
+
})
|
|
63
|
+
// Unknown core fields are reported-and-ignored by the specification. The
|
|
64
|
+
// loader does not assign them semantics, so passthrough preserves that
|
|
65
|
+
// non-fatal behavior without copying them into runtime metadata.
|
|
66
|
+
.passthrough();
|
|
67
|
+
|
|
68
|
+
export type PluginManifestSource =
|
|
69
|
+
| typeof LEGACY_PLUGIN_MANIFEST
|
|
70
|
+
| typeof STANDARD_PLUGIN_MANIFEST;
|
|
71
|
+
|
|
72
|
+
export interface ResolvedPluginManifest {
|
|
73
|
+
readonly source: PluginManifestSource;
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly version?: string;
|
|
76
|
+
readonly description?: string;
|
|
77
|
+
readonly homepage?: string;
|
|
78
|
+
readonly license?: unknown;
|
|
79
|
+
/** Parsed selected manifest, retained for Vellum-specific legacy fields. */
|
|
80
|
+
readonly raw: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type PluginManifestInstallAction = "keep" | "synthesize-legacy";
|
|
84
|
+
|
|
85
|
+
export class PluginManifestError extends Error {
|
|
86
|
+
constructor(
|
|
87
|
+
readonly pluginDir: string,
|
|
88
|
+
readonly source: PluginManifestSource | null,
|
|
89
|
+
message: string,
|
|
90
|
+
readonly cause?: unknown,
|
|
91
|
+
) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.name = "PluginManifestError";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** True when the directory carries either supported root manifest. */
|
|
98
|
+
export function hasPluginManifest(pluginDir: string): boolean {
|
|
99
|
+
return (
|
|
100
|
+
existsSync(join(pluginDir, LEGACY_PLUGIN_MANIFEST)) ||
|
|
101
|
+
existsSync(join(pluginDir, STANDARD_PLUGIN_MANIFEST))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decide whether an installed artifact needs the compatibility `package.json`.
|
|
107
|
+
*
|
|
108
|
+
* A `package.json` file is always preserved. A root `plugin.json`
|
|
109
|
+
* claims the Agent Plugins format only when it names the supported schema. A
|
|
110
|
+
* claimed standard manifest is validated before install; other `plugin.json`
|
|
111
|
+
* files remain foreign metadata beside the synthesized compatibility manifest.
|
|
112
|
+
*/
|
|
113
|
+
export function getPluginManifestInstallAction(
|
|
114
|
+
pluginDir: string,
|
|
115
|
+
): PluginManifestInstallAction {
|
|
116
|
+
if (existsSync(join(pluginDir, LEGACY_PLUGIN_MANIFEST))) {
|
|
117
|
+
return "keep";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const standardPath = join(pluginDir, STANDARD_PLUGIN_MANIFEST);
|
|
121
|
+
if (!existsSync(standardPath)) {
|
|
122
|
+
return "synthesize-legacy";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let json: unknown;
|
|
126
|
+
try {
|
|
127
|
+
json = JSON.parse(readFileSync(standardPath, "utf8"));
|
|
128
|
+
} catch {
|
|
129
|
+
return "synthesize-legacy";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
typeof json !== "object" ||
|
|
134
|
+
json === null ||
|
|
135
|
+
Array.isArray(json) ||
|
|
136
|
+
(json as Record<string, unknown>).$schema !==
|
|
137
|
+
AGENT_PLUGINS_MANIFEST_SCHEMA_URL
|
|
138
|
+
) {
|
|
139
|
+
return "synthesize-legacy";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
readPluginManifest(pluginDir);
|
|
143
|
+
return "keep";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read and validate the selected root manifest.
|
|
148
|
+
*
|
|
149
|
+
* Selection is deterministic: `package.json` wins when present, including
|
|
150
|
+
* when it is malformed. The loader never falls through
|
|
151
|
+
* from a selected malformed legacy manifest to `plugin.json`.
|
|
152
|
+
*/
|
|
153
|
+
export function readPluginManifest(pluginDir: string): ResolvedPluginManifest {
|
|
154
|
+
const source = existsSync(join(pluginDir, LEGACY_PLUGIN_MANIFEST))
|
|
155
|
+
? LEGACY_PLUGIN_MANIFEST
|
|
156
|
+
: existsSync(join(pluginDir, STANDARD_PLUGIN_MANIFEST))
|
|
157
|
+
? STANDARD_PLUGIN_MANIFEST
|
|
158
|
+
: null;
|
|
159
|
+
if (source === null) {
|
|
160
|
+
throw new PluginManifestError(
|
|
161
|
+
pluginDir,
|
|
162
|
+
null,
|
|
163
|
+
`plugin at ${pluginDir} is missing package.json and plugin.json`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const path = join(pluginDir, source);
|
|
168
|
+
let json: unknown;
|
|
169
|
+
try {
|
|
170
|
+
json = JSON.parse(readFileSync(path, "utf8"));
|
|
171
|
+
} catch (cause) {
|
|
172
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
173
|
+
throw new PluginManifestError(
|
|
174
|
+
pluginDir,
|
|
175
|
+
source,
|
|
176
|
+
`${source} at ${pluginDir} could not be read or parsed: ${reason}`,
|
|
177
|
+
cause,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const parsed =
|
|
182
|
+
source === LEGACY_PLUGIN_MANIFEST
|
|
183
|
+
? LegacyPluginManifestSchema.safeParse(json)
|
|
184
|
+
: StandardPluginManifestSchema.safeParse(json);
|
|
185
|
+
if (!parsed.success) {
|
|
186
|
+
throw new PluginManifestError(
|
|
187
|
+
pluginDir,
|
|
188
|
+
source,
|
|
189
|
+
`${source} at ${pluginDir} failed schema validation: ${parsed.error.message}`,
|
|
190
|
+
parsed.error,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
source,
|
|
196
|
+
name: parsed.data.name,
|
|
197
|
+
version: parsed.data.version,
|
|
198
|
+
description: parsed.data.description,
|
|
199
|
+
homepage: parsed.data.homepage,
|
|
200
|
+
license: parsed.data.license,
|
|
201
|
+
raw: parsed.data,
|
|
202
|
+
};
|
|
203
|
+
}
|
package/src/watch/watch-retro.ts
CHANGED
|
@@ -61,14 +61,12 @@ const WATCH_RETRO_WAKE_SOURCE = "watch-retro";
|
|
|
61
61
|
* under the `watch_retro` template, which the client draws as a paged card: the
|
|
62
62
|
* record on the first page, one question per page after it.
|
|
63
63
|
*
|
|
64
|
-
* **The daemon appends the card; the model
|
|
65
|
-
* is `clientless
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* retrospective's `skill_card` is appended by the daemon rather than requested
|
|
71
|
-
* by the model.
|
|
64
|
+
* **The daemon appends the card; the model calls `watch_retro_report`.** This
|
|
65
|
+
* wake is `clientless`. `ui_show` stays on the wire so background surfaces can
|
|
66
|
+
* persist, but this flow still reports through `watch_retro_report` so the
|
|
67
|
+
* daemon can append the card after the turn ends. The memory retrospective's
|
|
68
|
+
* `skill_card` is appended the same way: the daemon writes the surface, the
|
|
69
|
+
* model does not request it.
|
|
72
70
|
*
|
|
73
71
|
* **The append waits for the turn to end.** A `ui_surface` row written mid-turn
|
|
74
72
|
* can land between a persisted `tool_use` and its `tool_result`, an ordering
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
|
|
4
|
-
import type { AssistantConfig } from "../config/schema.js";
|
|
5
|
-
import { isAssistantDesktopEnabled } from "./desktop-feature.js";
|
|
6
|
-
|
|
7
|
-
describe("isAssistantDesktopEnabled", () => {
|
|
8
|
-
const config = {} as AssistantConfig;
|
|
9
|
-
|
|
10
|
-
test("needs both the flag and a containerized runtime", () => {
|
|
11
|
-
setOverridesForTesting({ "assistant-desktop": true });
|
|
12
|
-
expect(isAssistantDesktopEnabled(config, true)).toBe(true);
|
|
13
|
-
expect(isAssistantDesktopEnabled(config, false)).toBe(false);
|
|
14
|
-
|
|
15
|
-
setOverridesForTesting({ "assistant-desktop": false });
|
|
16
|
-
expect(isAssistantDesktopEnabled(config, true)).toBe(false);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
2
|
-
import { getIsContainerized } from "../config/env-registry.js";
|
|
3
|
-
import type { AssistantConfig } from "../config/schema.js";
|
|
4
|
-
|
|
5
|
-
const ASSISTANT_DESKTOP_FLAG = "assistant-desktop" as const;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Whether this daemon serves `/v1/desktop/stream`: the `assistant-desktop` flag
|
|
9
|
-
* plus a containerized runtime where desktop components can be installed.
|
|
10
|
-
*/
|
|
11
|
-
export function isAssistantDesktopEnabled(
|
|
12
|
-
config: AssistantConfig,
|
|
13
|
-
containerized: boolean = getIsContainerized(),
|
|
14
|
-
): boolean {
|
|
15
|
-
return (
|
|
16
|
-
containerized &&
|
|
17
|
-
isAssistantFeatureFlagEnabled(ASSISTANT_DESKTOP_FLAG, config)
|
|
18
|
-
);
|
|
19
|
-
}
|