@vellumai/assistant 0.11.10 → 0.11.11-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 +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/openapi.yaml +152 -55
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +60 -0
- package/src/__tests__/config-get-vision-flag.test.ts +36 -0
- package/src/__tests__/conversation-error.test.ts +38 -0
- package/src/__tests__/copy-composer-tc-templates.test.ts +2 -2
- package/src/__tests__/guardian-action-service-scope.test.ts +53 -0
- package/src/__tests__/headless-browser-mode.test.ts +63 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +118 -27
- package/src/__tests__/host-app-control-proxy.test.ts +2 -2
- package/src/__tests__/host-app-control-routes.test.ts +41 -0
- package/src/__tests__/host-bash-routes.test.ts +30 -1
- package/src/__tests__/host-browser-routes.test.ts +34 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +29 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +29 -0
- package/src/__tests__/host-proxy-base.test.ts +1 -0
- package/src/__tests__/host-transfer-routes-targeted.test.ts +86 -0
- package/src/__tests__/llm-schema.test.ts +32 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +2 -0
- package/src/__tests__/memory-config-file-parity.test.ts +18 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +15 -0
- package/src/__tests__/messaging-send-tool.test.ts +159 -13
- package/src/__tests__/messaging-shared.test.ts +19 -0
- package/src/__tests__/notification-events-source-context-probe.test.ts +211 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +40 -6
- package/src/__tests__/outlook-messaging-provider.test.ts +9 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +2 -2
- package/src/__tests__/schedule-routes.test.ts +30 -1
- package/src/__tests__/scheduler-result-notification.test.ts +209 -0
- package/src/__tests__/skills-install-extract.test.ts +26 -0
- package/src/__tests__/skills-install-staging.test.ts +11 -2
- package/src/api/constants/personality-sliders.test.ts +21 -0
- package/src/api/constants/personality-sliders.ts +64 -0
- package/src/api/index.ts +8 -0
- package/src/archive/ustar-size.test.ts +32 -0
- package/src/archive/ustar-size.ts +23 -0
- package/src/background-wake/next-wake.test.ts +13 -0
- package/src/background-wake/next-wake.ts +4 -2
- package/src/calls/voice-session-bridge.ts +26 -0
- package/src/cli/__tests__/catalog-search-help.test.ts +1 -0
- package/src/cli/commands/__tests__/plugins-search.test.ts +81 -0
- package/src/cli/commands/config.help.ts +3 -1
- package/src/cli/commands/memory/worker.ts +1 -1
- package/src/cli/commands/platform/__tests__/credits-format.test.ts +94 -5
- package/src/cli/commands/platform/__tests__/credits.test.ts +10 -0
- package/src/cli/commands/platform/credits-format.ts +57 -0
- package/src/cli/commands/platform/index.help.ts +19 -3
- package/src/cli/commands/plugins.help.ts +4 -1
- package/src/cli/commands/plugins.ts +2 -1
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-platform.ts +8 -10
- package/src/config/bundled-skills/messaging/SKILL.md +2 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -4
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +85 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +9 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +87 -20
- package/src/config/bundled-skills/screen-annotation/TOOLS.json +21 -5
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +67 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/input-modalities.test.ts +71 -0
- package/src/config/input-modalities.ts +95 -0
- package/src/config/schemas/heartbeat.ts +3 -3
- package/src/config/schemas/llm.ts +13 -3
- package/src/config/schemas/memory-storage.test.ts +39 -0
- package/src/config/schemas/memory-storage.ts +21 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/daemon/__tests__/embedding-reconcile.test.ts +77 -0
- package/src/daemon/__tests__/lifecycle-embedding-reconcile.test.ts +3 -0
- package/src/daemon/conversation-error.ts +20 -7
- package/src/daemon/embedding-reconcile.ts +64 -1
- package/src/daemon/handlers/config-embeddings.ts +31 -0
- package/src/daemon/host-app-control-proxy.ts +2 -0
- package/src/daemon/host-bash-proxy.ts +1 -0
- package/src/daemon/host-cu-proxy.ts +6 -6
- package/src/daemon/host-file-proxy.ts +6 -6
- package/src/daemon/host-proxy-base.ts +10 -9
- package/src/daemon/host-transfer-proxy.ts +21 -24
- package/src/daemon/mcp-reload-service.ts +6 -0
- package/src/daemon/providers-setup.ts +5 -26
- package/src/heartbeat/heartbeat-service.ts +72 -13
- package/src/live-voice/__tests__/live-voice-photo.test.ts +72 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +81 -0
- package/src/live-voice/live-voice-photo.ts +68 -4
- package/src/live-voice/live-voice-session.ts +36 -2
- package/src/live-voice/protocol.ts +98 -1
- package/src/mcp/__tests__/reload-signal-emission.test.ts +54 -0
- package/src/mcp/__tests__/reload-signal.test.ts +57 -0
- package/src/mcp/__tests__/startup.test.ts +150 -0
- package/src/mcp/reload-signal.ts +50 -0
- package/src/mcp/startup.ts +101 -0
- package/src/messaging/providers/outlook/adapter.ts +2 -13
- package/src/messaging/providers/outlook/client.ts +13 -1
- package/src/messaging/providers/outlook/types.ts +3 -0
- package/src/notifications/__tests__/decision-engine.test.ts +79 -6
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +27 -0
- package/src/notifications/__tests__/schedule-result-producer.test.ts +397 -0
- package/src/notifications/copy-composer.ts +33 -11
- package/src/notifications/decision-engine.ts +48 -11
- package/src/notifications/events-store.ts +68 -2
- package/src/notifications/home-feed-side-effect.ts +15 -3
- package/src/notifications/schedule-result-producer.ts +275 -0
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/seed-providers.ts +3 -2
- package/src/persistence/conversation-crud.ts +38 -0
- package/src/persistence/embeddings/embedding-backend.test.ts +162 -0
- package/src/persistence/embeddings/embedding-backend.ts +148 -11
- package/src/persistence/embeddings/embedding-openai.test.ts +113 -0
- package/src/persistence/embeddings/embedding-openai.ts +55 -4
- package/src/persistence/embeddings/embedding-types.test.ts +24 -0
- package/src/persistence/embeddings/embedding-types.ts +25 -1
- package/src/persistence/job-utils.ts +6 -2
- package/src/plugin-api/index.ts +2 -1
- package/src/plugin-api/vision-support.test.ts +81 -0
- package/src/plugin-api/vision-support.ts +57 -10
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +8 -0
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +1 -0
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +11 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +20 -0
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +3 -1
- package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +82 -0
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +9 -27
- package/src/providers/__tests__/provider-secret-catalog.test.ts +4 -0
- package/src/providers/__tests__/xml-tool-call-salvage.test.ts +172 -0
- package/src/providers/anthropic/client.ts +9 -4
- package/src/providers/inline-audio-support.test.ts +34 -0
- package/src/providers/inline-audio-support.ts +27 -0
- package/src/providers/model-catalog.ts +22 -2
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +50 -0
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +51 -0
- package/src/providers/openai/__tests__/chat-completions-provider-xml-salvage.test.ts +291 -0
- package/src/providers/openai/api-error-normalization.ts +18 -3
- package/src/providers/openai/chat-completions-provider.ts +113 -13
- package/src/providers/openai/responses-provider.ts +10 -8
- package/src/providers/provider-secret-catalog.ts +11 -1
- package/src/providers/vellum/client.ts +14 -1
- package/src/providers/vellum/personality-directions.test.ts +163 -0
- package/src/providers/vellum/personality-directions.ts +120 -0
- package/src/providers/xml-tool-call-salvage.ts +212 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +42 -4
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -6
- package/src/runtime/auth/__tests__/route-policy.test.ts +65 -0
- package/src/runtime/auth/same-actor.ts +29 -5
- package/src/runtime/background-job-runner.ts +38 -29
- package/src/runtime/guardian-action-service.ts +20 -1
- package/src/runtime/migrations/vbundle-validator.ts +3 -3
- package/src/runtime/pending-interactions.ts +4 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +24 -0
- package/src/runtime/routes/__tests__/platform-credits-routes.test.ts +99 -5
- package/src/runtime/routes/conversation-query-routes.ts +53 -11
- package/src/runtime/routes/document-comments-routes.ts +8 -2
- package/src/runtime/routes/guardian-action-routes.ts +7 -0
- package/src/runtime/routes/heartbeat-routes.ts +41 -53
- package/src/runtime/routes/host-app-control-routes.ts +10 -36
- package/src/runtime/routes/host-bash-routes.ts +11 -45
- package/src/runtime/routes/host-browser-routes.ts +24 -0
- package/src/runtime/routes/host-cu-routes.ts +11 -45
- package/src/runtime/routes/host-file-routes.ts +11 -44
- package/src/runtime/routes/host-proxy-result-binding.ts +57 -0
- package/src/runtime/routes/host-transfer-routes.ts +33 -25
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/integrations/a2a.ts +12 -3
- package/src/runtime/routes/integrations/vercel.ts +13 -3
- package/src/runtime/routes/platform-routes.ts +121 -24
- package/src/runtime/routes/schedule-routes.ts +15 -1
- package/src/schedule/__tests__/schedule-timezone.test.ts +21 -3
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +88 -0
- package/src/schedule/schedule-timezone.ts +25 -0
- package/src/schedule/scheduler.ts +19 -0
- package/src/schedule/worker.ts +119 -2
- package/src/skills/catalog-install.ts +40 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +3 -0
- package/src/tools/browser/browser-execution.ts +117 -53
- package/src/tools/browser/browser-status-constants.ts +6 -6
- package/src/tools/capability-offer.test.ts +87 -0
- package/src/tools/capability-offer.ts +93 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +17 -0
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/util/provider-error-patterns.ts +34 -0
- package/src/watcher/__tests__/engine.test.ts +5 -2
- package/src/watcher/engine.ts +7 -4
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +0 -487
- package/src/runtime/routes/sight-frame-routes.ts +0 -136
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Personality slider sidecar path, the five 0-100 axis ids, and the
|
|
3
|
+
* hosted-Qwen direction poles those sliders map onto.
|
|
4
|
+
*
|
|
5
|
+
* The web writes `data/personality-sliders.json`. The assistant reads the
|
|
6
|
+
* same path and maps each slider onto one left/right pole. Both import
|
|
7
|
+
* these constants from `@vellumai/assistant-api` so the ids cannot drift.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Workspace-relative sidecar holding the five 0-100 slider values. */
|
|
11
|
+
export const PERSONALITY_SLIDERS_PATH = "data/personality-sliders.json";
|
|
12
|
+
|
|
13
|
+
/** Sliders start centered. Center is no steer on the hosted-Qwen route. */
|
|
14
|
+
export const PERSONALITY_SLIDER_DEFAULT = 50;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Axis ids the personality sliders key their values by. Each is 0-100 with
|
|
18
|
+
* 0 = the left label and 100 = the right label.
|
|
19
|
+
*/
|
|
20
|
+
export const PERSONALITY_AXIS_IDS = {
|
|
21
|
+
companionCoworker: "companion-coworker",
|
|
22
|
+
genzBoomer: "genz-boomer",
|
|
23
|
+
executeCollaborate: "execute-collaborate",
|
|
24
|
+
playfulSerious: "playful-serious",
|
|
25
|
+
politeUnfiltered: "polite-unfiltered",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export type PersonalityAxisId =
|
|
29
|
+
(typeof PERSONALITY_AXIS_IDS)[keyof typeof PERSONALITY_AXIS_IDS];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* One-sided direction poles for each slider. Center (50) activates neither.
|
|
33
|
+
* Values below 50 activate `left`; values above 50 activate `right`.
|
|
34
|
+
*/
|
|
35
|
+
export const PERSONALITY_DIRECTION_AXES = [
|
|
36
|
+
{
|
|
37
|
+
sliderId: PERSONALITY_AXIS_IDS.companionCoworker,
|
|
38
|
+
left: "companion",
|
|
39
|
+
right: "coworker",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
sliderId: PERSONALITY_AXIS_IDS.genzBoomer,
|
|
43
|
+
left: "genz",
|
|
44
|
+
right: "boomer",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
sliderId: PERSONALITY_AXIS_IDS.executeCollaborate,
|
|
48
|
+
left: "independent",
|
|
49
|
+
right: "collaborative",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
sliderId: PERSONALITY_AXIS_IDS.playfulSerious,
|
|
53
|
+
left: "playful",
|
|
54
|
+
right: "serious",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
sliderId: PERSONALITY_AXIS_IDS.politeUnfiltered,
|
|
58
|
+
left: "polite",
|
|
59
|
+
right: "unfiltered",
|
|
60
|
+
},
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export type PersonalityDirectionAxis =
|
|
64
|
+
(typeof PERSONALITY_DIRECTION_AXES)[number];
|
package/src/api/index.ts
CHANGED
|
@@ -153,6 +153,14 @@ export {
|
|
|
153
153
|
type DocumentMutationTool,
|
|
154
154
|
REOPENABLE_DOCUMENT_MUTATION_TOOL_NAMES,
|
|
155
155
|
} from "./constants/document-tools.js";
|
|
156
|
+
export {
|
|
157
|
+
PERSONALITY_AXIS_IDS,
|
|
158
|
+
PERSONALITY_DIRECTION_AXES,
|
|
159
|
+
PERSONALITY_SLIDER_DEFAULT,
|
|
160
|
+
PERSONALITY_SLIDERS_PATH,
|
|
161
|
+
type PersonalityAxisId,
|
|
162
|
+
type PersonalityDirectionAxis,
|
|
163
|
+
} from "./constants/personality-sliders.js";
|
|
156
164
|
export {
|
|
157
165
|
MIN_INPUT_RESERVE_TOKENS,
|
|
158
166
|
type ProfileConfigIssue,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { MALFORMED_USTAR_SIZE, parseUstarSizeField } from "./ustar-size.js";
|
|
4
|
+
|
|
5
|
+
function headerWithSize(field: string): Uint8Array {
|
|
6
|
+
const header = new Uint8Array(512);
|
|
7
|
+
const bytes = Buffer.from(field, "utf-8");
|
|
8
|
+
header.set(bytes.subarray(0, 12), 124);
|
|
9
|
+
return header;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("parseUstarSizeField", () => {
|
|
13
|
+
test("reads a NUL-terminated octal size", () => {
|
|
14
|
+
expect(parseUstarSizeField(headerWithSize("00000001000\0"))).toBe(512);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("treats an empty field as zero", () => {
|
|
18
|
+
expect(parseUstarSizeField(headerWithSize("\0".repeat(12)))).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("rejects a negative octal size", () => {
|
|
22
|
+
expect(() => parseUstarSizeField(headerWithSize("-0000001000\0"))).toThrow(
|
|
23
|
+
MALFORMED_USTAR_SIZE,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("rejects a truncated header", () => {
|
|
28
|
+
expect(() => parseUstarSizeField(new Uint8Array(100))).toThrow(
|
|
29
|
+
MALFORMED_USTAR_SIZE,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const MALFORMED_USTAR_SIZE = "malformed tar size field";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse the ustar `size` field (bytes 124-135, octal, NUL or space
|
|
5
|
+
* terminated). Rejects non-finite and negative values so a crafted header
|
|
6
|
+
* cannot walk the archive cursor backwards.
|
|
7
|
+
*/
|
|
8
|
+
export function parseUstarSizeField(header: Uint8Array): number {
|
|
9
|
+
if (header.length < 136) {
|
|
10
|
+
throw new Error(MALFORMED_USTAR_SIZE);
|
|
11
|
+
}
|
|
12
|
+
const raw = header.subarray(124, 136);
|
|
13
|
+
let end = 0;
|
|
14
|
+
while (end < raw.length && raw[end] !== 0) {
|
|
15
|
+
end++;
|
|
16
|
+
}
|
|
17
|
+
const sizeStr = new TextDecoder().decode(raw.subarray(0, end)).trim();
|
|
18
|
+
const size = sizeStr ? Number.parseInt(sizeStr, 8) : 0;
|
|
19
|
+
if (!Number.isFinite(size) || size < 0) {
|
|
20
|
+
throw new Error(MALFORMED_USTAR_SIZE);
|
|
21
|
+
}
|
|
22
|
+
return size;
|
|
23
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import { setConfig } from "../__tests__/helpers/set-config.js";
|
|
4
|
+
import { hasSetConstructs } from "../schedule/recurrence-engine.js";
|
|
4
5
|
|
|
5
6
|
type MockHeartbeatConfig = {
|
|
6
7
|
enabled: boolean;
|
|
@@ -44,6 +45,7 @@ mock.module("../heartbeat/heartbeat-service.js", () => ({
|
|
|
44
45
|
}));
|
|
45
46
|
|
|
46
47
|
mock.module("../schedule/recurrence-engine.js", () => ({
|
|
48
|
+
hasSetConstructs,
|
|
47
49
|
computeNextRunAt: () => computedCronNextRunAt,
|
|
48
50
|
}));
|
|
49
51
|
|
|
@@ -80,6 +82,7 @@ describe("computeNextBackgroundWakeIntent", () => {
|
|
|
80
82
|
heartbeatConsecutiveRunCapReached = false;
|
|
81
83
|
schedules = [];
|
|
82
84
|
computedCronNextRunAt = NOW + 3_600_000;
|
|
85
|
+
setConfig("ui", {});
|
|
83
86
|
});
|
|
84
87
|
|
|
85
88
|
test("returns heartbeat-only interval wake intent", () => {
|
|
@@ -109,6 +112,16 @@ describe("computeNextBackgroundWakeIntent", () => {
|
|
|
109
112
|
expect(intent!.reason).toBe("heartbeat");
|
|
110
113
|
});
|
|
111
114
|
|
|
115
|
+
test("resolves heartbeat timezone from the user zone when unset", () => {
|
|
116
|
+
setConfig("ui", { detectedTimezone: "America/Los_Angeles" });
|
|
117
|
+
const intent = computeWakeIntent(NOW);
|
|
118
|
+
expect(intent).not.toBeNull();
|
|
119
|
+
expect(intent!.sourcePayload.heartbeat!.timezone).toBe(
|
|
120
|
+
"America/Los_Angeles",
|
|
121
|
+
);
|
|
122
|
+
setConfig("ui", {});
|
|
123
|
+
});
|
|
124
|
+
|
|
112
125
|
test("returns heartbeat-only cron wake intent", () => {
|
|
113
126
|
heartbeatConfig.cronExpression = "0 9 * * *";
|
|
114
127
|
heartbeatConfig.timezone = "America/New_York";
|
|
@@ -4,6 +4,7 @@ import { getConfig } from "../config/loader.js";
|
|
|
4
4
|
import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
5
5
|
import { computeNextRunAt } from "../schedule/recurrence-engine.js";
|
|
6
6
|
import { listSchedules, type ScheduleJob } from "../schedule/schedule-store.js";
|
|
7
|
+
import { resolveScheduleTimezone } from "../schedule/schedule-timezone.js";
|
|
7
8
|
|
|
8
9
|
export type BackgroundWakeIntentReason = "heartbeat" | "schedule" | "mixed";
|
|
9
10
|
|
|
@@ -110,6 +111,7 @@ function getHeartbeatWakeSource(now: number): HeartbeatWakeSource | null {
|
|
|
110
111
|
const serviceNextRunAt = service?.nextRunAt ?? null;
|
|
111
112
|
let nextRunAt = serviceNextRunAt;
|
|
112
113
|
const mode = config.cronExpression != null ? "cron" : "interval";
|
|
114
|
+
const timezone = resolveScheduleTimezone(config.timezone);
|
|
113
115
|
|
|
114
116
|
if (nextRunAt == null) {
|
|
115
117
|
if (config.cronExpression != null) {
|
|
@@ -118,7 +120,7 @@ function getHeartbeatWakeSource(now: number): HeartbeatWakeSource | null {
|
|
|
118
120
|
{
|
|
119
121
|
syntax: "cron",
|
|
120
122
|
expression: config.cronExpression,
|
|
121
|
-
timezone
|
|
123
|
+
timezone,
|
|
122
124
|
},
|
|
123
125
|
now,
|
|
124
126
|
);
|
|
@@ -139,7 +141,7 @@ function getHeartbeatWakeSource(now: number): HeartbeatWakeSource | null {
|
|
|
139
141
|
mode,
|
|
140
142
|
intervalMs: config.intervalMs,
|
|
141
143
|
cronExpression: config.cronExpression,
|
|
142
|
-
timezone
|
|
144
|
+
timezone,
|
|
143
145
|
activeHoursStart: config.activeHoursStart,
|
|
144
146
|
activeHoursEnd: config.activeHoursEnd,
|
|
145
147
|
maxConsecutiveRuns: config.maxConsecutiveRuns,
|
|
@@ -29,6 +29,7 @@ import { resolveChannelCapabilities } from "../daemon/conversation-runtime-assem
|
|
|
29
29
|
import { getOrCreateConversation } from "../daemon/conversation-store.js";
|
|
30
30
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
31
31
|
import {
|
|
32
|
+
newestPersistedSightFrame,
|
|
32
33
|
pendingStandaloneImagePersist,
|
|
33
34
|
SIGHT_FRAME_TURN_HOLD_MS,
|
|
34
35
|
} from "../live-voice/live-voice-photo.js";
|
|
@@ -803,6 +804,10 @@ export async function startVoiceTurn(
|
|
|
803
804
|
conversationReadyAt: 0,
|
|
804
805
|
admissionClearAt: 0,
|
|
805
806
|
sightHoldMs: 0,
|
|
807
|
+
// The camera frame this turn reads as the current view, and how old it
|
|
808
|
+
// was when the turn's own message landed. Null when the conversation
|
|
809
|
+
// carries no frame.
|
|
810
|
+
newestSightFrame: null as { attachmentId: string; ageMs: number } | null,
|
|
806
811
|
persistDoneAt: 0,
|
|
807
812
|
};
|
|
808
813
|
const eventSink: VoiceRunEventSink = {
|
|
@@ -1425,6 +1430,26 @@ export async function startVoiceTurn(
|
|
|
1425
1430
|
}
|
|
1426
1431
|
}
|
|
1427
1432
|
dispatch.persistDoneAt = Date.now();
|
|
1433
|
+
// Read now, under the flag this turn holds, rather than after the sight
|
|
1434
|
+
// hold: a frame can still land between the hold and the persist, when its
|
|
1435
|
+
// acquire beats this turn's and the turn retries behind it. What the loop
|
|
1436
|
+
// below reads is what the rows hold at this moment, so this is the frame the
|
|
1437
|
+
// answer is about. One indexed row; a log-only read that fails must not
|
|
1438
|
+
// take the turn down with it.
|
|
1439
|
+
try {
|
|
1440
|
+
const newestFrame = newestPersistedSightFrame(opts.conversationId);
|
|
1441
|
+
if (newestFrame) {
|
|
1442
|
+
dispatch.newestSightFrame = {
|
|
1443
|
+
attachmentId: newestFrame.attachmentId,
|
|
1444
|
+
ageMs: dispatch.persistDoneAt - newestFrame.capturedAt,
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
} catch (err) {
|
|
1448
|
+
log.warn(
|
|
1449
|
+
{ err, turnId, conversationId: opts.conversationId },
|
|
1450
|
+
"Could not read the newest camera frame for the dispatch timing log",
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1428
1453
|
try {
|
|
1429
1454
|
opts.callbacks?.persisted_user_message_id?.(messageId);
|
|
1430
1455
|
} catch (err) {
|
|
@@ -1851,6 +1876,7 @@ export async function startVoiceTurn(
|
|
|
1851
1876
|
admissionWaitMs:
|
|
1852
1877
|
dispatch.admissionClearAt - dispatch.conversationReadyAt,
|
|
1853
1878
|
sightHoldMs: dispatch.sightHoldMs,
|
|
1879
|
+
newestSightFrame: dispatch.newestSightFrame,
|
|
1854
1880
|
persistMs:
|
|
1855
1881
|
dispatch.persistDoneAt -
|
|
1856
1882
|
dispatch.admissionClearAt -
|
|
@@ -33,6 +33,7 @@ describe("catalog search help for setup-intent retrieval", () => {
|
|
|
33
33
|
expect(indexed).toContain("Setup <app> for me");
|
|
34
34
|
expect(indexed.toLowerCase()).toContain("before searching the web");
|
|
35
35
|
expect(indexed).toContain("assistant plugins search");
|
|
36
|
+
expect(indexed).toContain("assistant plugins install <name>");
|
|
36
37
|
expect(indexed).toContain("assistant skills search");
|
|
37
38
|
expect(indexed.toLowerCase()).toContain("channels");
|
|
38
39
|
expect(pluginsHelp.description).toContain("channels");
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant plugins search` human vs `--json` output.
|
|
3
|
+
*
|
|
4
|
+
* Search results include a `github:owner/repo@ref` PATH column. The model
|
|
5
|
+
* copies that locator into `plugins install`. The human footer tells it to
|
|
6
|
+
* install by marketplace name instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { PLUGINS_SEARCH_INSTALL_HINT } from "../plugins.help.js";
|
|
12
|
+
import { runCliCommand } from "./cli-test-harness.js";
|
|
13
|
+
|
|
14
|
+
const catalog = {
|
|
15
|
+
ref: "test-ref",
|
|
16
|
+
matches: [
|
|
17
|
+
{
|
|
18
|
+
name: "imessage",
|
|
19
|
+
path: "github:vellum-ai/imessage@abc123",
|
|
20
|
+
category: "productivity",
|
|
21
|
+
source: {
|
|
22
|
+
kind: "github" as const,
|
|
23
|
+
repo: "vellum-ai/imessage",
|
|
24
|
+
ref: "abc123",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
mock.module("../../lib/plugin-catalog-cache.js", () => ({
|
|
31
|
+
getPluginCatalog: async () => catalog,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const { registerPluginsCommand } = await import("../plugins.js");
|
|
35
|
+
|
|
36
|
+
function runSearch(args: string[]): Promise<{
|
|
37
|
+
stdout: string;
|
|
38
|
+
exitCode: number;
|
|
39
|
+
}> {
|
|
40
|
+
return runCliCommand(registerPluginsCommand, ["plugins", ...args]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("plugins search", () => {
|
|
44
|
+
test("human output ends with the marketplace-name install hint", async () => {
|
|
45
|
+
const { exitCode, stdout } = await runSearch(["search", "imessage"]);
|
|
46
|
+
|
|
47
|
+
expect(exitCode).toBe(0);
|
|
48
|
+
expect(stdout).toContain("imessage");
|
|
49
|
+
expect(stdout).toContain("github:vellum-ai/imessage@abc123");
|
|
50
|
+
expect(stdout).toContain('1 match for "imessage".');
|
|
51
|
+
expect(stdout.trimEnd().endsWith(PLUGINS_SEARCH_INSTALL_HINT)).toBe(true);
|
|
52
|
+
expect(PLUGINS_SEARCH_INSTALL_HINT).toContain(
|
|
53
|
+
"assistant plugins install <name>",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("empty human result has no install hint", async () => {
|
|
58
|
+
const { exitCode, stdout } = await runSearch(["search", "nothing-matches"]);
|
|
59
|
+
|
|
60
|
+
expect(exitCode).toBe(0);
|
|
61
|
+
expect(stdout).toContain('No plugins matched "nothing-matches".');
|
|
62
|
+
expect(stdout).not.toContain(PLUGINS_SEARCH_INSTALL_HINT);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("json output omits the install hint", async () => {
|
|
66
|
+
const { exitCode, stdout } = await runSearch([
|
|
67
|
+
"search",
|
|
68
|
+
"imessage",
|
|
69
|
+
"--json",
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
expect(exitCode).toBe(0);
|
|
73
|
+
expect(stdout).not.toContain(PLUGINS_SEARCH_INSTALL_HINT);
|
|
74
|
+
const parsed = JSON.parse(stdout) as {
|
|
75
|
+
query: string;
|
|
76
|
+
matches: Array<{ name: string }>;
|
|
77
|
+
};
|
|
78
|
+
expect(parsed.query).toBe("imessage");
|
|
79
|
+
expect(parsed.matches.map((m) => m.name)).toEqual(["imessage"]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -51,7 +51,9 @@ API Keys (agent shells refuse inline keys unless --generated is passed).
|
|
|
51
51
|
|
|
52
52
|
Examples:
|
|
53
53
|
$ assistant config set llm.defaultProvider.provider anthropic
|
|
54
|
-
$ assistant config set calls.enabled true
|
|
54
|
+
$ assistant config set calls.enabled true
|
|
55
|
+
$ assistant config set memory.embeddings.provider custom
|
|
56
|
+
$ assistant config set memory.embeddings.baseUrl https://gateway.example.com/v1`,
|
|
55
57
|
},
|
|
56
58
|
{
|
|
57
59
|
name: "get",
|
|
@@ -35,7 +35,7 @@ interface StopResponse {
|
|
|
35
35
|
interface EmbeddingStatus {
|
|
36
36
|
enabled: boolean;
|
|
37
37
|
degraded: boolean;
|
|
38
|
-
provider: "local" | "openai" | "gemini" | "ollama" | null;
|
|
38
|
+
provider: "local" | "openai" | "gemini" | "ollama" | "custom" | null;
|
|
39
39
|
model: string | null;
|
|
40
40
|
reason: string | null;
|
|
41
41
|
}
|
|
@@ -18,11 +18,29 @@ const BASE: PlatformCreditsResult = {
|
|
|
18
18
|
daily_limit_snoozed: false,
|
|
19
19
|
low_balance_threshold: 5,
|
|
20
20
|
low_balance_warning: false,
|
|
21
|
+
plan_credit_remaining: 9.1,
|
|
22
|
+
plan_credit_total: 20,
|
|
23
|
+
plan_credit_used_fraction: 0.545,
|
|
24
|
+
plan_credits_spent: false,
|
|
25
|
+
extra_credit_remaining: 33.07,
|
|
26
|
+
credits_expiring_soon: 9.1,
|
|
27
|
+
next_credit_expiry_at: "2026-10-01T00:00:00Z",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const NO_GRANTS: PlatformCreditsResult = {
|
|
31
|
+
...BASE,
|
|
32
|
+
plan_credit_remaining: null,
|
|
33
|
+
plan_credit_total: null,
|
|
34
|
+
plan_credit_used_fraction: null,
|
|
35
|
+
plan_credits_spent: null,
|
|
36
|
+
extra_credit_remaining: null,
|
|
37
|
+
credits_expiring_soon: null,
|
|
38
|
+
next_credit_expiry_at: null,
|
|
21
39
|
};
|
|
22
40
|
|
|
23
41
|
describe("formatCreditsLines", () => {
|
|
24
42
|
test("labels daily spend as counted against the daily limit, never as total spend", () => {
|
|
25
|
-
const lines = formatCreditsLines(
|
|
43
|
+
const lines = formatCreditsLines(NO_GRANTS);
|
|
26
44
|
|
|
27
45
|
expect(lines[2]).toBe(
|
|
28
46
|
"Today: $3.25 counted against the $10.00 daily limit",
|
|
@@ -31,20 +49,20 @@ describe("formatCreditsLines", () => {
|
|
|
31
49
|
});
|
|
32
50
|
|
|
33
51
|
test("says when no daily limit is set and flags a reached or skipped limit", () => {
|
|
34
|
-
expect(formatCreditsLines({ ...
|
|
52
|
+
expect(formatCreditsLines({ ...NO_GRANTS, daily_limit: null })[2]).toBe(
|
|
35
53
|
"Today: $3.25 counted against the daily limit (none set)",
|
|
36
54
|
);
|
|
37
55
|
expect(
|
|
38
|
-
formatCreditsLines({ ...
|
|
56
|
+
formatCreditsLines({ ...NO_GRANTS, daily_limit_reached: true })[2],
|
|
39
57
|
).toEndWith(" (limit reached)");
|
|
40
58
|
expect(
|
|
41
|
-
formatCreditsLines({ ...
|
|
59
|
+
formatCreditsLines({ ...NO_GRANTS, daily_limit_snoozed: true })[2],
|
|
42
60
|
).toEndWith(" (limit skipped for today)");
|
|
43
61
|
});
|
|
44
62
|
|
|
45
63
|
test("omits the daily line without daily spend and adds the low-balance warning", () => {
|
|
46
64
|
const lines = formatCreditsLines({
|
|
47
|
-
...
|
|
65
|
+
...NO_GRANTS,
|
|
48
66
|
daily_spend: null,
|
|
49
67
|
low_balance_warning: true,
|
|
50
68
|
});
|
|
@@ -55,6 +73,77 @@ describe("formatCreditsLines", () => {
|
|
|
55
73
|
);
|
|
56
74
|
});
|
|
57
75
|
|
|
76
|
+
test("reports plan credit left with the meter percentage, extra credit, and expiry", () => {
|
|
77
|
+
const lines = formatCreditsLines(BASE);
|
|
78
|
+
|
|
79
|
+
expect(lines[2]).toBe(
|
|
80
|
+
"Plan: $9.10 of $20.00 plan credit left (55% used)",
|
|
81
|
+
);
|
|
82
|
+
expect(lines[3]).toBe(
|
|
83
|
+
"Extra: $33.07 bought or earned on top of plan credit",
|
|
84
|
+
);
|
|
85
|
+
expect(lines[4]).toBe(
|
|
86
|
+
"Expiring: $9.10 within 30 days (next expiry 2026-10-01T00:00:00Z)",
|
|
87
|
+
);
|
|
88
|
+
expect(lines[5]).toStartWith("Today: $3.25 counted against");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("says plan credit is used up and skips the expiry line when nothing is scheduled", () => {
|
|
92
|
+
const spent = {
|
|
93
|
+
...BASE,
|
|
94
|
+
plan_credit_remaining: 0,
|
|
95
|
+
plan_credit_used_fraction: 1,
|
|
96
|
+
plan_credits_spent: true,
|
|
97
|
+
credits_expiring_soon: 0,
|
|
98
|
+
next_credit_expiry_at: null,
|
|
99
|
+
};
|
|
100
|
+
const lines = formatCreditsLines({
|
|
101
|
+
...spent,
|
|
102
|
+
extra_credit_remaining: 42.17,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(lines[2]).toBe(
|
|
106
|
+
"Plan: plan credit used up or expired; managed usage now draws on extra credit",
|
|
107
|
+
);
|
|
108
|
+
expect(lines[3]).toBe(
|
|
109
|
+
"Extra: $42.17 bought or earned on top of plan credit",
|
|
110
|
+
);
|
|
111
|
+
expect(lines[4]).toStartWith("Today:");
|
|
112
|
+
|
|
113
|
+
expect(formatCreditsLines({ ...spent, extra_credit_remaining: 0 })[2]).toBe(
|
|
114
|
+
"Plan: plan credit used up or expired, and no extra credit remains",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("reports the spent state with an unknown wallet when the remaining amount is omitted", () => {
|
|
119
|
+
const lines = formatCreditsLines({
|
|
120
|
+
...BASE,
|
|
121
|
+
plan_credit_remaining: null,
|
|
122
|
+
plan_credit_total: 0,
|
|
123
|
+
plan_credit_used_fraction: 1,
|
|
124
|
+
plan_credits_spent: true,
|
|
125
|
+
extra_credit_remaining: null,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(lines[2]).toBe(
|
|
129
|
+
"Plan: plan credit used up or expired; whether extra credit remains was not reported",
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("shows the next expiry when it falls beyond the 30-day window", () => {
|
|
134
|
+
const lines = formatCreditsLines({ ...BASE, credits_expiring_soon: 0 });
|
|
135
|
+
|
|
136
|
+
expect(lines[4]).toBe(
|
|
137
|
+
"Expiry: next credit expiry 2026-10-01T00:00:00Z (nothing expires within 30 days)",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("does not claim an empty 30-day window when that figure was not reported", () => {
|
|
142
|
+
const lines = formatCreditsLines({ ...BASE, credits_expiring_soon: null });
|
|
143
|
+
|
|
144
|
+
expect(lines[4]).toBe("Expiry: next credit expiry 2026-10-01T00:00:00Z");
|
|
145
|
+
});
|
|
146
|
+
|
|
58
147
|
test("formats a negative remaining balance with the sign first", () => {
|
|
59
148
|
expect(formatCreditsLines({ ...BASE, remaining: -2.5 })[0]).toStartWith(
|
|
60
149
|
"Remaining: -$2.50 USD",
|
|
@@ -22,6 +22,13 @@ describe("assistant platform credits", () => {
|
|
|
22
22
|
daily_limit_snoozed: false,
|
|
23
23
|
low_balance_threshold: 5,
|
|
24
24
|
low_balance_warning: false,
|
|
25
|
+
plan_credit_remaining: 9.1,
|
|
26
|
+
plan_credit_total: 20,
|
|
27
|
+
plan_credit_used_fraction: 0.545,
|
|
28
|
+
plan_credits_spent: false,
|
|
29
|
+
extra_credit_remaining: 33.07,
|
|
30
|
+
credits_expiring_soon: 9.1,
|
|
31
|
+
next_credit_expiry_at: "2026-10-01T00:00:00Z",
|
|
25
32
|
},
|
|
26
33
|
};
|
|
27
34
|
});
|
|
@@ -41,6 +48,9 @@ describe("assistant platform credits", () => {
|
|
|
41
48
|
expect(parsed.daily_limit).toBe(10);
|
|
42
49
|
expect(parsed.daily_limit_reached).toBe(false);
|
|
43
50
|
expect(parsed.low_balance_warning).toBe(false);
|
|
51
|
+
expect(parsed.plan_credit_remaining).toBe(9.1);
|
|
52
|
+
expect(parsed.plan_credit_used_fraction).toBe(0.545);
|
|
53
|
+
expect(parsed.extra_credit_remaining).toBe(33.07);
|
|
44
54
|
});
|
|
45
55
|
|
|
46
56
|
test("plain text mode does not emit JSON to stdout", async () => {
|
|
@@ -13,6 +13,13 @@ export interface PlatformCreditsResult {
|
|
|
13
13
|
daily_limit_snoozed: boolean;
|
|
14
14
|
low_balance_threshold: number | null;
|
|
15
15
|
low_balance_warning: boolean;
|
|
16
|
+
plan_credit_remaining: number | null;
|
|
17
|
+
plan_credit_total: number | null;
|
|
18
|
+
plan_credit_used_fraction: number | null;
|
|
19
|
+
plan_credits_spent: boolean | null;
|
|
20
|
+
extra_credit_remaining: number | null;
|
|
21
|
+
credits_expiring_soon: number | null;
|
|
22
|
+
next_credit_expiry_at: string | null;
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/** Human-readable lines for `assistant platform credits`. */
|
|
@@ -22,6 +29,56 @@ export function formatCreditsLines(result: PlatformCreditsResult): string[] {
|
|
|
22
29
|
`Remaining: ${formatCostUsd(result.remaining)} ${result.unit} (as of ${result.as_of})${staleNote}`,
|
|
23
30
|
`Settled: ${formatCostUsd(result.settled)} Pending: ${formatCostUsd(result.pending)}`,
|
|
24
31
|
];
|
|
32
|
+
if (result.plan_credits_spent === true) {
|
|
33
|
+
// Mirrors the web usage panel: extra credit is only said to fund usage
|
|
34
|
+
// once the wallet provably holds some, and an unreported wallet is not
|
|
35
|
+
// an empty one.
|
|
36
|
+
const extra = result.extra_credit_remaining;
|
|
37
|
+
lines.push(
|
|
38
|
+
extra === null
|
|
39
|
+
? "Plan: plan credit used up or expired; whether extra credit remains was not reported"
|
|
40
|
+
: extra > 0
|
|
41
|
+
? "Plan: plan credit used up or expired; managed usage now draws on extra credit"
|
|
42
|
+
: "Plan: plan credit used up or expired, and no extra credit remains",
|
|
43
|
+
);
|
|
44
|
+
} else if (
|
|
45
|
+
result.plan_credit_remaining !== null &&
|
|
46
|
+
result.plan_credit_total !== null
|
|
47
|
+
) {
|
|
48
|
+
const pct =
|
|
49
|
+
result.plan_credit_used_fraction === null
|
|
50
|
+
? ""
|
|
51
|
+
: ` (${Math.round(result.plan_credit_used_fraction * 100)}% used)`;
|
|
52
|
+
lines.push(
|
|
53
|
+
`Plan: ${formatCostUsd(result.plan_credit_remaining)} of ${formatCostUsd(result.plan_credit_total)} plan credit left${pct}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (result.extra_credit_remaining !== null) {
|
|
57
|
+
lines.push(
|
|
58
|
+
`Extra: ${formatCostUsd(result.extra_credit_remaining)} bought or earned on top of plan credit`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
result.credits_expiring_soon !== null &&
|
|
63
|
+
result.credits_expiring_soon > 0
|
|
64
|
+
) {
|
|
65
|
+
const when = result.next_credit_expiry_at
|
|
66
|
+
? ` (next expiry ${result.next_credit_expiry_at})`
|
|
67
|
+
: "";
|
|
68
|
+
lines.push(
|
|
69
|
+
`Expiring: ${formatCostUsd(result.credits_expiring_soon)} within 30 days${when}`,
|
|
70
|
+
);
|
|
71
|
+
} else if (result.next_credit_expiry_at) {
|
|
72
|
+
// A null 30-day figure means the platform did not report it, so only an
|
|
73
|
+
// actual zero earns the "nothing expires within 30 days" claim.
|
|
74
|
+
const window =
|
|
75
|
+
result.credits_expiring_soon === null
|
|
76
|
+
? ""
|
|
77
|
+
: " (nothing expires within 30 days)";
|
|
78
|
+
lines.push(
|
|
79
|
+
`Expiry: next credit expiry ${result.next_credit_expiry_at}${window}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
25
82
|
if (result.daily_spend !== null) {
|
|
26
83
|
const limit =
|
|
27
84
|
result.daily_limit === null
|
|
@@ -72,10 +72,11 @@ Examples:
|
|
|
72
72
|
{
|
|
73
73
|
name: "credits",
|
|
74
74
|
description:
|
|
75
|
-
"Show the organization's
|
|
75
|
+
"Show the organization's credit balance, plan-credit usage, and daily-limit state",
|
|
76
76
|
helpText: `
|
|
77
|
-
Fetches the org's credit balance,
|
|
78
|
-
the platform
|
|
77
|
+
Fetches the org's credit balance, plan-included credit usage, extra credit,
|
|
78
|
+
upcoming expiry, today's spend, and daily-limit state from the platform
|
|
79
|
+
billing summary.
|
|
79
80
|
|
|
80
81
|
Fields:
|
|
81
82
|
remaining Effective balance (settled minus pending charges) in USD
|
|
@@ -91,6 +92,21 @@ Fields:
|
|
|
91
92
|
daily_limit_snoozed True when the limit is skipped for the rest of today
|
|
92
93
|
low_balance_threshold Low-balance alert threshold in USD
|
|
93
94
|
low_balance_warning True when the balance is below the threshold and auto top-up is off
|
|
95
|
+
plan_credit_remaining Unused plan-included credit (initial credit, Pro bundle) in USD
|
|
96
|
+
plan_credit_total What those plan-included grants were worth in total, in USD
|
|
97
|
+
plan_credit_used_fraction
|
|
98
|
+
Share of plan credit used, 0 to 1 (the in-app usage meter)
|
|
99
|
+
plan_credits_spent True when plan credit is used up or expired (extra_credit_remaining
|
|
100
|
+
says whether anything still funds usage); null with no reading
|
|
101
|
+
extra_credit_remaining Credit bought or earned on top of plan credit, in USD
|
|
102
|
+
credits_expiring_soon Credit on grants expiring within 30 days, in USD
|
|
103
|
+
next_credit_expiry_at Earliest upcoming grant expiry (ISO 8601), or null
|
|
104
|
+
|
|
105
|
+
Grant fields are null when the platform does not report grant figures. Plan
|
|
106
|
+
credit mixes grants with different lifetimes (only the Pro bundle turns over
|
|
107
|
+
with the billing cycle), so it has no single reset date. next_credit_expiry_at
|
|
108
|
+
is the earliest expiry across all grants, plan-included or not, and
|
|
109
|
+
'assistant platform subscription' gives the billing cycle boundary.
|
|
94
110
|
|
|
95
111
|
Requires platform credentials (run 'assistant platform connect' first or
|
|
96
112
|
ensure VELLUM_PLATFORM_URL is set and credentials are stored).
|
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
PLUGIN_UPGRADE_STRATEGIES,
|
|
9
9
|
} from "../lib/plugin-constants.js";
|
|
10
10
|
|
|
11
|
+
export const PLUGINS_SEARCH_INSTALL_HINT =
|
|
12
|
+
"Install the plugin with `assistant plugins install <name>`.";
|
|
13
|
+
|
|
11
14
|
export const pluginsHelp: CliCommandHelp = {
|
|
12
15
|
name: "plugins",
|
|
13
16
|
description:
|
|
@@ -166,7 +169,7 @@ Arguments:
|
|
|
166
169
|
user sentence. Use the product or plugin name. Anchors like
|
|
167
170
|
^example work.
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
${PLUGINS_SEARCH_INSTALL_HINT}
|
|
170
173
|
If nothing matches, try 'assistant skills search <query>', then web search.
|
|
171
174
|
|
|
172
175
|
Examples:
|