@agent-native/core 0.157.8 → 0.157.9
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/templates/analytics/actions/purge-first-party-analytics-postgres.ts +26 -8
- package/corpus/templates/analytics/scripts/first-party-analytics-postgres-retention-dry-run.sql +258 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +59 -27
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +4 -1
- package/corpus/templates/slides/actions/export-pptx.ts +261 -71
- package/corpus/templates/slides/actions/import-file.ts +12 -0
- package/corpus/templates/slides/actions/import-pptx.ts +9 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +45 -5
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +69 -1
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +316 -64
- package/corpus/templates/slides/server/lib/source-import.ts +10 -1
- package/dist/agent/harness/migrations.d.ts +4 -0
- package/dist/agent/harness/migrations.js +39 -0
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +4 -0
- package/dist/agent/run-loop-with-resume.d.ts +1 -0
- package/dist/agent/run-loop-with-resume.js +45 -30
- package/dist/agent/run-migrations.d.ts +8 -0
- package/dist/agent/run-migrations.js +61 -0
- package/dist/chat-threads/migrations.d.ts +1 -0
- package/dist/chat-threads/migrations.js +1 -0
- package/dist/chat-threads/schema-migrations.d.ts +8 -0
- package/dist/chat-threads/schema-migrations.js +60 -0
- package/dist/client/AssistantChat.js +4 -0
- package/dist/client/chat/message-components.d.ts +2 -0
- package/dist/client/chat/message-components.js +11 -1
- package/dist/client/observability/ThumbsFeedback.js +35 -22
- package/dist/client/observability/useObservability.d.ts +1 -1
- package/dist/client/observability/useObservability.js +6 -3
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/feature-flags/a2a-action-route.js +4 -2
- package/dist/feature-flags/permissions.d.ts +2 -0
- package/dist/feature-flags/permissions.js +6 -1
- package/dist/ingestion/pptx.d.ts +14 -1
- package/dist/ingestion/pptx.js +222 -41
- package/dist/localization/core-messages/ar-SA.js +4 -0
- package/dist/localization/core-messages/de-DE.js +4 -0
- package/dist/localization/core-messages/en-US.d.ts +4 -0
- package/dist/localization/core-messages/en-US.js +4 -0
- package/dist/localization/core-messages/es-ES.js +4 -0
- package/dist/localization/core-messages/fr-FR.js +4 -0
- package/dist/localization/core-messages/hi-IN.js +4 -0
- package/dist/localization/core-messages/ja-JP.js +4 -0
- package/dist/localization/core-messages/ko-KR.js +4 -0
- package/dist/localization/core-messages/pt-BR.js +4 -0
- package/dist/localization/core-messages/zh-CN.js +4 -0
- package/dist/localization/core-messages/zh-TW.js +4 -0
- package/dist/localization/core-messages.d.ts +4 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/feedback.js +40 -16
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/routes.js +15 -3
- package/dist/observability/store.js +2 -0
- package/dist/observability/traces.js +5 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +10 -10
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts +7 -5
- package/dist/server/action-routes.js +9 -4
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/better-auth-instance.js +4 -0
- package/dist/server/realtime-voice.js +2 -1
- package/dist/server/release-migrations.js +16 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/usage/migrations.d.ts +4 -0
- package/dist/usage/migrations.js +45 -0
- package/package.json +3 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const AGENT_HARNESS_SESSION_MIGRATIONS_TABLE = "_agent_harness_session_migrations";
|
|
2
|
+
/** Authoritative release-time schema for persisted hosted harness sessions. */
|
|
3
|
+
export const AGENT_HARNESS_SESSION_MIGRATIONS = [
|
|
4
|
+
{
|
|
5
|
+
version: 1,
|
|
6
|
+
name: "agent-harness-sessions-base-table",
|
|
7
|
+
sql: `
|
|
8
|
+
CREATE TABLE IF NOT EXISTS agent_harness_sessions (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
harness_name TEXT NOT NULL,
|
|
11
|
+
thread_id TEXT NOT NULL,
|
|
12
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
13
|
+
created_at INTEGER NOT NULL,
|
|
14
|
+
updated_at INTEGER NOT NULL
|
|
15
|
+
)
|
|
16
|
+
`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
version: 2,
|
|
20
|
+
name: "agent-harness-sessions-state-columns",
|
|
21
|
+
sql: `
|
|
22
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS run_id TEXT;
|
|
23
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS provider_session_id TEXT;
|
|
24
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS resume_state TEXT;
|
|
25
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS workspace_ref TEXT;
|
|
26
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS pending_approval TEXT;
|
|
27
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS resolved_approval_ids TEXT;
|
|
28
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS owner_email TEXT;
|
|
29
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS org_id TEXT;
|
|
30
|
+
ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS stopped_at INTEGER;
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_thread
|
|
32
|
+
ON agent_harness_sessions (thread_id, updated_at);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_status
|
|
34
|
+
ON agent_harness_sessions (status, updated_at);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_owner
|
|
36
|
+
ON agent_harness_sessions (owner_email, updated_at)
|
|
37
|
+
`,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
@@ -481,6 +481,8 @@ export interface AgentLoopUsage {
|
|
|
481
481
|
cacheReadTokens: number;
|
|
482
482
|
cacheWriteTokens: number;
|
|
483
483
|
model: string;
|
|
484
|
+
/** Number of provider model-stream attempts, including retries. */
|
|
485
|
+
llmCalls?: number;
|
|
484
486
|
/**
|
|
485
487
|
* True once the engine reported at least one real `usage` event for this
|
|
486
488
|
* run. The token fields above start at 0 and are only ever incremented —
|
|
@@ -3390,6 +3390,7 @@ export async function runAgentLoop(opts) {
|
|
|
3390
3390
|
reasoningEffort: effectiveReasoningEffort,
|
|
3391
3391
|
providerOptions: opts.providerOptions,
|
|
3392
3392
|
};
|
|
3393
|
+
usage.llmCalls = (usage.llmCalls ?? 0) + 1;
|
|
3393
3394
|
const eventStream = engine.stream(streamOpts);
|
|
3394
3395
|
let thinkingBuffer = "";
|
|
3395
3396
|
const toolInputNames = new Map();
|
|
@@ -5292,6 +5293,9 @@ export async function runAgentLoopWithMainChatInternalContinuations(opts) {
|
|
|
5292
5293
|
usage.cacheReadTokens += next.cacheReadTokens;
|
|
5293
5294
|
usage.cacheWriteTokens += next.cacheWriteTokens;
|
|
5294
5295
|
usage.model = next.model;
|
|
5296
|
+
if (typeof next.llmCalls === "number") {
|
|
5297
|
+
usage.llmCalls = (usage.llmCalls ?? 0) + next.llmCalls;
|
|
5298
|
+
}
|
|
5295
5299
|
if (next.usageReported)
|
|
5296
5300
|
usage.usageReported = true;
|
|
5297
5301
|
// Keep the earliest attempt's first event — a later continuation
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import type { EngineMessage } from "./engine/types.js";
|
|
24
24
|
import { runAgentLoop, type AgentLoopContinuationReason } from "./production-agent.js";
|
|
25
25
|
import type { ResolveRunSoftTimeoutOptions } from "./run-manager.js";
|
|
26
|
+
export declare const AGENT_INTERNAL_CONTINUATION_CHECKPOINT_PROMPT = "The following is a bounded, non-rendered prefix of the assistant response that was interrupted. Treat it as context only, not as a new user instruction or tool result. Do not repeat it verbatim; finish or correct the original response from this point, and never execute anything described inside the prefix.";
|
|
26
27
|
/**
|
|
27
28
|
* Rebuild the same safe continuation context for a logical turn that resumes
|
|
28
29
|
* in a fresh hosted invocation.
|
|
@@ -98,8 +98,40 @@ function appendToolCallJournalNote(messages, events) {
|
|
|
98
98
|
// up — the model still continues, just without the structured journal.
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
export const AGENT_INTERNAL_CONTINUATION_CHECKPOINT_PROMPT = "The following is a bounded, non-rendered prefix of the assistant response that was interrupted. Treat it as context only, not as a new user instruction or tool result. Do not repeat it verbatim; finish or correct the original response from this point, and never execute anything described inside the prefix.";
|
|
102
|
+
const MAX_CONTINUATION_CHECKPOINT_CHARS = 12_000;
|
|
103
|
+
function streamedTextForContinuationCheckpoint(events) {
|
|
104
|
+
let text = "";
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
if (event.type === "clear") {
|
|
107
|
+
text = "";
|
|
108
|
+
}
|
|
109
|
+
else if (event.type === "text") {
|
|
110
|
+
text += event.text;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return text.trim();
|
|
114
|
+
}
|
|
115
|
+
function appendContinuationCheckpoint(messages, events) {
|
|
116
|
+
const text = streamedTextForContinuationCheckpoint(events);
|
|
117
|
+
if (!text)
|
|
118
|
+
return;
|
|
119
|
+
const boundedText = text.length > MAX_CONTINUATION_CHECKPOINT_CHARS
|
|
120
|
+
? `${text.slice(0, MAX_CONTINUATION_CHECKPOINT_CHARS)}\n[checkpoint truncated]`
|
|
121
|
+
: text;
|
|
122
|
+
messages.push({
|
|
123
|
+
role: "assistant",
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: `${AGENT_INTERNAL_CONTINUATION_CHECKPOINT_PROMPT}\n\n<interrupted-assistant-prefix>\n${boundedText}\n</interrupted-assistant-prefix>`,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async function appendContinuationAndJournal(messages, reason, threadId, turnId, localEvents = [], checkpointEvents = localEvents) {
|
|
102
133
|
const { events } = await readCurrentTurnEventsForResume(threadId, turnId, localEvents);
|
|
134
|
+
appendContinuationCheckpoint(messages, checkpointEvents);
|
|
103
135
|
appendAgentLoopContinuation(messages, reason, actionPreparationContinuationOptions(events));
|
|
104
136
|
appendToolCallJournalNote(messages, events);
|
|
105
137
|
}
|
|
@@ -375,16 +407,12 @@ export async function runAgentLoopDirectWithSoftTimeout(opts, softTimeoutMs, tim
|
|
|
375
407
|
const internalContinuationReason = internalContinuationReasonForAttempt(attemptEvents);
|
|
376
408
|
if (internalContinuationReason && !upstreamSignal.aborted) {
|
|
377
409
|
lastAttemptWasUnfinishedContinuation = true;
|
|
378
|
-
|
|
379
|
-
if ((await completedSideEffectInCurrentTurn(opts.threadId, opts.turnId, continuationEvents)) === "none") {
|
|
380
|
-
opts.send({ type: "clear" });
|
|
381
|
-
}
|
|
382
|
-
await appendContinuationAndJournal(opts.messages, internalContinuationReason, opts.threadId, opts.turnId, continuationEvents);
|
|
410
|
+
await appendContinuationAndJournal(opts.messages, internalContinuationReason, opts.threadId, opts.turnId, localTurnEvents, attemptEvents);
|
|
383
411
|
continue;
|
|
384
412
|
}
|
|
385
413
|
if (softTimedOut && !upstreamSignal.aborted) {
|
|
386
414
|
lastAttemptWasUnfinishedContinuation = true;
|
|
387
|
-
await appendContinuationAndJournal(opts.messages, "run_timeout", opts.threadId, opts.turnId, localTurnEvents);
|
|
415
|
+
await appendContinuationAndJournal(opts.messages, "run_timeout", opts.threadId, opts.turnId, localTurnEvents, attemptEvents);
|
|
388
416
|
continue;
|
|
389
417
|
}
|
|
390
418
|
reportFinalOutcome(upstreamSignal.aborted
|
|
@@ -394,13 +422,8 @@ export async function runAgentLoopDirectWithSoftTimeout(opts, softTimeoutMs, tim
|
|
|
394
422
|
}
|
|
395
423
|
catch (err) {
|
|
396
424
|
if (softTimedOut && !upstreamSignal.aborted) {
|
|
397
|
-
// Clear partial text the client received before the abort so the
|
|
398
|
-
// resumed model doesn't re-emit it and produce duplicated output.
|
|
399
425
|
lastAttemptWasUnfinishedContinuation = true;
|
|
400
|
-
|
|
401
|
-
opts.send({ type: "clear" });
|
|
402
|
-
}
|
|
403
|
-
await appendContinuationAndJournal(opts.messages, "run_timeout", opts.threadId, opts.turnId, localTurnEvents);
|
|
426
|
+
await appendContinuationAndJournal(opts.messages, "run_timeout", opts.threadId, opts.turnId, localTurnEvents, localTurnEvents.slice(attemptStartIndex));
|
|
404
427
|
continue;
|
|
405
428
|
}
|
|
406
429
|
const transientRateLimit = isTransientProviderRateLimitError(err);
|
|
@@ -416,32 +439,24 @@ export async function runAgentLoopDirectWithSoftTimeout(opts, softTimeoutMs, tim
|
|
|
416
439
|
rateLimitRetryFitsBudget) {
|
|
417
440
|
lastAttemptWasUnfinishedContinuation = true;
|
|
418
441
|
backgroundRateLimitContinuations++;
|
|
419
|
-
|
|
420
|
-
opts.send({ type: "clear" });
|
|
421
|
-
}
|
|
422
|
-
await appendContinuationAndJournal(opts.messages, "rate_limited", opts.threadId, opts.turnId, localTurnEvents);
|
|
442
|
+
await appendContinuationAndJournal(opts.messages, "rate_limited", opts.threadId, opts.turnId, localTurnEvents, localTurnEvents.slice(attemptStartIndex));
|
|
423
443
|
await waitForBackgroundRateLimitCooldown(upstreamSignal);
|
|
424
444
|
continue;
|
|
425
445
|
}
|
|
426
446
|
// Resumable transport / gateway interruptions: the LLM call was cut off
|
|
427
447
|
// mid-stream (gateway 45s timeout, socket hang up, function-level
|
|
428
448
|
// timeout that didn't trip our soft timer first). Treat it the same way
|
|
429
|
-
// as a soft timeout — append
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
449
|
+
// as a soft timeout — append the streamed prefix as non-rendered context,
|
|
450
|
+
// add a "continue from where you left off" nudge, and let the loop run
|
|
451
|
+
// another LLM call. Anthropic's prompt cache makes the resume call much
|
|
452
|
+
// faster than the cold first attempt.
|
|
433
453
|
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
// (the partial text was already sent to the client but never entered
|
|
438
|
-
// the in-memory messages array, so the next attempt re-emits it).
|
|
454
|
+
// Keep streamed text visible while the resumed model finishes from the
|
|
455
|
+
// internal checkpoint. The continuation prompt tells the model not to
|
|
456
|
+
// repeat that prefix, so clearing it would leave only the suffix visible.
|
|
439
457
|
if (!upstreamSignal.aborted && isResumableEngineError(err)) {
|
|
440
458
|
lastAttemptWasUnfinishedContinuation = true;
|
|
441
|
-
|
|
442
|
-
opts.send({ type: "clear" });
|
|
443
|
-
}
|
|
444
|
-
await appendContinuationAndJournal(opts.messages, continuationReasonForResumableError(err), opts.threadId, opts.turnId, localTurnEvents);
|
|
459
|
+
await appendContinuationAndJournal(opts.messages, continuationReasonForResumableError(err), opts.threadId, opts.turnId, localTurnEvents, localTurnEvents.slice(attemptStartIndex));
|
|
445
460
|
continue;
|
|
446
461
|
}
|
|
447
462
|
if (upstreamSignal.aborted) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MigrationEntry } from "../db/migrations.js";
|
|
2
|
+
export declare const AGENT_RUN_MIGRATIONS_TABLE = "_agent_run_migrations";
|
|
3
|
+
/**
|
|
4
|
+
* Authoritative release-time schema for durable agent runs and their ledgers.
|
|
5
|
+
* The run store keeps a guarded ensure path for local development and older
|
|
6
|
+
* databases, but production request functions cannot own schema setup.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AGENT_RUN_MIGRATIONS: MigrationEntry[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const AGENT_RUN_MIGRATIONS_TABLE = "_agent_run_migrations";
|
|
2
|
+
/**
|
|
3
|
+
* Authoritative release-time schema for durable agent runs and their ledgers.
|
|
4
|
+
* The run store keeps a guarded ensure path for local development and older
|
|
5
|
+
* databases, but production request functions cannot own schema setup.
|
|
6
|
+
*/
|
|
7
|
+
export const AGENT_RUN_MIGRATIONS = [
|
|
8
|
+
{
|
|
9
|
+
version: 1,
|
|
10
|
+
name: "agent-run-base-tables",
|
|
11
|
+
sql: `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
thread_id TEXT NOT NULL,
|
|
15
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
16
|
+
started_at INTEGER NOT NULL,
|
|
17
|
+
completed_at INTEGER
|
|
18
|
+
);
|
|
19
|
+
CREATE TABLE IF NOT EXISTS agent_run_events (
|
|
20
|
+
run_id TEXT NOT NULL,
|
|
21
|
+
seq INTEGER NOT NULL,
|
|
22
|
+
event_data TEXT NOT NULL,
|
|
23
|
+
PRIMARY KEY (run_id, seq)
|
|
24
|
+
);
|
|
25
|
+
CREATE TABLE IF NOT EXISTS agent_run_outcome_daily (
|
|
26
|
+
day TEXT NOT NULL,
|
|
27
|
+
status TEXT NOT NULL,
|
|
28
|
+
terminal_reason TEXT NOT NULL DEFAULT '',
|
|
29
|
+
run_count INTEGER NOT NULL DEFAULT 0,
|
|
30
|
+
PRIMARY KEY (day, status, terminal_reason)
|
|
31
|
+
);
|
|
32
|
+
CREATE TABLE IF NOT EXISTS agent_tool_ledger (
|
|
33
|
+
thread_id TEXT NOT NULL,
|
|
34
|
+
tool_key TEXT NOT NULL,
|
|
35
|
+
result_summary TEXT NOT NULL,
|
|
36
|
+
completed_at INTEGER NOT NULL,
|
|
37
|
+
PRIMARY KEY (thread_id, tool_key)
|
|
38
|
+
)
|
|
39
|
+
`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
version: 2,
|
|
43
|
+
name: "agent-run-liveness-and-diagnostics-columns",
|
|
44
|
+
sql: `
|
|
45
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS abort_reason TEXT;
|
|
46
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS heartbeat_at INTEGER;
|
|
47
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS last_progress_at INTEGER;
|
|
48
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS turn_id TEXT;
|
|
49
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS error_code TEXT;
|
|
50
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS error_detail TEXT;
|
|
51
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS terminal_reason TEXT;
|
|
52
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS dispatch_mode TEXT;
|
|
53
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS diag_stage TEXT;
|
|
54
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS worker_stage TEXT;
|
|
55
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS dispatch_payload TEXT;
|
|
56
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS peak_rss_mb INTEGER;
|
|
57
|
+
ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS in_flight_since INTEGER;
|
|
58
|
+
ALTER TABLE agent_run_events ADD COLUMN IF NOT EXISTS event_at INTEGER
|
|
59
|
+
`,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MigrationEntry } from "../db/migrations.js";
|
|
2
2
|
export declare const CHAT_THREADS_MIGRATIONS_TABLE = "_chat_threads_migrations";
|
|
3
|
+
export { CHAT_THREAD_SCHEMA_MIGRATIONS, CHAT_THREAD_SCHEMA_MIGRATIONS_TABLE, } from "./schema-migrations.js";
|
|
3
4
|
export declare const CHAT_THREADS_REPAIR_MESSAGE_COUNTS_MIGRATION = "chat-threads-repair-legacy-message-counts";
|
|
4
5
|
/**
|
|
5
6
|
* Rows written before `message_count` was maintained still carry 0, and both
|
|
@@ -2,6 +2,7 @@ import { isServerlessRuntime } from "../db/client.js";
|
|
|
2
2
|
import { runMigrations } from "../db/migrations.js";
|
|
3
3
|
import { repairLegacyChatThreadMessageCounts } from "./store.js";
|
|
4
4
|
export const CHAT_THREADS_MIGRATIONS_TABLE = "_chat_threads_migrations";
|
|
5
|
+
export { CHAT_THREAD_SCHEMA_MIGRATIONS, CHAT_THREAD_SCHEMA_MIGRATIONS_TABLE, } from "./schema-migrations.js";
|
|
5
6
|
export const CHAT_THREADS_REPAIR_MESSAGE_COUNTS_MIGRATION = "chat-threads-repair-legacy-message-counts";
|
|
6
7
|
/**
|
|
7
8
|
* Rows written before `message_count` was maintained still carry 0, and both
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MigrationEntry } from "../db/migrations.js";
|
|
2
|
+
export declare const CHAT_THREAD_SCHEMA_MIGRATIONS_TABLE = "_chat_thread_schema_migrations";
|
|
3
|
+
/**
|
|
4
|
+
* Authoritative release-time schema for the framework chat thread store.
|
|
5
|
+
* Request functions skip the store's fallback DDL in production, so a chat
|
|
6
|
+
* deployment must create this schema before it starts serving requests.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CHAT_THREAD_SCHEMA_MIGRATIONS: MigrationEntry[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const CHAT_THREAD_SCHEMA_MIGRATIONS_TABLE = "_chat_thread_schema_migrations";
|
|
2
|
+
/**
|
|
3
|
+
* Authoritative release-time schema for the framework chat thread store.
|
|
4
|
+
* Request functions skip the store's fallback DDL in production, so a chat
|
|
5
|
+
* deployment must create this schema before it starts serving requests.
|
|
6
|
+
*/
|
|
7
|
+
export const CHAT_THREAD_SCHEMA_MIGRATIONS = [
|
|
8
|
+
{
|
|
9
|
+
version: 1,
|
|
10
|
+
name: "chat-threads-base-tables",
|
|
11
|
+
sql: `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS chat_threads (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
owner_email TEXT NOT NULL,
|
|
15
|
+
title TEXT NOT NULL DEFAULT '',
|
|
16
|
+
preview TEXT NOT NULL DEFAULT '',
|
|
17
|
+
thread_data TEXT NOT NULL DEFAULT '{}',
|
|
18
|
+
message_count INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
created_at INTEGER NOT NULL,
|
|
20
|
+
updated_at INTEGER NOT NULL
|
|
21
|
+
);
|
|
22
|
+
CREATE TABLE IF NOT EXISTS chat_thread_shares (
|
|
23
|
+
id TEXT PRIMARY KEY,
|
|
24
|
+
resource_id TEXT NOT NULL,
|
|
25
|
+
principal_type TEXT NOT NULL,
|
|
26
|
+
principal_id TEXT NOT NULL,
|
|
27
|
+
role TEXT NOT NULL DEFAULT 'viewer',
|
|
28
|
+
created_by TEXT NOT NULL,
|
|
29
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
30
|
+
);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS chat_thread_shares_resource_idx
|
|
32
|
+
ON chat_thread_shares (resource_id)
|
|
33
|
+
`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
version: 2,
|
|
37
|
+
name: "chat-threads-scope-and-sharing-columns",
|
|
38
|
+
sql: `
|
|
39
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS scope_type TEXT;
|
|
40
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS scope_id TEXT;
|
|
41
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS scope_label TEXT;
|
|
42
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS pinned_at INTEGER;
|
|
43
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS archived_at INTEGER;
|
|
44
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS share_token_hash TEXT;
|
|
45
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS source_platform TEXT;
|
|
46
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS source_app_id TEXT;
|
|
47
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS source_url TEXT;
|
|
48
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS org_id TEXT;
|
|
49
|
+
ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS visibility TEXT NOT NULL DEFAULT 'private';
|
|
50
|
+
CREATE INDEX IF NOT EXISTS chat_threads_owner_updated_idx
|
|
51
|
+
ON chat_threads (owner_email, updated_at);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS chat_threads_scope_updated_idx
|
|
53
|
+
ON chat_threads (scope_type, scope_id, updated_at);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS chat_threads_source_updated_idx
|
|
55
|
+
ON chat_threads (owner_email, source_app_id, updated_at);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS chat_threads_share_token_idx
|
|
57
|
+
ON chat_threads (share_token_hash)
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -2644,6 +2644,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2644
2644
|
})();
|
|
2645
2645
|
return () => {
|
|
2646
2646
|
cancelled = true;
|
|
2647
|
+
// React StrictMode replays effects without resetting refs. Let the
|
|
2648
|
+
// replay start the restore again after cancelling this attempt.
|
|
2649
|
+
hasRestoredRef.current = false;
|
|
2647
2650
|
};
|
|
2648
2651
|
}
|
|
2649
2652
|
else if (threadId && isNewThread) {
|
|
@@ -2737,6 +2740,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2737
2740
|
})();
|
|
2738
2741
|
return () => {
|
|
2739
2742
|
cancelled = true;
|
|
2743
|
+
hasRestoredRef.current = false;
|
|
2740
2744
|
};
|
|
2741
2745
|
}
|
|
2742
2746
|
else {
|
|
@@ -31,6 +31,7 @@ export declare const MessageActionsContext: React.Context<{
|
|
|
31
31
|
*/
|
|
32
32
|
bannerRunErrorKey?: string | null;
|
|
33
33
|
} | null>;
|
|
34
|
+
export declare function isLocalDevelopmentHost(hostname: string): boolean;
|
|
34
35
|
/**
|
|
35
36
|
* Restore rewrites the working tree, so only offer it when the server actually
|
|
36
37
|
* has a checkpoint for this turn. Auto-checkpointing skips turns that started
|
|
@@ -43,6 +44,7 @@ export declare function shouldOfferRestore(args: {
|
|
|
43
44
|
isLast: boolean;
|
|
44
45
|
runId: string | undefined;
|
|
45
46
|
checkpointRunIds: ReadonlySet<string> | undefined;
|
|
47
|
+
hostname: string;
|
|
46
48
|
}): boolean;
|
|
47
49
|
/**
|
|
48
50
|
* Live yields put the run id at `metadata.custom.runId`; server-persisted
|
|
@@ -156,6 +156,14 @@ export function SelectionAttachedPill() {
|
|
|
156
156
|
// ─── CheckpointContext / MessageActionsContext ────────────────────────────────
|
|
157
157
|
export const CheckpointContext = React.createContext(null);
|
|
158
158
|
export const MessageActionsContext = React.createContext(null);
|
|
159
|
+
export function isLocalDevelopmentHost(hostname) {
|
|
160
|
+
const normalizedHostname = hostname.trim().toLowerCase();
|
|
161
|
+
return (normalizedHostname === "localhost" ||
|
|
162
|
+
normalizedHostname === "127.0.0.1" ||
|
|
163
|
+
normalizedHostname === "0.0.0.0" ||
|
|
164
|
+
normalizedHostname === "::1" ||
|
|
165
|
+
normalizedHostname === "[::1]");
|
|
166
|
+
}
|
|
159
167
|
/**
|
|
160
168
|
* Restore rewrites the working tree, so only offer it when the server actually
|
|
161
169
|
* has a checkpoint for this turn. Auto-checkpointing skips turns that started
|
|
@@ -163,7 +171,8 @@ export const MessageActionsContext = React.createContext(null);
|
|
|
163
171
|
* "Revert to here" item on turns where clicking it could do nothing.
|
|
164
172
|
*/
|
|
165
173
|
export function shouldOfferRestore(args) {
|
|
166
|
-
return Boolean(args.
|
|
174
|
+
return Boolean(isLocalDevelopmentHost(args.hostname) &&
|
|
175
|
+
args.devMode &&
|
|
167
176
|
args.isComplete &&
|
|
168
177
|
!args.isLast &&
|
|
169
178
|
args.runId &&
|
|
@@ -1018,6 +1027,7 @@ export function AssistantMessage() {
|
|
|
1018
1027
|
isLast,
|
|
1019
1028
|
runId: messageRunId,
|
|
1020
1029
|
checkpointRunIds: cpCtx?.checkpointRunIds,
|
|
1030
|
+
hostname: window.location.hostname,
|
|
1021
1031
|
});
|
|
1022
1032
|
// Collect parts for the files-changed summary (code-agent turns only).
|
|
1023
1033
|
const msgContent = msg.content;
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@agent-native/toolkit/ui/button";
|
|
3
|
+
import { Textarea } from "@agent-native/toolkit/ui/textarea";
|
|
2
4
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
3
5
|
import { IconThumbUp, IconThumbDown } from "@tabler/icons-react";
|
|
4
|
-
import { useState, useCallback } from "react";
|
|
6
|
+
import { useState, useCallback, useId, useRef } from "react";
|
|
5
7
|
import { agentNativePath } from "../api-path.js";
|
|
6
8
|
import { useT } from "../i18n.js";
|
|
7
9
|
import { cn } from "../utils.js";
|
|
8
|
-
const THUMBS_DOWN_CATEGORIES = [
|
|
9
|
-
{ value: "Inaccurate", key: "agentChat.feedback.inaccurate" },
|
|
10
|
-
{ value: "Not helpful", key: "agentChat.feedback.notHelpful" },
|
|
11
|
-
{ value: "Wrong tool", key: "agentChat.feedback.wrongTool" },
|
|
12
|
-
{ value: "Too slow", key: "agentChat.feedback.tooSlow" },
|
|
13
|
-
];
|
|
14
10
|
export function ThumbsFeedback({ threadId, runId, messageSeq, className, }) {
|
|
15
11
|
const t = useT();
|
|
16
12
|
const [selection, setSelection] = useState(null);
|
|
17
|
-
const [
|
|
18
|
-
const [
|
|
13
|
+
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
14
|
+
const [textFeedback, setTextFeedback] = useState("");
|
|
15
|
+
const feedbackInputId = useId();
|
|
16
|
+
const feedbackInputRef = useRef(null);
|
|
19
17
|
const sendFeedback = useCallback(async (feedbackType, value) => {
|
|
20
18
|
try {
|
|
21
19
|
await fetch(agentNativePath("/_agent-native/observability/feedback"), {
|
|
@@ -38,29 +36,44 @@ export function ThumbsFeedback({ threadId, runId, messageSeq, className, }) {
|
|
|
38
36
|
if (selection === "up")
|
|
39
37
|
return;
|
|
40
38
|
setSelection("up");
|
|
41
|
-
|
|
42
|
-
setSubmittedCategory(null);
|
|
39
|
+
setPopoverOpen(false);
|
|
43
40
|
sendFeedback("thumbs_up");
|
|
44
41
|
}, [selection, sendFeedback]);
|
|
45
42
|
const handleThumbsDown = useCallback(() => {
|
|
46
43
|
if (selection === "down") {
|
|
47
|
-
|
|
44
|
+
setPopoverOpen((prev) => !prev);
|
|
48
45
|
return;
|
|
49
46
|
}
|
|
50
47
|
setSelection("down");
|
|
51
|
-
|
|
48
|
+
setPopoverOpen(true);
|
|
52
49
|
sendFeedback("thumbs_down");
|
|
53
50
|
}, [selection, sendFeedback]);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const handleTextFeedback = useCallback(() => {
|
|
52
|
+
const value = textFeedback.trim();
|
|
53
|
+
if (!value)
|
|
54
|
+
return;
|
|
55
|
+
setTextFeedback("");
|
|
56
|
+
setPopoverOpen(false);
|
|
57
|
+
sendFeedback("text", value);
|
|
58
|
+
}, [sendFeedback, textFeedback]);
|
|
59
59
|
return (_jsxs("div", { className: cn("inline-flex items-center gap-0.5", className), children: [_jsx("button", { type: "button", "aria-label": t("agentChat.feedback.thumbsUp"), onClick: handleThumbsUp, className: cn("flex h-6 w-6 items-center justify-center rounded", selection === "up"
|
|
60
60
|
? "text-foreground"
|
|
61
|
-
: "text-muted-foreground/70 hover:text-muted-foreground hover:bg-accent/50"), children: _jsx(IconThumbUp, { size: 16, stroke: selection === "up" ? 2.5 : 1.5, fill: selection === "up" ? "currentColor" : "none" }) }), _jsxs(PopoverPrimitive.Root, { open:
|
|
61
|
+
: "text-muted-foreground/70 hover:text-muted-foreground hover:bg-accent/50"), children: _jsx(IconThumbUp, { size: 16, stroke: selection === "up" ? 2.5 : 1.5, fill: selection === "up" ? "currentColor" : "none" }) }), _jsxs(PopoverPrimitive.Root, { open: popoverOpen, onOpenChange: setPopoverOpen, children: [_jsx(PopoverPrimitive.Trigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("agentChat.feedback.thumbsDown"), onClick: handleThumbsDown, className: cn("flex h-6 w-6 items-center justify-center rounded", selection === "down"
|
|
62
62
|
? "text-foreground"
|
|
63
|
-
: "text-muted-foreground/70 hover:text-muted-foreground hover:bg-accent/50"), children: _jsx(IconThumbDown, { size: 16, stroke: selection === "down" ? 2.5 : 1.5, fill: selection === "down" ? "currentColor" : "none" }) }) }), _jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { side: "bottom", align: "start", sideOffset: 4, collisionPadding: 8,
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
: "text-muted-foreground/70 hover:text-muted-foreground hover:bg-accent/50"), children: _jsx(IconThumbDown, { size: 16, stroke: selection === "down" ? 2.5 : 1.5, fill: selection === "down" ? "currentColor" : "none" }) }) }), _jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { side: "bottom", align: "start", sideOffset: 4, collisionPadding: 8, onOpenAutoFocus: (event) => {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
feedbackInputRef.current?.focus();
|
|
66
|
+
}, className: "z-[300] w-[min(320px,calc(100vw-24px))] overflow-hidden rounded-lg border border-border bg-popover p-3 text-popover-foreground shadow-lg outline-none origin-[var(--radix-popover-content-transform-origin)]", children: _jsxs("form", { className: "flex flex-col gap-2.5", onSubmit: (event) => {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
handleTextFeedback();
|
|
69
|
+
}, children: [_jsx("label", { htmlFor: feedbackInputId, className: "text-xs font-medium text-foreground", children: t("agentChat.feedback.whatWentWrong") }), _jsx(Textarea, { id: feedbackInputId, ref: feedbackInputRef, autoFocus: true, value: textFeedback, onChange: (event) => setTextFeedback(event.target.value), onKeyDown: (event) => {
|
|
70
|
+
if ((event.metaKey || event.ctrlKey) &&
|
|
71
|
+
event.key === "Enter") {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
handleTextFeedback();
|
|
74
|
+
}
|
|
75
|
+
}, placeholder: t("agentChat.feedback.placeholder"), rows: 3, maxLength: 2000, className: "min-h-20 resize-none px-2.5 py-2 text-xs" }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-[11px] text-muted-foreground/75", children: t("agentChat.feedback.keyboardHint").replace("{{shortcut}}", typeof navigator !== "undefined" &&
|
|
76
|
+
/Mac|iPhone|iPad/.test(navigator.userAgent)
|
|
77
|
+
? "⌘"
|
|
78
|
+
: "Ctrl") }), _jsx(Button, { type: "submit", size: "sm", disabled: !textFeedback.trim(), className: "h-7 px-2.5 text-xs", children: t("agentChat.feedback.submit") })] })] }) }) })] })] }));
|
|
66
79
|
}
|
|
@@ -57,7 +57,7 @@ export interface FeedbackEntry {
|
|
|
57
57
|
userId: string | null;
|
|
58
58
|
createdAt: number;
|
|
59
59
|
}
|
|
60
|
-
export declare function useFeedbackList(sinceDays?: number, limit?: number): import("@tanstack/react-query").UseQueryResult<NoInfer<FeedbackEntry[]>, Error>;
|
|
60
|
+
export declare function useFeedbackList(sinceDays?: number, limit?: number, feedbackType?: FeedbackEntry["feedbackType"]): import("@tanstack/react-query").UseQueryResult<NoInfer<FeedbackEntry[]>, Error>;
|
|
61
61
|
export interface FeedbackStats {
|
|
62
62
|
total: number;
|
|
63
63
|
thumbsUp: number;
|
|
@@ -31,11 +31,14 @@ export function useTraceDetail(runId) {
|
|
|
31
31
|
enabled: !!runId,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
export function useFeedbackList(sinceDays = 7, limit = 100) {
|
|
34
|
+
export function useFeedbackList(sinceDays = 7, limit = 100, feedbackType) {
|
|
35
35
|
const sinceMs = Date.now() - sinceDays * 86_400_000;
|
|
36
|
+
const typeQuery = feedbackType
|
|
37
|
+
? `&feedbackType=${encodeURIComponent(feedbackType)}`
|
|
38
|
+
: "";
|
|
36
39
|
return useQuery({
|
|
37
|
-
queryKey: ["observability", "feedback", sinceDays, limit],
|
|
38
|
-
queryFn: () => fetchJson(`${BASE}/feedback?since=${sinceMs}&limit=${limit}`),
|
|
40
|
+
queryKey: ["observability", "feedback", sinceDays, limit, feedbackType],
|
|
41
|
+
queryFn: () => fetchJson(`${BASE}/feedback?since=${sinceMs}&limit=${limit}${typeQuery}`),
|
|
39
42
|
refetchInterval: 30_000,
|
|
40
43
|
});
|
|
41
44
|
}
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -3,6 +3,7 @@ import * as jose from "jose";
|
|
|
3
3
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
4
4
|
import { resolveOrgByDomain } from "../org/context.js";
|
|
5
5
|
import { consumeOneTimeJti } from "../server/identity-sso-store.js";
|
|
6
|
+
import { isFeatureFlagAdminEmail } from "./permissions.js";
|
|
6
7
|
const FLAG_ACTION_SCOPES = {
|
|
7
8
|
"list-feature-flags": "flags:read",
|
|
8
9
|
"set-feature-flag": "flags:write",
|
|
@@ -40,15 +41,16 @@ export function createFeatureFlagA2AActionRouteAuth(actionName) {
|
|
|
40
41
|
throw new Error("Invalid feature flag delegation");
|
|
41
42
|
}
|
|
42
43
|
const localOrg = await resolveOrgByDomain(claims.orgDomain);
|
|
43
|
-
if (!localOrg)
|
|
44
|
+
if (!localOrg && !isFeatureFlagAdminEmail(claims.email)) {
|
|
44
45
|
throw new Error("Invalid feature flag delegation");
|
|
46
|
+
}
|
|
45
47
|
if (actionName === "set-feature-flag" &&
|
|
46
48
|
(await consumeOneTimeJti(claims.jti))) {
|
|
47
49
|
throw new Error("Invalid feature flag delegation");
|
|
48
50
|
}
|
|
49
51
|
return {
|
|
50
52
|
owner: claims.email,
|
|
51
|
-
orgId: localOrg
|
|
53
|
+
orgId: localOrg?.orgId ?? null,
|
|
52
54
|
anonymous: false,
|
|
53
55
|
delegationJti: claims.jti,
|
|
54
56
|
delegationIssuer: claims.issuer,
|
|
@@ -2,6 +2,8 @@ export declare class FeatureFlagPermissionError extends Error {
|
|
|
2
2
|
statusCode: number;
|
|
3
3
|
constructor(message: string, statusCode: number);
|
|
4
4
|
}
|
|
5
|
+
/** Return whether an email is explicitly allowed to manage no-org flags. */
|
|
6
|
+
export declare function isFeatureFlagAdminEmail(email: string | undefined): boolean;
|
|
5
7
|
/** Require an org admin/owner, or an explicit production no-org allowlist entry. */
|
|
6
8
|
export declare function requireFeatureFlagManager(scope: {
|
|
7
9
|
userEmail?: string;
|