@agent-native/core 0.100.0 → 0.100.2
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +4 -0
- package/corpus/core/src/client/composer/PromptComposer.tsx +4 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +30 -3
- package/corpus/core/src/integrations/webhook-handler.ts +4 -2
- package/corpus/core/src/secrets/crypto.ts +98 -47
- package/corpus/core/src/secrets/index.ts +2 -1
- package/corpus/core/src/secrets/schema.ts +3 -0
- package/corpus/core/src/secrets/storage.ts +164 -31
- package/corpus/templates/assets/app/components/layout/Layout.tsx +4 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
- package/corpus/templates/assets/changelog/2026-07-13-assets-keeps-the-right-chat-sidebar-closed-when-the-full-pag.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +9 -0
- package/corpus/templates/calendar/actions/list-events.ts +659 -44
- package/corpus/templates/calendar/changelog/2026-07-13-calendar-inventory-reads-report-source-coverage.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-connector-catalog.ts +8 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +173 -51
- package/corpus/templates/calendar/server/lib/ical-fetcher.ts +13 -2
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/mail/AGENTS.md +12 -0
- package/corpus/templates/mail/actions/list-emails.ts +486 -4
- package/corpus/templates/mail/changelog/2026-07-13-coverage-aware-connected-inbox-inventory.md +6 -0
- package/corpus/templates/mail/server/db/schema.ts +17 -0
- package/corpus/templates/mail/server/lib/google-auth.ts +33 -5
- package/corpus/templates/mail/server/lib/inventory-cursor.ts +406 -0
- package/corpus/templates/mail/server/lib/list-inbox-emails.ts +21 -2
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +4 -9
- package/corpus/templates/mail/server/plugins/db.ts +20 -0
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +2 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +2 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +3 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +20 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +3 -2
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/crypto.d.ts +35 -13
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +78 -41
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +2 -1
- package/dist/secrets/index.js.map +1 -1
- package/dist/secrets/schema.d.ts +20 -1
- package/dist/secrets/schema.d.ts.map +1 -1
- package/dist/secrets/schema.js +3 -0
- package/dist/secrets/schema.js.map +1 -1
- package/dist/secrets/storage.d.ts +7 -7
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +121 -30
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Storage layer for the framework secrets registry.
|
|
3
3
|
*
|
|
4
|
-
* Values are encrypted at rest with AES-256-GCM. The
|
|
5
|
-
* derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the
|
|
6
|
-
* `BETTER_AUTH_SECRET` env var (fallback so templates don't need a
|
|
7
|
-
* secret during development).
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* for
|
|
4
|
+
* Values are encrypted at rest with AES-256-GCM. The workspace-shared
|
|
5
|
+
* encryption key is derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the
|
|
6
|
+
* existing `BETTER_AUTH_SECRET` env var (fallback so templates don't need a
|
|
7
|
+
* second secret during development). Deployments that only have the legacy
|
|
8
|
+
* app-scoped key continue to work until shared key material is configured;
|
|
9
|
+
* once configured, new rows use the shared key and reads retain a legacy-key
|
|
10
|
+
* fallback for existing rows.
|
|
11
11
|
*
|
|
12
12
|
* Secret values are NEVER logged and NEVER returned from any route handler.
|
|
13
13
|
*/
|
|
@@ -17,8 +17,11 @@ import { randomUUID } from "node:crypto";
|
|
|
17
17
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
18
18
|
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
19
19
|
import {
|
|
20
|
-
encryptSecretValue as
|
|
21
|
-
|
|
20
|
+
encryptSecretValue as encryptLegacyValue,
|
|
21
|
+
encryptSharedSecretValue as encryptValue,
|
|
22
|
+
decryptSharedSecretValue as decryptValue,
|
|
23
|
+
decryptSecretValue as decryptLegacyValue,
|
|
24
|
+
hasSharedSecretEncryptionKeyMaterial,
|
|
22
25
|
} from "./crypto.js";
|
|
23
26
|
import type { SecretScope } from "./register.js";
|
|
24
27
|
import { APP_SECRETS_CREATE_SQL } from "./schema.js";
|
|
@@ -61,6 +64,11 @@ async function ensureTable(): Promise<void> {
|
|
|
61
64
|
"url_allowlist",
|
|
62
65
|
`ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS url_allowlist TEXT`,
|
|
63
66
|
);
|
|
67
|
+
await ensureColumnExists(
|
|
68
|
+
"app_secrets",
|
|
69
|
+
"shared_encrypted_value",
|
|
70
|
+
`ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS shared_encrypted_value TEXT`,
|
|
71
|
+
);
|
|
64
72
|
return;
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -86,6 +94,16 @@ async function ensureTable(): Promise<void> {
|
|
|
86
94
|
} catch {
|
|
87
95
|
// Column already exists — expected
|
|
88
96
|
}
|
|
97
|
+
|
|
98
|
+
// Additive migration: workspace-shared ciphertext. Keep the legacy
|
|
99
|
+
// encrypted_value column so older app versions remain readable.
|
|
100
|
+
try {
|
|
101
|
+
await client.execute(
|
|
102
|
+
`ALTER TABLE app_secrets ADD COLUMN shared_encrypted_value TEXT`,
|
|
103
|
+
);
|
|
104
|
+
} catch {
|
|
105
|
+
// Column already exists — expected
|
|
106
|
+
}
|
|
89
107
|
})().catch((err) => {
|
|
90
108
|
_initPromise = undefined;
|
|
91
109
|
throw err;
|
|
@@ -95,7 +113,9 @@ async function ensureTable(): Promise<void> {
|
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
// ---------------------------------------------------------------------------
|
|
98
|
-
// Encryption — see ./crypto.ts
|
|
116
|
+
// Encryption — see ./crypto.ts. Keep encrypted_value on the legacy app-key
|
|
117
|
+
// format for mixed-version deployments, and add shared_encrypted_value when a
|
|
118
|
+
// stable workspace key is configured so sibling apps can read the same row.
|
|
99
119
|
// ---------------------------------------------------------------------------
|
|
100
120
|
|
|
101
121
|
/**
|
|
@@ -141,7 +161,14 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
|
|
|
141
161
|
}
|
|
142
162
|
const client = getDbExec();
|
|
143
163
|
const now = Date.now();
|
|
144
|
-
|
|
164
|
+
// Dual-write during rollout: old readers continue using encrypted_value,
|
|
165
|
+
// while new readers prefer the nullable shared ciphertext. An app-only
|
|
166
|
+
// deployment leaves the shared column null; on an update, an existing
|
|
167
|
+
// shared ciphertext is preserved by the upsert below.
|
|
168
|
+
const encrypted = encryptLegacyValue(value);
|
|
169
|
+
const sharedEncrypted = hasSharedSecretEncryptionKeyMaterial()
|
|
170
|
+
? encryptValue(value)
|
|
171
|
+
: null;
|
|
145
172
|
const id = randomUUID();
|
|
146
173
|
|
|
147
174
|
// Atomic upsert by (scope, scope_id, key). Previously this was a
|
|
@@ -155,10 +182,11 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
|
|
|
155
182
|
// keeps its original id (any stored references stay stable); only a
|
|
156
183
|
// genuinely new row gets the freshly generated `id`. This syntax is
|
|
157
184
|
// portable across SQLite (UPSERT since 3.24) and Postgres.
|
|
158
|
-
const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, description, url_allowlist, created_at, updated_at)
|
|
159
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
185
|
+
const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)
|
|
186
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
160
187
|
ON CONFLICT (scope, scope_id, key) DO UPDATE SET
|
|
161
188
|
encrypted_value = excluded.encrypted_value,
|
|
189
|
+
shared_encrypted_value = COALESCE(excluded.shared_encrypted_value, app_secrets.shared_encrypted_value),
|
|
162
190
|
description = excluded.description,
|
|
163
191
|
url_allowlist = excluded.url_allowlist,
|
|
164
192
|
updated_at = excluded.updated_at`;
|
|
@@ -168,6 +196,7 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
|
|
|
168
196
|
scopeId,
|
|
169
197
|
key,
|
|
170
198
|
encrypted,
|
|
199
|
+
sharedEncrypted,
|
|
171
200
|
description ?? null,
|
|
172
201
|
urlAllowlist ?? null,
|
|
173
202
|
now,
|
|
@@ -201,6 +230,80 @@ export interface ReadSecretResult {
|
|
|
201
230
|
updatedAt: number;
|
|
202
231
|
}
|
|
203
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Read the shared-key format and retain compatibility with rows written before
|
|
235
|
+
* app_secrets moved to its workspace-shared encryption boundary. The legacy
|
|
236
|
+
* fallback is only useful when the current app owns the old row; sibling apps
|
|
237
|
+
* will receive shared-key ciphertext after the next vault sync or update.
|
|
238
|
+
*/
|
|
239
|
+
interface DecryptedAppSecretValue {
|
|
240
|
+
value: string;
|
|
241
|
+
/** True when the app-scoped fallback decrypted encrypted_value. */
|
|
242
|
+
usedLegacyKey: boolean;
|
|
243
|
+
/** True when shared_encrypted_value was not the source of the plaintext. */
|
|
244
|
+
needsSharedCiphertext: boolean;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function decryptAppSecretValue(
|
|
248
|
+
encrypted: string,
|
|
249
|
+
sharedEncrypted?: string | null,
|
|
250
|
+
): DecryptedAppSecretValue {
|
|
251
|
+
if (sharedEncrypted) {
|
|
252
|
+
try {
|
|
253
|
+
return {
|
|
254
|
+
value: decryptValue(sharedEncrypted),
|
|
255
|
+
usedLegacyKey: false,
|
|
256
|
+
needsSharedCiphertext: false,
|
|
257
|
+
};
|
|
258
|
+
} catch {
|
|
259
|
+
// Fall through to the legacy column. A partially migrated row may have
|
|
260
|
+
// a stale shared ciphertext while the old app-key value is valid.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
return {
|
|
265
|
+
value: decryptValue(encrypted),
|
|
266
|
+
usedLegacyKey: false,
|
|
267
|
+
needsSharedCiphertext: true,
|
|
268
|
+
};
|
|
269
|
+
} catch {
|
|
270
|
+
return {
|
|
271
|
+
value: decryptLegacyValue(encrypted),
|
|
272
|
+
usedLegacyKey: true,
|
|
273
|
+
needsSharedCiphertext: true,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Populate the shared column without touching encrypted_value. This is
|
|
280
|
+
* intentionally best-effort: a read must still succeed if a deployment's DB
|
|
281
|
+
* role cannot update the row. The NULL predicate prevents a concurrent writer
|
|
282
|
+
* from overwriting newly-populated shared ciphertext, and leaving updated_at
|
|
283
|
+
* untouched preserves the row's user-visible ordering/metadata.
|
|
284
|
+
*/
|
|
285
|
+
async function populateSharedAppSecret(
|
|
286
|
+
id: unknown,
|
|
287
|
+
value: string,
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
if (
|
|
290
|
+
id === undefined ||
|
|
291
|
+
id === null ||
|
|
292
|
+
!hasSharedSecretEncryptionKeyMaterial()
|
|
293
|
+
) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
await getDbExec().execute({
|
|
298
|
+
sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,
|
|
299
|
+
args: [encryptValue(value), id],
|
|
300
|
+
});
|
|
301
|
+
} catch {
|
|
302
|
+
// Migration is opportunistic. Preserve the successful read if the update
|
|
303
|
+
// is unavailable due to a read-only role, transient DB failure, or race.
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
204
307
|
type AppSecretsReadQuery = { sql: string; args: unknown[] };
|
|
205
308
|
|
|
206
309
|
/**
|
|
@@ -244,15 +347,22 @@ export async function readAppSecret(
|
|
|
244
347
|
): Promise<ReadSecretResult | null> {
|
|
245
348
|
const { key, scope, scopeId } = ref;
|
|
246
349
|
const { rows } = await executeAppSecretsRead({
|
|
247
|
-
sql: `SELECT encrypted_value, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,
|
|
350
|
+
sql: `SELECT encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,
|
|
248
351
|
args: [scope, scopeId, key],
|
|
249
352
|
});
|
|
250
353
|
if (rows.length === 0) return null;
|
|
251
354
|
try {
|
|
252
|
-
const
|
|
355
|
+
const encrypted = rows[0].encrypted_value as string;
|
|
356
|
+
const decrypted = decryptAppSecretValue(
|
|
357
|
+
encrypted,
|
|
358
|
+
rows[0].shared_encrypted_value as string | null,
|
|
359
|
+
);
|
|
360
|
+
if (decrypted.needsSharedCiphertext) {
|
|
361
|
+
await populateSharedAppSecret(rows[0].id, decrypted.value);
|
|
362
|
+
}
|
|
253
363
|
return {
|
|
254
|
-
value,
|
|
255
|
-
last4: last4(value),
|
|
364
|
+
value: decrypted.value,
|
|
365
|
+
last4: last4(decrypted.value),
|
|
256
366
|
updatedAt: Number(rows[0].updated_at ?? 0),
|
|
257
367
|
};
|
|
258
368
|
} catch {
|
|
@@ -273,7 +383,7 @@ export async function readAppSecrets(args: {
|
|
|
273
383
|
|
|
274
384
|
const placeholders = keys.map(() => "?").join(", ");
|
|
275
385
|
const { rows } = await executeAppSecretsRead({
|
|
276
|
-
sql: `SELECT key, encrypted_value, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? AND key IN (${placeholders})`,
|
|
386
|
+
sql: `SELECT key, encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key IN (${placeholders})`,
|
|
277
387
|
args: [args.scope, args.scopeId, ...keys],
|
|
278
388
|
});
|
|
279
389
|
const results = new Map<string, ReadSecretResult>();
|
|
@@ -281,10 +391,17 @@ export async function readAppSecrets(args: {
|
|
|
281
391
|
const key = String(row.key ?? "");
|
|
282
392
|
if (!key) continue;
|
|
283
393
|
try {
|
|
284
|
-
const
|
|
394
|
+
const encrypted = row.encrypted_value as string;
|
|
395
|
+
const decrypted = decryptAppSecretValue(
|
|
396
|
+
encrypted,
|
|
397
|
+
row.shared_encrypted_value as string | null,
|
|
398
|
+
);
|
|
399
|
+
if (decrypted.needsSharedCiphertext) {
|
|
400
|
+
await populateSharedAppSecret(row.id, decrypted.value);
|
|
401
|
+
}
|
|
285
402
|
results.set(key, {
|
|
286
|
-
value,
|
|
287
|
-
last4: last4(value),
|
|
403
|
+
value: decrypted.value,
|
|
404
|
+
last4: last4(decrypted.value),
|
|
288
405
|
updatedAt: Number(row.updated_at ?? 0),
|
|
289
406
|
});
|
|
290
407
|
} catch {
|
|
@@ -330,15 +447,22 @@ export async function readAppSecretMeta(
|
|
|
330
447
|
const { key, scope, scopeId } = ref;
|
|
331
448
|
const client = getDbExec();
|
|
332
449
|
const { rows } = await client.execute({
|
|
333
|
-
sql: `SELECT encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,
|
|
450
|
+
sql: `SELECT id, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,
|
|
334
451
|
args: [scope, scopeId, key],
|
|
335
452
|
});
|
|
336
453
|
if (rows.length === 0) return null;
|
|
337
454
|
const row = rows[0];
|
|
338
455
|
let last4Value = "";
|
|
339
456
|
try {
|
|
340
|
-
const
|
|
341
|
-
|
|
457
|
+
const encrypted = row.encrypted_value as string;
|
|
458
|
+
const decrypted = decryptAppSecretValue(
|
|
459
|
+
encrypted,
|
|
460
|
+
row.shared_encrypted_value as string | null,
|
|
461
|
+
);
|
|
462
|
+
if (decrypted.needsSharedCiphertext) {
|
|
463
|
+
await populateSharedAppSecret(row.id, decrypted.value);
|
|
464
|
+
}
|
|
465
|
+
last4Value = last4(decrypted.value);
|
|
342
466
|
} catch {
|
|
343
467
|
last4Value = "";
|
|
344
468
|
}
|
|
@@ -366,18 +490,26 @@ export async function listAppSecretsForScope(
|
|
|
366
490
|
await ensureTable();
|
|
367
491
|
const client = getDbExec();
|
|
368
492
|
const { rows } = await client.execute({
|
|
369
|
-
sql: `SELECT key, encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? ORDER BY updated_at DESC`,
|
|
493
|
+
sql: `SELECT id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? ORDER BY updated_at DESC`,
|
|
370
494
|
args: [scope, scopeId],
|
|
371
495
|
});
|
|
372
|
-
|
|
496
|
+
const results: SecretMeta[] = [];
|
|
497
|
+
for (const row of rows) {
|
|
373
498
|
let last4Value = "";
|
|
374
499
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
500
|
+
const encrypted = row.encrypted_value as string;
|
|
501
|
+
const decrypted = decryptAppSecretValue(
|
|
502
|
+
encrypted,
|
|
503
|
+
row.shared_encrypted_value as string | null,
|
|
504
|
+
);
|
|
505
|
+
if (decrypted.needsSharedCiphertext) {
|
|
506
|
+
await populateSharedAppSecret(row.id, decrypted.value);
|
|
507
|
+
}
|
|
508
|
+
last4Value = last4(decrypted.value);
|
|
377
509
|
} catch {
|
|
378
510
|
last4Value = "";
|
|
379
511
|
}
|
|
380
|
-
|
|
512
|
+
results.push({
|
|
381
513
|
key: row.key as string,
|
|
382
514
|
scope,
|
|
383
515
|
scopeId,
|
|
@@ -386,8 +518,9 @@ export async function listAppSecretsForScope(
|
|
|
386
518
|
urlAllowlist: parseAllowlist(row.url_allowlist as string | null),
|
|
387
519
|
createdAt: Number(row.created_at ?? 0),
|
|
388
520
|
updatedAt: Number(row.updated_at ?? 0),
|
|
389
|
-
};
|
|
390
|
-
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return results;
|
|
391
524
|
}
|
|
392
525
|
|
|
393
526
|
function parseAllowlist(raw: string | null): string[] | null {
|
|
@@ -67,6 +67,10 @@ export function Layout({ children }: LayoutProps) {
|
|
|
67
67
|
useAgentChatHomeHandoffLinks({
|
|
68
68
|
storageKey: ASSETS_CHAT_STORAGE_KEY,
|
|
69
69
|
isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
|
|
70
|
+
// A direct /chat/:id link already names the thread and must preserve the
|
|
71
|
+
// normal chat-to-sidebar transition. Only the empty home chat needs the
|
|
72
|
+
// recent marker that proves an agent handoff is in flight.
|
|
73
|
+
requireActiveHandoff: location.pathname === "/",
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isAgentChatHomeHandoffActive,
|
|
2
3
|
markAgentChatHomeHandoff,
|
|
3
4
|
useAgentRouteState,
|
|
4
5
|
getBrowserTabId,
|
|
@@ -190,7 +191,8 @@ export function useNavigationState() {
|
|
|
190
191
|
onNavigate: (_command, path) => {
|
|
191
192
|
if (
|
|
192
193
|
isCreatePath(location.pathname) &&
|
|
193
|
-
!isCreatePath(pathnameFromPath(path))
|
|
194
|
+
!isCreatePath(pathnameFromPath(path)) &&
|
|
195
|
+
isAgentChatHomeHandoffActive(ASSETS_CHAT_STORAGE_KEY)
|
|
194
196
|
) {
|
|
195
197
|
markAgentChatHomeHandoff(ASSETS_CHAT_STORAGE_KEY);
|
|
196
198
|
}
|
|
@@ -37,6 +37,15 @@ Detailed event, availability, booking, storage, and UI rules live in
|
|
|
37
37
|
with `stageAs` and analyze them with `query-staged-dataset`.
|
|
38
38
|
- For Google Calendar, distinguish an empty calendar from missing auth,
|
|
39
39
|
reauth-needed, or fetch failures.
|
|
40
|
+
- `list-events` remains the UI-compatible event list by default. External MCP
|
|
41
|
+
callers receive its compact, paginated version 1 inventory envelope
|
|
42
|
+
unless they explicitly request `format: "legacy"`; use `format: "inventory"`
|
|
43
|
+
for that same coverage-aware result from other callers. Preserve its
|
|
44
|
+
account coverage, `sourceCoverage`, and `coverageComplete` fields: Google
|
|
45
|
+
account, ICS feed, overlay, and local-booking sources are independent, and a
|
|
46
|
+
partial source failure is not an empty calendar. Pass `accountEmails` only
|
|
47
|
+
for connected accounts; the action validates the whole requested set before
|
|
48
|
+
provider work.
|
|
40
49
|
- Google Calendar working locations are status events (`eventType:
|
|
41
50
|
"workingLocation"`). Sync and display them as working locations, keep them
|
|
42
51
|
transparent/non-blocking, and preserve `workingLocationProperties` instead of
|