@mycadet/assistant 0.12.10 → 0.13.0
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/openapi.yaml +2 -708
- package/package.json +2 -3
- package/scripts/sync-web-search-catalog.ts +1 -2
- package/src/__tests__/cloud-profile-guard.test.ts +41 -5
- package/src/__tests__/config-schema.test.ts +6 -38
- package/src/__tests__/conversation-queue.test.ts +45 -6
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
- package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +0 -51
- package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
- package/src/__tests__/run-due-schedules.test.ts +0 -4
- package/src/__tests__/schedule-store.test.ts +0 -12
- package/src/__tests__/stt-stream-session.test.ts +4 -4
- package/src/__tests__/telephony-tts-guard.test.ts +2 -50
- package/src/__tests__/voice-config-update.test.ts +12 -167
- package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
- package/src/calls/resolve-call-tts-provider.ts +6 -25
- package/src/calls/telephony-credential-preflight.ts +0 -10
- package/src/calls/telephony-tts-capability.ts +1 -17
- package/src/cli/commands/tts.ts +1 -2
- package/src/cli/index.help.ts +0 -2
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
- package/src/config/schemas/__tests__/stt.test.ts +10 -14
- package/src/config/schemas/llm.ts +3 -5
- package/src/config/schemas/services.ts +5 -6
- package/src/config/schemas/stt.ts +1 -3
- package/src/config/schemas/tts.ts +1 -30
- package/src/daemon/daemon-readiness.ts +4 -29
- package/src/daemon/lifecycle.ts +0 -31
- package/src/daemon/providers-setup.ts +0 -8
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -66
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-voice-credential-preflight.ts +9 -33
- package/src/live-voice/live-voice-session.ts +10 -6
- package/src/live-voice/live-voice-tts.ts +1 -8
- package/src/plugins/defaults/memory/worker.ts +3 -3
- package/src/prompts/persona-resolver.ts +0 -35
- package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
- package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
- package/src/providers/search-provider-catalog.ts +7 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
- package/src/providers/speech-to-text/provider-catalog.ts +2 -73
- package/src/providers/speech-to-text/resolve.ts +3 -120
- package/src/runtime/http-router-types.ts +1 -3
- package/src/runtime/http-server.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/conversation-query-routes.ts +0 -96
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/live-voice-routes.test.ts +4 -11
- package/src/runtime/routes/live-voice-routes.ts +8 -11
- package/src/runtime/routes/secret-routes.ts +0 -6
- package/src/runtime/routes/tts-routes.ts +0 -38
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
- package/src/schedule/schedule-store.ts +0 -7
- package/src/schedule/scheduler.ts +0 -5
- package/src/schedule/worker.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
- package/src/stt/daemon-batch-transcriber.ts +4 -38
- package/src/stt/types.ts +1 -9
- package/src/tools/network/__tests__/web-search.test.ts +3 -250
- package/src/tools/network/web-search.ts +4 -165
- package/src/tts/__tests__/provider-resolution.test.ts +0 -5
- package/src/tts/provider-catalog.ts +0 -4
- package/src/tts/types.ts +0 -1
- package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
- package/src/__tests__/migration-export-http.test.ts +0 -557
- package/src/__tests__/migration-export-streaming.test.ts +0 -350
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
- package/src/__tests__/migration-import-from-path.test.ts +0 -324
- package/src/__tests__/migration-import-from-url.test.ts +0 -588
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
- package/src/__tests__/migration-jobs-status.test.ts +0 -139
- package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
- package/src/__tests__/migration-transport.test.ts +0 -1089
- package/src/__tests__/migration-validate-http.test.ts +0 -634
- package/src/__tests__/migration-wizard.test.ts +0 -1395
- package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
- package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
- package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
- package/src/__tests__/validation-results-screen.test.ts +0 -1130
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
- package/src/background-wake/background-wake-routes.test.ts +0 -906
- package/src/background-wake/cloud-client.test.ts +0 -328
- package/src/background-wake/cloud-client.ts +0 -196
- package/src/background-wake/next-wake.test.ts +0 -335
- package/src/background-wake/next-wake.ts +0 -188
- package/src/background-wake/publisher.ts +0 -93
- package/src/background-wake/wake-intent-hooks.test.ts +0 -234
- package/src/cli/commands/__tests__/backup.test.ts +0 -363
- package/src/cli/commands/backup.help.ts +0 -180
- package/src/cli/commands/backup.ts +0 -398
- package/src/cloud/cloud-speech.test.ts +0 -335
- package/src/cloud/cloud-speech.ts +0 -300
- package/src/config/cloud-speech-defaults.ts +0 -300
- package/src/config/sanitize-for-transfer.ts +0 -111
- package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
- package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
- package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
- package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
- package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
- package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
- package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
- package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
- package/src/runtime/migrations/gcs-signed-url.ts +0 -190
- package/src/runtime/migrations/job-registry.ts +0 -297
- package/src/runtime/migrations/migration-transport.ts +0 -612
- package/src/runtime/migrations/migration-wizard.ts +0 -747
- package/src/runtime/migrations/origin-mode.ts +0 -42
- package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
- package/src/runtime/migrations/staged-import-path.ts +0 -116
- package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
- package/src/runtime/migrations/validation-results-screen.ts +0 -469
- package/src/runtime/migrations/vbundle-builder.ts +0 -1382
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
- package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
- package/src/runtime/migrations/vbundle-importer.ts +0 -826
- package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
- package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
- package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
- package/src/runtime/migrations/vbundle-validator.ts +0 -724
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
- package/src/runtime/routes/background-wake-routes.ts +0 -387
- package/src/runtime/routes/migration-routes.ts +0 -2584
- package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
- package/src/tools/network/cloud-search-proxy.ts +0 -211
- package/src/tts/__tests__/cadet-provider.test.ts +0 -253
- package/src/tts/providers/cadet-provider.ts +0 -244
- package/src/types/tar-stream.d.ts +0 -66
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type { BackgroundWakeIntent } from "./next-wake.js";
|
|
4
|
-
|
|
5
|
-
let mockCloudAssistantId: string;
|
|
6
|
-
let mockClientFetch: ReturnType<typeof mock>;
|
|
7
|
-
let mockCreateClient: ReturnType<typeof mock>;
|
|
8
|
-
|
|
9
|
-
mock.module("../cloud/client.js", () => ({
|
|
10
|
-
CadetCloudClient: {
|
|
11
|
-
create: () => mockCreateClient(),
|
|
12
|
-
},
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
const {
|
|
16
|
-
clearBackgroundWakeIntent,
|
|
17
|
-
completeBackgroundWakeLease,
|
|
18
|
-
publishBackgroundWakeIntent,
|
|
19
|
-
renewBackgroundWakeLease,
|
|
20
|
-
} = await import("./cloud-client.js");
|
|
21
|
-
|
|
22
|
-
const NOW = 1_800_000_000_000;
|
|
23
|
-
|
|
24
|
-
describe("background wake platform client", () => {
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
mockCloudAssistantId = "asst-123";
|
|
27
|
-
mockClientFetch = mock(async () => new Response('{"status":"stored"}'));
|
|
28
|
-
mockCreateClient = mock(async () => ({
|
|
29
|
-
cloudAssistantId: mockCloudAssistantId,
|
|
30
|
-
fetch: mockClientFetch,
|
|
31
|
-
}));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("publishes derived wake intent fields to Django", async () => {
|
|
35
|
-
const result = await publishBackgroundWakeIntent(intentFixture());
|
|
36
|
-
|
|
37
|
-
expect(result).toEqual({ status: "published", httpStatus: 200 });
|
|
38
|
-
expect(mockClientFetch).toHaveBeenCalledTimes(1);
|
|
39
|
-
const [path, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
40
|
-
expect(path).toBe("/v1/assistants/asst-123/background-wake-intent/");
|
|
41
|
-
expect(init.method).toBe("PUT");
|
|
42
|
-
expect(new Headers(init.headers).get("Content-Type")).toBe(
|
|
43
|
-
"application/json",
|
|
44
|
-
);
|
|
45
|
-
expect(JSON.parse(String(init.body))).toEqual({
|
|
46
|
-
reason: "schedule",
|
|
47
|
-
source_generation: "bw1:opaque-hash",
|
|
48
|
-
computed_at: new Date(NOW - 1_000).toISOString(),
|
|
49
|
-
next_wake_at: new Date(NOW + 60_000).toISOString(),
|
|
50
|
-
actual_next_due_at: new Date(NOW + 60_000).toISOString(),
|
|
51
|
-
source_payload: {
|
|
52
|
-
heartbeat: null,
|
|
53
|
-
schedules: [
|
|
54
|
-
{
|
|
55
|
-
id: "schedule-1",
|
|
56
|
-
nextRunAt: NOW + 60_000,
|
|
57
|
-
mode: "wake",
|
|
58
|
-
createdBy: "defer",
|
|
59
|
-
status: "active",
|
|
60
|
-
updatedAt: NOW - 5_000,
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("preserves stable string sourceGeneration across recomputes", async () => {
|
|
68
|
-
await publishBackgroundWakeIntent(
|
|
69
|
-
intentFixture({
|
|
70
|
-
computedAt: NOW - 1_000,
|
|
71
|
-
sourceGeneration: "bw1:stable-source-hash",
|
|
72
|
-
}),
|
|
73
|
-
);
|
|
74
|
-
await publishBackgroundWakeIntent(
|
|
75
|
-
intentFixture({
|
|
76
|
-
computedAt: NOW,
|
|
77
|
-
sourceGeneration: "bw1:stable-source-hash",
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const [, firstInit] = mockClientFetch.mock.calls[0] as [
|
|
82
|
-
string,
|
|
83
|
-
RequestInit,
|
|
84
|
-
];
|
|
85
|
-
const [, secondInit] = mockClientFetch.mock.calls[1] as [
|
|
86
|
-
string,
|
|
87
|
-
RequestInit,
|
|
88
|
-
];
|
|
89
|
-
expect(JSON.parse(String(firstInit.body))).toMatchObject({
|
|
90
|
-
source_generation: "bw1:stable-source-hash",
|
|
91
|
-
computed_at: new Date(NOW - 1_000).toISOString(),
|
|
92
|
-
});
|
|
93
|
-
expect(JSON.parse(String(secondInit.body))).toMatchObject({
|
|
94
|
-
source_generation: "bw1:stable-source-hash",
|
|
95
|
-
computed_at: new Date(NOW).toISOString(),
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("preserves numeric-looking sourceGeneration as a string", async () => {
|
|
100
|
-
await publishBackgroundWakeIntent(
|
|
101
|
-
intentFixture({ sourceGeneration: "42" }),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const [, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
105
|
-
expect(JSON.parse(String(init.body)).source_generation).toBe("42");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("clears wake intent with the last computed snapshot when available", async () => {
|
|
109
|
-
mockClientFetch = mock(async () => new Response('{"status":"cleared"}'));
|
|
110
|
-
mockCreateClient = mock(async () => ({
|
|
111
|
-
cloudAssistantId: mockCloudAssistantId,
|
|
112
|
-
fetch: mockClientFetch,
|
|
113
|
-
}));
|
|
114
|
-
|
|
115
|
-
const result = await clearBackgroundWakeIntent(
|
|
116
|
-
intentFixture({ sourceGeneration: "bw1:opaque-hash" }),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
expect(result).toEqual({ status: "cleared", httpStatus: 200 });
|
|
120
|
-
expect(mockClientFetch).toHaveBeenCalledTimes(1);
|
|
121
|
-
const [path, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
122
|
-
expect(path).toBe("/v1/assistants/asst-123/background-wake-intent/");
|
|
123
|
-
expect(init.method).toBe("DELETE");
|
|
124
|
-
expect(JSON.parse(String(init.body))).toEqual({
|
|
125
|
-
source_generation: "bw1:opaque-hash",
|
|
126
|
-
computed_at: new Date(NOW - 1_000).toISOString(),
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("clears wake intent without generation data when no snapshot is available", async () => {
|
|
131
|
-
await clearBackgroundWakeIntent(null);
|
|
132
|
-
|
|
133
|
-
const [, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
134
|
-
expect(init.method).toBe("DELETE");
|
|
135
|
-
expect(JSON.parse(String(init.body))).toEqual({});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("renews background wake lease through Django", async () => {
|
|
139
|
-
const result = await renewBackgroundWakeLease("lease/with/slash");
|
|
140
|
-
|
|
141
|
-
expect(result).toEqual({ status: "renewed", httpStatus: 200 });
|
|
142
|
-
expect(mockClientFetch).toHaveBeenCalledTimes(1);
|
|
143
|
-
const [path, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
144
|
-
expect(path).toBe(
|
|
145
|
-
"/v1/assistants/asst-123/background-wake-leases/lease%2Fwith%2Fslash/renew/",
|
|
146
|
-
);
|
|
147
|
-
expect(init.method).toBe("POST");
|
|
148
|
-
expect(JSON.parse(String(init.body))).toEqual({});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("completes background wake lease with the next intent", async () => {
|
|
152
|
-
const result = await completeBackgroundWakeLease({
|
|
153
|
-
leaseId: "lease-123",
|
|
154
|
-
status: "completed",
|
|
155
|
-
nextIntent: intentFixture({ sourceGeneration: "bw1:next-hash" }),
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(result).toEqual({ status: "completed", httpStatus: 200 });
|
|
159
|
-
expect(mockClientFetch).toHaveBeenCalledTimes(1);
|
|
160
|
-
const [path, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
161
|
-
expect(path).toBe(
|
|
162
|
-
"/v1/assistants/asst-123/background-wake-leases/lease-123/complete/",
|
|
163
|
-
);
|
|
164
|
-
expect(init.method).toBe("POST");
|
|
165
|
-
expect(JSON.parse(String(init.body))).toEqual({
|
|
166
|
-
status: "completed",
|
|
167
|
-
next_intent: {
|
|
168
|
-
reason: "schedule",
|
|
169
|
-
source_generation: "bw1:next-hash",
|
|
170
|
-
computed_at: new Date(NOW - 1_000).toISOString(),
|
|
171
|
-
next_wake_at: new Date(NOW + 60_000).toISOString(),
|
|
172
|
-
actual_next_due_at: new Date(NOW + 60_000).toISOString(),
|
|
173
|
-
source_payload: {
|
|
174
|
-
heartbeat: null,
|
|
175
|
-
schedules: [
|
|
176
|
-
{
|
|
177
|
-
id: "schedule-1",
|
|
178
|
-
nextRunAt: NOW + 60_000,
|
|
179
|
-
mode: "wake",
|
|
180
|
-
createdBy: "defer",
|
|
181
|
-
status: "active",
|
|
182
|
-
updatedAt: NOW - 5_000,
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("completes failed background wake lease with an error", async () => {
|
|
191
|
-
await completeBackgroundWakeLease({
|
|
192
|
-
leaseId: "lease-123",
|
|
193
|
-
status: "failed",
|
|
194
|
-
error: "scheduler exploded",
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
const [, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
198
|
-
expect(JSON.parse(String(init.body))).toEqual({
|
|
199
|
-
status: "failed",
|
|
200
|
-
error: "scheduler exploded",
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("completes background wake lease with an explicit null next intent", async () => {
|
|
205
|
-
await completeBackgroundWakeLease({
|
|
206
|
-
leaseId: "lease-123",
|
|
207
|
-
status: "completed",
|
|
208
|
-
nextIntent: null,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const [, init] = mockClientFetch.mock.calls[0] as [string, RequestInit];
|
|
212
|
-
expect(JSON.parse(String(init.body))).toEqual({
|
|
213
|
-
status: "completed",
|
|
214
|
-
next_intent: null,
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("skips when platform prerequisites are missing", async () => {
|
|
219
|
-
mockCreateClient = mock(async () => null);
|
|
220
|
-
|
|
221
|
-
await expect(publishBackgroundWakeIntent(intentFixture())).resolves.toEqual(
|
|
222
|
-
{
|
|
223
|
-
status: "skipped",
|
|
224
|
-
reason: "missing_platform_client",
|
|
225
|
-
},
|
|
226
|
-
);
|
|
227
|
-
expect(mockClientFetch).not.toHaveBeenCalled();
|
|
228
|
-
|
|
229
|
-
mockCreateClient = mock(async () => ({
|
|
230
|
-
cloudAssistantId: "",
|
|
231
|
-
fetch: mockClientFetch,
|
|
232
|
-
}));
|
|
233
|
-
|
|
234
|
-
await expect(publishBackgroundWakeIntent(intentFixture())).resolves.toEqual(
|
|
235
|
-
{
|
|
236
|
-
status: "skipped",
|
|
237
|
-
reason: "missing_platform_assistant_id",
|
|
238
|
-
},
|
|
239
|
-
);
|
|
240
|
-
expect(mockClientFetch).not.toHaveBeenCalled();
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("treats Django disabled no-op responses as success", async () => {
|
|
244
|
-
mockClientFetch = mock(async () => new Response('{"status":"disabled"}'));
|
|
245
|
-
mockCreateClient = mock(async () => ({
|
|
246
|
-
cloudAssistantId: mockCloudAssistantId,
|
|
247
|
-
fetch: mockClientFetch,
|
|
248
|
-
}));
|
|
249
|
-
|
|
250
|
-
await expect(publishBackgroundWakeIntent(intentFixture())).resolves.toEqual(
|
|
251
|
-
{
|
|
252
|
-
status: "published",
|
|
253
|
-
httpStatus: 200,
|
|
254
|
-
},
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("attaches an abort-timeout signal to every platform request", async () => {
|
|
259
|
-
// Guards the publisher's single-in-flight serialization: a wake request
|
|
260
|
-
// that hangs (half-open socket during a cloud reconnect) would wedge the
|
|
261
|
-
// in-flight promise and silence every future refresh. Each request must
|
|
262
|
-
// carry a timeout so it rejects instead of hanging forever.
|
|
263
|
-
await publishBackgroundWakeIntent(intentFixture());
|
|
264
|
-
await clearBackgroundWakeIntent(intentFixture());
|
|
265
|
-
await renewBackgroundWakeLease("lease-123");
|
|
266
|
-
await completeBackgroundWakeLease({
|
|
267
|
-
leaseId: "lease-123",
|
|
268
|
-
status: "completed",
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
expect(mockClientFetch).toHaveBeenCalledTimes(4);
|
|
272
|
-
for (const call of mockClientFetch.mock.calls) {
|
|
273
|
-
const [, init] = call as [string, RequestInit];
|
|
274
|
-
expect(init.signal).toBeInstanceOf(AbortSignal);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test("throws on non-OK platform responses", async () => {
|
|
279
|
-
mockClientFetch = mock(
|
|
280
|
-
async () => new Response("bad gateway", { status: 502 }),
|
|
281
|
-
);
|
|
282
|
-
mockCreateClient = mock(async () => ({
|
|
283
|
-
cloudAssistantId: mockCloudAssistantId,
|
|
284
|
-
fetch: mockClientFetch,
|
|
285
|
-
}));
|
|
286
|
-
|
|
287
|
-
await expect(publishBackgroundWakeIntent(intentFixture())).rejects.toThrow(
|
|
288
|
-
"Failed to publish background wake intent: HTTP 502: bad gateway",
|
|
289
|
-
);
|
|
290
|
-
await expect(clearBackgroundWakeIntent(intentFixture())).rejects.toThrow(
|
|
291
|
-
"Failed to clear background wake intent: HTTP 502: bad gateway",
|
|
292
|
-
);
|
|
293
|
-
await expect(renewBackgroundWakeLease("lease-123")).rejects.toThrow(
|
|
294
|
-
"Failed to renew background wake lease: HTTP 502: bad gateway",
|
|
295
|
-
);
|
|
296
|
-
await expect(
|
|
297
|
-
completeBackgroundWakeLease({ leaseId: "lease-123", status: "failed" }),
|
|
298
|
-
).rejects.toThrow(
|
|
299
|
-
"Failed to complete background wake lease: HTTP 502: bad gateway",
|
|
300
|
-
);
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
function intentFixture(
|
|
305
|
-
overrides: Partial<BackgroundWakeIntent> = {},
|
|
306
|
-
): BackgroundWakeIntent {
|
|
307
|
-
return {
|
|
308
|
-
nextWakeAt: NOW + 60_000,
|
|
309
|
-
actualNextDueAt: NOW + 60_000,
|
|
310
|
-
reason: "schedule",
|
|
311
|
-
sourceGeneration: "bw1:opaque-hash",
|
|
312
|
-
computedAt: NOW - 1_000,
|
|
313
|
-
sourcePayload: {
|
|
314
|
-
heartbeat: null,
|
|
315
|
-
schedules: [
|
|
316
|
-
{
|
|
317
|
-
id: "schedule-1",
|
|
318
|
-
nextRunAt: NOW + 60_000,
|
|
319
|
-
mode: "wake",
|
|
320
|
-
createdBy: "defer",
|
|
321
|
-
status: "active",
|
|
322
|
-
updatedAt: NOW - 5_000,
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
},
|
|
326
|
-
...overrides,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { CadetCloudClient } from "../cloud/client.js";
|
|
2
|
-
import type { BackgroundWakeIntent } from "./next-wake.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Upper bound for a single background-wake platform request.
|
|
6
|
-
*
|
|
7
|
-
* Every call here is a lightweight control-plane request (publish/clear the
|
|
8
|
-
* wake intent, renew/complete a lease), so it must settle quickly. The bound
|
|
9
|
-
* exists because the publisher serializes refreshes behind a single in-flight
|
|
10
|
-
* promise: a request that hangs (half-open socket during a cloud reconnect)
|
|
11
|
-
* would wedge that promise and silence every future refresh. The timeout
|
|
12
|
-
* guarantees each request rejects instead of hanging, so the publisher always
|
|
13
|
-
* makes forward progress.
|
|
14
|
-
*/
|
|
15
|
-
const PLATFORM_REQUEST_TIMEOUT_MS = 15_000;
|
|
16
|
-
|
|
17
|
-
export type BackgroundWakeIntentClientResult = {
|
|
18
|
-
status: "published" | "cleared" | "skipped";
|
|
19
|
-
httpStatus?: number;
|
|
20
|
-
reason?: "missing_platform_client" | "missing_platform_assistant_id";
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type BackgroundWakeLeaseClientResult = {
|
|
24
|
-
status: "renewed" | "completed" | "skipped";
|
|
25
|
-
httpStatus?: number;
|
|
26
|
-
reason?: "missing_platform_client" | "missing_platform_assistant_id";
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type BackgroundWakeIntentSnapshot = Pick<
|
|
30
|
-
BackgroundWakeIntent,
|
|
31
|
-
"sourceGeneration" | "computedAt"
|
|
32
|
-
> | null;
|
|
33
|
-
|
|
34
|
-
export async function publishBackgroundWakeIntent(
|
|
35
|
-
intent: BackgroundWakeIntent,
|
|
36
|
-
): Promise<BackgroundWakeIntentClientResult> {
|
|
37
|
-
const client = await CadetCloudClient.create();
|
|
38
|
-
if (!client) {
|
|
39
|
-
return { status: "skipped", reason: "missing_platform_client" };
|
|
40
|
-
}
|
|
41
|
-
if (!client.cloudAssistantId) {
|
|
42
|
-
return { status: "skipped", reason: "missing_platform_assistant_id" };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const response = await client.fetch(intentPath(client.cloudAssistantId), {
|
|
46
|
-
method: "PUT",
|
|
47
|
-
headers: { "Content-Type": "application/json" },
|
|
48
|
-
body: JSON.stringify({
|
|
49
|
-
reason: intent.reason,
|
|
50
|
-
source_generation: intent.sourceGeneration,
|
|
51
|
-
computed_at: toIsoString(intent.computedAt),
|
|
52
|
-
next_wake_at: toIsoString(intent.nextWakeAt),
|
|
53
|
-
actual_next_due_at: toIsoString(intent.actualNextDueAt),
|
|
54
|
-
source_payload: intent.sourcePayload,
|
|
55
|
-
}),
|
|
56
|
-
signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
await throwIfNotOk(response, "publish background wake intent");
|
|
60
|
-
return { status: "published", httpStatus: response.status };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function clearBackgroundWakeIntent(
|
|
64
|
-
intentSnapshot: BackgroundWakeIntentSnapshot = null,
|
|
65
|
-
): Promise<BackgroundWakeIntentClientResult> {
|
|
66
|
-
const client = await CadetCloudClient.create();
|
|
67
|
-
if (!client) {
|
|
68
|
-
return { status: "skipped", reason: "missing_platform_client" };
|
|
69
|
-
}
|
|
70
|
-
if (!client.cloudAssistantId) {
|
|
71
|
-
return { status: "skipped", reason: "missing_platform_assistant_id" };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const body: Record<string, unknown> = {};
|
|
75
|
-
if (intentSnapshot) {
|
|
76
|
-
body.source_generation = intentSnapshot.sourceGeneration;
|
|
77
|
-
body.computed_at = toIsoString(intentSnapshot.computedAt);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const response = await client.fetch(intentPath(client.cloudAssistantId), {
|
|
81
|
-
method: "DELETE",
|
|
82
|
-
headers: { "Content-Type": "application/json" },
|
|
83
|
-
body: JSON.stringify(body),
|
|
84
|
-
signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
await throwIfNotOk(response, "clear background wake intent");
|
|
88
|
-
return { status: "cleared", httpStatus: response.status };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function renewBackgroundWakeLease(
|
|
92
|
-
leaseId: string,
|
|
93
|
-
): Promise<BackgroundWakeLeaseClientResult> {
|
|
94
|
-
const client = await CadetCloudClient.create();
|
|
95
|
-
if (!client) {
|
|
96
|
-
return { status: "skipped", reason: "missing_platform_client" };
|
|
97
|
-
}
|
|
98
|
-
if (!client.cloudAssistantId) {
|
|
99
|
-
return { status: "skipped", reason: "missing_platform_assistant_id" };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const response = await client.fetch(
|
|
103
|
-
leasePath(client.cloudAssistantId, leaseId, "renew"),
|
|
104
|
-
{
|
|
105
|
-
method: "POST",
|
|
106
|
-
headers: { "Content-Type": "application/json" },
|
|
107
|
-
body: JSON.stringify({}),
|
|
108
|
-
signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS),
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
await throwIfNotOk(response, "renew background wake lease");
|
|
113
|
-
return { status: "renewed", httpStatus: response.status };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function completeBackgroundWakeLease(args: {
|
|
117
|
-
leaseId: string;
|
|
118
|
-
status: "completed" | "failed" | "expired";
|
|
119
|
-
error?: string;
|
|
120
|
-
nextIntent?: BackgroundWakeIntent | null;
|
|
121
|
-
}): Promise<BackgroundWakeLeaseClientResult> {
|
|
122
|
-
const client = await CadetCloudClient.create();
|
|
123
|
-
if (!client) {
|
|
124
|
-
return { status: "skipped", reason: "missing_platform_client" };
|
|
125
|
-
}
|
|
126
|
-
if (!client.cloudAssistantId) {
|
|
127
|
-
return { status: "skipped", reason: "missing_platform_assistant_id" };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const body: Record<string, unknown> = {
|
|
131
|
-
status: args.status,
|
|
132
|
-
};
|
|
133
|
-
if (args.error) {
|
|
134
|
-
body.error = args.error;
|
|
135
|
-
}
|
|
136
|
-
if ("nextIntent" in args) {
|
|
137
|
-
body.next_intent = args.nextIntent
|
|
138
|
-
? {
|
|
139
|
-
reason: args.nextIntent.reason,
|
|
140
|
-
source_generation: args.nextIntent.sourceGeneration,
|
|
141
|
-
computed_at: toIsoString(args.nextIntent.computedAt),
|
|
142
|
-
next_wake_at: toIsoString(args.nextIntent.nextWakeAt),
|
|
143
|
-
actual_next_due_at: toIsoString(args.nextIntent.actualNextDueAt),
|
|
144
|
-
source_payload: args.nextIntent.sourcePayload,
|
|
145
|
-
}
|
|
146
|
-
: null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const response = await client.fetch(
|
|
150
|
-
leasePath(client.cloudAssistantId, args.leaseId, "complete"),
|
|
151
|
-
{
|
|
152
|
-
method: "POST",
|
|
153
|
-
headers: { "Content-Type": "application/json" },
|
|
154
|
-
body: JSON.stringify(body),
|
|
155
|
-
signal: AbortSignal.timeout(PLATFORM_REQUEST_TIMEOUT_MS),
|
|
156
|
-
},
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
await throwIfNotOk(response, "complete background wake lease");
|
|
160
|
-
return { status: "completed", httpStatus: response.status };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function intentPath(assistantId: string): string {
|
|
164
|
-
const encodedAssistantId = encodeURIComponent(assistantId);
|
|
165
|
-
return `/v1/assistants/${encodedAssistantId}/background-wake-intent/`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function leasePath(
|
|
169
|
-
assistantId: string,
|
|
170
|
-
leaseId: string,
|
|
171
|
-
action: "renew" | "complete",
|
|
172
|
-
): string {
|
|
173
|
-
const encodedAssistantId = encodeURIComponent(assistantId);
|
|
174
|
-
const encodedLeaseId = encodeURIComponent(leaseId);
|
|
175
|
-
return `/v1/assistants/${encodedAssistantId}/background-wake-leases/${encodedLeaseId}/${action}/`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function toIsoString(timestampMs: number): string {
|
|
179
|
-
return new Date(timestampMs).toISOString();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function throwIfNotOk(response: Response, action: string): Promise<void> {
|
|
183
|
-
if (response.ok) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
let body = "";
|
|
188
|
-
try {
|
|
189
|
-
body = await response.text();
|
|
190
|
-
} catch {
|
|
191
|
-
body = "";
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const detail = body ? `: ${body}` : "";
|
|
195
|
-
throw new Error(`Failed to ${action}: HTTP ${response.status}${detail}`);
|
|
196
|
-
}
|