@vellumai/assistant 0.4.30 → 0.4.32
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 +1 -1
- package/Dockerfile +14 -8
- package/README.md +2 -2
- package/docs/architecture/memory.md +28 -29
- package/docs/runbook-trusted-contacts.md +1 -4
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
- package/src/__tests__/anthropic-provider.test.ts +86 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/checker.test.ts +37 -98
- package/src/__tests__/commit-message-enrichment-service.test.ts +15 -4
- package/src/__tests__/config-schema.test.ts +6 -14
- package/src/__tests__/conflict-policy.test.ts +76 -0
- package/src/__tests__/conflict-store.test.ts +14 -20
- package/src/__tests__/contacts-tools.test.ts +8 -61
- package/src/__tests__/contradiction-checker.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +1 -19
- package/src/__tests__/followup-tools.test.ts +0 -30
- package/src/__tests__/gemini-provider.test.ts +79 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-routing-invariants.test.ts +6 -4
- package/src/__tests__/ipc-snapshot.test.ts +0 -4
- package/src/__tests__/managed-proxy-context.test.ts +163 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +13 -12
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/openai-provider.test.ts +82 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
- package/src/__tests__/recurrence-types.test.ts +0 -15
- package/src/__tests__/registry.test.ts +0 -10
- package/src/__tests__/schedule-tools.test.ts +28 -44
- package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
- package/src/__tests__/session-agent-loop.test.ts +0 -2
- package/src/__tests__/session-conflict-gate.test.ts +243 -388
- package/src/__tests__/session-profile-injection.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +2 -3
- package/src/__tests__/session-skill-tools.test.ts +0 -49
- package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/task-management-tools.test.ts +111 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
- package/src/__tests__/twilio-config.test.ts +0 -3
- package/src/amazon/session.ts +30 -91
- package/src/approvals/guardian-decision-primitive.ts +11 -7
- package/src/approvals/guardian-request-resolvers.ts +5 -3
- package/src/calls/call-controller.ts +423 -571
- package/src/calls/finalize-call.ts +20 -0
- package/src/calls/relay-access-wait.ts +340 -0
- package/src/calls/relay-server.ts +269 -899
- package/src/calls/relay-setup-router.ts +307 -0
- package/src/calls/relay-verification.ts +280 -0
- package/src/calls/twilio-config.ts +1 -8
- package/src/calls/voice-control-protocol.ts +184 -0
- package/src/calls/voice-session-bridge.ts +1 -8
- package/src/config/agent-schema.ts +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +7 -18
- package/src/config/bundled-skills/contacts/TOOLS.json +4 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +2 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +6 -12
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +3 -24
- package/src/config/bundled-skills/followups/TOOLS.json +0 -4
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
- package/src/config/bundled-tool-registry.ts +0 -5
- package/src/config/core-schema.ts +1 -1
- package/src/config/env.ts +0 -10
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +19 -0
- package/src/config/memory-schema.ts +0 -10
- package/src/config/schema.ts +2 -2
- package/src/config/system-prompt.ts +6 -0
- package/src/contacts/contact-store.ts +36 -62
- package/src/contacts/contacts-write.ts +14 -3
- package/src/contacts/types.ts +9 -4
- package/src/daemon/handlers/config-heartbeat.ts +1 -2
- package/src/daemon/handlers/contacts.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +1 -1
- package/src/daemon/handlers/session-history.ts +398 -0
- package/src/daemon/handlers/session-user-message.ts +982 -0
- package/src/daemon/handlers/sessions.ts +9 -1337
- package/src/daemon/ipc-contract/contacts.ts +2 -2
- package/src/daemon/ipc-contract/sessions.ts +0 -6
- package/src/daemon/ipc-contract-inventory.json +0 -1
- package/src/daemon/lifecycle.ts +0 -29
- package/src/daemon/session-agent-loop.ts +1 -45
- package/src/daemon/session-conflict-gate.ts +21 -82
- package/src/daemon/session-memory.ts +7 -52
- package/src/daemon/session-process.ts +3 -1
- package/src/daemon/session-runtime-assembly.ts +18 -35
- package/src/heartbeat/heartbeat-service.ts +5 -1
- package/src/home-base/app-link-store.ts +0 -7
- package/src/memory/conflict-intent.ts +3 -6
- package/src/memory/conflict-policy.ts +34 -0
- package/src/memory/conflict-store.ts +10 -18
- package/src/memory/contradiction-checker.ts +2 -2
- package/src/memory/conversation-attention-store.ts +1 -1
- package/src/memory/conversation-store.ts +0 -51
- package/src/memory/db-init.ts +8 -0
- package/src/memory/job-handlers/conflict.ts +24 -7
- package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
- package/src/memory/migrations/134-contacts-notes-column.ts +68 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/recall-cache.ts +0 -5
- package/src/memory/schema/calls.ts +274 -0
- package/src/memory/schema/contacts.ts +125 -0
- package/src/memory/schema/conversations.ts +129 -0
- package/src/memory/schema/guardian.ts +172 -0
- package/src/memory/schema/index.ts +8 -0
- package/src/memory/schema/infrastructure.ts +205 -0
- package/src/memory/schema/memory-core.ts +196 -0
- package/src/memory/schema/notifications.ts +191 -0
- package/src/memory/schema/tasks.ts +78 -0
- package/src/memory/schema.ts +1 -1402
- package/src/memory/slack-thread-store.ts +0 -69
- package/src/messaging/index.ts +0 -1
- package/src/messaging/types.ts +0 -38
- package/src/notifications/decisions-store.ts +2 -105
- package/src/notifications/deliveries-store.ts +0 -11
- package/src/notifications/preferences-store.ts +1 -58
- package/src/permissions/checker.ts +6 -17
- package/src/providers/anthropic/client.ts +6 -2
- package/src/providers/gemini/client.ts +13 -2
- package/src/providers/managed-proxy/constants.ts +55 -0
- package/src/providers/managed-proxy/context.ts +77 -0
- package/src/providers/registry.ts +112 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
- package/src/runtime/guardian-action-service.ts +3 -2
- package/src/runtime/guardian-outbound-actions.ts +3 -3
- package/src/runtime/guardian-reply-router.ts +4 -4
- package/src/runtime/http-server.ts +83 -710
- package/src/runtime/http-types.ts +0 -16
- package/src/runtime/middleware/auth.ts +0 -12
- package/src/runtime/routes/app-routes.ts +33 -0
- package/src/runtime/routes/approval-routes.ts +32 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
- package/src/runtime/routes/attachment-routes.ts +32 -0
- package/src/runtime/routes/brain-graph-routes.ts +27 -0
- package/src/runtime/routes/call-routes.ts +41 -0
- package/src/runtime/routes/channel-readiness-routes.ts +20 -0
- package/src/runtime/routes/channel-routes.ts +70 -0
- package/src/runtime/routes/contact-routes.ts +371 -29
- package/src/runtime/routes/conversation-attention-routes.ts +15 -0
- package/src/runtime/routes/conversation-routes.ts +192 -194
- package/src/runtime/routes/debug-routes.ts +15 -0
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/global-search-routes.ts +17 -2
- package/src/runtime/routes/guardian-action-routes.ts +23 -1
- package/src/runtime/routes/guardian-approval-interception.ts +2 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +26 -1
- package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
- package/src/runtime/routes/identity-routes.ts +20 -0
- package/src/runtime/routes/inbound-message-handler.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +5 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
- package/src/runtime/routes/integration-routes.ts +83 -0
- package/src/runtime/routes/invite-routes.ts +31 -0
- package/src/runtime/routes/migration-routes.ts +47 -17
- package/src/runtime/routes/pairing-routes.ts +18 -0
- package/src/runtime/routes/secret-routes.ts +20 -0
- package/src/runtime/routes/surface-action-routes.ts +26 -0
- package/src/runtime/routes/trust-rules-routes.ts +31 -0
- package/src/runtime/routes/twilio-routes.ts +79 -0
- package/src/schedule/recurrence-types.ts +1 -11
- package/src/tools/followups/followup_create.ts +9 -3
- package/src/tools/mcp/mcp-tool-factory.ts +0 -17
- package/src/tools/memory/definitions.ts +0 -6
- package/src/tools/network/script-proxy/session-manager.ts +38 -3
- package/src/tools/schedule/create.ts +1 -3
- package/src/tools/schedule/update.ts +9 -6
- package/src/twitter/session.ts +29 -77
- package/src/util/cookie-session.ts +114 -0
- package/src/workspace/git-service.ts +6 -4
- package/src/__tests__/conversation-routes.test.ts +0 -99
- package/src/__tests__/get-weather.test.ts +0 -393
- package/src/__tests__/task-tools.test.ts +0 -685
- package/src/__tests__/weather-skill-regression.test.ts +0 -276
- package/src/autonomy/autonomy-resolver.ts +0 -62
- package/src/autonomy/autonomy-store.ts +0 -138
- package/src/autonomy/disposition-mapper.ts +0 -31
- package/src/autonomy/index.ts +0 -11
- package/src/autonomy/types.ts +0 -43
- package/src/config/bundled-skills/weather/SKILL.md +0 -38
- package/src/config/bundled-skills/weather/TOOLS.json +0 -36
- package/src/config/bundled-skills/weather/icon.svg +0 -24
- package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
- package/src/contacts/startup-migration.ts +0 -21
- package/src/messaging/triage-engine.ts +0 -344
- package/src/tools/weather/service.ts +0 -712
|
@@ -42,10 +42,6 @@ describe("CommitEnrichmentService", () => {
|
|
|
42
42
|
beforeEach(() => {
|
|
43
43
|
_resetGitServiceRegistry();
|
|
44
44
|
_resetEnrichmentService();
|
|
45
|
-
// Previous tests' enrichment jobs may leave a stale index.lock if
|
|
46
|
-
// the git process exits but the lock file isn't flushed before the
|
|
47
|
-
// next test runs git operations in the shared testDir.
|
|
48
|
-
rmSync(join(testDir, ".git", "index.lock"), { force: true });
|
|
49
45
|
});
|
|
50
46
|
|
|
51
47
|
afterEach(async () => {
|
|
@@ -55,6 +51,14 @@ describe("CommitEnrichmentService", () => {
|
|
|
55
51
|
/* ignore */
|
|
56
52
|
}
|
|
57
53
|
_resetEnrichmentService();
|
|
54
|
+
|
|
55
|
+
// Remove stale index.lock left by async enrichment jobs that ran git
|
|
56
|
+
// commands concurrently. Without this, the next test's createCommit()
|
|
57
|
+
// can fail with "Unable to create index.lock: File exists".
|
|
58
|
+
const lockFile = join(testDir, ".git", "index.lock");
|
|
59
|
+
if (existsSync(lockFile)) {
|
|
60
|
+
rmSync(lockFile, { force: true });
|
|
61
|
+
}
|
|
58
62
|
});
|
|
59
63
|
|
|
60
64
|
afterAll(async () => {
|
|
@@ -82,6 +86,13 @@ describe("CommitEnrichmentService", () => {
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
async function createCommit(): Promise<string> {
|
|
89
|
+
// Remove stale index.lock left by async enrichment jobs that ran git
|
|
90
|
+
// commands concurrently in a previous test. Without this, git add -A
|
|
91
|
+
// can fail with "Unable to create index.lock: File exists".
|
|
92
|
+
const lockFile = join(testDir, ".git", "index.lock");
|
|
93
|
+
if (existsSync(lockFile)) {
|
|
94
|
+
rmSync(lockFile, { force: true });
|
|
95
|
+
}
|
|
85
96
|
writeFileSync(join(testDir, `file-${Date.now()}.txt`), "content");
|
|
86
97
|
await gitService.commitChanges("test commit");
|
|
87
98
|
return await gitService.getHeadHash();
|
|
@@ -168,10 +168,8 @@ describe("AssistantConfigSchema", () => {
|
|
|
168
168
|
expect(result.memory.conflicts).toEqual({
|
|
169
169
|
enabled: true,
|
|
170
170
|
gateMode: "soft",
|
|
171
|
-
reaskCooldownTurns: 3,
|
|
172
171
|
resolverLlmTimeoutMs: 12000,
|
|
173
172
|
relevanceThreshold: 0.3,
|
|
174
|
-
askOnIrrelevantTurns: false,
|
|
175
173
|
conflictableKinds: [
|
|
176
174
|
"preference",
|
|
177
175
|
"profile",
|
|
@@ -189,13 +187,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
189
187
|
expect(result.success).toBe(false);
|
|
190
188
|
});
|
|
191
189
|
|
|
192
|
-
test("rejects invalid memory.conflicts.askOnIrrelevantTurns", () => {
|
|
193
|
-
const result = AssistantConfigSchema.safeParse({
|
|
194
|
-
memory: { conflicts: { askOnIrrelevantTurns: 123 } },
|
|
195
|
-
});
|
|
196
|
-
expect(result.success).toBe(false);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
190
|
test("rejects invalid memory.conflicts.conflictableKinds entry", () => {
|
|
200
191
|
const result = AssistantConfigSchema.safeParse({
|
|
201
192
|
memory: { conflicts: { conflictableKinds: ["invalid_kind"] } },
|
|
@@ -536,11 +527,12 @@ describe("AssistantConfigSchema", () => {
|
|
|
536
527
|
expect(result.permissions.mode).toBe("strict");
|
|
537
528
|
});
|
|
538
529
|
|
|
539
|
-
test("
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
530
|
+
test("rejects permissions.mode legacy", () => {
|
|
531
|
+
expect(() =>
|
|
532
|
+
AssistantConfigSchema.parse({
|
|
533
|
+
permissions: { mode: "legacy" },
|
|
534
|
+
}),
|
|
535
|
+
).toThrow();
|
|
544
536
|
});
|
|
545
537
|
|
|
546
538
|
test("accepts explicit permissions.mode workspace", () => {
|
|
@@ -3,9 +3,11 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import {
|
|
4
4
|
isConflictKindEligible,
|
|
5
5
|
isConflictKindPairEligible,
|
|
6
|
+
isConflictUserEvidenced,
|
|
6
7
|
isDurableInstructionStatement,
|
|
7
8
|
isStatementConflictEligible,
|
|
8
9
|
isTransientTrackingStatement,
|
|
10
|
+
isUserEvidencedVerificationState,
|
|
9
11
|
} from "../memory/conflict-policy.js";
|
|
10
12
|
|
|
11
13
|
describe("conflict-policy", () => {
|
|
@@ -190,4 +192,78 @@ describe("conflict-policy", () => {
|
|
|
190
192
|
).toBe(true);
|
|
191
193
|
});
|
|
192
194
|
});
|
|
195
|
+
|
|
196
|
+
describe("isUserEvidencedVerificationState", () => {
|
|
197
|
+
test("accepts user_reported", () => {
|
|
198
|
+
expect(isUserEvidencedVerificationState("user_reported")).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("accepts user_confirmed", () => {
|
|
202
|
+
expect(isUserEvidencedVerificationState("user_confirmed")).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("accepts legacy_import", () => {
|
|
206
|
+
expect(isUserEvidencedVerificationState("legacy_import")).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("rejects assistant_inferred", () => {
|
|
210
|
+
expect(isUserEvidencedVerificationState("assistant_inferred")).toBe(
|
|
211
|
+
false,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("rejects unknown states", () => {
|
|
216
|
+
expect(isUserEvidencedVerificationState("")).toBe(false);
|
|
217
|
+
expect(isUserEvidencedVerificationState("auto_detected")).toBe(false);
|
|
218
|
+
expect(isUserEvidencedVerificationState("pending")).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("isConflictUserEvidenced", () => {
|
|
223
|
+
test("returns true when existing side is user-evidenced", () => {
|
|
224
|
+
expect(
|
|
225
|
+
isConflictUserEvidenced("user_reported", "assistant_inferred"),
|
|
226
|
+
).toBe(true);
|
|
227
|
+
expect(
|
|
228
|
+
isConflictUserEvidenced("user_confirmed", "assistant_inferred"),
|
|
229
|
+
).toBe(true);
|
|
230
|
+
expect(
|
|
231
|
+
isConflictUserEvidenced("legacy_import", "assistant_inferred"),
|
|
232
|
+
).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("returns true when candidate side is user-evidenced", () => {
|
|
236
|
+
expect(
|
|
237
|
+
isConflictUserEvidenced("assistant_inferred", "user_reported"),
|
|
238
|
+
).toBe(true);
|
|
239
|
+
expect(
|
|
240
|
+
isConflictUserEvidenced("assistant_inferred", "user_confirmed"),
|
|
241
|
+
).toBe(true);
|
|
242
|
+
expect(
|
|
243
|
+
isConflictUserEvidenced("assistant_inferred", "legacy_import"),
|
|
244
|
+
).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("returns true when both sides are user-evidenced", () => {
|
|
248
|
+
expect(isConflictUserEvidenced("user_reported", "user_confirmed")).toBe(
|
|
249
|
+
true,
|
|
250
|
+
);
|
|
251
|
+
expect(isConflictUserEvidenced("legacy_import", "user_reported")).toBe(
|
|
252
|
+
true,
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("returns false when neither side is user-evidenced", () => {
|
|
257
|
+
expect(
|
|
258
|
+
isConflictUserEvidenced("assistant_inferred", "assistant_inferred"),
|
|
259
|
+
).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("returns false for unknown states on both sides", () => {
|
|
263
|
+
expect(isConflictUserEvidenced("auto_detected", "pending")).toBe(false);
|
|
264
|
+
expect(
|
|
265
|
+
isConflictUserEvidenced("assistant_inferred", "auto_detected"),
|
|
266
|
+
).toBe(false);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
193
269
|
});
|
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
getPendingConflictByPair,
|
|
34
34
|
listPendingConflictDetails,
|
|
35
35
|
listPendingConflicts,
|
|
36
|
-
markConflictAsked,
|
|
37
36
|
resolveConflict,
|
|
38
37
|
} from "../memory/conflict-store.js";
|
|
39
38
|
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
@@ -60,6 +59,10 @@ function resetTables() {
|
|
|
60
59
|
function insertItemPair(
|
|
61
60
|
suffix: string,
|
|
62
61
|
scopeId = "default",
|
|
62
|
+
opts?: {
|
|
63
|
+
existingVerificationState?: string;
|
|
64
|
+
candidateVerificationState?: string;
|
|
65
|
+
},
|
|
63
66
|
): { existingItemId: string; candidateItemId: string } {
|
|
64
67
|
const db = getDb();
|
|
65
68
|
const now = Date.now();
|
|
@@ -76,7 +79,8 @@ function insertItemPair(
|
|
|
76
79
|
confidence: 0.8,
|
|
77
80
|
importance: 0.5,
|
|
78
81
|
fingerprint: `fp-existing-${suffix}`,
|
|
79
|
-
verificationState:
|
|
82
|
+
verificationState:
|
|
83
|
+
opts?.existingVerificationState ?? "assistant_inferred",
|
|
80
84
|
scopeId,
|
|
81
85
|
firstSeenAt: now,
|
|
82
86
|
lastSeenAt: now,
|
|
@@ -90,7 +94,8 @@ function insertItemPair(
|
|
|
90
94
|
confidence: 0.8,
|
|
91
95
|
importance: 0.5,
|
|
92
96
|
fingerprint: `fp-candidate-${suffix}`,
|
|
93
|
-
verificationState:
|
|
97
|
+
verificationState:
|
|
98
|
+
opts?.candidateVerificationState ?? "assistant_inferred",
|
|
94
99
|
scopeId,
|
|
95
100
|
firstSeenAt: now,
|
|
96
101
|
lastSeenAt: now,
|
|
@@ -218,24 +223,11 @@ describe("conflict-store", () => {
|
|
|
218
223
|
expect(pendingDefault[0].status).toBe("pending_clarification");
|
|
219
224
|
});
|
|
220
225
|
|
|
221
|
-
test("
|
|
222
|
-
const pair = insertItemPair("
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
existingItemId: pair.existingItemId,
|
|
226
|
-
candidateItemId: pair.candidateItemId,
|
|
227
|
-
relationship: "ambiguous_contradiction",
|
|
226
|
+
test("listPendingConflictDetails joins current statements and verification states", () => {
|
|
227
|
+
const pair = insertItemPair("details", "workspace-a", {
|
|
228
|
+
existingVerificationState: "user_confirmed",
|
|
229
|
+
candidateVerificationState: "assistant_inferred",
|
|
228
230
|
});
|
|
229
|
-
|
|
230
|
-
const askedAt = 1_734_000_000_000;
|
|
231
|
-
expect(markConflictAsked(conflict.id, askedAt)).toBe(true);
|
|
232
|
-
const updated = getConflictById(conflict.id);
|
|
233
|
-
expect(updated?.lastAskedAt).toBe(askedAt);
|
|
234
|
-
expect(updated?.updatedAt).toBe(askedAt);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("listPendingConflictDetails joins current statements", () => {
|
|
238
|
-
const pair = insertItemPair("details", "workspace-a");
|
|
239
231
|
createOrUpdatePendingConflict({
|
|
240
232
|
scopeId: "workspace-a",
|
|
241
233
|
existingItemId: pair.existingItemId,
|
|
@@ -250,6 +242,8 @@ describe("conflict-store", () => {
|
|
|
250
242
|
expect(details[0].candidateStatement).toBe("Candidate statement details");
|
|
251
243
|
expect(details[0].existingKind).toBe("fact");
|
|
252
244
|
expect(details[0].candidateKind).toBe("fact");
|
|
245
|
+
expect(details[0].existingVerificationState).toBe("user_confirmed");
|
|
246
|
+
expect(details[0].candidateVerificationState).toBe("assistant_inferred");
|
|
253
247
|
});
|
|
254
248
|
|
|
255
249
|
test("applyConflictResolution keeps candidate and resolves conflict row", () => {
|
|
@@ -140,17 +140,14 @@ describe("contact_upsert tool", () => {
|
|
|
140
140
|
expect(result.isError).toBe(false);
|
|
141
141
|
expect(result.content).toContain("Created contact");
|
|
142
142
|
expect(result.content).toContain("Alice");
|
|
143
|
-
expect(result.content).toContain("Importance: 0.50");
|
|
144
143
|
});
|
|
145
144
|
|
|
146
145
|
test("creates a contact with all fields", async () => {
|
|
147
146
|
const result = await executeContactUpsert(
|
|
148
147
|
{
|
|
149
148
|
display_name: "Bob",
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
response_expectation: "within_hours",
|
|
153
|
-
preferred_tone: "professional",
|
|
149
|
+
notes:
|
|
150
|
+
"Colleague at Acme Corp, prefers professional tone, responds within hours",
|
|
154
151
|
channels: [
|
|
155
152
|
{ type: "email", address: "bob@example.com", is_primary: true },
|
|
156
153
|
{ type: "slack", address: "@bob" },
|
|
@@ -161,10 +158,7 @@ describe("contact_upsert tool", () => {
|
|
|
161
158
|
|
|
162
159
|
expect(result.isError).toBe(false);
|
|
163
160
|
expect(result.content).toContain("Bob");
|
|
164
|
-
expect(result.content).toContain("
|
|
165
|
-
expect(result.content).toContain("0.80");
|
|
166
|
-
expect(result.content).toContain("within_hours");
|
|
167
|
-
expect(result.content).toContain("professional");
|
|
161
|
+
expect(result.content).toContain("Notes: Colleague at Acme Corp");
|
|
168
162
|
expect(result.content).toContain("email: bob@example.com");
|
|
169
163
|
expect(result.content).toContain("slack: @bob");
|
|
170
164
|
});
|
|
@@ -185,7 +179,7 @@ describe("contact_upsert tool", () => {
|
|
|
185
179
|
{
|
|
186
180
|
id: contactId,
|
|
187
181
|
display_name: "Charlie Updated",
|
|
188
|
-
|
|
182
|
+
notes: "Updated notes for Charlie",
|
|
189
183
|
},
|
|
190
184
|
ctx,
|
|
191
185
|
);
|
|
@@ -193,7 +187,7 @@ describe("contact_upsert tool", () => {
|
|
|
193
187
|
expect(updateResult.isError).toBe(false);
|
|
194
188
|
expect(updateResult.content).toContain("Updated contact");
|
|
195
189
|
expect(updateResult.content).toContain("Charlie Updated");
|
|
196
|
-
expect(updateResult.content).toContain("
|
|
190
|
+
expect(updateResult.content).toContain("Notes: Updated notes for Charlie");
|
|
197
191
|
});
|
|
198
192
|
|
|
199
193
|
test("auto-matches by channel address on create", async () => {
|
|
@@ -239,36 +233,6 @@ describe("contact_upsert tool", () => {
|
|
|
239
233
|
expect(result.isError).toBe(true);
|
|
240
234
|
expect(result.content).toContain("display_name is required");
|
|
241
235
|
});
|
|
242
|
-
|
|
243
|
-
test("rejects importance out of range", async () => {
|
|
244
|
-
const result = await executeContactUpsert(
|
|
245
|
-
{
|
|
246
|
-
display_name: "Test",
|
|
247
|
-
importance: 1.5,
|
|
248
|
-
},
|
|
249
|
-
ctx,
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
expect(result.isError).toBe(true);
|
|
253
|
-
expect(result.content).toContain(
|
|
254
|
-
"importance must be a number between 0 and 1",
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("rejects negative importance", async () => {
|
|
259
|
-
const result = await executeContactUpsert(
|
|
260
|
-
{
|
|
261
|
-
display_name: "Test",
|
|
262
|
-
importance: -0.1,
|
|
263
|
-
},
|
|
264
|
-
ctx,
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
expect(result.isError).toBe(true);
|
|
268
|
-
expect(result.content).toContain(
|
|
269
|
-
"importance must be a number between 0 and 1",
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
236
|
});
|
|
273
237
|
|
|
274
238
|
// ── contact_search ──────────────────────────────────────────────────
|
|
@@ -305,23 +269,6 @@ describe("contact_search tool", () => {
|
|
|
305
269
|
expect(result.content).toContain("Charlie");
|
|
306
270
|
});
|
|
307
271
|
|
|
308
|
-
test("searches by relationship", async () => {
|
|
309
|
-
await executeContactUpsert(
|
|
310
|
-
{ display_name: "Diana", relationship: "friend" },
|
|
311
|
-
ctx,
|
|
312
|
-
);
|
|
313
|
-
await executeContactUpsert(
|
|
314
|
-
{ display_name: "Eve", relationship: "colleague" },
|
|
315
|
-
ctx,
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
const result = await executeContactSearch({ relationship: "friend" }, ctx);
|
|
319
|
-
|
|
320
|
-
expect(result.isError).toBe(false);
|
|
321
|
-
expect(result.content).toContain("Diana");
|
|
322
|
-
expect(result.content).not.toContain("Eve");
|
|
323
|
-
});
|
|
324
|
-
|
|
325
272
|
test("returns no results message when nothing matches", async () => {
|
|
326
273
|
await executeContactUpsert({ display_name: "Existing" }, ctx);
|
|
327
274
|
|
|
@@ -380,7 +327,7 @@ describe("contact_merge tool", () => {
|
|
|
380
327
|
const r1 = await executeContactUpsert(
|
|
381
328
|
{
|
|
382
329
|
display_name: "Alice (Email)",
|
|
383
|
-
|
|
330
|
+
notes: "Prefers email",
|
|
384
331
|
channels: [{ type: "email", address: "alice@example.com" }],
|
|
385
332
|
},
|
|
386
333
|
ctx,
|
|
@@ -388,7 +335,7 @@ describe("contact_merge tool", () => {
|
|
|
388
335
|
const r2 = await executeContactUpsert(
|
|
389
336
|
{
|
|
390
337
|
display_name: "Alice (Slack)",
|
|
391
|
-
|
|
338
|
+
notes: "Active on Slack",
|
|
392
339
|
channels: [{ type: "slack", address: "@alice" }],
|
|
393
340
|
},
|
|
394
341
|
ctx,
|
|
@@ -407,7 +354,7 @@ describe("contact_merge tool", () => {
|
|
|
407
354
|
|
|
408
355
|
expect(result.isError).toBe(false);
|
|
409
356
|
expect(result.content).toContain("Merged");
|
|
410
|
-
expect(result.content).toContain("
|
|
357
|
+
expect(result.content).toContain("Notes: Prefers email\nActive on Slack"); // concatenated notes
|
|
411
358
|
expect(result.content).toContain("email: alice@example.com");
|
|
412
359
|
expect(result.content).toContain("slack: @alice");
|
|
413
360
|
|
|
@@ -201,7 +201,11 @@ describe("checkContradictions", () => {
|
|
|
201
201
|
expect(conflicts[0].existingItemId).toBe("item-existing-ambiguous");
|
|
202
202
|
expect(conflicts[0].candidateItemId).toBe("item-candidate-ambiguous");
|
|
203
203
|
expect(conflicts[0].relationship).toBe("ambiguous_contradiction");
|
|
204
|
-
expect(conflicts[0].clarificationQuestion).toContain(
|
|
204
|
+
expect(conflicts[0].clarificationQuestion).toContain("Pending conflict:");
|
|
205
|
+
expect(conflicts[0].clarificationQuestion).not.toContain(
|
|
206
|
+
"I have conflicting notes",
|
|
207
|
+
);
|
|
208
|
+
expect(conflicts[0].clarificationQuestion).not.toContain(
|
|
205
209
|
"Which one is correct?",
|
|
206
210
|
);
|
|
207
211
|
});
|
|
@@ -241,6 +241,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
241
241
|
"schedule/integration-status.ts", // integration status checks for scheduled reports
|
|
242
242
|
"daemon/handlers/oauth-connect.ts", // OAuth connect handler for integration setup
|
|
243
243
|
"daemon/handlers/config-slack-channel.ts", // Slack channel config credential management
|
|
244
|
+
"providers/managed-proxy/context.ts", // managed proxy API key lookup for provider initialization
|
|
244
245
|
]);
|
|
245
246
|
|
|
246
247
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -52,7 +52,6 @@ class MockSession {
|
|
|
52
52
|
| { skipPreMessageRollback?: boolean; isInteractive?: boolean }
|
|
53
53
|
| undefined;
|
|
54
54
|
public updateClientHistory: Array<{ hasNoClient: boolean }> = [];
|
|
55
|
-
public setSandboxOverrideCalls = 0;
|
|
56
55
|
private stale = false;
|
|
57
56
|
private processing = false;
|
|
58
57
|
public trustContext: Record<string, unknown> | null = null;
|
|
@@ -95,9 +94,7 @@ class MockSession {
|
|
|
95
94
|
return this._currentSender;
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
setSandboxOverride(): void {
|
|
99
|
-
this.setSandboxOverrideCalls += 1;
|
|
100
|
-
}
|
|
97
|
+
setSandboxOverride(): void {}
|
|
101
98
|
|
|
102
99
|
isProcessing(): boolean {
|
|
103
100
|
return this.processing;
|
|
@@ -433,21 +430,6 @@ describe("DaemonServer initial session hydration", () => {
|
|
|
433
430
|
expect(lastCreatedWorkingDir).toBe("/tmp/workspace");
|
|
434
431
|
});
|
|
435
432
|
|
|
436
|
-
test("ignores deprecated sandbox_set runtime override messages", async () => {
|
|
437
|
-
const server = new DaemonServer();
|
|
438
|
-
const internal = asDaemonServerTestAccess(server);
|
|
439
|
-
const { socket } = createFakeSocket();
|
|
440
|
-
|
|
441
|
-
await internal.sendInitialSession(socket);
|
|
442
|
-
const session = internal.sessions.get(conversation.id);
|
|
443
|
-
expect(session).toBeDefined();
|
|
444
|
-
expect(session!.setSandboxOverrideCalls).toBe(0);
|
|
445
|
-
|
|
446
|
-
internal.dispatchMessage({ type: "sandbox_set", enabled: false }, socket);
|
|
447
|
-
|
|
448
|
-
expect(session!.setSandboxOverrideCalls).toBe(0);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
433
|
test("sendInitialSession includes threadType in session_info", async () => {
|
|
452
434
|
conversation.threadType = "private";
|
|
453
435
|
const server = new DaemonServer();
|
|
@@ -122,36 +122,6 @@ describe("followup_create tool", () => {
|
|
|
122
122
|
expect(result.content).toContain("Reminder schedule: sched-abc");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
test("creates a follow-up with deprecated reminder_cron_id alias", async () => {
|
|
126
|
-
const result = await executeFollowupCreate(
|
|
127
|
-
{
|
|
128
|
-
channel: "email",
|
|
129
|
-
thread_id: "thread-789",
|
|
130
|
-
reminder_cron_id: "cron-abc",
|
|
131
|
-
},
|
|
132
|
-
ctx,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
expect(result.isError).toBe(false);
|
|
136
|
-
expect(result.content).toContain("Reminder schedule: cron-abc");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test("reminder_schedule_id takes precedence over reminder_cron_id", async () => {
|
|
140
|
-
const result = await executeFollowupCreate(
|
|
141
|
-
{
|
|
142
|
-
channel: "email",
|
|
143
|
-
thread_id: "thread-prio",
|
|
144
|
-
reminder_schedule_id: "sched-wins",
|
|
145
|
-
reminder_cron_id: "cron-loses",
|
|
146
|
-
},
|
|
147
|
-
ctx,
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
expect(result.isError).toBe(false);
|
|
151
|
-
expect(result.content).toContain("Reminder schedule: sched-wins");
|
|
152
|
-
expect(result.content).not.toContain("cron-loses");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
125
|
test("rejects missing channel", async () => {
|
|
156
126
|
const result = await executeFollowupCreate(
|
|
157
127
|
{
|
|
@@ -30,6 +30,7 @@ interface FakeChunk {
|
|
|
30
30
|
|
|
31
31
|
let fakeChunks: FakeChunk[] = [];
|
|
32
32
|
let lastStreamParams: Record<string, unknown> | null = null;
|
|
33
|
+
let lastConstructorOpts: Record<string, unknown> | null = null;
|
|
33
34
|
let shouldThrow: Error | null = null;
|
|
34
35
|
|
|
35
36
|
class FakeApiError extends Error {
|
|
@@ -43,7 +44,9 @@ class FakeApiError extends Error {
|
|
|
43
44
|
|
|
44
45
|
mock.module("@google/genai", () => ({
|
|
45
46
|
GoogleGenAI: class MockGoogleGenAI {
|
|
46
|
-
constructor(
|
|
47
|
+
constructor(opts: Record<string, unknown>) {
|
|
48
|
+
lastConstructorOpts = opts;
|
|
49
|
+
}
|
|
47
50
|
models = {
|
|
48
51
|
generateContentStream: async (params: Record<string, unknown>) => {
|
|
49
52
|
lastStreamParams = params;
|
|
@@ -108,6 +111,7 @@ describe("GeminiProvider", () => {
|
|
|
108
111
|
provider = new GeminiProvider("test-api-key", "gemini-3-flash");
|
|
109
112
|
fakeChunks = [];
|
|
110
113
|
lastStreamParams = null;
|
|
114
|
+
lastConstructorOpts = null;
|
|
111
115
|
shouldThrow = null;
|
|
112
116
|
});
|
|
113
117
|
|
|
@@ -726,4 +730,78 @@ describe("GeminiProvider", () => {
|
|
|
726
730
|
|
|
727
731
|
expect(result.usage).toEqual({ inputTokens: 0, outputTokens: 0 });
|
|
728
732
|
});
|
|
733
|
+
|
|
734
|
+
// -----------------------------------------------------------------------
|
|
735
|
+
// Managed transport — constructor configuration
|
|
736
|
+
// -----------------------------------------------------------------------
|
|
737
|
+
test("does not set httpOptions when managedBaseUrl is not provided", () => {
|
|
738
|
+
new GeminiProvider("test-key", "gemini-3-flash");
|
|
739
|
+
expect(lastConstructorOpts).toEqual({ apiKey: "test-key" });
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("sets httpOptions.baseUrl when managedBaseUrl is provided", () => {
|
|
743
|
+
new GeminiProvider("managed-key", "gemini-3-flash", {
|
|
744
|
+
managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
|
|
745
|
+
});
|
|
746
|
+
expect(lastConstructorOpts).toEqual({
|
|
747
|
+
apiKey: "managed-key",
|
|
748
|
+
httpOptions: {
|
|
749
|
+
baseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
test("managed transport produces same ProviderResponse shape", async () => {
|
|
755
|
+
const managedProvider = new GeminiProvider(
|
|
756
|
+
"managed-key",
|
|
757
|
+
"gemini-3-flash",
|
|
758
|
+
{
|
|
759
|
+
managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
|
|
760
|
+
},
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
fakeChunks = [textChunk("Hello from managed"), finishChunk("STOP", 15, 8)];
|
|
764
|
+
|
|
765
|
+
const result = await managedProvider.sendMessage([
|
|
766
|
+
{ role: "user", content: [{ type: "text", text: "Hi" }] },
|
|
767
|
+
]);
|
|
768
|
+
|
|
769
|
+
expect(result.content).toHaveLength(1);
|
|
770
|
+
expect(result.content[0]).toEqual({
|
|
771
|
+
type: "text",
|
|
772
|
+
text: "Hello from managed",
|
|
773
|
+
});
|
|
774
|
+
expect(result.model).toBe("gemini-3-flash-001");
|
|
775
|
+
expect(result.usage).toEqual({ inputTokens: 15, outputTokens: 8 });
|
|
776
|
+
expect(result.stopReason).toBe("STOP");
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("managed transport handles tool calls correctly", async () => {
|
|
780
|
+
const managedProvider = new GeminiProvider(
|
|
781
|
+
"managed-key",
|
|
782
|
+
"gemini-3-flash",
|
|
783
|
+
{
|
|
784
|
+
managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
|
|
785
|
+
},
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
fakeChunks = [
|
|
789
|
+
functionCallChunk([
|
|
790
|
+
{ id: "call_managed", name: "file_read", args: { path: "/tmp/test" } },
|
|
791
|
+
]),
|
|
792
|
+
finishChunk("STOP", 10, 15),
|
|
793
|
+
];
|
|
794
|
+
|
|
795
|
+
const result = await managedProvider.sendMessage([
|
|
796
|
+
{ role: "user", content: [{ type: "text", text: "Read /tmp/test" }] },
|
|
797
|
+
]);
|
|
798
|
+
|
|
799
|
+
expect(result.content).toHaveLength(1);
|
|
800
|
+
expect(result.content[0]).toEqual({
|
|
801
|
+
type: "tool_use",
|
|
802
|
+
id: "call_managed",
|
|
803
|
+
name: "file_read",
|
|
804
|
+
input: { path: "/tmp/test" },
|
|
805
|
+
});
|
|
806
|
+
});
|
|
729
807
|
});
|
|
@@ -71,7 +71,8 @@ const TEST_PRINCIPAL_ID = "test-principal-id";
|
|
|
71
71
|
|
|
72
72
|
function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
73
73
|
return {
|
|
74
|
-
|
|
74
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
75
|
+
actorExternalUserId: "guardian-1",
|
|
75
76
|
channel: "telegram",
|
|
76
77
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
77
78
|
...overrides,
|
|
@@ -80,7 +81,8 @@ function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
|
80
81
|
|
|
81
82
|
function trustedActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
82
83
|
return {
|
|
83
|
-
|
|
84
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
85
|
+
actorExternalUserId: undefined,
|
|
84
86
|
channel: "desktop",
|
|
85
87
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
86
88
|
...overrides,
|
|
@@ -254,7 +256,7 @@ describe("applyCanonicalGuardianDecision", () => {
|
|
|
254
256
|
|
|
255
257
|
expect(result.applied).toBe(true);
|
|
256
258
|
if (!result.applied) return;
|
|
257
|
-
// No grant minted because trusted actor has no
|
|
259
|
+
// No grant minted because trusted actor has no actorExternalUserId
|
|
258
260
|
expect(result.grantMinted).toBe(false);
|
|
259
261
|
});
|
|
260
262
|
|
|
@@ -94,7 +94,8 @@ const TEST_PRINCIPAL_ID = "test-principal-id";
|
|
|
94
94
|
|
|
95
95
|
function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
96
96
|
return {
|
|
97
|
-
|
|
97
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
98
|
+
actorExternalUserId: "guardian-1",
|
|
98
99
|
channel: "telegram",
|
|
99
100
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
100
101
|
...overrides,
|
|
@@ -103,7 +104,8 @@ function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
|
103
104
|
|
|
104
105
|
function trustedActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
105
106
|
return {
|
|
106
|
-
|
|
107
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
108
|
+
actorExternalUserId: undefined,
|
|
107
109
|
channel: "desktop",
|
|
108
110
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
109
111
|
...overrides,
|
|
@@ -1212,13 +1214,13 @@ describe("routing invariant: destination hints do not bypass tool_approval princ
|
|
|
1212
1214
|
});
|
|
1213
1215
|
|
|
1214
1216
|
// No pendingRequestIds passed — identity-based fallback uses
|
|
1215
|
-
// actor.
|
|
1217
|
+
// actor.actorExternalUserId which does not match any request's
|
|
1216
1218
|
// guardianExternalUserId (since it's null).
|
|
1217
1219
|
const result = await routeGuardianReply(
|
|
1218
1220
|
replyCtx({
|
|
1219
1221
|
messageText: "approve",
|
|
1220
1222
|
channel: "telegram",
|
|
1221
|
-
actor: guardianActor({
|
|
1223
|
+
actor: guardianActor({ actorExternalUserId: "guardian-tg-user" }),
|
|
1222
1224
|
conversationId: "conv-guardian-chat",
|
|
1223
1225
|
// pendingRequestIds: undefined — no delivery hints
|
|
1224
1226
|
approvalConversationGenerator: undefined,
|
|
@@ -101,10 +101,6 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
101
101
|
type: "usage_request",
|
|
102
102
|
sessionId: "sess-001",
|
|
103
103
|
},
|
|
104
|
-
sandbox_set: {
|
|
105
|
-
type: "sandbox_set",
|
|
106
|
-
enabled: true,
|
|
107
|
-
},
|
|
108
104
|
cu_session_create: {
|
|
109
105
|
type: "cu_session_create",
|
|
110
106
|
sessionId: "cu-sess-001",
|