@vellumai/assistant 0.9.0 → 0.9.1-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/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +4 -4
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +135 -59
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
- package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/config-loader-backfill.test.ts +103 -6
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +3 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -13
- package/src/__tests__/dynamic-page-surface.test.ts +101 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/invite-redemption-service.test.ts +4 -4
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +0 -22
- package/src/__tests__/llm-resolver.test.ts +49 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +11 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/skill-execute-input.test.ts +46 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/subagent-tools.test.ts +116 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +6 -6
- package/src/__tests__/voice-invite-redemption.test.ts +2 -2
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/agent/loop.ts +25 -5
- package/src/api/README.md +6 -6
- package/src/api/responses/conversation-message.ts +2 -4
- package/src/api/responses/home.ts +0 -4
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/plugins.ts +143 -2
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +228 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/upgrade-plugin.ts +270 -10
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +0 -1
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +12 -2
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/loader.ts +4 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/seed-inference-profiles.ts +25 -20
- package/src/contacts/contact-store.ts +87 -96
- package/src/contacts/contacts-write.ts +5 -21
- package/src/context/compactor.ts +2 -2
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop.ts +15 -4
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-tool-setup.ts +11 -3
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/handlers/config-channels.ts +20 -16
- package/src/daemon/handlers/config-slack-channel.ts +2 -3
- package/src/daemon/lifecycle.ts +0 -7
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/home/relationship-state.ts +2 -4
- package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/db-init.ts +7 -17
- package/src/memory/embedding-backend.ts +23 -0
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +52 -0
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-job.ts +19 -227
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/schema/contacts.ts +0 -4
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +5 -32
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/broadcaster.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +4 -3
- package/src/notifications/notification-utils.ts +17 -19
- package/src/notifications/types.ts +7 -0
- package/src/oauth/AGENTS.md +5 -24
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +6 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
- package/src/prompts/persona-resolver.ts +2 -2
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/actor-trust-resolver.ts +12 -44
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/channel-approval-types.ts +18 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-verification-service.ts +4 -3
- package/src/runtime/invite-redemption-service.ts +3 -3
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +171 -5
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/workflow-routes.test.ts +4 -43
- package/src/runtime/routes/workflow-routes.ts +0 -28
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/schedule/inference-profile.ts +2 -14
- package/src/subagent/manager.ts +6 -0
- package/src/subagent/types.ts +6 -0
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/schedule/create.ts +3 -9
- package/src/tools/schedule/update.ts +2 -10
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +34 -0
- package/src/tools/subagent/spawn.ts +34 -9
- package/src/tools/tool-approval-handler.ts +1 -3
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/util/platform.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/run-manager.test.ts +0 -25
- package/src/workflows/run-manager.ts +2 -24
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
|
|
26
26
|
import { z } from "zod";
|
|
27
27
|
|
|
28
|
+
import {
|
|
29
|
+
diffPlugin,
|
|
30
|
+
PluginDiffUnavailableError,
|
|
31
|
+
} from "../../cli/lib/diff-plugin.js";
|
|
28
32
|
import {
|
|
29
33
|
inspectPlugin,
|
|
30
34
|
PluginInspectNotFoundError,
|
|
@@ -58,7 +62,10 @@ import {
|
|
|
58
62
|
uninstallPlugin,
|
|
59
63
|
} from "../../cli/lib/uninstall-plugin.js";
|
|
60
64
|
import {
|
|
65
|
+
PluginMergeBaselineError,
|
|
61
66
|
PluginNotUpgradableError,
|
|
67
|
+
type PluginUpgradeStrategy,
|
|
68
|
+
PluginUpgradeStrategyUnsupportedError,
|
|
62
69
|
upgradePlugin,
|
|
63
70
|
} from "../../cli/lib/upgrade-plugin.js";
|
|
64
71
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
@@ -410,6 +417,12 @@ const pluginUpgradeRequestSchema = z.object({
|
|
|
410
417
|
.describe(
|
|
411
418
|
"Report what would change without modifying the install. Defaults to false.",
|
|
412
419
|
),
|
|
420
|
+
strategy: z
|
|
421
|
+
.enum(["ours", "theirs", "overwrite", "assistant"])
|
|
422
|
+
.optional()
|
|
423
|
+
.describe(
|
|
424
|
+
"How to reconcile local edits with the pin. `overwrite` (default) discards local edits and re-installs the pin; `ours`/`theirs` three-way merge, resolving conflicting hunks toward the local edit or the pin respectively; `assistant` is not yet supported.",
|
|
425
|
+
),
|
|
413
426
|
});
|
|
414
427
|
|
|
415
428
|
const pluginUpgradeResponseSchema = z.object({
|
|
@@ -452,6 +465,9 @@ const pluginUpgradeResponseSchema = z.object({
|
|
|
452
465
|
"Files materialized by the upgrade; null for a no-op or dry run.",
|
|
453
466
|
),
|
|
454
467
|
dryRun: z.boolean().describe("Whether this was a dry run (no changes made)."),
|
|
468
|
+
strategy: z
|
|
469
|
+
.enum(["ours", "theirs", "overwrite", "assistant"])
|
|
470
|
+
.describe("Conflict-resolution strategy the upgrade applied."),
|
|
455
471
|
provenanceWasUnknown: z
|
|
456
472
|
.boolean()
|
|
457
473
|
.describe(
|
|
@@ -459,6 +475,62 @@ const pluginUpgradeResponseSchema = z.object({
|
|
|
459
475
|
),
|
|
460
476
|
});
|
|
461
477
|
|
|
478
|
+
const pluginFileDiffSchema = z
|
|
479
|
+
.object({
|
|
480
|
+
path: z.string().describe("POSIX-relative path within the plugin root."),
|
|
481
|
+
status: z
|
|
482
|
+
.enum(["modified", "added", "removed"])
|
|
483
|
+
.describe(
|
|
484
|
+
"Whether the file was edited, newly added, or deleted since install.",
|
|
485
|
+
),
|
|
486
|
+
diff: z
|
|
487
|
+
.string()
|
|
488
|
+
.describe(
|
|
489
|
+
"Unified diff (`--- a/… / +++ b/…`) of the file. A short `Binary files differ` marker for binary files, or a `Baseline unavailable` marker when `reconstructed` is false.",
|
|
490
|
+
),
|
|
491
|
+
binary: z
|
|
492
|
+
.boolean()
|
|
493
|
+
.describe(
|
|
494
|
+
"True when either side was detected as binary (NUL byte present).",
|
|
495
|
+
),
|
|
496
|
+
reconstructed: z
|
|
497
|
+
.boolean()
|
|
498
|
+
.describe(
|
|
499
|
+
"True when the install-time baseline for this file was faithfully recovered (re-materialized bytes hash-match the install fingerprint). False when it could not be reconstructed (e.g. the curated adapter overlay changed since install), in which case `diff` is a marker, not a patch. Always true for added files.",
|
|
500
|
+
),
|
|
501
|
+
})
|
|
502
|
+
.describe("Unified diff of a single drifted file.");
|
|
503
|
+
|
|
504
|
+
const pluginDiffResponseSchema = z.object({
|
|
505
|
+
name: z
|
|
506
|
+
.string()
|
|
507
|
+
.describe("Install name. Matches `assistant plugins install <name>`."),
|
|
508
|
+
target: z
|
|
509
|
+
.string()
|
|
510
|
+
.describe("Absolute path to the installed plugin directory on the host."),
|
|
511
|
+
commit: z
|
|
512
|
+
.string()
|
|
513
|
+
.describe(
|
|
514
|
+
"Commit the baseline was re-materialized from (the recorded install SHA).",
|
|
515
|
+
),
|
|
516
|
+
committedAt: z
|
|
517
|
+
.string()
|
|
518
|
+
.nullable()
|
|
519
|
+
.describe(
|
|
520
|
+
"ISO-8601 committer timestamp (UTC) of `commit`; null when not recorded.",
|
|
521
|
+
),
|
|
522
|
+
clean: z
|
|
523
|
+
.boolean()
|
|
524
|
+
.describe(
|
|
525
|
+
"True when the on-disk tree exactly matches the re-materialized baseline.",
|
|
526
|
+
),
|
|
527
|
+
files: z
|
|
528
|
+
.array(pluginFileDiffSchema)
|
|
529
|
+
.describe(
|
|
530
|
+
"One entry per drifted file, sorted by path. Empty when `clean`.",
|
|
531
|
+
),
|
|
532
|
+
});
|
|
533
|
+
|
|
462
534
|
// ---------------------------------------------------------------------------
|
|
463
535
|
// Helpers
|
|
464
536
|
// ---------------------------------------------------------------------------
|
|
@@ -740,6 +812,45 @@ async function handleInspectPlugin({ pathParams = {} }: RouteHandlerArgs) {
|
|
|
740
812
|
}
|
|
741
813
|
}
|
|
742
814
|
|
|
815
|
+
// ---------------------------------------------------------------------------
|
|
816
|
+
// Handler — diff
|
|
817
|
+
// ---------------------------------------------------------------------------
|
|
818
|
+
|
|
819
|
+
async function handleDiffPlugin({ pathParams = {} }: RouteHandlerArgs) {
|
|
820
|
+
const rawName = pathParams.name ?? "";
|
|
821
|
+
|
|
822
|
+
try {
|
|
823
|
+
return await diffPlugin(
|
|
824
|
+
{ name: rawName },
|
|
825
|
+
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
826
|
+
);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
if (err instanceof InvalidPluginNameError) {
|
|
829
|
+
throw new BadRequestError(err.message);
|
|
830
|
+
}
|
|
831
|
+
if (
|
|
832
|
+
err instanceof PluginNotInstalledError ||
|
|
833
|
+
err instanceof PluginNotFoundError
|
|
834
|
+
) {
|
|
835
|
+
throw new NotFoundError(err.message);
|
|
836
|
+
}
|
|
837
|
+
// The install exists but recorded no commit to re-materialize a baseline
|
|
838
|
+
// from — a permanent state the caller cannot resolve by retrying. 409
|
|
839
|
+
// marks the request as well-formed but not actionable in the current state.
|
|
840
|
+
if (err instanceof PluginDiffUnavailableError) {
|
|
841
|
+
throw new ConflictError(err.message);
|
|
842
|
+
}
|
|
843
|
+
// A rate-limited or temporarily-down source (the plugin repo) is a
|
|
844
|
+
// retryable outage, not a conflict — 503.
|
|
845
|
+
if (err instanceof PluginSourceUnavailableError) {
|
|
846
|
+
throw new ServiceUnavailableError(err.message);
|
|
847
|
+
}
|
|
848
|
+
throw new InternalError(
|
|
849
|
+
err instanceof Error ? err.message : "plugin diff failed",
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
743
854
|
// ---------------------------------------------------------------------------
|
|
744
855
|
// Handler — upgrade
|
|
745
856
|
// ---------------------------------------------------------------------------
|
|
@@ -750,6 +861,10 @@ async function handleUpgradePlugin({
|
|
|
750
861
|
}: RouteHandlerArgs) {
|
|
751
862
|
const rawName = pathParams.name ?? "";
|
|
752
863
|
const dryRun = typeof body.dryRun === "boolean" ? body.dryRun : undefined;
|
|
864
|
+
const strategy =
|
|
865
|
+
typeof body.strategy === "string"
|
|
866
|
+
? (body.strategy as PluginUpgradeStrategy)
|
|
867
|
+
: undefined;
|
|
753
868
|
|
|
754
869
|
// Like install, the upgrade target ref is the curated marketplace pin
|
|
755
870
|
// (resolved inside `upgradePlugin` via `inspectPlugin`), never a
|
|
@@ -757,7 +872,7 @@ async function handleUpgradePlugin({
|
|
|
757
872
|
// upgrade at an unreviewed revision.
|
|
758
873
|
try {
|
|
759
874
|
const result = await upgradePlugin(
|
|
760
|
-
{ name: rawName, dryRun },
|
|
875
|
+
{ name: rawName, dryRun, strategy },
|
|
761
876
|
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
762
877
|
);
|
|
763
878
|
return {
|
|
@@ -770,10 +885,14 @@ async function handleUpgradePlugin({
|
|
|
770
885
|
target: result.target,
|
|
771
886
|
fileCount: result.fileCount,
|
|
772
887
|
dryRun: result.dryRun,
|
|
888
|
+
strategy: result.strategy,
|
|
773
889
|
provenanceWasUnknown: result.provenanceWasUnknown,
|
|
774
890
|
};
|
|
775
891
|
} catch (err) {
|
|
776
|
-
if (
|
|
892
|
+
if (
|
|
893
|
+
err instanceof InvalidPluginNameError ||
|
|
894
|
+
err instanceof PluginUpgradeStrategyUnsupportedError
|
|
895
|
+
) {
|
|
777
896
|
throw new BadRequestError(err.message);
|
|
778
897
|
}
|
|
779
898
|
if (err instanceof PluginNotInstalledError) {
|
|
@@ -782,6 +901,12 @@ async function handleUpgradePlugin({
|
|
|
782
901
|
if (err instanceof PluginNotFoundError) {
|
|
783
902
|
throw new NotFoundError(err.message);
|
|
784
903
|
}
|
|
904
|
+
// A merge strategy was requested but the install-time baseline can't be
|
|
905
|
+
// reconstructed — a well-formed request that isn't actionable in the
|
|
906
|
+
// current state (the caller can retry with `overwrite` or reinstall).
|
|
907
|
+
if (err instanceof PluginMergeBaselineError) {
|
|
908
|
+
throw new ConflictError(err.message);
|
|
909
|
+
}
|
|
785
910
|
// The install exists but has no marketplace entry to advance to — a
|
|
786
911
|
// permanent state the caller cannot resolve by retrying. 409 marks the
|
|
787
912
|
// request as well-formed but not actionable in the current state.
|
|
@@ -992,6 +1117,47 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
992
1117
|
},
|
|
993
1118
|
handler: handleInspectPlugin,
|
|
994
1119
|
},
|
|
1120
|
+
{
|
|
1121
|
+
operationId: "plugins_diff",
|
|
1122
|
+
endpoint: "plugins/:name/diff",
|
|
1123
|
+
method: "POST",
|
|
1124
|
+
policy: {
|
|
1125
|
+
requiredScopes: ["settings.read"],
|
|
1126
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
1127
|
+
},
|
|
1128
|
+
summary: "Diff a plugin against its install commit",
|
|
1129
|
+
description:
|
|
1130
|
+
"Show a unified diff of local edits to an installed plugin against the exact commit it was installed at (recorded in its `install-meta.json`). Computing the diff re-materializes the baseline through the install pipeline — a network clone plus a temp-dir write — so this is a POST: it is not a safe, cacheable GET even though it leaves persistent state untouched (requires only `settings.read`). Drift is classified against the install-time fingerprint so an adapter overlay that moved since install never reads as a local change. Returns the baseline `commit`, a `clean` flag, and a `files` array of `{ path, status, diff, binary, reconstructed }` for each modified/added/removed file. The baseline is the install commit, not the marketplace pin — comparing against the current pin is `POST /v1/plugins/:name/upgrade` with `dryRun`. A name with no installed copy returns 404; an install that recorded no commit or fingerprint returns 409; an unreachable source returns 503. Mirrors the CLI's `assistant plugins diff <name>`.",
|
|
1131
|
+
tags: ["plugins"],
|
|
1132
|
+
pathParams: [
|
|
1133
|
+
{
|
|
1134
|
+
name: "name",
|
|
1135
|
+
type: "string",
|
|
1136
|
+
description:
|
|
1137
|
+
"Install name. Must match the kebab-case name accepted by `assistant plugins install`.",
|
|
1138
|
+
},
|
|
1139
|
+
],
|
|
1140
|
+
responseBody: pluginDiffResponseSchema,
|
|
1141
|
+
additionalResponses: {
|
|
1142
|
+
"400": {
|
|
1143
|
+
description:
|
|
1144
|
+
"The plugin name failed sanitization (e.g. contained slashes, dots, or uppercase letters).",
|
|
1145
|
+
},
|
|
1146
|
+
"404": {
|
|
1147
|
+
description:
|
|
1148
|
+
"No copy of the plugin is installed, or its recorded commit resolves to nothing.",
|
|
1149
|
+
},
|
|
1150
|
+
"409": {
|
|
1151
|
+
description:
|
|
1152
|
+
"The install recorded no commit or no fingerprint to re-materialize and verify a baseline from.",
|
|
1153
|
+
},
|
|
1154
|
+
"503": {
|
|
1155
|
+
description:
|
|
1156
|
+
"The plugin source (GitHub) was temporarily unavailable; the diff is retryable.",
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
handler: handleDiffPlugin,
|
|
1160
|
+
},
|
|
995
1161
|
{
|
|
996
1162
|
operationId: "plugins_upgrade",
|
|
997
1163
|
endpoint: "plugins/:name/upgrade",
|
|
@@ -1002,7 +1168,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
1002
1168
|
},
|
|
1003
1169
|
summary: "Upgrade a plugin to the marketplace pin",
|
|
1004
1170
|
description:
|
|
1005
|
-
'Move an installed plugin to the marketplace\'s current pinned commit, re-materializing it under `<workspaceDir>/plugins/<name>/`. Always resolves against the curated marketplace pin (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the pin; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The assistant must be restarted to load the upgraded code.
|
|
1171
|
+
'Move an installed plugin to the marketplace\'s current pinned commit, re-materializing it under `<workspaceDir>/plugins/<name>/`. Always resolves against the curated marketplace pin (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the pin; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The assistant must be restarted to load the upgraded code. `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the pin wholesale; `ours`/`theirs` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit or the pin respectively; `assistant` is not yet supported (400). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
|
|
1006
1172
|
tags: ["plugins"],
|
|
1007
1173
|
pathParams: [
|
|
1008
1174
|
{
|
|
@@ -1017,7 +1183,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
1017
1183
|
additionalResponses: {
|
|
1018
1184
|
"400": {
|
|
1019
1185
|
description:
|
|
1020
|
-
"The plugin name failed sanitization (e.g. contained slashes, dots, or uppercase letters).",
|
|
1186
|
+
"The plugin name failed sanitization (e.g. contained slashes, dots, or uppercase letters), or the `assistant` strategy was requested (not yet supported).",
|
|
1021
1187
|
},
|
|
1022
1188
|
"404": {
|
|
1023
1189
|
description:
|
|
@@ -1025,7 +1191,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
1025
1191
|
},
|
|
1026
1192
|
"409": {
|
|
1027
1193
|
description:
|
|
1028
|
-
"The install exists but has no marketplace entry to advance to.",
|
|
1194
|
+
"The install exists but has no marketplace entry to advance to, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
|
|
1029
1195
|
},
|
|
1030
1196
|
"503": {
|
|
1031
1197
|
description:
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
11
|
-
import { getConfig } from "../../config/loader.js";
|
|
12
10
|
import { getOrCreateConversation } from "../../daemon/conversation-store.js";
|
|
13
11
|
import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../../daemon/trust-context.js";
|
|
14
12
|
import { bootstrapConversation } from "../../memory/conversation-bootstrap.js";
|
|
@@ -299,7 +297,6 @@ function handleCreateSchedule(body: Record<string, unknown>) {
|
|
|
299
297
|
}
|
|
300
298
|
|
|
301
299
|
if (mode === "workflow") {
|
|
302
|
-
assertWorkflowsEnabled();
|
|
303
300
|
const workflowName =
|
|
304
301
|
typeof body.workflowName === "string" ? body.workflowName.trim() : "";
|
|
305
302
|
if (!workflowName) {
|
|
@@ -351,17 +348,6 @@ function handleCreateSchedule(body: Record<string, unknown>) {
|
|
|
351
348
|
return handleListSchedules({});
|
|
352
349
|
}
|
|
353
350
|
|
|
354
|
-
/**
|
|
355
|
-
* Reject workflow-mode schedule operations when the `workflows` feature flag
|
|
356
|
-
* is off. Scheduled workflow runs would fail at trigger time (the run manager
|
|
357
|
-
* hard-fails with WorkflowsDisabledError), so we fail fast at the route.
|
|
358
|
-
*/
|
|
359
|
-
function assertWorkflowsEnabled(): void {
|
|
360
|
-
if (!isAssistantFeatureFlagEnabled("workflows", getConfig())) {
|
|
361
|
-
throw new BadRequestError("Workflows are not enabled.");
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
351
|
function handleToggleSchedule(id: string, body: Record<string, unknown>) {
|
|
366
352
|
const enabled = body.enabled;
|
|
367
353
|
if (typeof enabled !== "boolean") {
|
|
@@ -427,11 +413,6 @@ function handleUpdateSchedule(id: string, body: Record<string, unknown>) {
|
|
|
427
413
|
);
|
|
428
414
|
}
|
|
429
415
|
|
|
430
|
-
// Switching a schedule into workflow mode requires the workflows flag.
|
|
431
|
-
if (body.mode === "workflow") {
|
|
432
|
-
assertWorkflowsEnabled();
|
|
433
|
-
}
|
|
434
|
-
|
|
435
416
|
// Mirror the create-side validation: a schedule whose RESULTING mode is
|
|
436
417
|
// `workflow` must carry a non-empty `workflowName`. Without this, a PATCH can
|
|
437
418
|
// leave a workflow-mode schedule nameless — the scheduler then hits the
|
|
@@ -946,9 +927,6 @@ async function handleRunScheduleNow(id: string) {
|
|
|
946
927
|
// `schedule.message` (usually empty — workflow-mode create no longer requires
|
|
947
928
|
// a message), running a no-op normal turn instead of the workflow.
|
|
948
929
|
if (schedule.mode === "workflow") {
|
|
949
|
-
// Fail fast at the route when the flag is off (the run manager would also
|
|
950
|
-
// hard-fail with WorkflowsDisabledError); matches create/update handling.
|
|
951
|
-
assertWorkflowsEnabled();
|
|
952
930
|
if (!schedule.workflowName) {
|
|
953
931
|
throw new BadRequestError("Workflow schedule has no workflowName");
|
|
954
932
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import type { AssistantConfig } from "../../config/schema.js";
|
|
4
3
|
import type { WorkflowRun } from "../../workflows/journal-store.js";
|
|
5
4
|
import type { SavedWorkflowEntry } from "../../workflows/library.js";
|
|
6
5
|
import {
|
|
@@ -60,7 +59,6 @@ interface FakeManager {
|
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
function setup(opts: {
|
|
63
|
-
flagEnabled?: boolean;
|
|
64
62
|
runs?: WorkflowRun[];
|
|
65
63
|
saved?: SavedWorkflowEntry[];
|
|
66
64
|
/** Custom resume impl; defaults to a success that records the id. */
|
|
@@ -98,8 +96,6 @@ function setup(opts: {
|
|
|
98
96
|
__setWorkflowRoutesDeps({
|
|
99
97
|
getManager: () => manager,
|
|
100
98
|
listWorkflows: () => opts.saved ?? [],
|
|
101
|
-
getConfig: () => ({}) as AssistantConfig,
|
|
102
|
-
isFlagEnabled: () => opts.flagEnabled ?? true,
|
|
103
99
|
getAutoApproveThreshold: async () => opts.threshold ?? "none",
|
|
104
100
|
});
|
|
105
101
|
return { aborted, resumed, listCalls };
|
|
@@ -113,10 +109,9 @@ afterEach(() => {
|
|
|
113
109
|
// Happy paths
|
|
114
110
|
// ---------------------------------------------------------------------------
|
|
115
111
|
|
|
116
|
-
describe("workflow routes (
|
|
112
|
+
describe("workflow routes (happy paths)", () => {
|
|
117
113
|
beforeEach(() => {
|
|
118
114
|
setup({
|
|
119
|
-
flagEnabled: true,
|
|
120
115
|
runs: [
|
|
121
116
|
makeRun({ id: "run-1" }),
|
|
122
117
|
makeRun({ id: "run-2", status: "completed" }),
|
|
@@ -145,7 +140,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
145
140
|
|
|
146
141
|
test("listWorkflowRuns honors limit + status query params", async () => {
|
|
147
142
|
const { listCalls } = setup({
|
|
148
|
-
flagEnabled: true,
|
|
149
143
|
runs: [makeRun({ id: "run-1", status: "completed" })],
|
|
150
144
|
});
|
|
151
145
|
await route("listWorkflowRuns").handler({
|
|
@@ -163,7 +157,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
163
157
|
|
|
164
158
|
test("abortWorkflowRun aborts a known run", async () => {
|
|
165
159
|
const { aborted } = setup({
|
|
166
|
-
flagEnabled: true,
|
|
167
160
|
runs: [makeRun({ id: "run-1" })],
|
|
168
161
|
});
|
|
169
162
|
const result = (await route("abortWorkflowRun").handler({
|
|
@@ -175,7 +168,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
175
168
|
|
|
176
169
|
test("resumeWorkflowRun resumes an interrupted run", async () => {
|
|
177
170
|
const { resumed } = setup({
|
|
178
|
-
flagEnabled: true,
|
|
179
171
|
runs: [makeRun({ id: "run-1", status: "interrupted" })],
|
|
180
172
|
});
|
|
181
173
|
const result = (await route("resumeWorkflowRun").handler({
|
|
@@ -192,7 +184,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
192
184
|
// full-access posture it must refuse rather than silently bypass consent —
|
|
193
185
|
// resume() is never called.
|
|
194
186
|
const { resumed } = setup({
|
|
195
|
-
flagEnabled: true,
|
|
196
187
|
threshold: "medium",
|
|
197
188
|
runs: [
|
|
198
189
|
makeRun({
|
|
@@ -213,7 +204,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
213
204
|
// high-risk tools, so no prompt is needed — the side-effecting resume
|
|
214
205
|
// proceeds directly.
|
|
215
206
|
const { resumed } = setup({
|
|
216
|
-
flagEnabled: true,
|
|
217
207
|
threshold: "high",
|
|
218
208
|
runs: [
|
|
219
209
|
makeRun({
|
|
@@ -236,7 +226,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
236
226
|
// the object shape too — a strict parse would treat it as read-only and let
|
|
237
227
|
// the side-effecting resume through without approval.
|
|
238
228
|
const { resumed } = setup({
|
|
239
|
-
flagEnabled: true,
|
|
240
229
|
threshold: "medium",
|
|
241
230
|
runs: [
|
|
242
231
|
makeRun({
|
|
@@ -259,7 +248,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
259
248
|
// An explicit empty manifest grants no side effects, so the route resumes
|
|
260
249
|
// it directly — the gate keys on the stored manifest, not run existence.
|
|
261
250
|
const { resumed } = setup({
|
|
262
|
-
flagEnabled: true,
|
|
263
251
|
threshold: "medium",
|
|
264
252
|
runs: [
|
|
265
253
|
makeRun({
|
|
@@ -275,7 +263,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
275
263
|
|
|
276
264
|
test("resumeWorkflowRun maps a non-interrupted run to a 409 ConflictError", async () => {
|
|
277
265
|
setup({
|
|
278
|
-
flagEnabled: true,
|
|
279
266
|
runs: [makeRun({ id: "run-1", status: "completed" })],
|
|
280
267
|
resume: (id) => {
|
|
281
268
|
throw new WorkflowResumeNotPossibleError(
|
|
@@ -292,7 +279,6 @@ describe("workflow routes (flag on)", () => {
|
|
|
292
279
|
|
|
293
280
|
test("resumeWorkflowRun maps a cap error to a 429 TooManyRequestsError", async () => {
|
|
294
281
|
setup({
|
|
295
|
-
flagEnabled: true,
|
|
296
282
|
runs: [makeRun({ id: "run-1", status: "interrupted" })],
|
|
297
283
|
resume: () => {
|
|
298
284
|
throw new WorkflowRunCapError(3);
|
|
@@ -323,14 +309,14 @@ describe("workflow routes (flag on)", () => {
|
|
|
323
309
|
|
|
324
310
|
describe("workflow routes (unknown run)", () => {
|
|
325
311
|
test("getWorkflowRun throws NotFoundError for an unknown id", () => {
|
|
326
|
-
setup({
|
|
312
|
+
setup({ runs: [] });
|
|
327
313
|
expect(() =>
|
|
328
314
|
route("getWorkflowRun").handler({ pathParams: { id: "nope" } }),
|
|
329
315
|
).toThrow(NotFoundError);
|
|
330
316
|
});
|
|
331
317
|
|
|
332
318
|
test("abortWorkflowRun throws NotFoundError for an unknown id", () => {
|
|
333
|
-
const { aborted } = setup({
|
|
319
|
+
const { aborted } = setup({ runs: [] });
|
|
334
320
|
expect(() =>
|
|
335
321
|
route("abortWorkflowRun").handler({ pathParams: { id: "nope" } }),
|
|
336
322
|
).toThrow(NotFoundError);
|
|
@@ -338,35 +324,10 @@ describe("workflow routes (unknown run)", () => {
|
|
|
338
324
|
});
|
|
339
325
|
|
|
340
326
|
test("resumeWorkflowRun throws NotFoundError for an unknown id", async () => {
|
|
341
|
-
const { resumed } = setup({
|
|
327
|
+
const { resumed } = setup({ runs: [] });
|
|
342
328
|
await expect(
|
|
343
329
|
route("resumeWorkflowRun").handler({ pathParams: { id: "nope" } }),
|
|
344
330
|
).rejects.toThrow(NotFoundError);
|
|
345
331
|
expect(resumed).toEqual([]);
|
|
346
332
|
});
|
|
347
333
|
});
|
|
348
|
-
|
|
349
|
-
// ---------------------------------------------------------------------------
|
|
350
|
-
// Flag off → every route 404s
|
|
351
|
-
// ---------------------------------------------------------------------------
|
|
352
|
-
|
|
353
|
-
describe("workflow routes (flag off)", () => {
|
|
354
|
-
beforeEach(() => {
|
|
355
|
-
setup({ flagEnabled: false, runs: [makeRun({ id: "run-1" })], saved: [] });
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test.each([
|
|
359
|
-
["listWorkflowRuns", { queryParams: {} }],
|
|
360
|
-
["getWorkflowRun", { pathParams: { id: "run-1" } }],
|
|
361
|
-
["abortWorkflowRun", { pathParams: { id: "run-1" } }],
|
|
362
|
-
["listSavedWorkflows", {}],
|
|
363
|
-
] as const)("%s throws NotFoundError when the flag is off", (op, args) => {
|
|
364
|
-
expect(() => route(op).handler(args)).toThrow(NotFoundError);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test("resumeWorkflowRun rejects with NotFoundError when the flag is off", async () => {
|
|
368
|
-
await expect(
|
|
369
|
-
route("resumeWorkflowRun").handler({ pathParams: { id: "run-1" } }),
|
|
370
|
-
).rejects.toThrow(NotFoundError);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
@@ -5,17 +5,10 @@
|
|
|
5
5
|
* shared ROUTES array. They are read/abort surfaces over the workflow run
|
|
6
6
|
* manager and the saved-workflow library — a human (via the `vellum workflows`
|
|
7
7
|
* CLI or the app) can inspect runs and abort an in-flight one.
|
|
8
|
-
*
|
|
9
|
-
* Every handler is gated on the `workflows` feature flag: when it is off, the
|
|
10
|
-
* routes behave as if they do not exist (404), so disabling the flag fully
|
|
11
|
-
* hides the surface.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
10
|
import { z } from "zod";
|
|
15
11
|
|
|
16
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
17
|
-
import { getConfig } from "../../config/loader.js";
|
|
18
|
-
import type { AssistantConfig } from "../../config/schema.js";
|
|
19
12
|
import { getAutoApproveThreshold } from "../../permissions/gateway-threshold-reader.js";
|
|
20
13
|
import { isFullAccessThreshold } from "../../permissions/threshold.js";
|
|
21
14
|
import { manifestGrantsSideEffects } from "../../workflows/capabilities.js";
|
|
@@ -46,8 +39,6 @@ export interface WorkflowRoutesDeps {
|
|
|
46
39
|
"list" | "status" | "abort" | "resume"
|
|
47
40
|
>;
|
|
48
41
|
listWorkflows: typeof listWorkflows;
|
|
49
|
-
getConfig: () => AssistantConfig;
|
|
50
|
-
isFlagEnabled: (config: AssistantConfig) => boolean;
|
|
51
42
|
getAutoApproveThreshold: typeof getAutoApproveThreshold;
|
|
52
43
|
}
|
|
53
44
|
|
|
@@ -55,9 +46,6 @@ function defaultDeps(): WorkflowRoutesDeps {
|
|
|
55
46
|
return {
|
|
56
47
|
getManager: getWorkflowRunManager,
|
|
57
48
|
listWorkflows,
|
|
58
|
-
getConfig,
|
|
59
|
-
isFlagEnabled: (config) =>
|
|
60
|
-
isAssistantFeatureFlagEnabled("workflows", config),
|
|
61
49
|
getAutoApproveThreshold,
|
|
62
50
|
};
|
|
63
51
|
}
|
|
@@ -109,17 +97,6 @@ const savedWorkflowSchema = z.object({
|
|
|
109
97
|
// Handlers (transport-agnostic)
|
|
110
98
|
// ---------------------------------------------------------------------------
|
|
111
99
|
|
|
112
|
-
/**
|
|
113
|
-
* Throw a 404 when the `workflows` flag is off, hiding the whole surface. The
|
|
114
|
-
* NotFoundError (rather than a 403) keeps a disabled-flag indistinguishable
|
|
115
|
-
* from a route that does not exist.
|
|
116
|
-
*/
|
|
117
|
-
function assertFlagEnabled(): void {
|
|
118
|
-
if (!deps.isFlagEnabled(deps.getConfig())) {
|
|
119
|
-
throw new NotFoundError("Workflows are not enabled.");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
100
|
/**
|
|
124
101
|
* The single compact-run wire contract, inferred from {@link workflowRunSchema}.
|
|
125
102
|
* The HTTP routes return this shape; other producers (e.g. the CLI's client-side
|
|
@@ -162,7 +139,6 @@ function parseStatus(
|
|
|
162
139
|
}
|
|
163
140
|
|
|
164
141
|
function handleListRuns(queryParams: Record<string, string>) {
|
|
165
|
-
assertFlagEnabled();
|
|
166
142
|
const limit = parseLimit(queryParams.limit);
|
|
167
143
|
const status = parseStatus(queryParams.status);
|
|
168
144
|
const runs = deps.getManager().list({
|
|
@@ -173,7 +149,6 @@ function handleListRuns(queryParams: Record<string, string>) {
|
|
|
173
149
|
}
|
|
174
150
|
|
|
175
151
|
function handleGetRun(id: string) {
|
|
176
|
-
assertFlagEnabled();
|
|
177
152
|
const run = deps.getManager().status(id);
|
|
178
153
|
if (!run) {
|
|
179
154
|
throw new NotFoundError(`Workflow run ${id} not found`);
|
|
@@ -182,7 +157,6 @@ function handleGetRun(id: string) {
|
|
|
182
157
|
}
|
|
183
158
|
|
|
184
159
|
function handleAbortRun(id: string) {
|
|
185
|
-
assertFlagEnabled();
|
|
186
160
|
// status() is the source of truth for existence; abort() itself is a no-op
|
|
187
161
|
// for unknown/finished runs, so 404 on an unknown id is surfaced here.
|
|
188
162
|
const manager = deps.getManager();
|
|
@@ -194,7 +168,6 @@ function handleAbortRun(id: string) {
|
|
|
194
168
|
}
|
|
195
169
|
|
|
196
170
|
async function handleResumeRun(id: string) {
|
|
197
|
-
assertFlagEnabled();
|
|
198
171
|
const manager = deps.getManager();
|
|
199
172
|
// status() is the source of truth for existence, so 404 an unknown id here
|
|
200
173
|
// (matching abort) rather than letting resume's "not_found" reach the client
|
|
@@ -245,7 +218,6 @@ async function handleResumeRun(id: string) {
|
|
|
245
218
|
}
|
|
246
219
|
|
|
247
220
|
function handleListSavedWorkflows() {
|
|
248
|
-
assertFlagEnabled();
|
|
249
221
|
return { workflows: deps.listWorkflows() };
|
|
250
222
|
}
|
|
251
223
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
4
|
+
|
|
5
|
+
function readWorkspaceFile(name: string): string {
|
|
6
|
+
try {
|
|
7
|
+
const path = getWorkspacePromptPath(name);
|
|
8
|
+
if (!existsSync(path)) return "";
|
|
9
|
+
return readFileSync(path, "utf-8");
|
|
10
|
+
} catch {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse the `## Greetings` section from SOUL.md. Returns bullet items as an
|
|
17
|
+
* array of strings, or `null` if the section is missing or empty.
|
|
18
|
+
*/
|
|
19
|
+
export function parseGreetingsSection(content: string): string[] | null {
|
|
20
|
+
let inSection = false;
|
|
21
|
+
let sectionLevel: number | null = null;
|
|
22
|
+
const greetings: string[] = [];
|
|
23
|
+
|
|
24
|
+
for (const line of content.split("\n")) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
const heading = /^(#{1,6})\s+(.+?)\s*#*$/.exec(trimmed);
|
|
27
|
+
if (heading) {
|
|
28
|
+
const level = heading[1]!.length;
|
|
29
|
+
const title = heading[2]!.trim();
|
|
30
|
+
if (inSection) {
|
|
31
|
+
if (sectionLevel !== null && level <= sectionLevel) break;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (level === 2 && /^greetings$/i.test(title)) {
|
|
35
|
+
inSection = true;
|
|
36
|
+
sectionLevel = level;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!inSection) continue;
|
|
41
|
+
const bullet = /^(?:[-*+]\s+|\d+[.)]\s+)(.+)$/.exec(trimmed);
|
|
42
|
+
const greeting = bullet?.[1]?.trim();
|
|
43
|
+
if (greeting) {
|
|
44
|
+
greetings.push(greeting);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return greetings.length > 0 ? greetings : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function readWorkspaceGreetings(): string[] | null {
|
|
52
|
+
const soulContent = readWorkspaceFile("SOUL.md");
|
|
53
|
+
if (!soulContent) return null;
|
|
54
|
+
return parseGreetingsSection(soulContent);
|
|
55
|
+
}
|
|
@@ -29,17 +29,7 @@ export function publishIdentityChanged(
|
|
|
29
29
|
emoji: fields.emoji,
|
|
30
30
|
home: fields.home,
|
|
31
31
|
});
|
|
32
|
-
void publishSyncInvalidation(
|
|
33
|
-
[SYNC_TAGS.assistantIdentity, SYNC_TAGS.assistantIdentityIntro],
|
|
34
|
-
originClientId,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function publishIdentityIntroChanged(originClientId?: string): void {
|
|
39
|
-
void publishSyncInvalidation(
|
|
40
|
-
[SYNC_TAGS.assistantIdentityIntro],
|
|
41
|
-
originClientId,
|
|
42
|
-
);
|
|
32
|
+
void publishSyncInvalidation([SYNC_TAGS.assistantIdentity], originClientId);
|
|
43
33
|
}
|
|
44
34
|
|
|
45
35
|
export function publishConfigChanged(originClientId?: string): void {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateInferenceProfileKey } from "../config/inference-profile-validation.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Validate a schedule's inference-profile key against the configured
|
|
@@ -12,17 +12,5 @@ import { getConfigReadOnly } from "../config/loader.js";
|
|
|
12
12
|
export function validateScheduleInferenceProfile(
|
|
13
13
|
profile: string,
|
|
14
14
|
): string | null {
|
|
15
|
-
|
|
16
|
-
return "inferenceProfile must be a non-empty string";
|
|
17
|
-
}
|
|
18
|
-
const profiles = getConfigReadOnly().llm?.profiles ?? {};
|
|
19
|
-
if (!Object.prototype.hasOwnProperty.call(profiles, profile)) {
|
|
20
|
-
const available = Object.keys(profiles).sort();
|
|
21
|
-
const hint =
|
|
22
|
-
available.length > 0
|
|
23
|
-
? ` Available profiles: ${available.join(", ")}.`
|
|
24
|
-
: " No profiles defined in llm.profiles.";
|
|
25
|
-
return `Inference profile "${profile}" is not defined in llm.profiles.${hint}`;
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
15
|
+
return validateInferenceProfileKey(profile);
|
|
28
16
|
}
|